1
0
Fork 0

Adding upstream version 3.10.8.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-18 09:37:23 +02:00
parent 37e9b6d587
commit 03bfe4079e
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
356 changed files with 28857 additions and 0 deletions

29
util/convert.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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) })
})
}