Skip to content

Commit 9994d0f

Browse files
authored
Merge move cleanup func from options to return value
2 parents 8b26ce5 + c86d47b commit 9994d0f

File tree

9 files changed

+187
-37
lines changed

9 files changed

+187
-37
lines changed

.github/workflows/go.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ jobs:
2929
run: go build -v ./...
3030

3131
- name: Test
32+
run: go test ./...
33+
34+
- name: Test with coverage profiler
35+
if: ${{ matrix.goVersion == env.GO_VERSION }}
3236
run: go test -test.count=10 -race -covermode atomic -coverprofile=covprofile.out ./...
33-
37+
3438
- name: golangci-lint
3539
uses: golangci/golangci-lint-action@v2
3640
with:

env.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,40 @@ func (e *EnvT) T() T {
6464
// f - callback - fixture body.
6565
// Cache guarantee for call f exactly once for same Cache called and params combination.
6666
func (e *EnvT) Cache(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} {
67+
return e.cache(params, opt, f)
68+
}
69+
70+
// CacheWithCleanup call from fixture and manage call f and cache it.
71+
// CacheWithCleanup must be called direct from fixture - it use runtime stacktrace for
72+
// detect called method - it is part of cache key.
73+
// params - part of cache key. Usually - parameters, passed to fixture.
74+
// it allow use parametrized fixtures with different results.
75+
// params must be json serializable.
76+
// opt - fixture options, nil for default options.
77+
// f - callback - fixture body.
78+
// cleanup, returned from f called while fixture cleanup
79+
// Cache guarantee for call f exactly once for same Cache called and params combination.
80+
func (e *EnvT) CacheWithCleanup(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} {
81+
if opt == nil {
82+
opt = &FixtureOptions{}
83+
}
84+
85+
var resCleanupFunc FixtureCleanupFunc
86+
87+
var fWithoutCleanup FixtureCallbackFunc = func() (res interface{}, err error) {
88+
res, resCleanupFunc, err = f()
89+
return res, err
90+
}
91+
opt.cleanupFunc = func() {
92+
if resCleanupFunc != nil {
93+
resCleanupFunc()
94+
}
95+
}
96+
97+
return e.cache(params, opt, fWithoutCleanup)
98+
}
99+
100+
func (e *EnvT) cache(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} {
67101
if opt == nil {
68102
opt = globalEmptyFixtureOptions
69103
}
@@ -122,17 +156,18 @@ func (e *EnvT) onCreate() {
122156
// makeCacheKey generate cache key
123157
// must be called from first level of env functions - for detect external caller
124158
func makeCacheKey(testname string, params interface{}, opt *FixtureOptions, testCall bool) (cacheKey, error) {
125-
externalCallerLevel := 4
159+
externalCallerLevel := 5
126160
var pc = make([]uintptr, externalCallerLevel)
127161
var extCallerFrame runtime.Frame
128162
if externalCallerLevel == runtime.Callers(0, pc) {
129163
frames := runtime.CallersFrames(pc)
130164
frames.Next() // callers
131165
frames.Next() // the function
132-
frames.Next() // caller of the function
166+
frames.Next() // caller of the function (env private function)
167+
frames.Next() // caller of private function (env public function)
133168
extCallerFrame, _ = frames.Next() // external caller
134169
}
135-
scopeName := scopeName(testname, opt.Scope)
170+
scopeName := makeScopeName(testname, opt.Scope)
136171
return makeCacheKeyFromFrame(params, opt.Scope, extCallerFrame, scopeName, testCall)
137172
}
138173

@@ -173,7 +208,7 @@ func makeCacheKeyFromFrame(params interface{}, scope CacheScope, f runtime.Frame
173208

174209
func (e *EnvT) fixtureCallWrapper(key cacheKey, f FixtureCallbackFunc, opt *FixtureOptions) FixtureCallbackFunc {
175210
return func() (res interface{}, err error) {
176-
scopeName := scopeName(e.t.Name(), opt.Scope)
211+
scopeName := makeScopeName(e.t.Name(), opt.Scope)
177212

178213
e.m.Lock()
179214
si := e.scopes[scopeName]
@@ -191,15 +226,15 @@ func (e *EnvT) fixtureCallWrapper(key cacheKey, f FixtureCallbackFunc, opt *Fixt
191226

192227
res, err = f()
193228

194-
if opt.CleanupFunc != nil {
195-
si.t.Cleanup(opt.CleanupFunc)
229+
if opt.cleanupFunc != nil {
230+
si.t.Cleanup(opt.cleanupFunc)
196231
}
197232

198233
return res, err
199234
}
200235
}
201236

202-
func scopeName(testName string, scope CacheScope) string {
237+
func makeScopeName(testName string, scope CacheScope) string {
203238
switch scope {
204239
case ScopePackage:
205240
return packageScopeName

env_generic_sugar.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,15 @@ func Cache[TRes any](env Env, params any, opt *FixtureOptions, f func() (TRes, e
1414
}
1515
return res
1616
}
17+
18+
func CacheWithCleanup[TRes any](env Env, params any, opt *FixtureOptions, f func() (TRes, FixtureCleanupFunc, error)) TRes {
19+
callbackResult := env.CacheWithCleanup(params, opt, func() (res interface{}, cleanup FixtureCleanupFunc, err error) {
20+
return f()
21+
})
22+
23+
var res TRes
24+
if callbackResult != nil {
25+
res = callbackResult.(TRes)
26+
}
27+
return res
28+
}

env_generic_sugar_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,32 @@ func TestCacheGeneric(t *testing.T) {
2626
require.Equal(t, 2, res)
2727
}
2828

29+
func TestCacheWithCleanupGeneric(t *testing.T) {
30+
inParams := 123
31+
inOpt := &FixtureOptions{Scope: ScopeTest}
32+
33+
cleanupCalledBack := 0
34+
35+
env := envMock{onCacheWithCleanup: func(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} {
36+
require.Equal(t, inParams, params)
37+
require.Equal(t, inOpt, opt)
38+
res, _, _ := f()
39+
return res
40+
}}
41+
42+
res := CacheWithCleanup(env, inParams, inOpt, func() (int, FixtureCleanupFunc, error) {
43+
cleanup := func() {
44+
cleanupCalledBack++
45+
}
46+
return 2, cleanup, nil
47+
})
48+
require.Equal(t, 2, res)
49+
50+
}
51+
2952
type envMock struct {
30-
onCache func(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{}
53+
onCache func(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{}
54+
onCacheWithCleanup func(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{}
3155
}
3256

3357
func (e envMock) T() T {
@@ -37,3 +61,7 @@ func (e envMock) T() T {
3761
func (e envMock) Cache(params interface{}, opt *FixtureOptions, f FixtureCallbackFunc) interface{} {
3862
return e.onCache(params, opt, f)
3963
}
64+
65+
func (e envMock) CacheWithCleanup(params interface{}, opt *FixtureOptions, f FixtureCallbackWithCleanupFunc) interface{} {
66+
return e.onCacheWithCleanup(params, opt, f)
67+
}

env_test.go

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
1011
)
1112

1213
type testMock struct {
@@ -278,6 +279,58 @@ func Test_Env_Cache(t *testing.T) {
278279
})
279280
}
280281

282+
func Test_Env_CacheWithCleanup(t *testing.T) {
283+
t.Run("NilCleanup", func(t *testing.T) {
284+
tMock := &testMock{name: t.Name()}
285+
env := newTestEnv(tMock)
286+
287+
callbackCalled := 0
288+
var callbackFunc FixtureCallbackWithCleanupFunc = func() (res interface{}, cleanup FixtureCleanupFunc, err error) {
289+
callbackCalled++
290+
return callbackCalled, nil, nil
291+
}
292+
293+
res := env.CacheWithCleanup(nil, nil, callbackFunc)
294+
require.Equal(t, 1, res)
295+
require.Equal(t, 1, callbackCalled)
296+
297+
// got value from cache
298+
res = env.CacheWithCleanup(nil, nil, callbackFunc)
299+
require.Equal(t, 1, res)
300+
require.Equal(t, 1, callbackCalled)
301+
})
302+
303+
t.Run("WithCleanup", func(t *testing.T) {
304+
tMock := &testMock{name: t.Name()}
305+
env := newTestEnv(tMock)
306+
307+
callbackCalled := 0
308+
cleanupCalled := 0
309+
var callbackFunc FixtureCallbackWithCleanupFunc = func() (res interface{}, cleanup FixtureCleanupFunc, err error) {
310+
callbackCalled++
311+
cleanup = func() {
312+
cleanupCalled++
313+
}
314+
return callbackCalled, cleanup, nil
315+
}
316+
317+
res := env.CacheWithCleanup(nil, nil, callbackFunc)
318+
require.Equal(t, 1, res)
319+
require.Equal(t, 1, callbackCalled)
320+
require.Equal(t, cleanupCalled, 0)
321+
322+
// got value from cache
323+
res = env.CacheWithCleanup(nil, nil, callbackFunc)
324+
require.Equal(t, 1, res)
325+
require.Equal(t, 1, callbackCalled)
326+
require.Equal(t, cleanupCalled, 0)
327+
328+
tMock.callCleanup()
329+
require.Equal(t, 1, callbackCalled)
330+
require.Equal(t, 1, cleanupCalled)
331+
})
332+
}
333+
281334
func Test_FixtureWrapper(t *testing.T) {
282335
t.Run("ok", func(t *testing.T) {
283336
at := assert.New(t)
@@ -293,7 +346,7 @@ func Test_FixtureWrapper(t *testing.T) {
293346
cnt++
294347
return cnt, errors.New("test")
295348
}, &FixtureOptions{})
296-
si := e.scopes[scopeName(tMock.Name(), ScopeTest)]
349+
si := e.scopes[makeScopeName(tMock.Name(), ScopeTest)]
297350
at.Equal(0, cnt)
298351
at.Len(si.cacheKeys, 0)
299352
res1, err := w()
@@ -308,7 +361,7 @@ func Test_FixtureWrapper(t *testing.T) {
308361
w = e.fixtureCallWrapper(key2, func() (res interface{}, err error) {
309362
cnt++
310363
return cnt, nil
311-
}, &FixtureOptions{CleanupFunc: func() {
364+
}, &FixtureOptions{cleanupFunc: func() {
312365

313366
}})
314367
at.Len(tMock.cleanups, cleanupsLen)
@@ -416,7 +469,7 @@ func Test_Env_TearDown(t *testing.T) {
416469

417470
e1 := newTestEnv(t1)
418471
at.Len(e1.scopes, 1)
419-
at.Len(e1.scopes[scopeName(t1.name, ScopeTest)].Keys(), 0)
472+
at.Len(e1.scopes[makeScopeName(t1.name, ScopeTest)].Keys(), 0)
420473
at.Len(e1.c.store, 0)
421474

422475
e1.Cache(1, nil, func() (res interface{}, err error) {
@@ -426,31 +479,31 @@ func Test_Env_TearDown(t *testing.T) {
426479
return nil, nil
427480
})
428481
at.Len(e1.scopes, 1)
429-
at.Len(e1.scopes[scopeName(t1.name, ScopeTest)].Keys(), 2)
482+
at.Len(e1.scopes[makeScopeName(t1.name, ScopeTest)].Keys(), 2)
430483
at.Len(e1.c.store, 2)
431484

432485
t2 := &testMock{name: "mock2"}
433486
// defer t2.callCleanup - direct call e2.tearDown - for test
434487

435488
e2 := e1.cloneWithTest(t2)
436489
at.Len(e1.scopes, 2)
437-
at.Len(e1.scopes[scopeName(t1.name, ScopeTest)].Keys(), 2)
438-
at.Len(e1.scopes[scopeName(t2.name, ScopeTest)].Keys(), 0)
490+
at.Len(e1.scopes[makeScopeName(t1.name, ScopeTest)].Keys(), 2)
491+
at.Len(e1.scopes[makeScopeName(t2.name, ScopeTest)].Keys(), 0)
439492
at.Len(e1.c.store, 2)
440493

441494
e2.Cache(1, nil, func() (res interface{}, err error) {
442495
return nil, nil
443496
})
444497

445498
at.Len(e1.scopes, 2)
446-
at.Len(e1.scopes[scopeName(t1.name, ScopeTest)].Keys(), 2)
447-
at.Len(e1.scopes[scopeName(t2.name, ScopeTest)].Keys(), 1)
499+
at.Len(e1.scopes[makeScopeName(t1.name, ScopeTest)].Keys(), 2)
500+
at.Len(e1.scopes[makeScopeName(t2.name, ScopeTest)].Keys(), 1)
448501
at.Len(e1.c.store, 3)
449502

450503
// finish first test and tearDown e1
451504
e1.tearDown()
452505
at.Len(e1.scopes, 1)
453-
at.Len(e1.scopes[scopeName(t2.name, ScopeTest)].Keys(), 1)
506+
at.Len(e1.scopes[makeScopeName(t2.name, ScopeTest)].Keys(), 1)
454507
at.Len(e1.c.store, 1)
455508

456509
e2.tearDown()
@@ -479,10 +532,14 @@ func Test_MakeCacheKey(t *testing.T) {
479532
var res cacheKey
480533
var err error
481534

482-
envFunc := func() {
535+
privateEnvFunc := func() {
483536
res, err = makeCacheKey("asdf", 222, globalEmptyFixtureOptions, true)
484537
}
485-
envFunc()
538+
539+
publicEnvFunc := func() {
540+
privateEnvFunc()
541+
}
542+
publicEnvFunc() // external caller
486543
at.NoError(err)
487544

488545
expected := cacheKey(`{"func":"github.com/rekby/fixenv.Test_MakeCacheKey","fname":".../env_test.go","scope":0,"scope_name":"asdf","params":222}`)
@@ -601,15 +658,15 @@ func Test_ScopeName(t *testing.T) {
601658
for _, c := range table {
602659
t.Run(c.name, func(t *testing.T) {
603660
at := assert.New(t)
604-
at.Equal(c.result, scopeName(c.testName, c.scope))
661+
at.Equal(c.result, makeScopeName(c.testName, c.scope))
605662
})
606663
}
607664
})
608665

609666
t.Run("unexpected_scope", func(t *testing.T) {
610667
at := assert.New(t)
611668
at.Panics(func() {
612-
scopeName("asd", -1)
669+
makeScopeName("asd", -1)
613670
})
614671
})
615672
}

examples/custom_env/custom_env_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ func NewEnv(t *testing.T) (context.Context, *Env) {
3737
}
3838

3939
func testServer(e fixenv.Env, response string) *httptest.Server {
40-
return fixenv.Cache(e, response, nil, func() (_ *httptest.Server, err error) {
40+
return fixenv.CacheWithCleanup(e, response, nil, func() (_ *httptest.Server, cleanup fixenv.FixtureCleanupFunc, err error) {
4141
resp := []byte(response)
4242

4343
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
4444
_, _ = writer.Write(resp)
4545
}))
4646
e.T().(testing.TB).Logf("Http server start. %q url: %q", response, server.URL)
47-
e.T().Cleanup(func() {
47+
cleanup = func() {
4848
server.Close()
4949
e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL)
50-
})
51-
return server, nil
50+
}
51+
return server, cleanup, nil
5252
})
5353
}
5454

examples/custom_env_with_shared_content/custom_env_with_shared_content_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ func NewEnv(t *testing.T) *Env {
2727
}
2828

2929
func testServer(e *Env) *httptest.Server {
30-
return fixenv.Cache(e, "", nil, func() (res *httptest.Server, err error) {
30+
return fixenv.CacheWithCleanup(e, "", nil, func() (res *httptest.Server, cleanup fixenv.FixtureCleanupFunc, err error) {
3131
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
3232
_, _ = writer.Write([]byte(e.Resp))
3333
}))
3434
e.T().(testing.TB).Logf("Http server start, url: %q", server.URL)
35-
e.T().Cleanup(func() {
35+
cleanup = func() {
3636
server.Close()
3737
e.T().(testing.TB).Logf("Http server stop, url: %q", server.URL)
38-
})
39-
return server, nil
38+
}
39+
return server, cleanup, nil
4040
})
4141
}
4242

examples/simple/http_server_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ import (
1515
)
1616

1717
func testServer(e fixenv.Env, response string) *httptest.Server {
18-
return e.Cache(response, nil, func() (res interface{}, err error) {
18+
return e.CacheWithCleanup(response, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) {
1919
resp := []byte(response)
2020

2121
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
2222
_, _ = writer.Write(resp)
2323
}))
2424
e.T().(testing.TB).Logf("Http server start. %q url: %q", response, server.URL)
25-
e.T().Cleanup(func() {
25+
cleanup = func() {
2626
server.Close()
2727
e.T().(testing.TB).Logf("Http server stop. %q url: %q", response, server.URL)
28-
})
29-
return server, nil
28+
}
29+
return server, cleanup, nil
3030
}).(*httptest.Server)
3131
}
3232

0 commit comments

Comments
 (0)