Adding upstream version 3.10.8.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
37e9b6d587
commit
03bfe4079e
356 changed files with 28857 additions and 0 deletions
29
util/convert.go
Normal file
29
util/convert.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ConvertMap[FromType, ToType any](objects []FromType, converter func(f FromType) ToType) []ToType {
|
||||
converted := make([]ToType, 0, len(objects))
|
||||
for _, object := range objects {
|
||||
converted = append(converted, converter(object))
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func ParseInt(s string) int64 {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%s %w", s, err))
|
||||
}
|
||||
return v
|
||||
}
|
26
util/convert_test.go
Normal file
26
util/convert_test.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type A struct{}
|
||||
|
||||
func (A) FA() {}
|
||||
|
||||
type AI interface {
|
||||
FA()
|
||||
}
|
||||
|
||||
func TestConvertInterface(t *testing.T) {
|
||||
length := 10
|
||||
input := make([]A, length)
|
||||
output := ConvertMap(input, func(a A) AI { return a })
|
||||
assert.EqualValues(t, length, len(output))
|
||||
}
|
152
util/exec.go
Normal file
152
util/exec.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
)
|
||||
|
||||
type CommandOptions struct {
|
||||
ExitCodes []int
|
||||
Log logger.MessageInterface
|
||||
}
|
||||
|
||||
func (o *CommandOptions) setDefaults() {
|
||||
if o.ExitCodes == nil {
|
||||
o.ExitCodes = []int{0}
|
||||
}
|
||||
if o.Log == nil {
|
||||
o.Log = logger.NewLogger()
|
||||
}
|
||||
}
|
||||
|
||||
func CommandWithErr(ctx context.Context, options CommandOptions, prog string, args ...string) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
CommandWithOptions(ctx, options, prog, args...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Command(ctx context.Context, log logger.MessageInterface, prog string, args ...string) string {
|
||||
options := CommandOptions{
|
||||
Log: log,
|
||||
}
|
||||
return CommandWithOptions(ctx, options, prog, args...)
|
||||
}
|
||||
|
||||
func CommandWithOptions(ctx context.Context, options CommandOptions, prog string, args ...string) string {
|
||||
if ctx == nil {
|
||||
panic("ctx context.Context is nil")
|
||||
}
|
||||
options.setDefaults()
|
||||
options.Log.Log(1, logger.Trace, "%s\n", args)
|
||||
cmd := exec.Command(prog, args...)
|
||||
SetSysProcAttribute(cmd)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
cmd.Env = append(
|
||||
cmd.Env,
|
||||
"GIT_TERMINAL_PROMPT=0",
|
||||
)
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctxErr := watchCtx(ctx, cmd.Process)
|
||||
err = cmd.Wait()
|
||||
interruptErr := <-ctxErr
|
||||
// If cmd.Wait returned an error, prefer that.
|
||||
// Otherwise, report any error from the interrupt goroutine.
|
||||
if interruptErr != nil && err == nil {
|
||||
err = interruptErr
|
||||
}
|
||||
|
||||
var code int
|
||||
if err == nil {
|
||||
code = 0
|
||||
} else if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
code = exiterr.ExitCode()
|
||||
if code == -1 {
|
||||
panic("killed")
|
||||
}
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, valid := range options.ExitCodes {
|
||||
if valid == code {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
panic(fmt.Errorf("%w: exit code: %d, %v, %v, %v", err, code, out.String(), prog, args))
|
||||
}
|
||||
options.Log.Log(1, logger.Trace, "%s\n", out.String())
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// wrappedError wraps an error without relying on fmt.Errorf.
|
||||
type wrappedError struct {
|
||||
prefix string
|
||||
err error
|
||||
}
|
||||
|
||||
func (w wrappedError) Error() string {
|
||||
return w.prefix + ": " + w.err.Error()
|
||||
}
|
||||
|
||||
func (w wrappedError) Unwrap() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func watchCtx(ctx context.Context, p *os.Process) <-chan error {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errc := make(chan error)
|
||||
go func() {
|
||||
select {
|
||||
case errc <- nil:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
var err error
|
||||
if killErr := kill(p); killErr == nil {
|
||||
// We appear to have successfully delivered a kill signal, so any
|
||||
// program behavior from this point may be due to ctx.
|
||||
err = ctx.Err()
|
||||
} else if !errors.Is(killErr, os.ErrProcessDone) {
|
||||
err = wrappedError{
|
||||
prefix: "util: exec: error sending signal",
|
||||
err: killErr,
|
||||
}
|
||||
}
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
return errc
|
||||
}
|
66
util/exec_test.go
Normal file
66
util/exec_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
something := "SOMETHING"
|
||||
assert.EqualValues(t, something, Command(context.Background(), nil, "echo", "-n", something))
|
||||
|
||||
assert.Panics(t, func() { Command(context.Background(), nil, "false") })
|
||||
}
|
||||
|
||||
func TestCommandWithOptions(t *testing.T) {
|
||||
assert.EqualValues(t, "", CommandWithOptions(context.Background(), CommandOptions{
|
||||
ExitCodes: []int{0, 1},
|
||||
}, "false"))
|
||||
}
|
||||
|
||||
func TestCommandWithErr(t *testing.T) {
|
||||
assert.NoError(t, CommandWithErr(context.Background(), CommandOptions{}, "true"))
|
||||
assert.Error(t, CommandWithErr(context.Background(), CommandOptions{}, "false"))
|
||||
}
|
||||
|
||||
func TestCommandTimeout(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go func() {
|
||||
// blocks forever because of the firewall at 4.4.4.4 and
|
||||
// the git clone process forks a git-remote-https child process
|
||||
assert.PanicsWithValue(t, "killed", func() {
|
||||
_ = Command(ctx, nil, "git", "clone", "https://4.4.4.4", filepath.Join(tmp, "something"))
|
||||
})
|
||||
}()
|
||||
|
||||
pattern := "git-remote-https origin https://4.4.4.4"
|
||||
ps := []string{"-x", "-o", "pid,ppid,pgid,args"}
|
||||
|
||||
Retry(func() {
|
||||
out := Command(context.Background(), nil, "ps", ps...)
|
||||
if !strings.Contains(out, pattern) {
|
||||
panic(out + " does not contain " + pattern)
|
||||
}
|
||||
}, 5)
|
||||
|
||||
cancel()
|
||||
<-ctx.Done()
|
||||
|
||||
Retry(func() {
|
||||
out := Command(context.Background(), nil, "ps", ps...)
|
||||
if strings.Contains(out, pattern) {
|
||||
panic(out + " contains " + pattern)
|
||||
}
|
||||
}, 5)
|
||||
}
|
24
util/exec_unix.go
Normal file
24
util/exec_unix.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func SetSysProcAttribute(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
}
|
||||
|
||||
func kill(p *os.Process) error {
|
||||
return syscall.Kill(-p.Pid, syscall.SIGKILL)
|
||||
}
|
22
util/exec_windows.go
Normal file
22
util/exec_windows.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
//go:build windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func SetSysProcAttribute(cmd *exec.Cmd) {
|
||||
}
|
||||
|
||||
func kill(p *os.Process) error {
|
||||
return p.Kill()
|
||||
}
|
20
util/file.go
Normal file
20
util/file.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func FileExists(pathname string) bool {
|
||||
_, err := os.Stat(pathname)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
panic(err)
|
||||
}
|
51
util/json.go
Normal file
51
util/json.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func jsonMap[T any](data any, key string) T {
|
||||
return jsonGet[T](jsonGetObject(data)[key])
|
||||
}
|
||||
|
||||
func jsonGetObject(data any) map[string]any {
|
||||
switch v := data.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case map[string]any:
|
||||
return v
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected %T", data))
|
||||
}
|
||||
}
|
||||
|
||||
func jsonElement[T any](data any, index int) T {
|
||||
return jsonGet[T](jsonGetArray(data)[index])
|
||||
}
|
||||
|
||||
func jsonGetArray(data any) []any {
|
||||
switch v := data.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case []any:
|
||||
return v
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected %T", data))
|
||||
}
|
||||
}
|
||||
|
||||
func jsonGet[T any](data any) T {
|
||||
switch v := data.(type) {
|
||||
case nil:
|
||||
var s T
|
||||
return s
|
||||
case T:
|
||||
return v
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected %T", data))
|
||||
}
|
||||
}
|
39
util/json_test.go
Normal file
39
util/json_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
var j any
|
||||
content := []byte(`
|
||||
{
|
||||
"A": "B",
|
||||
"C": 1,
|
||||
"D": 2.1,
|
||||
"E": true,
|
||||
"F": null,
|
||||
"G": ["A", 1]
|
||||
}
|
||||
`)
|
||||
assert.NoError(t, json.Unmarshal(content, &j))
|
||||
assert.EqualValues(t, "B", jsonMap[string](jsonGetObject(j), "A"))
|
||||
assert.EqualValues(t, 1, int(jsonMap[float64](jsonGetObject(j), "C")))
|
||||
assert.EqualValues(t, 2.1, jsonMap[float64](jsonGetObject(j), "D"))
|
||||
assert.EqualValues(t, true, jsonMap[bool](jsonGetObject(j), "E"))
|
||||
|
||||
assert.EqualValues(t, nil, jsonMap[any](jsonGetObject(j), "F"))
|
||||
assert.EqualValues(t, "", jsonMap[string](jsonGetObject(j), "F"))
|
||||
assert.EqualValues(t, 0, jsonMap[int](jsonGetObject(j), "F"))
|
||||
|
||||
a := jsonMap[any](jsonGetObject(j), "G")
|
||||
assert.EqualValues(t, "A", jsonElement[string](a, 0))
|
||||
assert.EqualValues(t, 1, int(jsonElement[float64](a, 1)))
|
||||
}
|
98
util/panic.go
Normal file
98
util/panic.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var packagePrefix string
|
||||
|
||||
func init() {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
packagePrefix = strings.TrimSuffix(filename, "util/panic.go")
|
||||
if packagePrefix == filename {
|
||||
// in case the source code file is moved, we can not trim the suffix, the code above should also be updated.
|
||||
panic("unable to detect correct package prefix, please update file: " + filename)
|
||||
}
|
||||
}
|
||||
|
||||
func getStack() string {
|
||||
callersLength := 5
|
||||
var callers []uintptr
|
||||
var callersCount int
|
||||
for {
|
||||
callers = make([]uintptr, callersLength)
|
||||
callersCount = runtime.Callers(4, callers)
|
||||
if callersCount <= 0 {
|
||||
panic("runtime.Callers <= 0")
|
||||
}
|
||||
if callersCount >= callersLength {
|
||||
callersLength *= 2
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
callers = callers[:callersCount]
|
||||
frames := runtime.CallersFrames(callers)
|
||||
stack := make([]string, 0, 10)
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
if strings.HasPrefix(frame.File, packagePrefix) {
|
||||
file := strings.TrimPrefix(frame.File, packagePrefix)
|
||||
if file == "util/panic.go" || file == "util/terminate.go" && more {
|
||||
continue
|
||||
}
|
||||
var function string
|
||||
dot := strings.LastIndex(frame.Function, ".")
|
||||
if dot >= 0 {
|
||||
function = frame.Function[dot+1:]
|
||||
}
|
||||
stack = append(stack, fmt.Sprintf("%s:%d:%s", file, frame.Line, function))
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Join(stack, "\n") + "\n"
|
||||
}
|
||||
|
||||
type PanicError interface {
|
||||
error
|
||||
Stack() string
|
||||
}
|
||||
|
||||
type panicError struct {
|
||||
message string
|
||||
stack string
|
||||
}
|
||||
|
||||
func (o panicError) Error() string { return o.message }
|
||||
func (o panicError) Stack() string { return o.stack }
|
||||
|
||||
func PanicToError(fun func()) (err PanicError) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
recoveredErr, ok := r.(error)
|
||||
var message string
|
||||
if ok {
|
||||
message = recoveredErr.Error()
|
||||
}
|
||||
err = panicError{
|
||||
message: message,
|
||||
stack: getStack(),
|
||||
}
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fun()
|
||||
|
||||
return err
|
||||
}
|
24
util/panic_test.go
Normal file
24
util/panic_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPanicToError(t *testing.T) {
|
||||
assert.NoError(t, PanicToError(func() {}))
|
||||
|
||||
errorMessage := "ERROR"
|
||||
panicingFun := func() {
|
||||
panic(errors.New(errorMessage))
|
||||
}
|
||||
err := PanicToError(panicingFun)
|
||||
assert.EqualValues(t, errorMessage, err.Error())
|
||||
assert.Contains(t, err.Stack(), "PanicToError")
|
||||
}
|
23
util/rand.go
Normal file
23
util/rand.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
)
|
||||
|
||||
func RandSeq(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[random.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
16
util/rand_test.go
Normal file
16
util/rand_test.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRandSeq(t *testing.T) {
|
||||
l := 23
|
||||
assert.Len(t, RandSeq(l), l)
|
||||
}
|
33
util/retry.go
Normal file
33
util/retry.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func retry(fun func()) (status interface{}) {
|
||||
defer func() {
|
||||
status = recover()
|
||||
}()
|
||||
|
||||
fun()
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func Retry(fun func(), tries int) {
|
||||
errors := make([]interface{}, 0, tries)
|
||||
for i := 0; i < tries; i++ {
|
||||
err := retry(fun)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
errors = append(errors, err)
|
||||
<-time.After(1 * time.Second)
|
||||
}
|
||||
panic(fmt.Errorf("Retry: failed %v", errors))
|
||||
}
|
24
util/retry_test.go
Normal file
24
util/retry_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRetryOK(t *testing.T) {
|
||||
Retry(func() {}, 1)
|
||||
i := 0
|
||||
Retry(func() {
|
||||
i++
|
||||
if i < 3 {
|
||||
panic(fmt.Errorf("i == %d", i))
|
||||
}
|
||||
}, 5)
|
||||
assert.EqualValues(t, i, 3)
|
||||
}
|
42
util/terminate.go
Normal file
42
util/terminate.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Terminated terminated
|
||||
type Terminated struct{}
|
||||
|
||||
func (err Terminated) Error() string {
|
||||
return "execution is terminated"
|
||||
}
|
||||
|
||||
func MaybeTerminate(ctx context.Context) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
panic(Terminated{})
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func CatchTerminate(f func()) (status bool) {
|
||||
status = false
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
_, ok := r.(Terminated)
|
||||
if ok {
|
||||
status = true
|
||||
} else {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
f()
|
||||
|
||||
return status
|
||||
}
|
22
util/terminate_test.go
Normal file
22
util/terminate_test.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTerminate(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
assert.False(t, CatchTerminate(func() { MaybeTerminate(ctx) }))
|
||||
cancel()
|
||||
assert.True(t, CatchTerminate(func() { MaybeTerminate(ctx) }))
|
||||
assert.Panics(t, func() {
|
||||
CatchTerminate(func() { panic(true) })
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue