diff --git a/cache.go b/cache.go index 8926f5c..d493411 100644 --- a/cache.go +++ b/cache.go @@ -14,7 +14,7 @@ type cache struct { type cacheKey string type cacheVal struct { - res *Result + res *result err error } @@ -28,7 +28,7 @@ func newCache() *cache { // GetOrSet atomic get exist values from cache or call f for set new value and return it. // it has guarantee about only one f will execute same time for the key. // but many f may execute simultaneously for different keys -func (c *cache) GetOrSet(key cacheKey, f FixtureFunction) (*Result, error) { +func (c *cache) GetOrSet(key cacheKey, f fixtureFunction) (*result, error) { res, ok := c.get(key) if ok { return res.res, res.err @@ -57,7 +57,7 @@ func (c *cache) get(key cacheKey) (cacheVal, bool) { return val, ok } -func (c *cache) setOnce(key cacheKey, f FixtureFunction) { +func (c *cache) setOnce(key cacheKey, f fixtureFunction) { c.m.Lock() setOnce := c.setLocks[key] if setOnce == nil { @@ -68,7 +68,7 @@ func (c *cache) setOnce(key cacheKey, f FixtureFunction) { setOnce.Do(func() { var err = errors.New("unexpected exit from function") - var res *Result + var res *result // save result must be deferred because f() may stop goroutine without result // for example by panic or GoExit diff --git a/cache_test.go b/cache_test.go index 16f430a..7244cdb 100644 --- a/cache_test.go +++ b/cache_test.go @@ -19,8 +19,8 @@ func TestCache_DeleteKeys(t *testing.T) { k2 := cacheKey("k2") k3 := cacheKey("k3") val1 := "test1" - valFunc := func() (*Result, error) { - return NewResult(val1), nil + valFunc := func() (*result, error) { + return newResult(val1), nil } c.setOnce(k1, valFunc) @@ -37,8 +37,8 @@ func TestCache_DeleteKeys(t *testing.T) { requireEquals(t, val1, res.res.Value.(string)) val2 := "test2" - c.setOnce(k1, func() (res *Result, err error) { - return NewResult(val2), nil + c.setOnce(k1, func() (res *result, err error) { + return newResult(val2), nil }) res, ok = c.get(k1) requireTrue(t, ok) @@ -47,8 +47,8 @@ func TestCache_DeleteKeys(t *testing.T) { t.Run("mutex", func(t *testing.T) { c := newCache() - c.setOnce("asd", func() (res *Result, err error) { - return NewResult(nil), nil + c.setOnce("asd", func() (res *result, err error) { + return newResult(nil), nil }) c.m.RLock() @@ -77,17 +77,17 @@ func TestCache_Get(t *testing.T) { _, ok := c.get("qwe") requireFalse(t, ok) - c.store["asd"] = cacheVal{res: NewResult("val")} + c.store["asd"] = cacheVal{res: newResult("val")} res, ok := c.get("asd") requireTrue(t, ok) - requireEquals(t, cacheVal{res: NewResult("val")}, res) + requireEquals(t, cacheVal{res: newResult("val")}, res) }) t.Run("read_mutex", func(t *testing.T) { c := newCache() - c.setOnce("asd", func() (res *Result, err error) { - return NewResult(nil), nil + c.setOnce("asd", func() (res *result, err error) { + return newResult(nil), nil }) c.m.RLock() _, ok := c.get("asd") @@ -97,8 +97,8 @@ func TestCache_Get(t *testing.T) { t.Run("write_mutex", func(t *testing.T) { c := newCache() - c.setOnce("asd", func() (res *Result, err error) { - return NewResult(nil), nil + c.setOnce("asd", func() (res *result, err error) { + return newResult(nil), nil }) c.m.Lock() var ok bool @@ -125,17 +125,17 @@ func TestCache_SetOnce(t *testing.T) { c := newCache() cnt := 0 key1 := cacheKey("1") - c.setOnce(key1, func() (res *Result, err error) { + c.setOnce(key1, func() (res *result, err error) { cnt++ - return NewResult(1), nil + return newResult(1), nil }) requireEquals(t, 1, cnt) requireEquals(t, 1, c.store[key1].res.Value) noError(t, c.store[key1].err) - c.setOnce(key1, func() (res *Result, err error) { + c.setOnce(key1, func() (res *result, err error) { cnt++ - return NewResult(2), nil + return newResult(2), nil }) requireEquals(t, 1, cnt) requireEquals(t, 1, c.store[key1].res.Value) @@ -147,13 +147,13 @@ func TestCache_SetOnce(t *testing.T) { key1 := cacheKey("1") key2 := cacheKey("2") cnt := 0 - c.setOnce(key1, func() (res *Result, err error) { + c.setOnce(key1, func() (res *result, err error) { cnt++ - return NewResult(1), nil + return newResult(1), nil }) - c.setOnce(key2, func() (res *Result, err error) { + c.setOnce(key2, func() (res *result, err error) { cnt++ - return NewResult(2), nil + return newResult(2), nil }) requireEquals(t, 2, cnt) requireEquals(t, 1, c.store[key1].res.Value) @@ -170,9 +170,9 @@ func TestCache_SetOnce(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - c.setOnce(key, func() (res *Result, err error) { + c.setOnce(key, func() (res *result, err error) { runtime.Goexit() - return NewResult(3), nil + return newResult(3), nil }) }() wg.Wait() @@ -193,11 +193,11 @@ func TestCache_SetOnce(t *testing.T) { // first go func() { - c.setOnce(key, func() (res *Result, err error) { + c.setOnce(key, func() (res *result, err error) { close(firstMuStarted) <-firstMuNeedFinish - return NewResult(1), nil + return newResult(1), nil }) close(firstMuFinished) @@ -210,9 +210,9 @@ func TestCache_SetOnce(t *testing.T) { var doneSecond int64 go func() { - c.setOnce(key, func() (res *Result, err error) { + c.setOnce(key, func() (res *result, err error) { // func will not call never - return NewResult(2), nil + return newResult(2), nil }) // must executed only after fist func finished @@ -254,12 +254,12 @@ func TestCache_SetOnce(t *testing.T) { // first go func() { - c.setOnce(key1, func() (res *Result, err error) { + c.setOnce(key1, func() (res *result, err error) { close(firstMuStarted) <-firstMuNeedFinish - return NewResult(1), nil + return newResult(1), nil }) close(firstMuFinished) @@ -269,9 +269,9 @@ func TestCache_SetOnce(t *testing.T) { <-firstMuStarted // call second func in same goroutine - c.setOnce(key2, func() (res *Result, err error) { + c.setOnce(key2, func() (res *result, err error) { // func will not call never - return NewResult(2), nil + return newResult(2), nil }) // allow finish first func after second already finished @@ -301,8 +301,8 @@ func TestCache_GetOrSetRaceCondition(_ *testing.T) { for j := 0; j < iterations; j++ { key := cacheKey(strconv.Itoa(rand.Intn(rndMaxBound))) - v, ok := c.GetOrSet(key, func() (res *Result, err error) { - return NewResult(1), nil + v, ok := c.GetOrSet(key, func() (res *result, err error) { + return newResult(1), nil }) _ = v _ = ok diff --git a/env.go b/env.go index 7358585..a650574 100644 --- a/env.go +++ b/env.go @@ -64,11 +64,7 @@ func (e *EnvT) T() T { return e.t } -// 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) -// See to generic wrapper: CacheResult -func (e *EnvT) CacheResult(f FixtureFunction, options ...CacheOptions) interface{} { +func (e *EnvT) cacheResult(f fixtureFunction, options ...CacheOptions) interface{} { var cacheOptions CacheOptions switch len(options) { case 0: @@ -85,7 +81,7 @@ func (e *EnvT) CacheResult(f FixtureFunction, options ...CacheOptions) interface // cache must be call from first-level public function // UserFunction->EnvFunction->cache for good determine caller name -func (e *EnvT) cache(f FixtureFunction, options CacheOptions) interface{} { +func (e *EnvT) cache(f fixtureFunction, options CacheOptions) interface{} { key, err := makeCacheKey(e.t.Name(), options, false) if err != nil { e.t.Fatalf("failed to create cache key: %v", err) @@ -210,8 +206,8 @@ func makeCacheKeyFromFrame(params interface{}, scope CacheScope, f runtime.Frame } -func (e *EnvT) fixtureCallWrapper(key cacheKey, f FixtureFunction, options CacheOptions) FixtureFunction { - return func() (res *Result, err error) { +func (e *EnvT) fixtureCallWrapper(key cacheKey, f fixtureFunction, options CacheOptions) fixtureFunction { + return func() (res *result, err error) { scopeName := makeScopeName(e.t.Name(), options.Scope) e.m.Lock() @@ -233,7 +229,7 @@ func (e *EnvT) fixtureCallWrapper(key cacheKey, f FixtureFunction, options Cache // force exactly least one of res, err != nil if res == nil && err == nil { - res = NewResult(nil) + res = newResult(nil) } if res != nil && res.Cleanup != nil { si.t.Cleanup(res.Cleanup) diff --git a/env_generic_sugar.go b/env_generic_sugar.go index 3ef7adb..d43d6f6 100644 --- a/env_generic_sugar.go +++ b/env_generic_sugar.go @@ -19,19 +19,19 @@ func CacheResult[TRes any](env Env, f GenericFixtureFunction[TRes], options ...C } addSkipLevelCache(&cacheOptions) - var oldStyleFunc FixtureFunction = func() (*Result, error) { + var oldStyleFunc fixtureFunction = func() (*result, error) { res, err := f() - var oldStyleRes *Result + var oldStyleRes *result if res != nil { - oldStyleRes = &Result{ + oldStyleRes = &result{ Value: res.Value, ResultAdditional: res.ResultAdditional, } } return oldStyleRes, err } - res := env.CacheResult(oldStyleFunc, cacheOptions) + res := env.cacheResult(oldStyleFunc, cacheOptions) if res == nil { var zero TRes return zero diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index ae7e09d..e2e2f8e 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -19,7 +19,7 @@ func TestCacheResultGeneric(t *testing.T) { cleanupCalledBack := 0 - env := envMock{onCacheResult: func(opt CacheOptions, f FixtureFunction) interface{} { + env := envMock{onCacheResult: func(opt CacheOptions, f fixtureFunction) interface{} { opt.additionlSkipExternalCalls-- requireEquals(t, inOpt, opt) res, _ := f() @@ -118,14 +118,14 @@ func TestCacheResultPanic(t *testing.T) { } type envMock struct { - 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) CacheResult(f FixtureFunction, options ...CacheOptions) interface{} { +func (e envMock) cacheResult(f fixtureFunction, options ...CacheOptions) interface{} { var opts CacheOptions switch len(options) { case 0: diff --git a/env_test.go b/env_test.go index 5b4b13b..22ee9e6 100644 --- a/env_test.go +++ b/env_test.go @@ -72,9 +72,9 @@ func Test_Env_CacheResult(t *testing.T) { e := New(tMock) rndFix := func(e Env) int { - return e.CacheResult(func() (*Result, error) { - return NewResult(rand.Int()), nil - }).(int) + return CacheResult(e, func() (*GenericResult[int], error) { + return NewGenericResult(rand.Int()), nil + }) } first := rndFix(e) second := rndFix(e) @@ -86,9 +86,9 @@ func Test_Env_CacheResult(t *testing.T) { 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) + return CacheResult(e, func() (*GenericResult[int], error) { + return NewGenericResult(rand.Int()), nil + }, CacheOptions{CacheKey: name}) } first1 := rndFix(e, "first") first2 := rndFix(e, "first") @@ -105,21 +105,21 @@ func Test_Env_CacheResult(t *testing.T) { callbackCalled := 0 cleanupCalled := 0 - var callbackFunc FixtureFunction = func() (*Result, error) { + var callbackFunc fixtureFunction = func() (*result, error) { callbackCalled++ cleanup := func() { cleanupCalled++ } - return NewResultWithCleanup(callbackCalled, cleanup), nil + return newResultWithCleanup(callbackCalled, cleanup), nil } - res := env.CacheResult(callbackFunc) + res := env.cacheResult(callbackFunc) requireEquals(t, 1, res) requireEquals(t, 1, callbackCalled) requireEquals(t, cleanupCalled, 0) // got value from cache - res = env.CacheResult(callbackFunc) + res = env.cacheResult(callbackFunc) requireEquals(t, 1, res) requireEquals(t, 1, callbackCalled) requireEquals(t, cleanupCalled, 0) @@ -133,9 +133,9 @@ func Test_Env_CacheResult(t *testing.T) { 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) + return CacheResult(e, func() (*GenericResult[int], error) { + return NewGenericResult(rand.Int()), nil + }, CacheOptions{CacheKey: name}, CacheOptions{CacheKey: name}) } requirePanic(t, func() { rndFix(e, "first") @@ -148,9 +148,9 @@ func Test_Env_CacheResult(t *testing.T) { testErr := errors.New("test err") failedFix := func(e Env) int { - return e.CacheResult(func() (*Result, error) { + return CacheResult(e, func() (*GenericResult[int], error) { return nil, testErr - }).(int) + }) } done := make(chan bool) go func() { @@ -163,7 +163,7 @@ func Test_Env_CacheResult(t *testing.T) { t.Run("check_unserializable_params", func(t *testing.T) { tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} e := newTestEnv(tMock) - e.CacheResult(func() (*Result, error) { + CacheResult(e, func() (*GenericResult[int], error) { return nil, ErrSkipTest }, CacheOptions{CacheKey: func() {}}) requireEquals(t, len(tMock.Fatals), 1) @@ -172,7 +172,7 @@ func Test_Env_CacheResult(t *testing.T) { tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} e := newTestEnv(tMock) requirePanic(t, func() { - e.CacheResult(func() (*Result, error) { + CacheResult(e, func() (*GenericResult[int], error) { return nil, ErrSkipTest }) }) @@ -189,9 +189,9 @@ func Test_FixtureWrapper(t *testing.T) { key := cacheKey("asd") cnt := 0 - w := e.fixtureCallWrapper(key, func() (res *Result, err error) { + w := e.fixtureCallWrapper(key, func() (res *result, err error) { cnt++ - return NewResult(cnt), errors.New("test") + return newResult(cnt), errors.New("test") }, CacheOptions{}) si := e.scopes[makeScopeName(tMock.Name(), ScopeTest)] requireEquals(t, 0, cnt) @@ -205,10 +205,10 @@ func Test_FixtureWrapper(t *testing.T) { cnt = 0 key2 := cacheKey("asd") cleanupsLen := len(tMock.Cleanups) - w = e.fixtureCallWrapper(key2, func() (res *Result, err error) { + w = e.fixtureCallWrapper(key2, func() (res *result, err error) { cnt++ cleanup := func() {} - return NewResultWithCleanup(cnt, cleanup), nil + return newResultWithCleanup(cnt, cleanup), nil }, CacheOptions{}) requireEquals(t, len(tMock.Cleanups), cleanupsLen) _, _ = w() @@ -222,8 +222,8 @@ func Test_FixtureWrapper(t *testing.T) { e := newTestEnv(tMock) tMock.TestName = "mock2" - w := e.fixtureCallWrapper("asd", func() (res *Result, err error) { - return NewResult(nil), nil + w := e.fixtureCallWrapper("asd", func() (res *result, err error) { + return newResult(nil), nil }, CacheOptions{}) runUntilFatal(func() { _, _ = w() @@ -241,11 +241,10 @@ func Test_Env_Skip(t *testing.T) { skipFixtureCallTimes := 0 skipFixture := func() int { - res := tEnv.CacheResult(func() (*Result, error) { + return CacheResult(tEnv, func() (*GenericResult[int], error) { skipFixtureCallTimes++ return nil, ErrSkipTest }) - return res.(int) } assertGoExit := func(callback func()) { @@ -316,10 +315,10 @@ func Test_Env_TearDown(t *testing.T) { requireEquals(t, len(e1.scopes[makeScopeName(t1.TestName, ScopeTest)].Keys()), 0) requireEquals(t, len(e1.c.store), 0) - e1.CacheResult(func() (*Result, error) { - return NewResult(nil), nil + CacheResult(e1, func() (*GenericResult[any], error) { + return NewGenericResult[any](nil), nil }, CacheOptions{CacheKey: 1}) - e1.CacheResult(func() (*Result, error) { + CacheResult(e1, func() (*GenericResult[int], error) { return nil, nil }, CacheOptions{CacheKey: 2}) requireEquals(t, len(e1.scopes), 1) @@ -335,7 +334,7 @@ func Test_Env_TearDown(t *testing.T) { requireEquals(t, len(e1.scopes[makeScopeName(t2.TestName, ScopeTest)].Keys()), 0) requireEquals(t, len(e1.c.store), 2) - e2.CacheResult(func() (*Result, error) { + CacheResult(e2, func() (*GenericResult[int], error) { return nil, nil }, CacheOptions{CacheKey: 1}) diff --git a/examples/simple/http_server_test.go b/examples/simple/http_server_test.go index 78aaa32..d7fbdaa 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 { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[*httptest.Server], error) { resp := []byte(response) server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) { @@ -26,10 +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.NewResultWithCleanup(server, cleanup), nil + return fixenv.NewGenericResultWithCleanup(server, cleanup), nil } - return e.CacheResult(f, fixenv.CacheOptions{CacheKey: response}).(*httptest.Server) + return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: response}) } func TestHttpServer(t *testing.T) { diff --git a/interface.go b/interface.go index 709af8b..72c6601 100644 --- a/interface.go +++ b/interface.go @@ -13,9 +13,7 @@ type Env interface { // T - return t object of current test/benchmark. T() T - // 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{} + cacheResult(f fixtureFunction, options ...CacheOptions) interface{} } var ( @@ -51,26 +49,24 @@ const ( // it called exactly once for every succesully call fixture type FixtureCleanupFunc func() -// FixtureFunction - callback function with structured result -// the function can return ErrSkipTest error for skip the test -type FixtureFunction func() (*Result, error) +// ResultAdditional contains metadata for both generic and legacy fixtures. +type ResultAdditional struct { + Cleanup FixtureCleanupFunc +} + +type fixtureFunction func() (*result, error) -// Result of fixture callback -type Result struct { +type result struct { Value interface{} ResultAdditional } -type ResultAdditional struct { - Cleanup FixtureCleanupFunc -} - -func NewResult(res interface{}) *Result { - return &Result{Value: res} +func newResult(res interface{}) *result { + return &result{Value: res} } -func NewResultWithCleanup(res interface{}, cleanup FixtureCleanupFunc) *Result { - return &Result{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}} +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 d5efbdd..2133f6a 100644 --- a/sf/context.go +++ b/sf/context.go @@ -6,9 +6,9 @@ import ( ) func Context(e fixenv.Env) context.Context { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[context.Context], error) { ctx, ctxCancel := context.WithCancel(context.Background()) - return fixenv.NewResultWithCleanup(ctx, fixenv.FixtureCleanupFunc(ctxCancel)), nil + return fixenv.NewGenericResultWithCleanup(ctx, fixenv.FixtureCleanupFunc(ctxCancel)), nil } - return e.CacheResult(f).(context.Context) + return fixenv.CacheResult(e, f) } diff --git a/sf/filesystem.go b/sf/filesystem.go index 5f0b3ed..5222cc5 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 { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[string], 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,9 +16,9 @@ func TempDir(e fixenv.Env) string { _ = os.RemoveAll(dir) e.T().Logf("Temp dir removed: %v", dir) } - return fixenv.NewResultWithCleanup(dir, clean), nil + return fixenv.NewGenericResultWithCleanup(dir, clean), nil } - return e.CacheResult(f).(string) + return fixenv.CacheResult(e, f) } // TempFile return path to empty existed file in TempDir @@ -29,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 { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[string], 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.NewResult(fName), nil + return fixenv.NewGenericResult(fName), nil } - return e.CacheResult(f).(string) + return fixenv.CacheResult(e, f) } diff --git a/sf/http.go b/sf/http.go index 71a5fe7..2c0fc2b 100644 --- a/sf/http.go +++ b/sf/http.go @@ -6,9 +6,9 @@ import ( ) func HTTPServer(e fixenv.Env) *httptest.Server { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[*httptest.Server], error) { server := httptest.NewServer(nil) - return fixenv.NewResultWithCleanup(server, server.Close), nil + return fixenv.NewGenericResultWithCleanup(server, server.Close), nil } - return e.CacheResult(f).(*httptest.Server) + return fixenv.CacheResult(e, f) } diff --git a/sf/network.go b/sf/network.go index 6d29840..acb9f14 100644 --- a/sf/network.go +++ b/sf/network.go @@ -10,14 +10,14 @@ func FreeLocalTCPAddress(e fixenv.Env) string { } func FreeLocalTCPAddressNamed(e fixenv.Env, name string) string { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[string], 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.NewResult(addr), nil + return fixenv.NewGenericResult(addr), nil } - return e.CacheResult(f, fixenv.CacheOptions{CacheKey: name}).(string) + return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: name}) } func LocalTCPListener(e fixenv.Env) *net.TCPListener { @@ -25,14 +25,18 @@ func LocalTCPListener(e fixenv.Env) *net.TCPListener { } func LocalTCPListenerNamed(e fixenv.Env, name string) *net.TCPListener { - f := func() (*fixenv.Result, error) { + f := func() (*fixenv.GenericResult[*net.TCPListener], error) { listener, err := net.Listen("tcp", "localhost:0") clean := func() { if listener != nil { _ = listener.Close() } } - return fixenv.NewResultWithCleanup(listener, clean), err + if err != nil { + return nil, err + } + tcpListener := listener.(*net.TCPListener) + return fixenv.NewGenericResultWithCleanup(tcpListener, clean), nil } - return e.CacheResult(f, fixenv.CacheOptions{CacheKey: name}).(*net.TCPListener) + return fixenv.CacheResult(e, f, fixenv.CacheOptions{CacheKey: name}) }