Add fault pkg and errors struct (#2236)
## Does this PR need a docs update or release note? - [x] ⛔ No ## Type of change - [x] 🧹 Tech Debt/Cleanup ## Issue(s) * #1970 ## Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
78b9f2752e
commit
4852667468
291
src/pkg/fault/example_fault_test.go
Normal file
291
src/pkg/fault/example_fault_test.go
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
package fault_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// mock helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctrl any
|
||||||
|
items = []string{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockController struct {
|
||||||
|
errors any
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectClient() error { return nil }
|
||||||
|
func dependencyCall() error { return nil }
|
||||||
|
func getIthItem(i string) error { return nil }
|
||||||
|
func getData() ([]string, error) { return nil, nil }
|
||||||
|
func storeData([]string, *fault.Errors) {}
|
||||||
|
|
||||||
|
type mockOper struct {
|
||||||
|
Errors *fault.Errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOperation() mockOper { return mockOper{fault.New(true)} }
|
||||||
|
func (m mockOper) Run() *fault.Errors { return m.Errors }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// examples
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ExampleNewErrors highlights assumptions and best practices
|
||||||
|
// for generating Errors structs.
|
||||||
|
func Example_new() {
|
||||||
|
// Errors should only be generated during the construction of
|
||||||
|
// another controller, such as a new Backup or Restore Operations.
|
||||||
|
// Configurations like failFast are set during construction.
|
||||||
|
//
|
||||||
|
// Generating new fault.Errors structs outside of an operation
|
||||||
|
// controller is a smell, and should be avoided. If you need
|
||||||
|
// to aggregate errors, you should accept an interface and pass
|
||||||
|
// an Errors instance into it.
|
||||||
|
ctrl = mockController{
|
||||||
|
errors: fault.New(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleErrorsFail describes the assumptions and best practices
|
||||||
|
// for setting the Failure error.
|
||||||
|
func Example_errors_Fail() {
|
||||||
|
errs := fault.New(false)
|
||||||
|
|
||||||
|
// Fail() should be used to record any error that highlights a
|
||||||
|
// non-recoverable failure in a process.
|
||||||
|
//
|
||||||
|
// Fail() should only get called in the last step before returning
|
||||||
|
// a fault.Errors from a controller. In all other cases, you
|
||||||
|
// should simply return an error and expect the upstream controller
|
||||||
|
// to call Fail() for you.
|
||||||
|
if err := connectClient(); err != nil {
|
||||||
|
// normally, you'd want to
|
||||||
|
// return errs.Fail(err)
|
||||||
|
errs.Fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the topmost handler of the error should set the Fail() err.
|
||||||
|
// This will normally be the operation controller itself.
|
||||||
|
// IE: Fail() is not Wrap(). In lower levels, errors should get
|
||||||
|
// wrapped and returned like normal, and only handled by errors
|
||||||
|
// at the end.
|
||||||
|
lowLevelCall := func() error {
|
||||||
|
if err := dependencyCall(); err != nil {
|
||||||
|
// wrap here, deeper into the stack
|
||||||
|
return errors.Wrap(err, "dependency")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := lowLevelCall(); err != nil {
|
||||||
|
// fail here, at the top of the stack
|
||||||
|
errs.Fail(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleErrorsAdd describes the assumptions and best practices
|
||||||
|
// for aggregating iterable or recoverable errors.
|
||||||
|
func Example_errors_Add() {
|
||||||
|
errs := fault.New(false)
|
||||||
|
|
||||||
|
// Add() should be used to record any error in a recoverable
|
||||||
|
// part of processing.
|
||||||
|
//
|
||||||
|
// Add() should only get called in the last step in handling an
|
||||||
|
// error within a loop or stream that does not otherwise return
|
||||||
|
// an error. In all other cases, you should simply return an error
|
||||||
|
// and expect the upstream point of iteration to call Add() for you.
|
||||||
|
for _, i := range items {
|
||||||
|
if err := getIthItem(i); err != nil {
|
||||||
|
errs.Add(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of failFast behavior, iteration should exit as soon
|
||||||
|
// as an error occurs. Errors does not expose the failFast flag
|
||||||
|
// directly. Instead, iterators should check the value of Err().
|
||||||
|
// If it is non-nil, then the loop shold break.
|
||||||
|
for _, i := range items {
|
||||||
|
if errs.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
errs.Add(getIthItem(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the topmost handler of the error should Add() the err.
|
||||||
|
// This will normally be the iteration loop itself.
|
||||||
|
// IE: Add() is not Wrap(). In lower levels, errors should get
|
||||||
|
// wrapped and returned like normally, and only added to the
|
||||||
|
// errors at the end.
|
||||||
|
clientBasedGetter := func(s string) error {
|
||||||
|
if err := dependencyCall(); err != nil {
|
||||||
|
// wrap here, deeper into the stack
|
||||||
|
return errors.Wrap(err, "dependency")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range items {
|
||||||
|
if err := clientBasedGetter(i); err != nil {
|
||||||
|
// add here, within the iteraton loop
|
||||||
|
errs.Add(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleErrorsErr describes retrieving the non-recoverable error.
|
||||||
|
func Example_errors_Err() {
|
||||||
|
errs := fault.New(false)
|
||||||
|
errs.Fail(errors.New("catastrophe"))
|
||||||
|
|
||||||
|
// Err() gets the primary failure error.
|
||||||
|
err := errs.Err()
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// if multiple Failures occur, each one after the first gets
|
||||||
|
// added to the Errs slice.
|
||||||
|
errs.Fail(errors.New("another catastrophe"))
|
||||||
|
errSl := errs.Errs()
|
||||||
|
|
||||||
|
for _, e := range errSl {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Err() is nil, then you can assume the operation completed.
|
||||||
|
// A complete operation is not necessarily an error-free operation.
|
||||||
|
//
|
||||||
|
// Even if Err() is nil, Errs() can be non-empty.
|
||||||
|
// Make sure you check both.
|
||||||
|
|
||||||
|
errs = fault.New(true)
|
||||||
|
|
||||||
|
// If failFast is set to true, then the first error Add()ed gets
|
||||||
|
// promoted to the Err() position.
|
||||||
|
|
||||||
|
errs.Add(errors.New("not catastrophic, but still becomes the Err()"))
|
||||||
|
err = errs.Err()
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Output: catastrophe
|
||||||
|
// another catastrophe
|
||||||
|
// not catastrophic, but still becomes the Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleErrorsErrs describes retrieving individual errors.
|
||||||
|
func Example_errors_Errs() {
|
||||||
|
errs := fault.New(false)
|
||||||
|
errs.Add(errors.New("not catastrophic"))
|
||||||
|
errs.Add(errors.New("something unwanted"))
|
||||||
|
|
||||||
|
// Errs() gets the slice errors that were recorded, but were
|
||||||
|
// considered recoverable.
|
||||||
|
errSl := errs.Errs()
|
||||||
|
for _, err := range errSl {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errs() only needs to be investigated by the end user at the
|
||||||
|
// conclusion of an operation. Checking Errs() within lower-
|
||||||
|
// layer code is a smell. Funcs should return an error if they
|
||||||
|
// need upstream handlers to recognize failure states.
|
||||||
|
//
|
||||||
|
// If Errs() is nil, then you can assume that no recoverable or
|
||||||
|
// iteration-based errors occurred. But that does not necessarily
|
||||||
|
// mean the operation was able to complete.
|
||||||
|
//
|
||||||
|
// Even if Errs() contains zero items, Err() can be non-nil.
|
||||||
|
// Make sure you check both.
|
||||||
|
|
||||||
|
// Output: not catastrophic
|
||||||
|
// something unwanted
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleErrorsE2e showcases a more complex integration.
|
||||||
|
func Example_errors_e2e() {
|
||||||
|
oper := newOperation()
|
||||||
|
|
||||||
|
// imagine that we're a user, calling into corso SDK.
|
||||||
|
// (fake funcs used here to minimize example bloat)
|
||||||
|
//
|
||||||
|
// The operation is our controller, we expect it to
|
||||||
|
// generate a new fault.Errors when constructed, and
|
||||||
|
// to return that struct when we call Run()
|
||||||
|
errs := oper.Run()
|
||||||
|
|
||||||
|
// Let's investigate what went on inside. Since we're at
|
||||||
|
// the top of our controller, and returning a fault.Errors,
|
||||||
|
// all the error handlers set the Fail() case.
|
||||||
|
/* Run() */
|
||||||
|
func() *fault.Errors {
|
||||||
|
if err := connectClient(); err != nil {
|
||||||
|
// Fail() here; we're top level in the controller
|
||||||
|
// and this is a non-recoverable issue
|
||||||
|
return oper.Errors.Fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := getData()
|
||||||
|
if err != nil {
|
||||||
|
return oper.Errors.Fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeData will aggregate iterated errors into
|
||||||
|
// oper.Errors.
|
||||||
|
storeData(data, oper.Errors)
|
||||||
|
|
||||||
|
// return oper.Errors here, in part to ensure it's
|
||||||
|
// non-nil, and because we don't know if we've
|
||||||
|
// aggregated any iterated errors yet.
|
||||||
|
return oper.Errors
|
||||||
|
}()
|
||||||
|
|
||||||
|
// What about the lower level handling? storeData didn't
|
||||||
|
// return an error, so what's happening there?
|
||||||
|
/* storeData */
|
||||||
|
func(data []any, errs *fault.Errors) {
|
||||||
|
// this is downstream in our code somewhere
|
||||||
|
storer := func(a any) error {
|
||||||
|
if err := dependencyCall(); err != nil {
|
||||||
|
// we're not passing in or calling fault.Errors here,
|
||||||
|
// because this isn't the iteration handler, it's just
|
||||||
|
// a regular error.
|
||||||
|
return errors.Wrap(err, "dependency")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
if errs.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := storer(d); err != nil {
|
||||||
|
// Since we're at the top of the iteration, we need
|
||||||
|
// to add each error to the fault.Errors struct.
|
||||||
|
errs.Add(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(nil, nil)
|
||||||
|
|
||||||
|
// then at the end of the oper.Run, we investigate the results.
|
||||||
|
if errs.Err() != nil {
|
||||||
|
// handle the primary error
|
||||||
|
fmt.Println("err occurred", errs.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, err := range errs.Errs() {
|
||||||
|
// handle each recoverable error
|
||||||
|
fmt.Println("recoverable err occurred", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/pkg/fault/fault.go
Normal file
127
src/pkg/fault/fault.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package fault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Errors struct {
|
||||||
|
mu *sync.Mutex
|
||||||
|
|
||||||
|
// err identifies non-recoverable errors. This includes
|
||||||
|
// non-start cases (ex: cannot connect to client), hard-
|
||||||
|
// stop issues (ex: credentials expired) or conscious exit
|
||||||
|
// cases (ex: iteration error + failFast config).
|
||||||
|
err error
|
||||||
|
|
||||||
|
// errs is the accumulation of recoverable or iterated
|
||||||
|
// errors. Eg: if a process is retrieving N items, and
|
||||||
|
// 1 of the items fails to be retrieved, but the rest of
|
||||||
|
// them succeed, we'd expect to see 1 error added to this
|
||||||
|
// slice.
|
||||||
|
errs []error
|
||||||
|
|
||||||
|
// if failFast is true, the first errs addition will
|
||||||
|
// get promoted to the err value. This signifies a
|
||||||
|
// non-recoverable processing state, causing any running
|
||||||
|
// processes to exit.
|
||||||
|
failFast bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorsData provides the errors data alone, without sync
|
||||||
|
// controls, allowing the data to be persisted.
|
||||||
|
type ErrorsData struct {
|
||||||
|
Err error `json:"err"`
|
||||||
|
Errs []error `json:"errs"`
|
||||||
|
FailFast bool `json:"failFast"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New constructs a new error with default values in place.
|
||||||
|
func New(failFast bool) *Errors {
|
||||||
|
return &Errors{
|
||||||
|
mu: &sync.Mutex{},
|
||||||
|
errs: []error{},
|
||||||
|
failFast: failFast,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the primary error. If not nil, this
|
||||||
|
// indicates the operation exited prior to completion.
|
||||||
|
func (e *Errors) Err() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errs returns the slice of recoverable and
|
||||||
|
// iterated errors.
|
||||||
|
func (e *Errors) Errs() []error {
|
||||||
|
return e.errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns the plain set of error data
|
||||||
|
// without any sync properties.
|
||||||
|
func (e *Errors) Data() ErrorsData {
|
||||||
|
return ErrorsData{
|
||||||
|
Err: e.err,
|
||||||
|
Errs: slices.Clone(e.errs),
|
||||||
|
FailFast: e.failFast,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: introduce Failer interface
|
||||||
|
|
||||||
|
// Fail sets the non-recoverable error (ie: errors.err)
|
||||||
|
// in the errors struct. If a non-recoverable error is
|
||||||
|
// already present, the error gets added to the errs slice.
|
||||||
|
func (e *Errors) Fail(err error) *Errors {
|
||||||
|
if err == nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
return e.setErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setErr handles setting errors.err. Sync locking gets
|
||||||
|
// handled upstream of this call.
|
||||||
|
func (e *Errors) setErr(err error) *Errors {
|
||||||
|
if e.err != nil {
|
||||||
|
return e.addErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.err = err
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: introduce Adder interface
|
||||||
|
|
||||||
|
// Add appends the error to the slice of recoverable and
|
||||||
|
// iterated errors (ie: errors.errs). If failFast is true,
|
||||||
|
// the first Added error will get copied to errors.err,
|
||||||
|
// causing the errors struct to identify as non-recoverably
|
||||||
|
// failed.
|
||||||
|
func (e *Errors) Add(err error) *Errors {
|
||||||
|
if err == nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
return e.addErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addErr handles adding errors to errors.errs. Sync locking
|
||||||
|
// gets handled upstream of this call.
|
||||||
|
func (e *Errors) addErr(err error) *Errors {
|
||||||
|
if e.err == nil && e.failFast {
|
||||||
|
e.setErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.errs = append(e.errs, err)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
202
src/pkg/fault/fault_test.go
Normal file
202
src/pkg/fault/fault_test.go
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package fault_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FaultErrorsUnitSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFaultErrorsUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(FaultErrorsUnitSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FaultErrorsUnitSuite) TestNew() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
n := fault.New(false)
|
||||||
|
assert.NotNil(t, n)
|
||||||
|
|
||||||
|
n = fault.New(true)
|
||||||
|
assert.NotNil(t, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FaultErrorsUnitSuite) TestErr() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
failFast bool
|
||||||
|
fail error
|
||||||
|
add error
|
||||||
|
expect assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil, failFast",
|
||||||
|
failFast: true,
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed",
|
||||||
|
fail: assert.AnError,
|
||||||
|
expect: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed, failFast",
|
||||||
|
fail: assert.AnError,
|
||||||
|
failFast: true,
|
||||||
|
expect: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "added",
|
||||||
|
add: assert.AnError,
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "added, failFast",
|
||||||
|
add: assert.AnError,
|
||||||
|
failFast: true,
|
||||||
|
expect: assert.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
n := fault.New(test.failFast)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
|
||||||
|
e := n.Fail(test.fail)
|
||||||
|
require.NotNil(t, e)
|
||||||
|
|
||||||
|
e = n.Add(test.add)
|
||||||
|
require.NotNil(t, e)
|
||||||
|
|
||||||
|
test.expect(t, n.Err())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FaultErrorsUnitSuite) TestFail() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
n := fault.New(false)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
|
||||||
|
n.Fail(assert.AnError)
|
||||||
|
assert.Error(t, n.Err())
|
||||||
|
assert.Empty(t, n.Errs())
|
||||||
|
|
||||||
|
n.Fail(assert.AnError)
|
||||||
|
assert.Error(t, n.Err())
|
||||||
|
assert.NotEmpty(t, n.Errs())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FaultErrorsUnitSuite) TestErrs() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
failFast bool
|
||||||
|
fail error
|
||||||
|
add error
|
||||||
|
expect assert.ValueAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
expect: assert.Empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil, failFast",
|
||||||
|
failFast: true,
|
||||||
|
expect: assert.Empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed",
|
||||||
|
fail: assert.AnError,
|
||||||
|
expect: assert.Empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed, failFast",
|
||||||
|
fail: assert.AnError,
|
||||||
|
failFast: true,
|
||||||
|
expect: assert.Empty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "added",
|
||||||
|
add: assert.AnError,
|
||||||
|
expect: assert.NotEmpty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "added, failFast",
|
||||||
|
add: assert.AnError,
|
||||||
|
failFast: true,
|
||||||
|
expect: assert.NotEmpty,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
n := fault.New(test.failFast)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
|
||||||
|
e := n.Fail(test.fail)
|
||||||
|
require.NotNil(t, e)
|
||||||
|
|
||||||
|
e = n.Add(test.add)
|
||||||
|
require.NotNil(t, e)
|
||||||
|
|
||||||
|
test.expect(t, n.Errs())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FaultErrorsUnitSuite) TestAdd() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
n := fault.New(true)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
|
||||||
|
n.Add(assert.AnError)
|
||||||
|
assert.Error(t, n.Err())
|
||||||
|
assert.Len(t, n.Errs(), 1)
|
||||||
|
|
||||||
|
n.Add(assert.AnError)
|
||||||
|
assert.Error(t, n.Err())
|
||||||
|
assert.Len(t, n.Errs(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FaultErrorsUnitSuite) TestData() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
// not fail-fast
|
||||||
|
n := fault.New(false)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
|
||||||
|
n.Fail(errors.New("fail"))
|
||||||
|
n.Add(errors.New("1"))
|
||||||
|
n.Add(errors.New("2"))
|
||||||
|
|
||||||
|
d := n.Data()
|
||||||
|
assert.Equal(t, n.Err(), d.Err)
|
||||||
|
assert.ElementsMatch(t, n.Errs(), d.Errs)
|
||||||
|
assert.False(t, d.FailFast)
|
||||||
|
|
||||||
|
// fail-fast
|
||||||
|
n = fault.New(true)
|
||||||
|
require.NotNil(t, n)
|
||||||
|
|
||||||
|
n.Fail(errors.New("fail"))
|
||||||
|
n.Add(errors.New("1"))
|
||||||
|
n.Add(errors.New("2"))
|
||||||
|
|
||||||
|
d = n.Data()
|
||||||
|
assert.Equal(t, n.Err(), d.Err)
|
||||||
|
assert.ElementsMatch(t, n.Errs(), d.Errs)
|
||||||
|
assert.True(t, d.FailFast)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user