🎉 Release v0.9.0
This commit is contained in:
commit
67eb618288
12 changed files with 623 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
.task/
|
||||
46
.golangci.yaml
Normal file
46
.golangci.yaml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
version: "2"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
||||
- copyloopvar
|
||||
- dupl
|
||||
- err113
|
||||
- errname
|
||||
- exptostd
|
||||
- fatcontext
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godot
|
||||
- godox
|
||||
- gosec
|
||||
- perfsprint
|
||||
- testifylint
|
||||
settings:
|
||||
testifylint:
|
||||
enable-all: true
|
||||
disable:
|
||||
- require-error
|
||||
gocognit:
|
||||
min-complexity: 16
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
gocritic:
|
||||
enable-all: true
|
||||
settings:
|
||||
hugeParam:
|
||||
sizeThreshold: 255
|
||||
exclusions:
|
||||
rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- err113
|
||||
- gocyclo
|
||||
- gocognit
|
||||
46
AGENTS.md
Normal file
46
AGENTS.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Agent Guidelines for waiterr
|
||||
|
||||
This document outlines the conventions and commands for agents operating within the `waiterr` Go project.
|
||||
|
||||
## Build/Lint/Test Commands
|
||||
|
||||
- **Lint:** `task lint`
|
||||
- **Test All:** `task test`
|
||||
- **Test Single File:** `go test -run <TestName> <path/to/file_test.go>` (e.g., `go test -run TestNewHappy ./ezcache_test.go`)
|
||||
- **Format:** `task fmt`
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- **Module**: `codeberg.org/danjones000/waiterr`
|
||||
- **Go version**: 1.25.3
|
||||
- **Imports:** Group standard library imports separately from third-party imports.
|
||||
- **Formatting:** Adhere to `go fmt` standards.
|
||||
- **Naming Conventions:**
|
||||
- Variables: `camelCase`
|
||||
- Functions/Methods: `CamelCase` (exported), `camelCase` (unexported)
|
||||
- Packages: `lowercase`
|
||||
- **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.
|
||||
- **Testing**:
|
||||
- Use `github.com/nalgeon/be`
|
||||
- Tests should be in a separate package, such as waiterr_test
|
||||
- **sync.WaitGroup**: Do not use the `WaitGroup.Add` or `WaitGroup.Done` functions. Instead, we should rely on `WaitGroup.Go` to spawn new goroutines.
|
||||
|
||||
## Git Commit Guidelines
|
||||
- **Format**: Prepend commit messages with a gitmoji emoji (see https://gitmoji.dev)
|
||||
- **Style**: Write detailed commit messages that explain what changed and why
|
||||
- **Examples**: `✨ Add JSON export functionality for log entries`, `🐛 Fix date parsing for RFC3339 timestamps`, `📝 Update README with configuration examples`
|
||||
|
||||
## Git Flow Workflow
|
||||
- **Main branches**: `stable` (production-ready), `develop` (integration branch)
|
||||
- **Development**: Always commit new features/fixes to `develop` branch or appropriate feature branches
|
||||
- **Branch prefixes**:
|
||||
- `feat/feature-name` - New features, merge to `develop` when complete
|
||||
- `bug/bug-name` - Bug fixes (non-urgent), merge to `develop` when complete
|
||||
- `rel/version` - Release preparation branches, merge to `stable` and then **also** merge `stable` back to `develop`
|
||||
- `hot/version` - Hotfixes for production issues follow same merge rules as releases
|
||||
- **Version tags**: Prefix all version tags with `v` (e.g., `v1.0.2`, `v0.0.6`)
|
||||
- **Releases**: Update CHANGELOG.md with a summary of changes for each new version
|
||||
- **Never commit directly to** `stable` branch (only merge from `rel/` or `hot/` branches)
|
||||
- After merging to `stable`, always merge it back to `develop`
|
||||
- **Before starting work**: Ensure you're on `develop` branch or create an appropriate feature branch from it
|
||||
22
CHANGELOG.md
Normal file
22
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Changelog
|
||||
|
||||
## v0.9.0 - 2025-11-13
|
||||
|
||||
### Added
|
||||
- Add examples for WaitErr methods
|
||||
- Add test for WaitForError when firstErr is already set
|
||||
- Add test for WaitForError panic
|
||||
- Add tests for Unwrap method
|
||||
- Add MIT License
|
||||
- Implement waiterr.WaitForError
|
||||
- Implement core waiterr package with initial functionality and tests.
|
||||
- Initial project setup with basic scaffolding, build tools, and agent guidelines.
|
||||
|
||||
### Fixed
|
||||
- Fix race condition in TestWaitForErrorFirstErrSet using synctest
|
||||
|
||||
### Changed
|
||||
- Update comments in waiterr.go
|
||||
- Update WaitForError comment and add README.md for project documentation.
|
||||
- Update AGENTS.md with new `sync.WaitGroup` guidelines.
|
||||
- Update Go version and linter exclusions for test files.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Dan Jones
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
104
README.md
Normal file
104
README.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# waiterr
|
||||
|
||||
`waiterr` is a Go package that wraps `sync.WaitGroup` with enhanced error handling capabilities. It allows you to run multiple goroutines, collect any errors they return, and wait for their completion, either returning the first error encountered or aggregating all errors.
|
||||
|
||||
## Features
|
||||
|
||||
- **`Go(f func() error)`**: Runs a function `f` in a new goroutine, storing any error it returns.
|
||||
- **`Wait() error`**: Waits for all goroutines to complete and returns a combined error of all non-nil errors.
|
||||
- **`WaitForError() error`**: Waits for the first error to be returned by any goroutine and immediately returns that error. If all goroutines complete without error, it returns `nil`.
|
||||
- **`Unwrap() []error`**: Returns a slice of all non-nil errors encountered by the goroutines.
|
||||
|
||||
## Installation
|
||||
|
||||
To install `waiterr`, use `go get`:
|
||||
|
||||
```bash
|
||||
go get codeberg.org/danjones000/waiterr
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here's a basic example of how to use `waiterr`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"codeberg.org/danjones000/waiterr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
we := new(waiterr.WaitErr)
|
||||
|
||||
we.Go(func() error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Println("Goroutine 1 finished")
|
||||
return nil
|
||||
})
|
||||
|
||||
we.Go(func() error {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Println("Goroutine 2 finished with an error")
|
||||
return errors.New("something went wrong in goroutine 2")
|
||||
})
|
||||
|
||||
we.Go(func() error {
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
fmt.Println("Goroutine 3 finished")
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for all goroutines and get all errors
|
||||
if err := we.Wait(); err != nil {
|
||||
fmt.Printf("All goroutines finished. Combined error: %v
|
||||
", err)
|
||||
}
|
||||
|
||||
// You can also get the first error immediately
|
||||
we2 := new(waiterr.WaitErr)
|
||||
we2.Go(func() error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return errors.New("first error from we2")
|
||||
})
|
||||
we2.Go(func() error {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return errors.New("second error from we2")
|
||||
})
|
||||
|
||||
if err := we2.WaitForError(); err != nil {
|
||||
fmt.Printf("First error from we2: %v
|
||||
", err)
|
||||
}
|
||||
|
||||
// Get all unwrapped errors
|
||||
unwrappedErrors := we.Unwrap()
|
||||
if len(unwrappedErrors) > 0 {
|
||||
fmt.Println("Unwrapped errors:")
|
||||
for i, err := range unwrappedErrors {
|
||||
fmt.Printf(" %d: %v
|
||||
", i+1, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to the [AGENTS.md](AGENTS.md) file for guidelines on contributing to this project, including code style, commit messages, and Git workflow.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Go Version
|
||||
|
||||
Go 1.25.3
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `github.com/nalgeon/be v0.3.0` for testing.
|
||||
55
Taskfile.yml
Normal file
55
Taskfile.yml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: fmt
|
||||
- task: test
|
||||
- task: lint
|
||||
|
||||
fmt:
|
||||
desc: Format go code
|
||||
sources:
|
||||
- '**/*.go'
|
||||
cmds:
|
||||
- go fmt ./...
|
||||
- go mod tidy
|
||||
|
||||
lint:
|
||||
desc: Do static analysis
|
||||
sources:
|
||||
- '**/*.go'
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
test:
|
||||
desc: Run unit tests
|
||||
deps: [fmt]
|
||||
sources:
|
||||
- '**/*.go'
|
||||
generates:
|
||||
- build/cover.out
|
||||
cmds:
|
||||
- go test -race -cover -coverprofile build/cover.out ./...
|
||||
|
||||
coverage-report:
|
||||
desc: Build coverage report
|
||||
deps: [test]
|
||||
sources:
|
||||
- build/cover.out
|
||||
generates:
|
||||
- build/cover.html
|
||||
cmds:
|
||||
- go tool cover -html=build/cover.out -o build/cover.html
|
||||
|
||||
serve-report:
|
||||
desc: Serve the coverage report
|
||||
deps: [coverage-report]
|
||||
cmds:
|
||||
- ip addr list | grep inet
|
||||
- python3 -m http.server -d build 3434
|
||||
|
||||
serve-docs:
|
||||
desc: Serve the current docs
|
||||
cmds:
|
||||
- godoc -http=0.0.0.0:3434 -play
|
||||
90
example_test.go
Normal file
90
example_test.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package waiterr_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"codeberg.org/danjones000/waiterr"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
we := new(waiterr.WaitErr)
|
||||
|
||||
we.Go(func() error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Println("Goroutine 1 finished")
|
||||
return nil
|
||||
})
|
||||
|
||||
we.Go(func() error {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Println("Goroutine 2 finished with an error")
|
||||
return errors.New("something went wrong in goroutine 2")
|
||||
})
|
||||
|
||||
we.Go(func() error {
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
fmt.Println("Goroutine 3 finished")
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for all goroutines and get all errors
|
||||
if err := we.Wait(); err != nil {
|
||||
fmt.Printf("All goroutines finished. Combined error:\n%v\n", err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Goroutine 2 finished with an error
|
||||
// Goroutine 1 finished
|
||||
// Goroutine 3 finished
|
||||
// All goroutines finished. Combined error:
|
||||
// something went wrong in goroutine 2
|
||||
}
|
||||
|
||||
func ExampleWaitErr_WaitForError() {
|
||||
we := new(waiterr.WaitErr)
|
||||
we.Go(func() error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return errors.New("first error from we")
|
||||
})
|
||||
we.Go(func() error {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return errors.New("second error from we")
|
||||
})
|
||||
|
||||
if err := we.WaitForError(); err != nil {
|
||||
fmt.Printf("First error returned from we: %v\n", err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// First error returned from we: second error from we
|
||||
|
||||
}
|
||||
|
||||
func ExampleWaitErr_Unwrap() {
|
||||
we := new(waiterr.WaitErr)
|
||||
we.Go(func() error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return errors.New("first error from we")
|
||||
})
|
||||
we.Go(func() error {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return errors.New("second error from we")
|
||||
})
|
||||
we.Go(func() error {
|
||||
time.Sleep(75 * time.Millisecond)
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = we.Wait()
|
||||
|
||||
errs := we.Unwrap()
|
||||
for _, e := range errs {
|
||||
fmt.Println(e)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// second error from we
|
||||
// first error from we
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module codeberg.org/danjones000/waiterr
|
||||
|
||||
go 1.25.3
|
||||
|
||||
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=
|
||||
105
waiterr.go
Normal file
105
waiterr.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package waiterr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WaitErr wraps a [sync.WaitGroup] with error handling.
|
||||
type WaitErr struct {
|
||||
wg sync.WaitGroup
|
||||
errs []error
|
||||
mut sync.RWMutex
|
||||
firstErr error
|
||||
firstErrOnce sync.Once
|
||||
errCh chan error // Buffered channel of size 1
|
||||
initErrChOnce sync.Once
|
||||
}
|
||||
|
||||
// Go runs f in its own goroutine. When f returns, its error is stored, and returned
|
||||
// with [WaitErr.Wait].
|
||||
func (we *WaitErr) Go(f func() error) {
|
||||
we.initErrChOnce.Do(func() {
|
||||
we.errCh = make(chan error, 1)
|
||||
})
|
||||
wrap := func() {
|
||||
err := f()
|
||||
|
||||
if err != nil {
|
||||
we.firstErrOnce.Do(func() {
|
||||
we.mut.Lock() // Acquire lock before writing to firstErr
|
||||
we.firstErr = err
|
||||
we.mut.Unlock() // Release lock after writing
|
||||
|
||||
// Non-blocking send to errCh
|
||||
select {
|
||||
case we.errCh <- err:
|
||||
default:
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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. It will panic if called before Go.
|
||||
func (we *WaitErr) WaitForError() error {
|
||||
if we.errCh == nil {
|
||||
panic("WaitForError called before Go")
|
||||
}
|
||||
// Check if an error has already been set
|
||||
we.mut.RLock()
|
||||
if we.firstErr != nil {
|
||||
err := we.firstErr
|
||||
we.mut.RUnlock()
|
||||
return err
|
||||
}
|
||||
we.mut.RUnlock()
|
||||
|
||||
// Create a channel to signal when all goroutines are done
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
we.wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-we.errCh:
|
||||
return err
|
||||
case <-done:
|
||||
// All goroutines finished, and no error was sent to errCh
|
||||
// Re-check firstErr in case it was set just before 'done' was closed
|
||||
we.mut.RLock()
|
||||
defer we.mut.RUnlock()
|
||||
return we.firstErr // This will be nil if no error occurred
|
||||
}
|
||||
}
|
||||
|
||||
// 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 no errors were returned, or all errors are nil, it returns 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
|
||||
}
|
||||
125
waiterr_test.go
Normal file
125
waiterr_test.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package waiterr_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
|
||||
"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)
|
||||
}
|
||||
|
||||
func TestUnwrap(tt *testing.T) {
|
||||
tt.Run("two errors", func(t *testing.T) {
|
||||
we := new(waiterr.WaitErr)
|
||||
er1 := errors.New("error one")
|
||||
er2 := errors.New("error two")
|
||||
|
||||
we.Go(func() error { return er1 })
|
||||
we.Go(func() error { return nil })
|
||||
we.Go(func() error { return er2 })
|
||||
we.Go(func() error { return nil })
|
||||
|
||||
_ = we.Wait() // Ensure all goroutines complete
|
||||
|
||||
unwrapped := we.Unwrap()
|
||||
be.Equal(t, len(unwrapped), 2)
|
||||
be.True(t, (unwrapped[0] == er1 && unwrapped[1] == er2) || (unwrapped[0] == er2 && unwrapped[1] == er1))
|
||||
|
||||
})
|
||||
|
||||
tt.Run("no errors", func(t *testing.T) {
|
||||
weNoErr := new(waiterr.WaitErr)
|
||||
weNoErr.Go(func() error { return nil })
|
||||
weNoErr.Go(func() error { return nil })
|
||||
_ = weNoErr.Wait()
|
||||
be.Equal(t, weNoErr.Unwrap(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWaitForErrorPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("The code did not panic")
|
||||
}
|
||||
}()
|
||||
|
||||
we := new(waiterr.WaitErr)
|
||||
_ = we.WaitForError()
|
||||
}
|
||||
|
||||
func TestWaitForErrorFirstErrSet(tt *testing.T) {
|
||||
we := new(waiterr.WaitErr)
|
||||
expectedErr := errors.New("pre-set error")
|
||||
|
||||
synctest.Test(tt, func(t *testing.T) {
|
||||
we.Go(func() error { return expectedErr })
|
||||
// synctest.Wait ensures that the gorouting has finished before anything else.
|
||||
synctest.Wait()
|
||||
|
||||
we.Go(func() error { return errors.New("another error") })
|
||||
synctest.Wait()
|
||||
|
||||
we.Go(func() error { return nil })
|
||||
|
||||
actualErr := we.WaitForError()
|
||||
be.Err(t, actualErr, expectedErr)
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue