Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move cache to one function #34

Merged
merged 10 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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{}
Expand All @@ -119,6 +123,43 @@ 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) {
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, err := f()
resCleanupFunc = result.Cleanup
return result.Value, err
}

opt := &FixtureOptions{}
opt.Scope = cacheOptions.Scope
opt.additionlSkipExternalCalls = cacheOptions.additionlSkipExternalCalls
opt.cleanupFunc = resCleanupFunc

opt.cleanupFunc = func() {
if resCleanupFunc != nil {
resCleanupFunc()
}
}

return e.cache(cacheOptions.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{} {
Expand Down
62 changes: 62 additions & 0 deletions env_generic_sugar.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

package fixenv

import "fmt"

// Cache is call f once per cache scope (default per test) and cache result (success or error).
// All other calls of the f will return same result
// Deprecated: Use CacheResult
func Cache[TRes any](env Env, cacheKey any, opt *FixtureOptions, f func() (TRes, error)) TRes {
addSkipLevel(&opt)
callbackResult := env.Cache(cacheKey, opt, func() (res interface{}, err error) {
Expand All @@ -16,6 +21,10 @@ 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
// 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) {
Expand All @@ -29,9 +38,62 @@ 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) {
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(oldStyleFunc, cacheOptions)
return res.(TRes)
}

// GenericFixtureFunction - callback function with structured result
type GenericFixtureFunction[ResT any] func() (*GenericResult[ResT], error)

// GenericResult of fixture callback
type GenericResult[ResT any] struct {
Value ResT
ResultAdditional
}

// 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] {
return &GenericResult[ResT]{Value: res, ResultAdditional: ResultAdditional{Cleanup: cleanup}}
}

func addSkipLevel(optspp **FixtureOptions) {
if *optspp == nil {
*optspp = &FixtureOptions{}
}
(*optspp).additionlSkipExternalCalls++
}

func addSkipLevelCache(optspp *CacheOptions) {
(*optspp).additionlSkipExternalCalls++
}
112 changes: 112 additions & 0 deletions env_generic_sugar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package fixenv

import (
"fmt"
"github.com/rekby/fixenv/internal"
"github.com/stretchr/testify/assert"
"math/rand"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -89,12 +92,108 @@ 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.Value
}}

f := func() (*GenericResult[int], error) {
cleanup := func() {
cleanupCalledBack++
}
return NewGenericResultWithCleanup(2, cleanup), nil
}
res := CacheResult(env, f, inOpt)
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, func() (*GenericResult[int], error) {
return NewGenericResult(1), nil
})
}
f2 := func() int {
return CacheResult(env, func() (*GenericResult[int], error) {
return NewGenericResult(2), nil
})
}

require.Equal(t, 1, f1())
require.Equal(t, 2, f2())
})
}

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{}
onCacheResult func(opts CacheOptions, f FixtureFunction) interface{}
}

func (e envMock) T() T {
Expand All @@ -108,3 +207,16 @@ 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(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)
}
82 changes: 81 additions & 1 deletion env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fixenv
import (
"errors"
"github.com/rekby/fixenv/internal"
"math/rand"
"runtime"
"sync"
"testing"
Expand Down Expand Up @@ -301,6 +302,86 @@ 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("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}
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)
Expand Down Expand Up @@ -651,7 +732,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")
Expand Down
Loading