From e9d86ef33cf37b39c5fadca9bc1a0ea8f634be6a Mon Sep 17 00:00:00 2001 From: Hiroto Funakoshi Date: Wed, 19 Oct 2022 14:51:40 +0900 Subject: [PATCH] Bugfix Circuit Breaker half-open error handling (#1811) * bugfix cb half-open error handling Signed-off-by: hlts2 * refactor logic Signed-off-by: hlts2 * add fail function test Signed-off-by: hlts2 * add ignores counter for internal state error Signed-off-by: hlts2 * fix counter type from atomic to pure pointer Signed-off-by: hlts2 * fix counter interface Signed-off-by: hlts2 * make format Signed-off-by: hlts2 Signed-off-by: hlts2 Co-authored-by: Kiichiro YUKAWA --- internal/circuitbreaker/breaker.go | 33 +- internal/circuitbreaker/breaker_test.go | 205 ++++++------ internal/circuitbreaker/counter.go | 13 +- internal/circuitbreaker/counter_test.go | 403 ++++++++++++++++++++++-- 4 files changed, 525 insertions(+), 129 deletions(-) diff --git a/internal/circuitbreaker/breaker.go b/internal/circuitbreaker/breaker.go index d38dc47b8c..4e29ab1eb8 100644 --- a/internal/circuitbreaker/breaker.go +++ b/internal/circuitbreaker/breaker.go @@ -24,9 +24,9 @@ import ( ) type breaker struct { - key string // breaker key for logging - count atomic.Value // type: *count - tripped int32 // tripped flag. when flag value is 1, breaker state is "Open" or "HalfOpen". + key string // breaker key for logging + count *count // type: *count + tripped int32 // tripped flag. when flag value is 1, breaker state is "Open" or "HalfOpen". closedErrRate float32 closedErrShouldTrip Tripper @@ -41,7 +41,8 @@ type breaker struct { func newBreaker(key string, opts ...BreakerOption) (*breaker, error) { b := &breaker{ - key: key, + key: key, + count: &count{}, } for _, opt := range append(defaultBreakerOpts, opts...) { if err := opt(b); err != nil { @@ -54,7 +55,6 @@ func newBreaker(key string, opts ...BreakerOption) (*breaker, error) { log.Warn(oerr) } } - b.count.Store(&count{}) if b.closedErrShouldTrip == nil { b.closedErrShouldTrip = NewRateTripper(b.closedErrRate, b.minSamples) @@ -69,6 +69,7 @@ func newBreaker(key string, opts ...BreakerOption) (*breaker, error) { // If the current breaker state is "Open", this function returns ErrCircuitBreakerOpenState. func (b *breaker) do(ctx context.Context, fn func(ctx context.Context) (val interface{}, err error)) (val interface{}, st State, err error) { if st, err := b.isReady(); err != nil { + b.count.onIgnore() return nil, st, err } val, err = fn(ctx) @@ -107,9 +108,7 @@ func (b *breaker) isReady() (st State, err error) { // For flow control in the "Half-Open" state. It is limited to 50%. // If this modulo is used, 1/2 of the requests will be error. And if an error occurs, mark as failures. - cnt := b.count.Load().(*count) - if cnt.Total()%2 == 0 { - cnt.onFail() + if b.count.Total()%2 == 0 { return st, errors.ErrCircuitBreakerHalfOpenFlowLimitation } } @@ -117,30 +116,28 @@ func (b *breaker) isReady() (st State, err error) { } func (b *breaker) success() { - cnt := b.count.Load().(*count) - cnt.onSuccess() + b.count.onSuccess() // halfOpenErrShouldTrip.ShouldTrip returns true when the sum of the number of successes and failures is greater than the b.minSamples and when the error rate is greater than the b.halfOpenErrRate. // In other words, if the error rate is less than the b.halfOpenErrRate, it can be judged that the success rate is high, so this function change to the "Close" state from "Half-Open". if st := b.currentState(); st == StateHalfOpen && - cnt.Total() >= b.minSamples && - !b.halfOpenErrShouldTrip.ShouldTrip(cnt) { + b.count.Successes()+b.count.Fails() >= b.minSamples && + !b.halfOpenErrShouldTrip.ShouldTrip(b.count) { log.Infof("the operation succeeded, circuit breaker state for '%s' changed,\tfrom: %s, to: %s", b.key, st.String(), StateClosed.String()) b.reset() } } func (b *breaker) fail() { - cnt := b.count.Load().(*count) - cnt.onFail() + b.count.onFail() var ok bool var st State switch st = b.currentState(); st { case StateHalfOpen: - ok = b.halfOpenErrShouldTrip.ShouldTrip(cnt) + ok = b.halfOpenErrShouldTrip.ShouldTrip(b.count) case StateClosed: - ok = b.closedErrShouldTrip.ShouldTrip(cnt) + ok = b.closedErrShouldTrip.ShouldTrip(b.count) default: return } @@ -172,14 +169,14 @@ func (b *breaker) reset() { atomic.StoreInt32(&b.tripped, 0) atomic.StoreInt64(&b.openExp, 0) atomic.StoreInt64(&b.closedRefreshExp, time.Now().Add(b.cloedRefreshTimeout).UnixNano()) - b.count.Load().(*count).reset() + b.count.reset() } func (b *breaker) trip() { atomic.StoreInt32(&b.tripped, 1) atomic.StoreInt64(&b.openExp, time.Now().Add(b.openTimeout).UnixNano()) atomic.StoreInt64(&b.closedRefreshExp, 0) - b.count.Load().(*count).reset() + b.count.reset() } func (b *breaker) isTripped() (ok bool) { diff --git a/internal/circuitbreaker/breaker_test.go b/internal/circuitbreaker/breaker_test.go index 0348d2637d..4ba1202be2 100644 --- a/internal/circuitbreaker/breaker_test.go +++ b/internal/circuitbreaker/breaker_test.go @@ -17,7 +17,6 @@ import ( "context" "fmt" "reflect" - "sync/atomic" "testing" "time" @@ -124,7 +123,7 @@ func Test_breaker_do(t *testing.T) { } type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -273,7 +272,7 @@ func Test_breaker_do(t *testing.T) { func Test_breaker_isReady(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -323,17 +322,16 @@ func Test_breaker_isReady(t *testing.T) { } }(), func() test { - var atCount atomic.Value - atCount.Store(&count{ + cnt := &count{ successes: 1, - }) + } return test{ name: "return the StateHalfOpen and nil when the current state is HalfOpen", fields: fields{ key: "insertRPC", tripped: 1, openExp: time.Now().Add(-100 * time.Second).UnixNano(), - count: atCount, + count: cnt, }, want: want{ wantSt: StateHalfOpen, @@ -343,15 +341,14 @@ func Test_breaker_isReady(t *testing.T) { } }(), func() test { - var atCount atomic.Value - atCount.Store(&count{}) + cnt := &count{} return test{ name: "return the StateHalfOpen and error when the current state is HalfOpen but the flow is being limited", fields: fields{ key: "insertRPC", tripped: 1, openExp: time.Now().Add(-100 * time.Second).UnixNano(), - count: atCount, + count: cnt, }, want: want{ wantSt: StateHalfOpen, @@ -361,9 +358,8 @@ func Test_breaker_isReady(t *testing.T) { if err := defaultCheckFunc(w, s, err); err != nil { return err } - cnt := atCount.Load().(*count) - if got := cnt.Fails(); got != 1 { - return fmt.Errorf("failures is not equals. want: %d, but got: %d", 2, got) + if got := cnt.Fails(); got != 0 { + return fmt.Errorf("failures is not equals. want: %d, but got: %d", 0, got) } return nil }, @@ -427,7 +423,7 @@ func Test_breaker_isReady(t *testing.T) { func Test_breaker_success(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -453,18 +449,17 @@ func Test_breaker_success(t *testing.T) { } tests := []test{ func() test { - var atCount atomic.Value - atCount.Store(&count{ + cnt := &count{ successes: 10, failures: 10, - }) + } halfOpenErrRate := float32(0.5) minSamples := int64(10) return test{ name: "the current state change from HalfOpen to Close when the success rate is higher", fields: fields{ key: "insertRPC", - count: atCount, + count: cnt, tripped: 1, openExp: time.Now().Add(-100 * time.Second).UnixNano(), halfOpenErrRate: halfOpenErrRate, @@ -481,18 +476,17 @@ func Test_breaker_success(t *testing.T) { } }(), func() test { - var atCount atomic.Value - atCount.Store(&count{ + cnt := &count{ successes: 10, failures: 11, - }) + } halfOpenErrRate := float32(0.5) minSamples := int64(10) return test{ name: "the current state do not change from HalfOpen to Close when the success rate is less", fields: fields{ key: "insertRPC", - count: atCount, + count: cnt, tripped: 1, openExp: time.Now().Add(-100 * time.Second).UnixNano(), halfOpenErrRate: halfOpenErrRate, @@ -551,7 +545,7 @@ func Test_breaker_success(t *testing.T) { func Test_breaker_fail(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -570,71 +564,102 @@ func Test_breaker_fail(t *testing.T) { want want checkFunc func(want) error beforeFunc func(*testing.T) - afterFunc func(*testing.T) + afterFunc func(*testing.T, *breaker) } defaultCheckFunc := func(w want) error { return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - fields: fields { - key: "", - count: nil, - tripped: 0, - closedErrRate: 0, - closedErrShouldTrip: nil, - halfOpenErrRate: 0, - halfOpenErrShouldTrip: nil, - minSamples: 0, - openTimeout: nil, - openExp: 0, - cloedRefreshTimeout: nil, - closedRefreshExp: 0, - }, - want: want{}, - checkFunc: defaultCheckFunc, - beforeFunc: func(t *testing.T,) { - t.Helper() - }, - afterFunc: func(t *testing.T,) { - t.Helper() - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - fields: fields { - key: "", - count: nil, - tripped: 0, - closedErrRate: 0, - closedErrShouldTrip: nil, - halfOpenErrRate: 0, - halfOpenErrShouldTrip: nil, - minSamples: 0, - openTimeout: nil, - openExp: 0, - cloedRefreshTimeout: nil, - closedRefreshExp: 0, - }, - want: want{}, - checkFunc: defaultCheckFunc, - beforeFunc: func(t *testing.T,) { - t.Helper() - }, - afterFunc: func(t *testing.T,) { - t.Helper() - }, - } - }(), - */ + func() test { + cnt := &count{ + successes: 10, + failures: 11, + } + closedErrRate := float32(0.5) + minSamples := int64(20) + return test{ + name: "the current state change from Close to Open when the failure rate is higher", + fields: fields{ + key: "insertRPC", + count: cnt, + tripped: 0, + closedErrRate: closedErrRate, + closedRefreshExp: time.Now().Add(100 * time.Second).UnixNano(), + closedErrShouldTrip: NewRateTripper(closedErrRate, minSamples), + minSamples: minSamples, + }, + checkFunc: defaultCheckFunc, + afterFunc: func(t *testing.T, b *breaker) { + t.Helper() + if b.tripped == 0 { + t.Errorf("state did not change: %d", b.tripped) + } + if total := cnt.Total(); total != 0 { + t.Errorf("count did not reset: %d", total) + } + }, + } + }(), + func() test { + cnt := &count{ + successes: 10, + failures: 11, + } + halfOpenErrRate := float32(0.5) + minSamples := int64(20) + return test{ + name: "the current state change from HalfOpen to Open when the failure rate is higher", + fields: fields{ + key: "insertRPC", + count: cnt, + tripped: 1, + openExp: time.Now().Add(-100 * time.Second).UnixNano(), + halfOpenErrRate: halfOpenErrRate, + halfOpenErrShouldTrip: NewRateTripper(halfOpenErrRate, minSamples), + minSamples: minSamples, + }, + checkFunc: defaultCheckFunc, + afterFunc: func(t *testing.T, b *breaker) { + t.Helper() + if b.tripped == 0 { + t.Errorf("state changed: %d", b.tripped) + } + if total := b.count.Total(); total != 0 { + t.Errorf("count did not reset: %d", total) + } + }, + } + }(), + func() test { + cnt := &count{ + successes: 10, + failures: 1, + } + halfOpenErrRate := float32(0.5) + minSamples := int64(10) + return test{ + name: "the current HalfOpen state dot not change when the failure rate does not reached the setting value", + fields: fields{ + key: "insertRPC", + count: cnt, + tripped: 1, + openExp: time.Now().Add(-100 * time.Second).UnixNano(), + halfOpenErrRate: halfOpenErrRate, + halfOpenErrShouldTrip: NewRateTripper(halfOpenErrRate, minSamples), + minSamples: minSamples, + }, + checkFunc: defaultCheckFunc, + afterFunc: func(t *testing.T, b *breaker) { + t.Helper() + if b.tripped == 0 { + t.Errorf("state changed: %d", b.tripped) + } + if total := b.count.Total(); total == 0 { + t.Errorf("count reseted: %d", total) + } + }, + } + }(), } for _, tc := range tests { @@ -645,9 +670,6 @@ func Test_breaker_fail(t *testing.T) { if test.beforeFunc != nil { test.beforeFunc(tt) } - if test.afterFunc != nil { - defer test.afterFunc(tt) - } checkFunc := test.checkFunc if test.checkFunc == nil { checkFunc = defaultCheckFunc @@ -666,6 +688,9 @@ func Test_breaker_fail(t *testing.T) { cloedRefreshTimeout: test.fields.cloedRefreshTimeout, closedRefreshExp: test.fields.closedRefreshExp, } + if test.afterFunc != nil { + defer test.afterFunc(tt, b) + } b.fail() if err := checkFunc(test.want); err != nil { @@ -678,7 +703,7 @@ func Test_breaker_fail(t *testing.T) { func Test_breaker_currentState(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -810,7 +835,7 @@ func Test_breaker_currentState(t *testing.T) { func Test_breaker_reset(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -937,7 +962,7 @@ func Test_breaker_reset(t *testing.T) { func Test_breaker_trip(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper @@ -1064,7 +1089,7 @@ func Test_breaker_trip(t *testing.T) { func Test_breaker_isTripped(t *testing.T) { type fields struct { key string - count atomic.Value + count *count tripped int32 closedErrRate float32 closedErrShouldTrip Tripper diff --git a/internal/circuitbreaker/counter.go b/internal/circuitbreaker/counter.go index bc20e906c7..9ca08a4ea7 100644 --- a/internal/circuitbreaker/counter.go +++ b/internal/circuitbreaker/counter.go @@ -19,9 +19,11 @@ type Counter interface { Total() int64 Successes() int64 Fails() int64 + Ignores() int64 } type count struct { + ignores int64 successes int64 failures int64 } @@ -34,8 +36,12 @@ func (c *count) Fails() (n int64) { return atomic.LoadInt64(&c.failures) } +func (c *count) Ignores() (n int64) { + return atomic.LoadInt64(&c.ignores) +} + func (c *count) Total() (n int64) { - return c.Successes() + c.Fails() + return c.Successes() + c.Fails() + c.Ignores() } func (c *count) onSuccess() { @@ -46,9 +52,14 @@ func (c *count) onFail() { atomic.AddInt64(&c.failures, 1) } +func (c *count) onIgnore() { + atomic.AddInt64(&c.ignores, 1) +} + func (c *count) reset() { atomic.StoreInt64(&c.failures, 0) atomic.StoreInt64(&c.successes, 0) + atomic.StoreInt64(&c.ignores, 0) } var _ Counter = (*count)(nil) diff --git a/internal/circuitbreaker/counter_test.go b/internal/circuitbreaker/counter_test.go index 025bc909b4..eff9933470 100644 --- a/internal/circuitbreaker/counter_test.go +++ b/internal/circuitbreaker/counter_test.go @@ -23,6 +23,7 @@ import ( func Test_count_Successes(t *testing.T) { type fields struct { + ignores int64 successes int64 failures int64 } @@ -34,8 +35,8 @@ func Test_count_Successes(t *testing.T) { fields fields want want checkFunc func(want, int64) error - beforeFunc func() - afterFunc func() + beforeFunc func(*testing.T) + afterFunc func(*testing.T) } defaultCheckFunc := func(w want, gotN int64) error { if !reflect.DeepEqual(gotN, w.wantN) { @@ -49,11 +50,18 @@ func Test_count_Successes(t *testing.T) { { name: "test_case_1", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, }, */ @@ -63,11 +71,18 @@ func Test_count_Successes(t *testing.T) { return test { name: "test_case_2", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, } }(), */ @@ -79,16 +94,17 @@ func Test_count_Successes(t *testing.T) { tt.Parallel() defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) if test.beforeFunc != nil { - test.beforeFunc() + test.beforeFunc(tt) } if test.afterFunc != nil { - defer test.afterFunc() + defer test.afterFunc(tt) } checkFunc := test.checkFunc if test.checkFunc == nil { checkFunc = defaultCheckFunc } c := &count{ + ignores: test.fields.ignores, successes: test.fields.successes, failures: test.fields.failures, } @@ -103,6 +119,7 @@ func Test_count_Successes(t *testing.T) { func Test_count_Fails(t *testing.T) { type fields struct { + ignores int64 successes int64 failures int64 } @@ -114,8 +131,8 @@ func Test_count_Fails(t *testing.T) { fields fields want want checkFunc func(want, int64) error - beforeFunc func() - afterFunc func() + beforeFunc func(*testing.T) + afterFunc func(*testing.T) } defaultCheckFunc := func(w want, gotN int64) error { if !reflect.DeepEqual(gotN, w.wantN) { @@ -129,11 +146,18 @@ func Test_count_Fails(t *testing.T) { { name: "test_case_1", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, }, */ @@ -143,11 +167,18 @@ func Test_count_Fails(t *testing.T) { return test { name: "test_case_2", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, } }(), */ @@ -159,16 +190,17 @@ func Test_count_Fails(t *testing.T) { tt.Parallel() defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) if test.beforeFunc != nil { - test.beforeFunc() + test.beforeFunc(tt) } if test.afterFunc != nil { - defer test.afterFunc() + defer test.afterFunc(tt) } checkFunc := test.checkFunc if test.checkFunc == nil { checkFunc = defaultCheckFunc } c := &count{ + ignores: test.fields.ignores, successes: test.fields.successes, failures: test.fields.failures, } @@ -181,8 +213,201 @@ func Test_count_Fails(t *testing.T) { } } +func Test_count_Ignores(t *testing.T) { + type fields struct { + ignores int64 + successes int64 + failures int64 + } + type want struct { + wantN int64 + } + type test struct { + name string + fields fields + want want + checkFunc func(want, int64) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, gotN int64) error { + if !reflect.DeepEqual(gotN, w.wantN) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotN, w.wantN) + } + return nil + } + tests := []test{ + // TODO test cases + /* + { + name: "test_case_1", + fields: fields { + ignores: 0, + successes: 0, + failures: 0, + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, + }, + */ + + // TODO test cases + /* + func() test { + return test { + name: "test_case_2", + fields: fields { + ignores: 0, + successes: 0, + failures: 0, + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, + } + }(), + */ + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + c := &count{ + ignores: test.fields.ignores, + successes: test.fields.successes, + failures: test.fields.failures, + } + + gotN := c.Ignores() + if err := checkFunc(test.want, gotN); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + +func Test_count_Total(t *testing.T) { + type fields struct { + ignores int64 + successes int64 + failures int64 + } + type want struct { + wantN int64 + } + type test struct { + name string + fields fields + want want + checkFunc func(want, int64) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want, gotN int64) error { + if !reflect.DeepEqual(gotN, w.wantN) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", gotN, w.wantN) + } + return nil + } + tests := []test{ + // TODO test cases + /* + { + name: "test_case_1", + fields: fields { + ignores: 0, + successes: 0, + failures: 0, + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, + }, + */ + + // TODO test cases + /* + func() test { + return test { + name: "test_case_2", + fields: fields { + ignores: 0, + successes: 0, + failures: 0, + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, + } + }(), + */ + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + c := &count{ + ignores: test.fields.ignores, + successes: test.fields.successes, + failures: test.fields.failures, + } + + gotN := c.Total() + if err := checkFunc(test.want, gotN); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + func Test_count_onSuccess(t *testing.T) { type fields struct { + ignores int64 successes int64 failures int64 } @@ -192,8 +417,8 @@ func Test_count_onSuccess(t *testing.T) { fields fields want want checkFunc func(want) error - beforeFunc func() - afterFunc func() + beforeFunc func(*testing.T) + afterFunc func(*testing.T) } defaultCheckFunc := func(w want) error { return nil @@ -204,11 +429,18 @@ func Test_count_onSuccess(t *testing.T) { { name: "test_case_1", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, }, */ @@ -218,11 +450,18 @@ func Test_count_onSuccess(t *testing.T) { return test { name: "test_case_2", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, } }(), */ @@ -234,16 +473,17 @@ func Test_count_onSuccess(t *testing.T) { tt.Parallel() defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) if test.beforeFunc != nil { - test.beforeFunc() + test.beforeFunc(tt) } if test.afterFunc != nil { - defer test.afterFunc() + defer test.afterFunc(tt) } checkFunc := test.checkFunc if test.checkFunc == nil { checkFunc = defaultCheckFunc } c := &count{ + ignores: test.fields.ignores, successes: test.fields.successes, failures: test.fields.failures, } @@ -258,6 +498,7 @@ func Test_count_onSuccess(t *testing.T) { func Test_count_onFail(t *testing.T) { type fields struct { + ignores int64 successes int64 failures int64 } @@ -267,8 +508,8 @@ func Test_count_onFail(t *testing.T) { fields fields want want checkFunc func(want) error - beforeFunc func() - afterFunc func() + beforeFunc func(*testing.T) + afterFunc func(*testing.T) } defaultCheckFunc := func(w want) error { return nil @@ -279,11 +520,18 @@ func Test_count_onFail(t *testing.T) { { name: "test_case_1", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, }, */ @@ -293,11 +541,18 @@ func Test_count_onFail(t *testing.T) { return test { name: "test_case_2", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, } }(), */ @@ -309,16 +564,17 @@ func Test_count_onFail(t *testing.T) { tt.Parallel() defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) if test.beforeFunc != nil { - test.beforeFunc() + test.beforeFunc(tt) } if test.afterFunc != nil { - defer test.afterFunc() + defer test.afterFunc(tt) } checkFunc := test.checkFunc if test.checkFunc == nil { checkFunc = defaultCheckFunc } c := &count{ + ignores: test.fields.ignores, successes: test.fields.successes, failures: test.fields.failures, } @@ -331,8 +587,100 @@ func Test_count_onFail(t *testing.T) { } } +func Test_count_onIgnore(t *testing.T) { + type fields struct { + ignores int64 + successes int64 + failures int64 + } + type want struct{} + type test struct { + name string + fields fields + want want + checkFunc func(want) error + beforeFunc func(*testing.T) + afterFunc func(*testing.T) + } + defaultCheckFunc := func(w want) error { + return nil + } + tests := []test{ + // TODO test cases + /* + { + name: "test_case_1", + fields: fields { + ignores: 0, + successes: 0, + failures: 0, + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, + }, + */ + + // TODO test cases + /* + func() test { + return test { + name: "test_case_2", + fields: fields { + ignores: 0, + successes: 0, + failures: 0, + }, + want: want{}, + checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, + } + }(), + */ + } + + for _, tc := range tests { + test := tc + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) + if test.beforeFunc != nil { + test.beforeFunc(tt) + } + if test.afterFunc != nil { + defer test.afterFunc(tt) + } + checkFunc := test.checkFunc + if test.checkFunc == nil { + checkFunc = defaultCheckFunc + } + c := &count{ + ignores: test.fields.ignores, + successes: test.fields.successes, + failures: test.fields.failures, + } + + c.onIgnore() + if err := checkFunc(test.want); err != nil { + tt.Errorf("error = %v", err) + } + }) + } +} + func Test_count_reset(t *testing.T) { type fields struct { + ignores int64 successes int64 failures int64 } @@ -342,8 +690,8 @@ func Test_count_reset(t *testing.T) { fields fields want want checkFunc func(want) error - beforeFunc func() - afterFunc func() + beforeFunc func(*testing.T) + afterFunc func(*testing.T) } defaultCheckFunc := func(w want) error { return nil @@ -354,11 +702,18 @@ func Test_count_reset(t *testing.T) { { name: "test_case_1", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, }, */ @@ -368,11 +723,18 @@ func Test_count_reset(t *testing.T) { return test { name: "test_case_2", fields: fields { + ignores: 0, successes: 0, failures: 0, }, want: want{}, checkFunc: defaultCheckFunc, + beforeFunc: func(t *testing.T,) { + t.Helper() + }, + afterFunc: func(t *testing.T,) { + t.Helper() + }, } }(), */ @@ -384,16 +746,17 @@ func Test_count_reset(t *testing.T) { tt.Parallel() defer goleak.VerifyNone(tt, goleak.IgnoreCurrent()) if test.beforeFunc != nil { - test.beforeFunc() + test.beforeFunc(tt) } if test.afterFunc != nil { - defer test.afterFunc() + defer test.afterFunc(tt) } checkFunc := test.checkFunc if test.checkFunc == nil { checkFunc = defaultCheckFunc } c := &count{ + ignores: test.fields.ignores, successes: test.fields.successes, failures: test.fields.failures, }