From 6cf796d9a5078a1a235cd9127e8b5aa7e67cb5c0 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Sun, 27 Aug 2023 14:58:31 +0300 Subject: [PATCH 01/10] init one functions --- env.go | 26 ++++++++++++++++++++++++++ env_generic_sugar.go | 31 +++++++++++++++++++++++++++++++ interface.go | 23 +++++++++++++++++++++++ result_generic.go | 4 ++++ 4 files changed, 84 insertions(+) create mode 100644 result_generic.go diff --git a/env.go b/env.go index 37f71f1..3249b56 100644 --- a/env.go +++ b/env.go @@ -119,6 +119,32 @@ func (e *EnvT) CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f Fix return e.cache(cacheKey, opt, fWithoutCleanup) } +func (e *EnvT) CacheResult(options *CacheOptions, f FixtureFunction) interface{} { + if options == nil { + options = &CacheOptions{} + } + + var resCleanupFunc FixtureCleanupFunc + + var fWithoutCleanup FixtureCallbackFunc = func() (res interface{}, err error) { + result := f() + resCleanupFunc = result.Cleanup + return result.Result, result.Error + } + + opt := &FixtureOptions{} + opt.Scope = options.Scope + + opt.cleanupFunc = func() { + if resCleanupFunc != nil { + resCleanupFunc() + } + } + + return e.cache(options.CacheKey, opt, fWithoutCleanup) + +} + // cache must be call from first-level public function // UserFunction->EnvFunction->cache for good determine caller name func (e *EnvT) cache(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} { diff --git a/env_generic_sugar.go b/env_generic_sugar.go index 09e0a0e..3f3d140 100644 --- a/env_generic_sugar.go +++ b/env_generic_sugar.go @@ -29,9 +29,40 @@ func CacheWithCleanup[TRes any](env Env, cacheKey any, opt *FixtureOptions, f fu return res } +func CacheResult[TRes any](env Env, opts *CacheOptions, f GenericFixtureFunction[TRes]) TRes { + addSkipLevelCache(&opts) + var oldStyleFunc FixtureFunction = func() Result { + res := f() + return Result{ + Result: res.Result, + Error: res.Error, + Cleanup: res.Cleanup, + } + } + res := env.CacheResult(opts, oldStyleFunc) + return res.(TRes) +} + +// GenericFixtureFunction - callback function with structured result +type GenericFixtureFunction[ResT any] func() GenericResult[ResT] + +// GenericResult of fixture callback +type GenericResult[ResT any] struct { + Result ResT + Error error + Cleanup FixtureCleanupFunc +} + func addSkipLevel(optspp **FixtureOptions) { if *optspp == nil { *optspp = &FixtureOptions{} } (*optspp).additionlSkipExternalCalls++ } + +func addSkipLevelCache(optspp **CacheOptions) { + if *optspp == nil { + *optspp = &CacheOptions{} + } + (*optspp).additionlSkipExternalCalls++ +} diff --git a/interface.go b/interface.go index 38f9b4d..0ae1046 100644 --- a/interface.go +++ b/interface.go @@ -7,6 +7,9 @@ type Env interface { // T - return t object of current test/benchmark. T() T + // CacheResult add + CacheResult(options *CacheOptions, f FixtureFunction) interface{} + // Cache cache result of f calls // f call exactly once for every combination of scope and params // params must be json serializable (deserialize not need) @@ -79,6 +82,26 @@ type FixtureOptions struct { cleanupFunc FixtureCleanupFunc } +// FixtureFunction - callback function with structured result +type FixtureFunction func() Result + +// Result of fixture callback +type Result struct { + Result interface{} + Error error + Cleanup FixtureCleanupFunc +} + +type CacheOptions struct { + // Scope for cache result + Scope CacheScope + + // Key for cache results, must be json serializable value + CacheKey interface{} + + additionlSkipExternalCalls int +} + // T is subtype of testing.TB type T interface { // Cleanup registers a function to be called when the test (or subtest) and all its subtests complete. diff --git a/result_generic.go b/result_generic.go new file mode 100644 index 0000000..9172458 --- /dev/null +++ b/result_generic.go @@ -0,0 +1,4 @@ +//go:build go1.18 +// +build go1.18 + +package fixenv From 95ccd587f53235d4233ebca67dbc669b43b4a18e Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Mon, 28 Aug 2023 09:53:30 +0300 Subject: [PATCH 02/10] switch to one cache function --- env.go | 1 + env_generic_sugar_test.go | 50 +++++++++++++++++++++++++++++++++++++++ sf/context.go | 8 +++++-- sf/filesystem.go | 16 +++++++++---- sf/http.go | 7 ++++-- sf/network.go | 6 ++--- 6 files changed, 77 insertions(+), 11 deletions(-) diff --git a/env.go b/env.go index 3249b56..5ec6a18 100644 --- a/env.go +++ b/env.go @@ -134,6 +134,7 @@ func (e *EnvT) CacheResult(options *CacheOptions, f FixtureFunction) interface{} opt := &FixtureOptions{} opt.Scope = options.Scope + opt.additionlSkipExternalCalls = options.additionlSkipExternalCalls opt.cleanupFunc = func() { if resCleanupFunc != nil { diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index dfa3f31..b54c35b 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -89,12 +89,58 @@ func TestCacheWithCleanupGeneric(t *testing.T) { require.Equal(t, 1, f1()) require.Equal(t, 2, f2()) }) +} +func TestCacheResultGeneric(t *testing.T) { + t.Run("PassParams", func(t *testing.T) { + inOpt := &CacheOptions{ + CacheKey: 123, + Scope: ScopeTest, + } + + cleanupCalledBack := 0 + + env := envMock{onCacheResult: func(opt *CacheOptions, f FixtureFunction) interface{} { + opt.additionlSkipExternalCalls-- + require.Equal(t, inOpt, opt) + res := f() + return res.Result + }} + + res := CacheResult(env, inOpt, func() GenericResult[int] { + cleanup := func() { + cleanupCalledBack++ + } + return GenericResult[int]{ + Result: 2, + Cleanup: cleanup, + } + }) + require.Equal(t, 2, res) + }) + t.Run("SkipAdditionalCache", func(t *testing.T) { + test := &internal.TestMock{TestName: t.Name()} + env := newTestEnv(test) + + f1 := func() int { + return CacheResult(env, nil, func() GenericResult[int] { + return GenericResult[int]{Result: 1} + }) + } + f2 := func() int { + return CacheResult(env, nil, func() GenericResult[int] { + return GenericResult[int]{Result: 2} + }) + } + require.Equal(t, 1, f1()) + require.Equal(t, 2, f2()) + }) } 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{} } func (e envMock) T() T { @@ -108,3 +154,7 @@ func (e envMock) Cache(params interface{}, opt *FixtureOptions, f FixtureCallbac func (e envMock) CacheWithCleanup(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} { return e.onCacheWithCleanup(params, opt, f) } + +func (e envMock) CacheResult(opts *CacheOptions, f FixtureFunction) interface{} { + return e.onCacheResult(opts, f) +} diff --git a/sf/context.go b/sf/context.go index d8ae8e9..3df2e3c 100644 --- a/sf/context.go +++ b/sf/context.go @@ -6,8 +6,12 @@ import ( ) func Context(e fixenv.Env) context.Context { - return e.CacheWithCleanup(nil, nil, func() (res interface{}, _ fixenv.FixtureCleanupFunc, _ error) { + return e.CacheResult(nil, func() fixenv.Result { ctx, ctxCancel := context.WithCancel(context.Background()) - return ctx, fixenv.FixtureCleanupFunc(ctxCancel), nil + return fixenv.Result{ + Result: ctx, + Error: nil, + Cleanup: fixenv.FixtureCleanupFunc(ctxCancel), + } }).(context.Context) } diff --git a/sf/filesystem.go b/sf/filesystem.go index f844afd..3b06294 100644 --- a/sf/filesystem.go +++ b/sf/filesystem.go @@ -8,7 +8,7 @@ import ( // TempDir return path for existet temporary folder // the folder will remove after test finish with all contents func TempDir(e fixenv.Env) string { - return e.CacheWithCleanup(nil, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + return e.CacheResult(nil, func() fixenv.Result { dir, err := os.MkdirTemp("", "fixenv-auto-") mustNoErr(e, err, "failed to create temp dir: %v", err) e.T().Logf("Temp dir created: %v", dir) @@ -16,7 +16,11 @@ func TempDir(e fixenv.Env) string { _ = os.RemoveAll(dir) e.T().Logf("Temp dir removed: %v", dir) } - return dir, clean, nil + return fixenv.Result{ + Result: dir, + Error: nil, + Cleanup: clean, + } }).(string) } @@ -28,11 +32,15 @@ func TempFile(e fixenv.Env) string { // TempFileNamed return path to empty file in TempDir // pattern is pattern for os.CreateTemp func TempFileNamed(e fixenv.Env, pattern string) string { - return e.Cache(nil, nil, func() (res interface{}, err error) { + return e.CacheResult(nil, func() fixenv.Result { dir := TempDir(e) f, err := os.CreateTemp(dir, pattern) mustNoErr(e, err, "failed to create temp file: %w", err) fName := f.Name() - return fName, f.Close() + err = f.Close() + mustNoErr(e, err, "failed to close temp file during initialize: %w", err) + return fixenv.Result{ + Result: fName, + } }).(string) } diff --git a/sf/http.go b/sf/http.go index 301b41f..e0f5e66 100644 --- a/sf/http.go +++ b/sf/http.go @@ -6,8 +6,11 @@ import ( ) func HTTPServer(e fixenv.Env) *httptest.Server { - return e.CacheWithCleanup(nil, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + return e.CacheResult(nil, func() fixenv.Result { server := httptest.NewServer(nil) - return server, server.Close, nil + return fixenv.Result{ + Result: server, + Cleanup: server.Close, + } }).(*httptest.Server) } diff --git a/sf/network.go b/sf/network.go index d2cd4c4..51da432 100644 --- a/sf/network.go +++ b/sf/network.go @@ -10,12 +10,12 @@ func FreeLocalTCPAddress(e fixenv.Env) string { } func FreeLocalTCPAddressNamed(e fixenv.Env, name string) string { - return e.Cache(name, nil, func() (res interface{}, err error) { + return e.CacheResult(&fixenv.CacheOptions{CacheKey: name}, func() fixenv.Result { listener := LocalTCPListenerNamed(e, "FreeLocalTCPAddressNamed-"+name) addr := listener.Addr().String() - err = listener.Close() + err := listener.Close() mustNoErr(e, err, "failed to close temp listener: %v", err) - return addr, nil + return fixenv.Result{Result: addr} }).(string) } From b1a7c40b77617d5b32ebb98985beded6a732c961 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Mon, 28 Aug 2023 10:06:01 +0300 Subject: [PATCH 03/10] switch to one cache function --- examples/custom_env/custom_env_test.go | 9 ++++++--- .../custom_env_with_shared_content_test.go | 6 +++--- examples/simple/http_server_test.go | 9 ++++++--- examples/simple/package_scope_test.go | 6 ++++-- examples/simple/simple_old_style_test.go | 4 ++-- examples/simple/simple_test.go | 4 ++-- examples/simple/use_cache_params_test.go | 4 ++-- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/examples/custom_env/custom_env_test.go b/examples/custom_env/custom_env_test.go index bd81a27..be3f9b3 100644 --- a/examples/custom_env/custom_env_test.go +++ b/examples/custom_env/custom_env_test.go @@ -37,18 +37,21 @@ func NewEnv(t *testing.T) (context.Context, *Env) { } func testServer(e fixenv.Env, response string) *httptest.Server { - return fixenv.CacheWithCleanup(e, response, nil, func() (_ *httptest.Server, cleanup fixenv.FixtureCleanupFunc, err error) { + return fixenv.CacheResult(e, &fixenv.CacheOptions{CacheKey: response}, func() fixenv.GenericResult[*httptest.Server] { resp := []byte(response) server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { _, _ = writer.Write(resp) })) e.T().(testing.TB).Logf("Http server start. %q url: %q", response, server.URL) - cleanup = func() { + cleanup := func() { server.Close() e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL) } - return server, cleanup, nil + return fixenv.GenericResult[*httptest.Server]{ + Result: server, + Cleanup: cleanup, + } }) } diff --git a/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go b/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go index afe92de..8ba71db 100644 --- a/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go +++ b/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go @@ -27,16 +27,16 @@ func NewEnv(t *testing.T) *Env { } func testServer(e *Env) *httptest.Server { - return fixenv.CacheWithCleanup(e, "", nil, func() (res *httptest.Server, cleanup fixenv.FixtureCleanupFunc, err error) { + return fixenv.CacheResult(e, nil, func() fixenv.GenericResult[*httptest.Server] { server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { _, _ = writer.Write([]byte(e.Resp)) })) e.T().(testing.TB).Logf("Http server start, url: %q", server.URL) - cleanup = func() { + cleanup := func() { server.Close() e.T().(testing.TB).Logf("Http server stop, url: %q", server.URL) } - return server, cleanup, nil + return fixenv.GenericResult[*httptest.Server]{Result: server, Cleanup: cleanup} }) } diff --git a/examples/simple/http_server_test.go b/examples/simple/http_server_test.go index 4c78333..142c32a 100644 --- a/examples/simple/http_server_test.go +++ b/examples/simple/http_server_test.go @@ -15,18 +15,21 @@ import ( ) func testServer(e fixenv.Env, response string) *httptest.Server { - return e.CacheWithCleanup(response, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + return e.CacheResult(&fixenv.CacheOptions{CacheKey: response}, func() fixenv.Result { resp := []byte(response) server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { _, _ = writer.Write(resp) })) e.T().(testing.TB).Logf("Http server start. %q url: %q", response, server.URL) - cleanup = func() { + cleanup := func() { server.Close() e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL) } - return server, cleanup, nil + return fixenv.Result{ + Result: server, + Cleanup: cleanup, + } }).(*httptest.Server) } diff --git a/examples/simple/package_scope_test.go b/examples/simple/package_scope_test.go index e5b8440..c443d68 100644 --- a/examples/simple/package_scope_test.go +++ b/examples/simple/package_scope_test.go @@ -29,9 +29,11 @@ func TestMain(m *testing.M) { // packageCounter fixture will call without cache once only func packageCounter(e fixenv.Env) int { - return fixenv.Cache(e, "", &fixenv.FixtureOptions{Scope: fixenv.ScopePackage}, func() (res int, err error) { + return fixenv.CacheResult(e, &fixenv.CacheOptions{Scope: fixenv.ScopePackage}, func() fixenv.GenericResult[int] { packageCounterVal++ - return packageCounterVal, nil + return fixenv.GenericResult[int]{ + Result: packageCounterVal, + } }) } diff --git a/examples/simple/simple_old_style_test.go b/examples/simple/simple_old_style_test.go index 80d4ae3..8046582 100644 --- a/examples/simple/simple_old_style_test.go +++ b/examples/simple/simple_old_style_test.go @@ -14,9 +14,9 @@ var ( // counterOldStyle fixture - increment globalCounter every non cached call // and return new globalCounter value func counterOldStyle(e fixenv.Env) int { - return e.Cache(nil, nil, func() (res interface{}, err error) { + return e.CacheResult(nil, func() fixenv.Result { globalCounter++ - return globalCounter, nil + return fixenv.Result{Result: globalCounter} }).(int) } diff --git a/examples/simple/simple_test.go b/examples/simple/simple_test.go index bb67d13..3b66aa6 100644 --- a/examples/simple/simple_test.go +++ b/examples/simple/simple_test.go @@ -12,10 +12,10 @@ import ( // counter fixture - increment globalCounter every non cached call // and return new globalCounter value func counter(e fixenv.Env) int { - return fixenv.Cache(e, nil, nil, func() (res int, err error) { + return fixenv.CacheResult(e, nil, func() fixenv.GenericResult[int] { globalCounter++ e.T().Logf("increment globalCounter to: ") - return globalCounter, nil + return fixenv.GenericResult[int]{Result: globalCounter} }) } diff --git a/examples/simple/use_cache_params_test.go b/examples/simple/use_cache_params_test.go index e390969..0c3d68d 100644 --- a/examples/simple/use_cache_params_test.go +++ b/examples/simple/use_cache_params_test.go @@ -14,8 +14,8 @@ import ( // namedRandom return random number for new name args // but return same value for all calls with same names func namedRandom(e fixenv.Env, name string) int { - return fixenv.Cache(e, name, nil, func() (res int, err error) { - return rand.Int(), nil + return fixenv.CacheResult(e, &fixenv.CacheOptions{CacheKey: name}, func() fixenv.GenericResult[int] { + return fixenv.GenericResult[int]{Result: rand.Int()} }) } From b96cfbac7432c87cf9e1850d98ebc72ea6a96af8 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Fri, 1 Sep 2023 11:32:31 +0300 Subject: [PATCH 04/10] reorder parameters, add helpers --- env.go | 22 +++++--- env_generic_sugar.go | 54 +++++++++++++------ env_generic_sugar_test.go | 40 ++++++++------ examples/custom_env/custom_env_test.go | 11 ++-- .../custom_env_with_shared_content_test.go | 8 +-- examples/simple/http_server_test.go | 11 ++-- examples/simple/package_scope_test.go | 9 ++-- examples/simple/simple_old_style_test.go | 7 +-- examples/simple/simple_test.go | 4 +- examples/simple/use_cache_params_test.go | 8 +-- interface.go | 24 ++++++--- sf/context.go | 11 ++-- sf/filesystem.go | 21 ++++---- sf/http.go | 10 ++-- sf/network.go | 7 +-- 15 files changed, 144 insertions(+), 103 deletions(-) diff --git a/env.go b/env.go index 5ec6a18..a0dbc7d 100644 --- a/env.go +++ b/env.go @@ -119,22 +119,28 @@ func (e *EnvT) CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f Fix return e.cache(cacheKey, opt, fWithoutCleanup) } -func (e *EnvT) CacheResult(options *CacheOptions, f FixtureFunction) interface{} { - if options == nil { - options = &CacheOptions{} +func (e *EnvT) CacheResult(f FixtureFunction, options ...CacheOptions) interface{} { + var cacheOptions CacheOptions + switch len(options) { + case 0: + cacheOptions = CacheOptions{} + case 1: + cacheOptions = options[0] + default: + panic(fmt.Errorf("max len of cache result cacheOptions is 1, given: %v", len(options))) } var resCleanupFunc FixtureCleanupFunc var fWithoutCleanup FixtureCallbackFunc = func() (res interface{}, err error) { - result := f() + result, err := f() resCleanupFunc = result.Cleanup - return result.Result, result.Error + return result.Value, err } opt := &FixtureOptions{} - opt.Scope = options.Scope - opt.additionlSkipExternalCalls = options.additionlSkipExternalCalls + opt.Scope = cacheOptions.Scope + opt.additionlSkipExternalCalls = cacheOptions.additionlSkipExternalCalls opt.cleanupFunc = func() { if resCleanupFunc != nil { @@ -142,7 +148,7 @@ func (e *EnvT) CacheResult(options *CacheOptions, f FixtureFunction) interface{} } } - return e.cache(options.CacheKey, opt, fWithoutCleanup) + return e.cache(cacheOptions.CacheKey, opt, fWithoutCleanup) } diff --git a/env_generic_sugar.go b/env_generic_sugar.go index 3f3d140..ac7455d 100644 --- a/env_generic_sugar.go +++ b/env_generic_sugar.go @@ -3,6 +3,8 @@ package fixenv +import "fmt" + func Cache[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, error)) TRes { addSkipLevel(&opt) callbackResult := env.Cache(cacheKey, opt, func() (res interface{}, err error) { @@ -29,28 +31,49 @@ func CacheWithCleanup[TRes any](env Env, cacheKey any, opt *FixtureOptions, f fu return res } -func CacheResult[TRes any](env Env, opts *CacheOptions, f GenericFixtureFunction[TRes]) TRes { - addSkipLevelCache(&opts) - var oldStyleFunc FixtureFunction = func() Result { - res := f() - return Result{ - Result: res.Result, - Error: res.Error, - Cleanup: res.Cleanup, +func CacheResult[TRes any](env Env, f GenericFixtureFunction[TRes], options ...CacheOptions) TRes { + var cacheOptions CacheOptions + switch len(options) { + case 0: + cacheOptions = CacheOptions{} + case 1: + cacheOptions = options[0] + default: + panic(fmt.Errorf("max len of cache result cacheOptions is 1, given: %v", len(options))) + } + + addSkipLevelCache(&cacheOptions) + var oldStyleFunc FixtureFunction = func() (*Result, error) { + res, err := f() + + var oldStyleRes *Result + if res != nil { + oldStyleRes = &Result{ + Value: res.Value, + ResultAdditional: res.ResultAdditional, + } } + return oldStyleRes, err } - res := env.CacheResult(opts, oldStyleFunc) + res := env.CacheResult(oldStyleFunc, cacheOptions) return res.(TRes) } // GenericFixtureFunction - callback function with structured result -type GenericFixtureFunction[ResT any] func() GenericResult[ResT] +type GenericFixtureFunction[ResT any] func() (*GenericResult[ResT], error) // GenericResult of fixture callback type GenericResult[ResT any] struct { - Result ResT - Error error - Cleanup FixtureCleanupFunc + Value ResT + ResultAdditional +} + +func NewGenericResult[ResT any](res ResT) (*GenericResult[ResT], error) { + return &GenericResult[ResT]{Value: res}, nil +} + +func NewGenericResultWithCleanup[ResT any](res ResT, cleanup FixtureCleanupFunc) (*GenericResult[ResT], error) { + return &GenericResult[ResT]{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}}, nil } func addSkipLevel(optspp **FixtureOptions) { @@ -60,9 +83,6 @@ func addSkipLevel(optspp **FixtureOptions) { (*optspp).additionlSkipExternalCalls++ } -func addSkipLevelCache(optspp **CacheOptions) { - if *optspp == nil { - *optspp = &CacheOptions{} - } +func addSkipLevelCache(optspp *CacheOptions) { (*optspp).additionlSkipExternalCalls++ } diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index b54c35b..3779b71 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -4,6 +4,7 @@ package fixenv import ( + "fmt" "github.com/rekby/fixenv/internal" "testing" @@ -92,29 +93,27 @@ func TestCacheWithCleanupGeneric(t *testing.T) { } func TestCacheResultGeneric(t *testing.T) { t.Run("PassParams", func(t *testing.T) { - inOpt := &CacheOptions{ + inOpt := CacheOptions{ CacheKey: 123, Scope: ScopeTest, } cleanupCalledBack := 0 - env := envMock{onCacheResult: func(opt *CacheOptions, f FixtureFunction) interface{} { + env := envMock{onCacheResult: func(opt CacheOptions, f FixtureFunction) interface{} { opt.additionlSkipExternalCalls-- require.Equal(t, inOpt, opt) - res := f() - return res.Result + res, _ := f() + return res.Value }} - res := CacheResult(env, inOpt, func() GenericResult[int] { + f := func() (*GenericResult[int], error) { cleanup := func() { cleanupCalledBack++ } - return GenericResult[int]{ - Result: 2, - Cleanup: cleanup, - } - }) + return NewGenericResultWithCleanup(2, cleanup) + } + res := CacheResult(env, f, inOpt) require.Equal(t, 2, res) }) t.Run("SkipAdditionalCache", func(t *testing.T) { @@ -122,13 +121,13 @@ func TestCacheResultGeneric(t *testing.T) { env := newTestEnv(test) f1 := func() int { - return CacheResult(env, nil, func() GenericResult[int] { - return GenericResult[int]{Result: 1} + return CacheResult(env, func() (*GenericResult[int], error) { + return NewGenericResult(1) }) } f2 := func() int { - return CacheResult(env, nil, func() GenericResult[int] { - return GenericResult[int]{Result: 2} + return CacheResult(env, func() (*GenericResult[int], error) { + return NewGenericResult(2) }) } @@ -140,7 +139,7 @@ func TestCacheResultGeneric(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 { @@ -155,6 +154,15 @@ func (e envMock) CacheWithCleanup(params interface{}, opt *FixtureOptions, f Fix return e.onCacheWithCleanup(params, opt, f) } -func (e envMock) CacheResult(opts *CacheOptions, f FixtureFunction) interface{} { +func (e envMock) CacheResult(f FixtureFunction, options ...CacheOptions) interface{} { + var opts CacheOptions + switch len(options) { + case 0: + // pass + case 1: + opts = options[0] + default: + panic(fmt.Errorf("max options len is 1, given: %v", len(options))) + } return e.onCacheResult(opts, f) } diff --git a/examples/custom_env/custom_env_test.go b/examples/custom_env/custom_env_test.go index be3f9b3..92be2bd 100644 --- a/examples/custom_env/custom_env_test.go +++ b/examples/custom_env/custom_env_test.go @@ -37,7 +37,7 @@ func NewEnv(t *testing.T) (context.Context, *Env) { } func testServer(e fixenv.Env, response string) *httptest.Server { - return fixenv.CacheResult(e, &fixenv.CacheOptions{CacheKey: response}, func() fixenv.GenericResult[*httptest.Server] { + f := func() (*fixenv.GenericResult[*httptest.Server], error) { resp := []byte(response) server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { @@ -48,11 +48,10 @@ func testServer(e fixenv.Env, response string) *httptest.Server { server.Close() e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL) } - return fixenv.GenericResult[*httptest.Server]{ - Result: server, - Cleanup: cleanup, - } - }) + return fixenv.NewGenericResultWithCleanup(server, cleanup) + } + + return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: response}) } func TestHttpServerSelfEnv(t *testing.T) { diff --git a/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go b/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go index 8ba71db..925f8df 100644 --- a/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go +++ b/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go @@ -27,7 +27,7 @@ func NewEnv(t *testing.T) *Env { } func testServer(e *Env) *httptest.Server { - return fixenv.CacheResult(e, nil, func() fixenv.GenericResult[*httptest.Server] { + f := func() (*fixenv.GenericResult[*httptest.Server], error) { server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { _, _ = writer.Write([]byte(e.Resp)) })) @@ -36,8 +36,10 @@ func testServer(e *Env) *httptest.Server { server.Close() e.T().(testing.TB).Logf("Http server stop, url: %q", server.URL) } - return fixenv.GenericResult[*httptest.Server]{Result: server, Cleanup: cleanup} - }) + return fixenv.NewGenericResultWithCleanup(server, cleanup) + } + + return fixenv.CacheResult(e, f) } func TestHttpServer(t *testing.T) { diff --git a/examples/simple/http_server_test.go b/examples/simple/http_server_test.go index 142c32a..cd2ff5f 100644 --- a/examples/simple/http_server_test.go +++ b/examples/simple/http_server_test.go @@ -15,7 +15,7 @@ import ( ) func testServer(e fixenv.Env, response string) *httptest.Server { - return e.CacheResult(&fixenv.CacheOptions{CacheKey: response}, func() fixenv.Result { + f := func() (*fixenv.Result, error) { resp := []byte(response) server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { @@ -26,11 +26,10 @@ func testServer(e fixenv.Env, response string) *httptest.Server { server.Close() e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL) } - return fixenv.Result{ - Result: server, - Cleanup: cleanup, - } - }).(*httptest.Server) + return fixenv.NewResultWithCleanup(server, cleanup) + } + + return e.CacheResult(f, fixenv.CacheOptions{CacheKey: response}).(*httptest.Server) } func TestHttpServer(t *testing.T) { diff --git a/examples/simple/package_scope_test.go b/examples/simple/package_scope_test.go index c443d68..7dbd7c5 100644 --- a/examples/simple/package_scope_test.go +++ b/examples/simple/package_scope_test.go @@ -29,12 +29,11 @@ func TestMain(m *testing.M) { // packageCounter fixture will call without cache once only func packageCounter(e fixenv.Env) int { - return fixenv.CacheResult(e, &fixenv.CacheOptions{Scope: fixenv.ScopePackage}, func() fixenv.GenericResult[int] { + f := func() (*fixenv.GenericResult[int], error) { packageCounterVal++ - return fixenv.GenericResult[int]{ - Result: packageCounterVal, - } - }) + return fixenv.NewGenericResult(packageCounterVal) + } + return fixenv.CacheResult(e, f, fixenv.CacheOptions{Scope: fixenv.ScopePackage}) } func TestPackageFirst(t *testing.T) { diff --git a/examples/simple/simple_old_style_test.go b/examples/simple/simple_old_style_test.go index 8046582..2b1b5f2 100644 --- a/examples/simple/simple_old_style_test.go +++ b/examples/simple/simple_old_style_test.go @@ -14,10 +14,11 @@ var ( // counterOldStyle fixture - increment globalCounter every non cached call // and return new globalCounter value func counterOldStyle(e fixenv.Env) int { - return e.CacheResult(nil, func() fixenv.Result { + f := func() (*fixenv.Result, error) { globalCounter++ - return fixenv.Result{Result: globalCounter} - }).(int) + return fixenv.NewResult(globalCounter) + } + return e.CacheResult(f).(int) } func TestCounterOldStyle(t *testing.T) { diff --git a/examples/simple/simple_test.go b/examples/simple/simple_test.go index 3b66aa6..c81cd10 100644 --- a/examples/simple/simple_test.go +++ b/examples/simple/simple_test.go @@ -12,10 +12,10 @@ import ( // counter fixture - increment globalCounter every non cached call // and return new globalCounter value func counter(e fixenv.Env) int { - return fixenv.CacheResult(e, nil, func() fixenv.GenericResult[int] { + return fixenv.CacheResult(e, func() (*fixenv.GenericResult[int], error) { globalCounter++ e.T().Logf("increment globalCounter to: ") - return fixenv.GenericResult[int]{Result: globalCounter} + return fixenv.NewGenericResult(globalCounter) }) } diff --git a/examples/simple/use_cache_params_test.go b/examples/simple/use_cache_params_test.go index 0c3d68d..922a36c 100644 --- a/examples/simple/use_cache_params_test.go +++ b/examples/simple/use_cache_params_test.go @@ -14,9 +14,11 @@ import ( // namedRandom return random number for new name args // but return same value for all calls with same names func namedRandom(e fixenv.Env, name string) int { - return fixenv.CacheResult(e, &fixenv.CacheOptions{CacheKey: name}, func() fixenv.GenericResult[int] { - return fixenv.GenericResult[int]{Result: rand.Int()} - }) + f := func() (*fixenv.GenericResult[int], error) { + return fixenv.NewGenericResult(rand.Int()) + } + + return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: name}) } func TestNamedRandom(t *testing.T) { diff --git a/interface.go b/interface.go index 0ae1046..48bd577 100644 --- a/interface.go +++ b/interface.go @@ -7,10 +7,11 @@ type Env interface { // T - return t object of current test/benchmark. T() T - // CacheResult add - CacheResult(options *CacheOptions, f FixtureFunction) 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 cache result of f calls + // Cache result of f calls // f call exactly once for every combination of scope and params // params must be json serializable (deserialize not need) Cache(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} @@ -83,15 +84,26 @@ type FixtureOptions struct { } // FixtureFunction - callback function with structured result -type FixtureFunction func() Result +type FixtureFunction func() (*Result, error) // Result of fixture callback type Result struct { - Result interface{} - Error error + Value interface{} + ResultAdditional +} + +type ResultAdditional struct { Cleanup FixtureCleanupFunc } +func NewResult(res interface{}) (*Result, error) { + return &Result{Value: res}, nil +} + +func NewResultWithCleanup(res interface{}, cleanup FixtureCleanupFunc) (*Result, error) { + return &Result{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}}, nil +} + type CacheOptions struct { // Scope for cache result Scope CacheScope diff --git a/sf/context.go b/sf/context.go index 3df2e3c..9318201 100644 --- a/sf/context.go +++ b/sf/context.go @@ -6,12 +6,9 @@ import ( ) func Context(e fixenv.Env) context.Context { - return e.CacheResult(nil, func() fixenv.Result { + f := func() (*fixenv.Result, error) { ctx, ctxCancel := context.WithCancel(context.Background()) - return fixenv.Result{ - Result: ctx, - Error: nil, - Cleanup: fixenv.FixtureCleanupFunc(ctxCancel), - } - }).(context.Context) + return fixenv.NewResultWithCleanup(ctx, fixenv.FixtureCleanupFunc(ctxCancel)) + } + return e.CacheResult(f).(context.Context) } diff --git a/sf/filesystem.go b/sf/filesystem.go index 3b06294..c5b09be 100644 --- a/sf/filesystem.go +++ b/sf/filesystem.go @@ -8,7 +8,7 @@ import ( // TempDir return path for existet temporary folder // the folder will remove after test finish with all contents func TempDir(e fixenv.Env) string { - return e.CacheResult(nil, func() fixenv.Result { + f := func() (*fixenv.Result, error) { dir, err := os.MkdirTemp("", "fixenv-auto-") mustNoErr(e, err, "failed to create temp dir: %v", err) e.T().Logf("Temp dir created: %v", dir) @@ -16,12 +16,9 @@ func TempDir(e fixenv.Env) string { _ = os.RemoveAll(dir) e.T().Logf("Temp dir removed: %v", dir) } - return fixenv.Result{ - Result: dir, - Error: nil, - Cleanup: clean, - } - }).(string) + return fixenv.NewResultWithCleanup(dir, clean) + } + return e.CacheResult(f).(string) } // TempFile return path to empty existed file in TempDir @@ -32,15 +29,15 @@ func TempFile(e fixenv.Env) string { // TempFileNamed return path to empty file in TempDir // pattern is pattern for os.CreateTemp func TempFileNamed(e fixenv.Env, pattern string) string { - return e.CacheResult(nil, func() fixenv.Result { + f := func() (*fixenv.Result, error) { dir := TempDir(e) f, err := os.CreateTemp(dir, pattern) mustNoErr(e, err, "failed to create temp file: %w", err) fName := f.Name() err = f.Close() mustNoErr(e, err, "failed to close temp file during initialize: %w", err) - return fixenv.Result{ - Result: fName, - } - }).(string) + return fixenv.NewResult(fName) + } + + return e.CacheResult(f).(string) } diff --git a/sf/http.go b/sf/http.go index e0f5e66..1fd099c 100644 --- a/sf/http.go +++ b/sf/http.go @@ -6,11 +6,9 @@ import ( ) func HTTPServer(e fixenv.Env) *httptest.Server { - return e.CacheResult(nil, func() fixenv.Result { + f := func() (*fixenv.Result, error) { server := httptest.NewServer(nil) - return fixenv.Result{ - Result: server, - Cleanup: server.Close, - } - }).(*httptest.Server) + return fixenv.NewResultWithCleanup(server, server.Close) + } + return e.CacheResult(f).(*httptest.Server) } diff --git a/sf/network.go b/sf/network.go index 51da432..c5b00cb 100644 --- a/sf/network.go +++ b/sf/network.go @@ -10,13 +10,14 @@ func FreeLocalTCPAddress(e fixenv.Env) string { } func FreeLocalTCPAddressNamed(e fixenv.Env, name string) string { - return e.CacheResult(&fixenv.CacheOptions{CacheKey: name}, func() fixenv.Result { + f := func() (*fixenv.Result, error) { listener := LocalTCPListenerNamed(e, "FreeLocalTCPAddressNamed-"+name) addr := listener.Addr().String() err := listener.Close() mustNoErr(e, err, "failed to close temp listener: %v", err) - return fixenv.Result{Result: addr} - }).(string) + return fixenv.NewResult(addr) + } + return e.CacheResult(f, fixenv.CacheOptions{CacheKey: name}).(string) } func LocalTCPListener(e fixenv.Env) *net.TCPListener { From b840f23d6253d1fbe77a0f08d5e59c79f3d11cf4 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Fri, 1 Sep 2023 11:37:27 +0300 Subject: [PATCH 05/10] fix counter style --- examples/simple/simple_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/simple/simple_test.go b/examples/simple/simple_test.go index c81cd10..c1bacca 100644 --- a/examples/simple/simple_test.go +++ b/examples/simple/simple_test.go @@ -12,11 +12,13 @@ import ( // counter fixture - increment globalCounter every non cached call // and return new globalCounter value func counter(e fixenv.Env) int { - return fixenv.CacheResult(e, func() (*fixenv.GenericResult[int], error) { + f := func() (*fixenv.GenericResult[int], error) { globalCounter++ e.T().Logf("increment globalCounter to: ") return fixenv.NewGenericResult(globalCounter) - }) + } + + return fixenv.CacheResult(e, f) } func TestCounter(t *testing.T) { From c7969573402be973738e73ed394c20439a174621 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Fri, 1 Sep 2023 11:47:02 +0300 Subject: [PATCH 06/10] explicit return nil error --- env_generic_sugar.go | 17 +++++++++++++---- env_generic_sugar_test.go | 6 +++--- examples/custom_env/custom_env_test.go | 2 +- .../custom_env_with_shared_content_test.go | 2 +- examples/simple/http_server_test.go | 2 +- examples/simple/package_scope_test.go | 2 +- examples/simple/simple_old_style_test.go | 2 +- examples/simple/simple_test.go | 2 +- examples/simple/use_cache_params_test.go | 2 +- interface.go | 8 ++++---- sf/context.go | 2 +- sf/filesystem.go | 4 ++-- sf/http.go | 2 +- sf/network.go | 2 +- 14 files changed, 32 insertions(+), 23 deletions(-) diff --git a/env_generic_sugar.go b/env_generic_sugar.go index ac7455d..5995e40 100644 --- a/env_generic_sugar.go +++ b/env_generic_sugar.go @@ -5,6 +5,8 @@ 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 func Cache[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, error)) TRes { addSkipLevel(&opt) callbackResult := env.Cache(cacheKey, opt, func() (res interface{}, err error) { @@ -18,6 +20,9 @@ func Cache[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, 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 func CacheWithCleanup[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, FixtureCleanupFunc, error)) TRes { addSkipLevel(&opt) callbackResult := env.CacheWithCleanup(cacheKey, opt, func() (res interface{}, cleanup FixtureCleanupFunc, err error) { @@ -31,6 +36,8 @@ func CacheWithCleanup[TRes any](env Env, cacheKey any, opt *FixtureOptions, f fu 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 { var cacheOptions CacheOptions switch len(options) { @@ -68,12 +75,14 @@ type GenericResult[ResT any] struct { ResultAdditional } -func NewGenericResult[ResT any](res ResT) (*GenericResult[ResT], error) { - return &GenericResult[ResT]{Value: res}, nil +// NewGenericResult return result struct and nil error. +// Use it for smaller boilerplate for define generic specifications +func NewGenericResult[ResT any](res ResT) *GenericResult[ResT] { + return &GenericResult[ResT]{Value: res} } -func NewGenericResultWithCleanup[ResT any](res ResT, cleanup FixtureCleanupFunc) (*GenericResult[ResT], error) { - return &GenericResult[ResT]{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}}, nil +func NewGenericResultWithCleanup[ResT any](res ResT, cleanup FixtureCleanupFunc) *GenericResult[ResT] { + return &GenericResult[ResT]{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}} } func addSkipLevel(optspp **FixtureOptions) { diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index 3779b71..a49a939 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -111,7 +111,7 @@ func TestCacheResultGeneric(t *testing.T) { cleanup := func() { cleanupCalledBack++ } - return NewGenericResultWithCleanup(2, cleanup) + return NewGenericResultWithCleanup(2, cleanup), nil } res := CacheResult(env, f, inOpt) require.Equal(t, 2, res) @@ -122,12 +122,12 @@ func TestCacheResultGeneric(t *testing.T) { f1 := func() int { return CacheResult(env, func() (*GenericResult[int], error) { - return NewGenericResult(1) + return NewGenericResult(1), nil }) } f2 := func() int { return CacheResult(env, func() (*GenericResult[int], error) { - return NewGenericResult(2) + return NewGenericResult(2), nil }) } diff --git a/examples/custom_env/custom_env_test.go b/examples/custom_env/custom_env_test.go index 92be2bd..98f9798 100644 --- a/examples/custom_env/custom_env_test.go +++ b/examples/custom_env/custom_env_test.go @@ -48,7 +48,7 @@ func testServer(e fixenv.Env, response string) *httptest.Server { server.Close() e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL) } - return fixenv.NewGenericResultWithCleanup(server, cleanup) + return fixenv.NewGenericResultWithCleanup(server, cleanup), nil } return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: response}) diff --git a/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go b/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go index 925f8df..38d9195 100644 --- a/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go +++ b/examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go @@ -36,7 +36,7 @@ func testServer(e *Env) *httptest.Server { server.Close() e.T().(testing.TB).Logf("Http server stop, url: %q", server.URL) } - return fixenv.NewGenericResultWithCleanup(server, cleanup) + return fixenv.NewGenericResultWithCleanup(server, cleanup), nil } return fixenv.CacheResult(e, f) diff --git a/examples/simple/http_server_test.go b/examples/simple/http_server_test.go index cd2ff5f..fd7e37a 100644 --- a/examples/simple/http_server_test.go +++ b/examples/simple/http_server_test.go @@ -26,7 +26,7 @@ func testServer(e fixenv.Env, response string) *httptest.Server { server.Close() e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL) } - return fixenv.NewResultWithCleanup(server, cleanup) + return fixenv.NewResultWithCleanup(server, cleanup), nil } return e.CacheResult(f, fixenv.CacheOptions{CacheKey: response}).(*httptest.Server) diff --git a/examples/simple/package_scope_test.go b/examples/simple/package_scope_test.go index 7dbd7c5..42241ec 100644 --- a/examples/simple/package_scope_test.go +++ b/examples/simple/package_scope_test.go @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { func packageCounter(e fixenv.Env) int { f := func() (*fixenv.GenericResult[int], error) { packageCounterVal++ - return fixenv.NewGenericResult(packageCounterVal) + return fixenv.NewGenericResult(packageCounterVal), nil } return fixenv.CacheResult(e, f, fixenv.CacheOptions{Scope: fixenv.ScopePackage}) } diff --git a/examples/simple/simple_old_style_test.go b/examples/simple/simple_old_style_test.go index 2b1b5f2..9a4a62c 100644 --- a/examples/simple/simple_old_style_test.go +++ b/examples/simple/simple_old_style_test.go @@ -16,7 +16,7 @@ var ( func counterOldStyle(e fixenv.Env) int { f := func() (*fixenv.Result, error) { globalCounter++ - return fixenv.NewResult(globalCounter) + return fixenv.NewResult(globalCounter), nil } return e.CacheResult(f).(int) } diff --git a/examples/simple/simple_test.go b/examples/simple/simple_test.go index c1bacca..4a42d00 100644 --- a/examples/simple/simple_test.go +++ b/examples/simple/simple_test.go @@ -15,7 +15,7 @@ func counter(e fixenv.Env) int { f := func() (*fixenv.GenericResult[int], error) { globalCounter++ e.T().Logf("increment globalCounter to: ") - return fixenv.NewGenericResult(globalCounter) + return fixenv.NewGenericResult(globalCounter), nil } return fixenv.CacheResult(e, f) diff --git a/examples/simple/use_cache_params_test.go b/examples/simple/use_cache_params_test.go index 922a36c..fb8fc45 100644 --- a/examples/simple/use_cache_params_test.go +++ b/examples/simple/use_cache_params_test.go @@ -15,7 +15,7 @@ import ( // but return same value for all calls with same names func namedRandom(e fixenv.Env, name string) int { f := func() (*fixenv.GenericResult[int], error) { - return fixenv.NewGenericResult(rand.Int()) + return fixenv.NewGenericResult(rand.Int()), nil } return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: name}) diff --git a/interface.go b/interface.go index 48bd577..97555d7 100644 --- a/interface.go +++ b/interface.go @@ -96,12 +96,12 @@ type ResultAdditional struct { Cleanup FixtureCleanupFunc } -func NewResult(res interface{}) (*Result, error) { - return &Result{Value: res}, nil +func NewResult(res interface{}) *Result { + return &Result{Value: res} } -func NewResultWithCleanup(res interface{}, cleanup FixtureCleanupFunc) (*Result, error) { - return &Result{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}}, nil +func NewResultWithCleanup(res interface{}, cleanup FixtureCleanupFunc) *Result { + return &Result{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}} } type CacheOptions struct { diff --git a/sf/context.go b/sf/context.go index 9318201..d5efbdd 100644 --- a/sf/context.go +++ b/sf/context.go @@ -8,7 +8,7 @@ import ( func Context(e fixenv.Env) context.Context { f := func() (*fixenv.Result, error) { ctx, ctxCancel := context.WithCancel(context.Background()) - return fixenv.NewResultWithCleanup(ctx, fixenv.FixtureCleanupFunc(ctxCancel)) + return fixenv.NewResultWithCleanup(ctx, fixenv.FixtureCleanupFunc(ctxCancel)), nil } return e.CacheResult(f).(context.Context) } diff --git a/sf/filesystem.go b/sf/filesystem.go index c5b09be..5f0b3ed 100644 --- a/sf/filesystem.go +++ b/sf/filesystem.go @@ -16,7 +16,7 @@ func TempDir(e fixenv.Env) string { _ = os.RemoveAll(dir) e.T().Logf("Temp dir removed: %v", dir) } - return fixenv.NewResultWithCleanup(dir, clean) + return fixenv.NewResultWithCleanup(dir, clean), nil } return e.CacheResult(f).(string) } @@ -36,7 +36,7 @@ func TempFileNamed(e fixenv.Env, pattern string) string { fName := f.Name() err = f.Close() mustNoErr(e, err, "failed to close temp file during initialize: %w", err) - return fixenv.NewResult(fName) + return fixenv.NewResult(fName), nil } return e.CacheResult(f).(string) diff --git a/sf/http.go b/sf/http.go index 1fd099c..71a5fe7 100644 --- a/sf/http.go +++ b/sf/http.go @@ -8,7 +8,7 @@ import ( func HTTPServer(e fixenv.Env) *httptest.Server { f := func() (*fixenv.Result, error) { server := httptest.NewServer(nil) - return fixenv.NewResultWithCleanup(server, server.Close) + return fixenv.NewResultWithCleanup(server, server.Close), nil } return e.CacheResult(f).(*httptest.Server) } diff --git a/sf/network.go b/sf/network.go index c5b00cb..919d310 100644 --- a/sf/network.go +++ b/sf/network.go @@ -15,7 +15,7 @@ func FreeLocalTCPAddressNamed(e fixenv.Env, name string) string { addr := listener.Addr().String() err := listener.Close() mustNoErr(e, err, "failed to close temp listener: %v", err) - return fixenv.NewResult(addr) + return fixenv.NewResult(addr), nil } return e.CacheResult(f, fixenv.CacheOptions{CacheKey: name}).(string) } From 50b561b0774c09355101e3bf0445bb60c36e2a2f Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Sun, 3 Sep 2023 01:19:38 +0300 Subject: [PATCH 07/10] test CacheResult --- env.go | 1 + env_generic_sugar.go | 2 ++ env_generic_sugar_test.go | 54 +++++++++++++++++++++++++++++++++++++++ env_test.go | 53 +++++++++++++++++++++++++++++++++++++- go.sum | 1 + interface.go | 2 ++ 6 files changed, 112 insertions(+), 1 deletion(-) diff --git a/env.go b/env.go index a0dbc7d..546601d 100644 --- a/env.go +++ b/env.go @@ -141,6 +141,7 @@ func (e *EnvT) CacheResult(f FixtureFunction, options ...CacheOptions) interface opt := &FixtureOptions{} opt.Scope = cacheOptions.Scope opt.additionlSkipExternalCalls = cacheOptions.additionlSkipExternalCalls + opt.cleanupFunc = resCleanupFunc opt.cleanupFunc = func() { if resCleanupFunc != nil { diff --git a/env_generic_sugar.go b/env_generic_sugar.go index 5995e40..0594e9a 100644 --- a/env_generic_sugar.go +++ b/env_generic_sugar.go @@ -7,6 +7,7 @@ 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) callbackResult := env.Cache(cacheKey, opt, func() (res interface{}, err error) { @@ -23,6 +24,7 @@ func Cache[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, // 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) callbackResult := env.CacheWithCleanup(cacheKey, opt, func() (res interface{}, cleanup FixtureCleanupFunc, err error) { diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index a49a939..78a826e 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -6,6 +6,8 @@ package fixenv import ( "fmt" "github.com/rekby/fixenv/internal" + "github.com/stretchr/testify/assert" + "math/rand" "testing" "github.com/stretchr/testify/require" @@ -91,6 +93,7 @@ func TestCacheWithCleanupGeneric(t *testing.T) { require.Equal(t, 2, f2()) }) } + func TestCacheResultGeneric(t *testing.T) { t.Run("PassParams", func(t *testing.T) { inOpt := CacheOptions{ @@ -136,6 +139,57 @@ func TestCacheResultGeneric(t *testing.T) { }) } +func TestCacheResultPanic(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + at := assert.New(t) + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := New(tMock) + + rndFix := func(e Env) int { + return CacheResult(e, func() (*GenericResult[int], error) { + return NewGenericResult(rand.Int()), nil + }) + } + first := rndFix(e) + second := rndFix(e) + + at.Equal(first, second) + }) + t.Run("Options", func(t *testing.T) { + at := assert.New(t) + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := New(tMock) + + rndFix := func(e Env, name string) int { + return CacheResult(e, func() (*GenericResult[int], error) { + return NewGenericResult(rand.Int()), nil + }, CacheOptions{CacheKey: name}) + } + first1 := rndFix(e, "first") + first2 := rndFix(e, "first") + second1 := rndFix(e, "second") + second2 := rndFix(e, "second") + + at.Equal(first1, first2) + at.Equal(second1, second2) + at.NotEqual(first1, second1) + }) + t.Run("Panic", func(t *testing.T) { + at := assert.New(t) + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := New(tMock) + + rndFix := func(e Env, name string) int { + return CacheResult(e, func() (*GenericResult[int], error) { + return NewGenericResult(rand.Int()), nil + }, CacheOptions{CacheKey: name}, CacheOptions{CacheKey: name}) + } + at.Panics(func() { + rndFix(e, "first") + }) + }) +} + type envMock struct { onCache func(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} onCacheWithCleanup func(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} diff --git a/env_test.go b/env_test.go index 6457fc3..fc45a4b 100644 --- a/env_test.go +++ b/env_test.go @@ -3,6 +3,7 @@ package fixenv import ( "errors" "github.com/rekby/fixenv/internal" + "math/rand" "runtime" "sync" "testing" @@ -301,6 +302,57 @@ func Test_Env_CacheWithCleanup(t *testing.T) { }) } +func Test_Env_CacheResult(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + at := assert.New(t) + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := New(tMock) + + rndFix := func(e Env) int { + return e.CacheResult(func() (*Result, error) { + return NewResult(rand.Int()), nil + }).(int) + } + first := rndFix(e) + second := rndFix(e) + + at.Equal(first, second) + }) + t.Run("Options", func(t *testing.T) { + at := assert.New(t) + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := New(tMock) + + rndFix := func(e Env, name string) int { + return e.CacheResult(func() (*Result, error) { + return NewResult(rand.Int()), nil + }, CacheOptions{CacheKey: name}).(int) + } + first1 := rndFix(e, "first") + first2 := rndFix(e, "first") + second1 := rndFix(e, "second") + second2 := rndFix(e, "second") + + at.Equal(first1, first2) + at.Equal(second1, second2) + at.NotEqual(first1, second1) + }) + t.Run("Panic", func(t *testing.T) { + at := assert.New(t) + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} + e := New(tMock) + + rndFix := func(e Env, name string) int { + return e.CacheResult(func() (*Result, error) { + return NewResult(rand.Int()), nil + }, CacheOptions{CacheKey: name}, CacheOptions{CacheKey: name}).(int) + } + at.Panics(func() { + rndFix(e, "first") + }) + }) +} + func Test_FixtureWrapper(t *testing.T) { t.Run("ok", func(t *testing.T) { at := assert.New(t) @@ -651,7 +703,6 @@ func TestNewEnv(t *testing.T) { tm.SkipGoexit = true New(tm) - //goland:noinspection GoDeprecation NewEnv(tm) if len(tm.Fatals) == 0 { t.Fatal("bad double login between new and NewEnv") diff --git a/go.sum b/go.sum index acb88a4..26500d5 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/interface.go b/interface.go index 97555d7..a171b3b 100644 --- a/interface.go +++ b/interface.go @@ -14,11 +14,13 @@ type Env 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: Use CacheResult 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: Use CacheResult CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} } From 32fe5ebf686390a4fba2e3b81b60012aba686ac6 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Sun, 24 Sep 2023 13:17:25 +0300 Subject: [PATCH 08/10] add tests --- env_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/env_test.go b/env_test.go index fc45a4b..ad28420 100644 --- a/env_test.go +++ b/env_test.go @@ -337,6 +337,35 @@ func Test_Env_CacheResult(t *testing.T) { at.Equal(second1, second2) at.NotEqual(first1, second1) }) + t.Run("WithCleanup", func(t *testing.T) { + tMock := &internal.TestMock{TestName: t.Name()} + env := newTestEnv(tMock) + + callbackCalled := 0 + cleanupCalled := 0 + var callbackFunc FixtureFunction = func() (*Result, error) { + callbackCalled++ + cleanup := func() { + cleanupCalled++ + } + return NewResultWithCleanup(callbackCalled, cleanup), nil + } + + res := env.CacheResult(callbackFunc) + require.Equal(t, 1, res) + require.Equal(t, 1, callbackCalled) + require.Equal(t, cleanupCalled, 0) + + // got value from cache + res = env.CacheResult(callbackFunc) + require.Equal(t, 1, res) + require.Equal(t, 1, callbackCalled) + require.Equal(t, cleanupCalled, 0) + + tMock.CallCleanup() + require.Equal(t, 1, callbackCalled) + require.Equal(t, 1, cleanupCalled) + }) t.Run("Panic", func(t *testing.T) { at := assert.New(t) tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} From e0ba363f8eedfebe90af5912b4239fbee5ba095e Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Sun, 24 Sep 2023 13:24:01 +0300 Subject: [PATCH 09/10] add deprecated methods --- env.go | 7 +++++++ interface.go | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/env.go b/env.go index 546601d..e66c7ca 100644 --- a/env.go +++ b/env.go @@ -83,6 +83,8 @@ func (e *EnvT) T() T { // 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{} { return e.cache(cacheKey, opt, f) } @@ -99,6 +101,8 @@ func (e *EnvT) Cache(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbac // 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{} @@ -119,6 +123,9 @@ func (e *EnvT) CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f Fix return e.cache(cacheKey, opt, fWithoutCleanup) } +// 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) func (e *EnvT) CacheResult(f FixtureFunction, options ...CacheOptions) interface{} { var cacheOptions CacheOptions switch len(options) { diff --git a/interface.go b/interface.go index a171b3b..cc1870d 100644 --- a/interface.go +++ b/interface.go @@ -14,13 +14,15 @@ type Env 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: Use CacheResult + // 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: Use CacheResult + // Deprecated: will be removed in next versions + // Use Env.CacheResult instead. CacheWithCleanup(cacheKey interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} } From fade6db043ceaba537f26bd6e8c49ee259538fdc Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Sun, 24 Sep 2023 18:34:20 +0300 Subject: [PATCH 10/10] add comemnt --- interface.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface.go b/interface.go index cc1870d..8e730c5 100644 --- a/interface.go +++ b/interface.go @@ -3,6 +3,12 @@ package fixenv import "errors" // Env - fixture cache engine. +// Env interface described TEnv method and need for easy reuse different Envs with +// same fixtures. +// +// The interface can be extended. +// Create own Envs with embed TEnv or the interface for auto-implement all methods +// in the future. type Env interface { // T - return t object of current test/benchmark. T() T