✨ Implement core waiterr package with initial functionality and tests.
This commit is contained in:
parent
8c595a9080
commit
635126fc98
7 changed files with 132 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
build/
|
build/
|
||||||
|
.task/
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
99aa06d3014798d86001c324468d497f
|
|
||||||
|
|
@ -21,7 +21,9 @@ This document outlines the conventions and commands for agents operating within
|
||||||
- Packages: `lowercase`
|
- Packages: `lowercase`
|
||||||
- **Error Handling:** Return errors explicitly. Check errors immediately after a function call that returns an error.
|
- **Error Handling:** Return errors explicitly. Check errors immediately after a function call that returns an error.
|
||||||
- **Linter Rules:** Refer to `.golangci.yaml` for detailed linting rules.
|
- **Linter Rules:** Refer to `.golangci.yaml` for detailed linting rules.
|
||||||
- **Testing**: Use `github.com/nalgeon/be`
|
- **Testing**:
|
||||||
|
- Use `github.com/nalgeon/be`
|
||||||
|
- Tests should be in a separate package, such as waiterr_test
|
||||||
|
|
||||||
## Git Commit Guidelines
|
## Git Commit Guidelines
|
||||||
- **Format**: Prepend commit messages with a gitmoji emoji (see https://gitmoji.dev)
|
- **Format**: Prepend commit messages with a gitmoji emoji (see https://gitmoji.dev)
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -1,3 +1,5 @@
|
||||||
module codeberg.org/danjones000/waiterr
|
module codeberg.org/danjones000/waiterr
|
||||||
|
|
||||||
go 1.24.9
|
go 1.24.9
|
||||||
|
|
||||||
|
require github.com/nalgeon/be v0.3.0
|
||||||
|
|
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/nalgeon/be v0.3.0 h1:QsPANqEtcOD5qT2S3KAtIkDBBn8SXUf/Lb5Bi/z4UqM=
|
||||||
|
github.com/nalgeon/be v0.3.0/go.mod h1:PMwMuBLopwKJkSHnr2qHyLcZYUTqNejN7A8RAqNWO3E=
|
||||||
58
waiterr.go
Normal file
58
waiterr.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package waiterr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WaitErr wraps a sync.WaitGroup with error handling.
|
||||||
|
type WaitErr struct {
|
||||||
|
wg sync.WaitGroup
|
||||||
|
errs []error
|
||||||
|
mut sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go runs f in its own goroutine. When f returns, its error is stored, and returned
|
||||||
|
// with we.Wait()
|
||||||
|
func (we *WaitErr) Go(f func() error) {
|
||||||
|
wrap := func() {
|
||||||
|
err := f()
|
||||||
|
we.mut.Lock()
|
||||||
|
defer we.mut.Unlock()
|
||||||
|
we.errs = append(we.errs, err)
|
||||||
|
}
|
||||||
|
we.wg.Go(wrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForError waits for the first error to be returned by one of our go routines, and immediately returns
|
||||||
|
// with that error. If all functions return successfully, a nil is returned.
|
||||||
|
func (we *WaitErr) WaitForError() error {
|
||||||
|
// Implement this
|
||||||
|
// If we already have an error, return it immediately without waiting
|
||||||
|
// If no error has yet been returned, wait for the very first error and return it
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all current goroutines to finish. Return an error that combines all errors returned
|
||||||
|
// in the group so far (if any).
|
||||||
|
func (we *WaitErr) Wait() error {
|
||||||
|
we.wg.Wait()
|
||||||
|
we.mut.RLock()
|
||||||
|
defer we.mut.RUnlock()
|
||||||
|
return errors.Join(we.errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns all non-nil errors returned by our functions.
|
||||||
|
// If we.errs is empty, or all errors are nil, just return nil.
|
||||||
|
func (we *WaitErr) Unwrap() []error {
|
||||||
|
errs := make([]error, 0, len(we.errs))
|
||||||
|
for _, e := range we.errs {
|
||||||
|
if e != nil {
|
||||||
|
errs = append(errs, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
66
waiterr_test.go
Normal file
66
waiterr_test.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package waiterr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nalgeon/be"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/waiterr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGo(t *testing.T) {
|
||||||
|
we := new(waiterr.WaitErr)
|
||||||
|
err := errors.New("uh-oh")
|
||||||
|
var run bool
|
||||||
|
we.Go(func() error {
|
||||||
|
run = true
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
be.Err(t, we.Wait(), err)
|
||||||
|
be.True(t, run)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWait(t *testing.T) {
|
||||||
|
we := new(waiterr.WaitErr)
|
||||||
|
er1 := errors.New("uh-oh")
|
||||||
|
er2 := errors.New("oops")
|
||||||
|
we.Go(func() error { return er1 })
|
||||||
|
we.Go(func() error { return nil })
|
||||||
|
we.Go(func() error { return er2 })
|
||||||
|
|
||||||
|
err := we.Wait()
|
||||||
|
be.Err(t, err, er1, er2)
|
||||||
|
if ers, ok := err.(interface{ Unwrap() []error }); ok {
|
||||||
|
all := ers.Unwrap()
|
||||||
|
be.Equal(t, len(all), 2)
|
||||||
|
be.True(t, all[0] == er1 || all[0] == er2)
|
||||||
|
be.True(t, all[1] == er2 || all[1] == er1)
|
||||||
|
} else {
|
||||||
|
t.Fatal("Returned error should have Unwrap method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWaitForError(t *testing.T) {
|
||||||
|
we := new(waiterr.WaitErr)
|
||||||
|
er1 := errors.New("uh-oh")
|
||||||
|
er2 := errors.New("oops")
|
||||||
|
we.Go(func() error { return nil })
|
||||||
|
we.Go(func() error { return er1 })
|
||||||
|
we.Go(func() error { return er2 })
|
||||||
|
|
||||||
|
err := we.WaitForError()
|
||||||
|
// Due to how goroutines run, it is possible that either of those return first. This is an acceptable limitation
|
||||||
|
be.True(t, err == er1 || err == er2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWaitForErrorNoErr(t *testing.T) {
|
||||||
|
we := new(waiterr.WaitErr)
|
||||||
|
we.Go(func() error { return nil })
|
||||||
|
we.Go(func() error { return nil })
|
||||||
|
we.Go(func() error { return nil })
|
||||||
|
|
||||||
|
err := we.WaitForError()
|
||||||
|
be.Err(t, err, nil)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue