diff --git a/env.go b/env.go index 72a02ec..7358585 100644 --- a/env.go +++ b/env.go @@ -50,12 +50,6 @@ func New(t T) *EnvT { return env } -// NewEnv create EnvT from test -// Deprecated: use New instead -func NewEnv(t T) *EnvT { - return New(t) -} - func newEnv(t T, c *cache, m sync.Locker, scopes map[string]*scopeInfo) *EnvT { return &EnvT{ t: t, @@ -70,69 +64,6 @@ func (e *EnvT) T() T { return e.t } -// Cache call from fixture and manage call f and cache it. -// Cache must be called direct from fixture - it use runtime stacktrace for -// detect called method - it is part of cache key. -// params - part of cache key. Usually - parameters, passed to fixture. -// -// it allow use parametrized fixtures with different results. -// params must be json serializable. -// -// opt - fixture options, nil for default options. -// f - callback - fixture body. -// Cache guarantee for call f exactly once for same Cache called and params combination. -// Deprecated: will be removed in next versions. -// Use EnvT.CacheResult instead -func (e *EnvT) Cache(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} { - if opt == nil { - opt = &FixtureOptions{} - } - var fixtureFunc FixtureFunction = func() (*Result, error) { - res, err := f() - return NewResult(res), err - } - - options := CacheOptions{ - Scope: opt.Scope, - CacheKey: cacheKey, - additionlSkipExternalCalls: opt.additionlSkipExternalCalls, - } - return e.cache(fixtureFunc, options) -} - -// CacheWithCleanup call from fixture and manage call f and cache it. -// CacheWithCleanup must be called direct from fixture - it use runtime stacktrace for -// detect called method - it is part of cache key. -// params - part of cache key. Usually - parameters, passed to fixture. -// -// it allow use parametrized fixtures with different results. -// params must be json serializable. -// -// opt - fixture options, nil for default options. -// f - callback - fixture body. -// cleanup, returned from f called while fixture cleanup -// Cache guarantee for call f exactly once for same Cache called and params combination. -// Deprecated: will be removed in next versions. -// Use EnvT.CacheResult instead -func (e *EnvT) CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} { - if opt == nil { - opt = &FixtureOptions{} - } - - var fixtureFunc FixtureFunction = func() (*Result, error) { - res, resCleanupFunc, err := f() - return NewResultWithCleanup(res, resCleanupFunc), err - } - - options := CacheOptions{ - Scope: opt.Scope, - CacheKey: cacheKey, - additionlSkipExternalCalls: opt.additionlSkipExternalCalls, - } - - return e.cache(fixtureFunc, options) -} - // CacheResult call f callback once and cache result (ok and error), // then return same result for all calls of the callback without additional calls // f with same options calls max once per test (or defined test scope) diff --git a/env_generic_sugar.go b/env_generic_sugar.go index 22accc2..8c8d458 100644 --- a/env_generic_sugar.go +++ b/env_generic_sugar.go @@ -5,43 +5,6 @@ package fixenv import "fmt" -// Cache is call f once per cache scope (default per test) and cache result (success or error). -// All other calls of the f will return same result -// Deprecated: Use CacheResult -func Cache[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, error)) TRes { - addSkipLevel(&opt) - - res := CacheResult(env, func() (*GenericResult[TRes], error) { - fRes, err := f() - return NewGenericResult(fRes), err - }, CacheOptions{ - Scope: opt.Scope, - CacheKey: cacheKey, - additionlSkipExternalCalls: opt.additionlSkipExternalCalls, - }) - - return res -} - -// CacheWithCleanup is call f once per cache scope (default per test) and cache result (success or error). -// All other calls of the f will return same result. -// Used when fixture need own cleanup after exit from test scope -// Deprecated: Use CacheResult -func CacheWithCleanup[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, FixtureCleanupFunc, error)) TRes { - addSkipLevel(&opt) - - res := CacheResult(env, func() (*GenericResult[TRes], error) { - res, cleanup, err := f() - return NewGenericResultWithCleanup(res, cleanup), err - }, CacheOptions{ - Scope: opt.Scope, - CacheKey: cacheKey, - additionlSkipExternalCalls: opt.additionlSkipExternalCalls, - }) - - return res -} - // CacheResult is call f once per cache scope (default per test) and cache result (success or error). // All other calls of the f will return same result. func CacheResult[TRes any](env Env, f GenericFixtureFunction[TRes], options ...CacheOptions) TRes { @@ -91,13 +54,6 @@ func NewGenericResultWithCleanup[ResT any](res ResT, cleanup FixtureCleanupFunc) return &GenericResult[ResT]{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}} } -func addSkipLevel(optspp **FixtureOptions) { - if *optspp == nil { - *optspp = &FixtureOptions{} - } - (*optspp).additionlSkipExternalCalls++ -} - func addSkipLevelCache(optspp *CacheOptions) { (*optspp).additionlSkipExternalCalls++ } diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index af0c347..f4d061c 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -10,90 +10,6 @@ import ( "testing" ) -func TestCacheGeneric(t *testing.T) { - t.Run("PassParams", func(t *testing.T) { - inParams := 123 - inOpt := &FixtureOptions{Scope: ScopeTest} - - env := envMock{ - onCacheResult: func(opts CacheOptions, f FixtureFunction) interface{} { - opts.additionlSkipExternalCalls-- - requireEquals(t, inParams, opts.CacheKey) - requireEquals(t, inOpt.Scope, opts.Scope) - res, _ := f() - return res.Value - }, - } - - res := Cache(env, inParams, inOpt, func() (int, error) { - return 2, nil - }) - requireEquals(t, 2, res) - }) - t.Run("SkipAdditionalCache", func(t *testing.T) { - test := &internal.TestMock{TestName: t.Name()} - env := newTestEnv(test) - - f1 := func() int { - return Cache(env, nil, nil, func() (int, error) { - return 1, nil - }) - } - f2 := func() int { - return Cache(env, nil, nil, func() (int, error) { - return 2, nil - }) - } - - requireEquals(t, 1, f1()) - requireEquals(t, 2, f2()) - }) -} - -func TestCacheWithCleanupGeneric(t *testing.T) { - t.Run("PassParams", func(t *testing.T) { - inParams := 123 - inOpt := &FixtureOptions{Scope: ScopeTest} - - cleanupCalledBack := 0 - - env := envMock{ - onCacheResult: func(opts CacheOptions, f FixtureFunction) interface{} { - requireEquals(t, inParams, opts.CacheKey) - requireEquals(t, inOpt.Scope, opts.Scope) - res, _ := f() - return res.Value - }, - } - - res := CacheWithCleanup(env, inParams, inOpt, func() (int, FixtureCleanupFunc, error) { - cleanup := func() { - cleanupCalledBack++ - } - return 2, cleanup, nil - }) - requireEquals(t, 2, res) - }) - t.Run("SkipAdditionalCache", func(t *testing.T) { - test := &internal.TestMock{TestName: t.Name()} - env := newTestEnv(test) - - f1 := func() int { - return CacheWithCleanup(env, nil, nil, func() (int, FixtureCleanupFunc, error) { - return 1, nil, nil - }) - } - f2 := func() int { - return CacheWithCleanup(env, nil, nil, func() (int, FixtureCleanupFunc, error) { - return 2, nil, nil - }) - } - - requireEquals(t, 1, f1()) - requireEquals(t, 2, f2()) - }) -} - func TestCacheResultGeneric(t *testing.T) { t.Run("PassParams", func(t *testing.T) { inOpt := CacheOptions{ @@ -188,23 +104,13 @@ func TestCacheResultPanic(t *testing.T) { } type envMock struct { - onCache func(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} - onCacheWithCleanup func(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} - onCacheResult func(opts CacheOptions, f FixtureFunction) interface{} + onCacheResult func(opts CacheOptions, f FixtureFunction) interface{} } func (e envMock) T() T { panic("not implemented") } -func (e envMock) Cache(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} { - return e.onCache(params, opt, f) -} - -func (e envMock) CacheWithCleanup(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} { - return e.onCacheWithCleanup(params, opt, f) -} - func (e envMock) CacheResult(f FixtureFunction, options ...CacheOptions) interface{} { var opts CacheOptions switch len(options) { diff --git a/env_test.go b/env_test.go index 278c218..5b4b13b 100644 --- a/env_test.go +++ b/env_test.go @@ -66,222 +66,6 @@ func Test_Env__NewEnv(t *testing.T) { }) } -func testFailedFixture(env Env) { - env.CacheResult(func() (*Result, error) { - return nil, errors.New("test error") - }) -} - -func Test_Env_Cache(t *testing.T) { - t.Run("simple", func(t *testing.T) { - e := New(t) - - val := 0 - cntF := func() int { - res := e.Cache(nil, nil, func() (res interface{}, err error) { - val++ - e.T().Logf("val: ", val) - return val, nil - }) - return res.(int) - } - - requireEquals(t, 1, cntF()) - requireEquals(t, 1, cntF()) - - val = 2 - requireEquals(t, 1, cntF()) - }) - - t.Run("subtest_and_test_scope", func(t *testing.T) { - e := New(t) - - val := 0 - cntF := func(env Env) int { - res := env.Cache(nil, &FixtureOptions{Scope: ScopeTest}, func() (res interface{}, err error) { - val++ - return val, nil - }) - return res.(int) - } - - requireEquals(t, 1, cntF(e)) - requireEquals(t, 1, cntF(e)) - - t.Run("subtest", func(t *testing.T) { - subEnv := New(t) - requireEquals(t, 2, cntF(subEnv)) - requireEquals(t, 2, cntF(subEnv)) - }) - - requireEquals(t, 1, cntF(e)) - - }) - - t.Run("subtest_and_package_scope", func(t *testing.T) { - e := New(t) - _, mainClose := CreateMainTestEnv(nil) - defer mainClose() - - val := 0 - cntF := func(env Env) int { - res := env.Cache(nil, &FixtureOptions{Scope: ScopePackage}, func() (res interface{}, err error) { - val++ - return val, nil - }) - return res.(int) - } - - requireEquals(t, 1, cntF(e)) - requireEquals(t, 1, cntF(e)) - - t.Run("subtest", func(t *testing.T) { - subEnv := New(t) - requireEquals(t, 1, cntF(subEnv)) - requireEquals(t, 1, cntF(subEnv)) - }) - - requireEquals(t, 1, cntF(e)) - - }) - - t.Run("fail_on_fixture_err", func(t *testing.T) { - tMock := &internal.TestMock{TestName: "mock"} - defer tMock.CallCleanup() - - e := newTestEnv(tMock) - requireEquals(t, len(tMock.Fatals), 0) - - runUntilFatal(func() { - testFailedFixture(e) - }) - requireEquals(t, len(tMock.Fatals), 1) - - // log message contains fixture name - requireContains(t, tMock.Fatals[0].ResultString, "testFailedFixture") - }) - - t.Run("not_serializable_param", func(t *testing.T) { - type paramT struct { - F func() // can't serialize func to json - } - param := paramT{} - tMock := &internal.TestMock{TestName: "mock"} - defer tMock.CallCleanup() - e := newTestEnv(tMock) - runUntilFatal(func() { - e.Cache(param, nil, func() (res interface{}, err error) { - return nil, nil - }) - }) - requireEquals(t, len(tMock.Fatals), 1) - }) - - t.Run("cache_by_caller_func", func(t *testing.T) { - tMock := &internal.TestMock{TestName: "mock"} - e := newTestEnv(tMock) - - cnt := 0 - res := e.Cache(nil, &FixtureOptions{Scope: ScopeTest}, func() (res interface{}, err error) { - cnt++ - return cnt, nil - }) - requireEquals(t, 1, res) - - res = e.Cache(nil, &FixtureOptions{Scope: ScopeTest}, func() (res interface{}, err error) { - cnt++ - return cnt, nil - }) - requireEquals(t, 1, res) - }) - - t.Run("different_cache_for_diff_anonim_function", func(t *testing.T) { - tMock := &internal.TestMock{TestName: "mock"} - e := newTestEnv(tMock) - - cnt := 0 - func() { - res := e.Cache(nil, &FixtureOptions{Scope: ScopeTest}, func() (res interface{}, err error) { - cnt++ - return cnt, nil - }) - requireEquals(t, 1, res) - }() - - func() { - res := e.Cache(nil, &FixtureOptions{Scope: ScopeTest}, func() (res interface{}, err error) { - cnt++ - return cnt, nil - }) - requireEquals(t, 2, res) - }() - - }) - - t.Run("check_unreachable_code", func(t *testing.T) { - tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} - e := New(tMock) - requirePanic(t, func() { - e.Cache(nil, nil, func() (res interface{}, err error) { - return nil, ErrSkipTest - }) - }) - requireEquals(t, 1, tMock.SkipCount) - }) -} - -func Test_Env_CacheWithCleanup(t *testing.T) { - t.Run("NilCleanup", func(t *testing.T) { - tMock := &internal.TestMock{TestName: t.Name()} - env := newTestEnv(tMock) - - callbackCalled := 0 - var callbackFunc FixtureCallbackWithCleanupFunc = func() (res interface{}, cleanup FixtureCleanupFunc, err error) { - callbackCalled++ - return callbackCalled, nil, nil - } - - res := env.CacheWithCleanup(nil, nil, callbackFunc) - requireEquals(t, 1, res) - requireEquals(t, 1, callbackCalled) - - // got value from cache - res = env.CacheWithCleanup(nil, nil, callbackFunc) - requireEquals(t, 1, res) - requireEquals(t, 1, callbackCalled) - }) - - t.Run("WithCleanup", func(t *testing.T) { - tMock := &internal.TestMock{TestName: t.Name()} - env := newTestEnv(tMock) - - callbackCalled := 0 - cleanupCalled := 0 - var callbackFunc FixtureCallbackWithCleanupFunc = func() (res interface{}, cleanup FixtureCleanupFunc, err error) { - callbackCalled++ - cleanup = func() { - cleanupCalled++ - } - return callbackCalled, cleanup, nil - } - - res := env.CacheWithCleanup(nil, nil, callbackFunc) - requireEquals(t, 1, res) - requireEquals(t, 1, callbackCalled) - requireEquals(t, cleanupCalled, 0) - - // got value from cache - res = env.CacheWithCleanup(nil, nil, callbackFunc) - requireEquals(t, 1, res) - requireEquals(t, 1, callbackCalled) - requireEquals(t, cleanupCalled, 0) - - tMock.CallCleanup() - requireEquals(t, 1, callbackCalled) - requireEquals(t, 1, cleanupCalled) - }) -} - func Test_Env_CacheResult(t *testing.T) { t.Run("Simple", func(t *testing.T) { tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} @@ -376,6 +160,24 @@ func Test_Env_CacheResult(t *testing.T) { <-done requireEquals(t, 1, len(tMock.Fatals)) }) + t.Run("check_unserializable_params", func(t *testing.T) { + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := newTestEnv(tMock) + e.CacheResult(func() (*Result, error) { + return nil, ErrSkipTest + }, CacheOptions{CacheKey: func() {}}) + requireEquals(t, len(tMock.Fatals), 1) + }) + t.Run("check_unreachable_code", func(t *testing.T) { + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := newTestEnv(tMock) + requirePanic(t, func() { + e.CacheResult(func() (*Result, error) { + return nil, ErrSkipTest + }) + }) + requireEquals(t, 1, tMock.SkipCount) + }) } func Test_FixtureWrapper(t *testing.T) { @@ -699,17 +501,6 @@ func Test_ScopeName(t *testing.T) { }) } -func TestNewEnv(t *testing.T) { - tm := &internal.TestMock{} - tm.SkipGoexit = true - New(tm) - - NewEnv(tm) - if len(tm.Fatals) == 0 { - t.Fatal("bad double login between new and NewEnv") - } -} - func runUntilFatal(f func()) { stopped := make(chan bool) go func() { diff --git a/examples/simple/simple_test.go b/examples/simple/simple_test.go index a43a807..cd74195 100644 --- a/examples/simple/simple_test.go +++ b/examples/simple/simple_test.go @@ -47,13 +47,11 @@ func TestCounter(t *testing.T) { // counterTestAndSubtest increment counter every non cached call // and cache result for top level test and all of subtests func counterTestAndSubtest(e fixenv.Env) int { - return fixenv.Cache(e, "", &fixenv.FixtureOptions{ - Scope: fixenv.ScopeTestAndSubtests, - }, func() (res int, err error) { + return fixenv.CacheResult(e, func() (*fixenv.GenericResult[int], error) { globalTestAndSubtestCounter++ e.T().Logf("increment globalTestAndSubtestCounter to: ") - return globalTestAndSubtestCounter, nil - }) + return fixenv.NewGenericResult(globalTestAndSubtestCounter), nil + }, fixenv.CacheOptions{Scope: fixenv.ScopeTestAndSubtests}) } func TestTestAndSubtestCounter(t *testing.T) { diff --git a/interface.go b/interface.go index 8af68c1..709af8b 100644 --- a/interface.go +++ b/interface.go @@ -16,20 +16,6 @@ type Env interface { // CacheResult add result of call f to cache and return same result for all // calls for the same function and cache options within cache scope CacheResult(f FixtureFunction, options ...CacheOptions) interface{} - - // Cache result of f calls - // f call exactly once for every combination of scope and params - // params must be json serializable (deserialize not need) - // Deprecated: will be removed in next versions - // Use Env.CacheResult instead. - Cache(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} - - // CacheWithCleanup cache result of f calls - // f call exactly once for every combination of scope and params - // params must be json serializable (deserialize not need) - // Deprecated: will be removed in next versions - // Use Env.CacheResult instead. - CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} } var ( @@ -60,35 +46,11 @@ const ( ScopeTestAndSubtests ) -// FixtureCallbackFunc - function, which result can cached -// res - result for cache. -// if err not nil - T().Fatalf() will called with error message -// if res exit without return (panic, GoExit, t.FailNow, ...) -// then cache error about unexpected exit -type FixtureCallbackFunc func() (res interface{}, err error) - -// FixtureCallbackWithCleanupFunc - function, which result can cached -// res - result for cache. -// cleanup - if not nil - call on fixture cleanup. It called exactly once for every successfully call fixture -// if err not nil - T().Fatalf() will called with error message -// if res exit without return (panic, GoExit, t.FailNow, ...) -// then cache error about unexpected exit -type FixtureCallbackWithCleanupFunc func() (res interface{}, cleanup FixtureCleanupFunc, err error) - // FixtureCleanupFunc - callback function for cleanup after // fixture value out from lifetime scope // it called exactly once for every succesully call fixture type FixtureCleanupFunc func() -// FixtureOptions options for fixenv engine -// for custom manage fixture -type FixtureOptions struct { - // Scope for cache result - Scope CacheScope - - additionlSkipExternalCalls int -} - // FixtureFunction - callback function with structured result // the function can return ErrSkipTest error for skip the test type FixtureFunction func() (*Result, error) diff --git a/result_generic.go b/result_generic.go deleted file mode 100644 index 9172458..0000000 --- a/result_generic.go +++ /dev/null @@ -1,4 +0,0 @@ -//go:build go1.18 -// +build go1.18 - -package fixenv diff --git a/sf/network.go b/sf/network.go index 919d310..6d29840 100644 --- a/sf/network.go +++ b/sf/network.go @@ -25,11 +25,14 @@ func LocalTCPListener(e fixenv.Env) *net.TCPListener { } func LocalTCPListenerNamed(e fixenv.Env, name string) *net.TCPListener { - return e.CacheWithCleanup(name, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + f := func() (*fixenv.Result, error) { listener, err := net.Listen("tcp", "localhost:0") clean := func() { - _ = listener.Close() + if listener != nil { + _ = listener.Close() + } } - return listener, clean, err - }).(*net.TCPListener) + return fixenv.NewResultWithCleanup(listener, clean), err + } + return e.CacheResult(f, fixenv.CacheOptions{CacheKey: name}).(*net.TCPListener) } diff --git a/test_helpers_test.go b/test_helpers_test.go index 85fa63a..4ba3b90 100644 --- a/test_helpers_test.go +++ b/test_helpers_test.go @@ -3,7 +3,6 @@ package fixenv import ( "encoding/json" "reflect" - "strings" "testing" ) @@ -73,13 +72,6 @@ func requireNotNil(t *testing.T, v interface{}) { } } -func requireContains(t *testing.T, s, substr string) { - t.Helper() - if !strings.Contains(s, substr) { - t.Fatalf("'%s' must contains '%s'", s, substr) - } -} - func requirePanic(t *testing.T, f func()) { t.Helper() defer func() {