diff --git a/env.go b/env.go index e66c7ca..e1e1e7f 100644 --- a/env.go +++ b/env.go @@ -299,7 +299,10 @@ func (e *EnvT) fixtureCallWrapper(key cacheKey, f FixtureCallbackFunc, opt *Fixt e.m.Unlock() if si == nil { - e.t.Fatalf("Unexpected scope. Create env for test %q", scopeName) + if opt.Scope == ScopePackage { + e.t.Fatalf("Initialize package scope before use ", scopeName) + } + e.t.Fatalf("Unexpected scope: %q", scopeName) // not reachable return nil, nil } diff --git a/examples/simple_main_test/example_test.go b/examples/simple_main_test/example_test.go new file mode 100644 index 0000000..9e47708 --- /dev/null +++ b/examples/simple_main_test/example_test.go @@ -0,0 +1,41 @@ +//go:build go1.18 +// +build go1.18 + +package simple_main_test + +import ( + "github.com/rekby/fixenv" + "math/rand" + "testing" +) + +var global int = -1 + +func FSingleRandom(e fixenv.Env) int { + var f fixenv.GenericFixtureFunction[int] = func() (*fixenv.GenericResult[int], error) { + return fixenv.NewGenericResult(rand.Int()), nil + } + return fixenv.CacheResult(e, f, fixenv.CacheOptions{Scope: fixenv.ScopePackage}) +} + +func TestFirst(t *testing.T) { + e := fixenv.New(t) + if global == -1 { + global = FSingleRandom(e) + } + + if singleRnd := FSingleRandom(e); singleRnd != global { + t.Fatalf("%v != %v", singleRnd, global) + } +} + +func TestSecond(t *testing.T) { + e := fixenv.New(t) + if global == -1 { + global = FSingleRandom(e) + } + + if singleRnd := FSingleRandom(e); singleRnd != global { + t.Fatalf("%v != %v", singleRnd, global) + } +} diff --git a/examples/simple_main_test/testmain_test.go b/examples/simple_main_test/testmain_test.go new file mode 100644 index 0000000..42580a0 --- /dev/null +++ b/examples/simple_main_test/testmain_test.go @@ -0,0 +1,11 @@ +package simple_main + +import ( + "github.com/rekby/fixenv" + "os" + "testing" +) + +func TestMain(m *testing.M) { + os.Exit(fixenv.RunTests(m)) +} diff --git a/maintest.go b/maintest.go index 9c54d51..0cd83e9 100644 --- a/maintest.go +++ b/maintest.go @@ -1,11 +1,15 @@ package fixenv import ( + "errors" "fmt" "log" "sync" ) +var errPackageEnvInitializedSecondTime = errors.New("package env initialize second time. It must be call not more then once. ") +var errTooManyOptionalArgs = errors.New("allow not more then one optional arg") + // FatalfFunction function signature of Fatalf type FatalfFunction func(format string, args ...interface{}) @@ -34,18 +38,52 @@ type CreateMainTestEnvOpts struct { SkipNow SkipNowFunction } +// packageLevelVirtualTest now used for tests only +var lastPackageLevelVirtualTest *virtualTest + // CreateMainTestEnv called from TestMain for create global environment. // It need only for use ScopePackage cache scope. // If ScopePackage not used - no need to create main env. func CreateMainTestEnv(opts *CreateMainTestEnvOpts) (env *EnvT, tearDown func()) { + // TODO: handle second time initialize globalMutex.Lock() packageLevelVirtualTest := newVirtualTest(opts) + lastPackageLevelVirtualTest = packageLevelVirtualTest globalMutex.Unlock() env = New(packageLevelVirtualTest) // register global test for env return env, packageLevelVirtualTest.cleanup } +// RunTests runs the tests. It returns an exit code to pass to os.Exit. +// +// Usage: +// declare in _test file TestMain function: +// +// func TestMain(m *testing.M) { +// os.Exit(fixenv.RunTests(m)) +// } +func RunTests(m RunTestsI, opts ...CreateMainTestEnvOpts) int { + var options *CreateMainTestEnvOpts + switch len(opts) { + case 0: + // pass + case 1: + options = &opts[0] + default: + panic(errTooManyOptionalArgs) + } + + _, cancel := CreateMainTestEnv(options) + defer cancel() + return m.Run() +} + +type RunTestsI interface { + // Run runs the tests. It returns an exit code to pass to os.Exit. + Run() (code int) +} + // virtualTest implement T interface for global env scope type virtualTest struct { m sync.Mutex diff --git a/maintest_test.go b/maintest_test.go index aa44dc7..ddea0f2 100644 --- a/maintest_test.go +++ b/maintest_test.go @@ -1,6 +1,7 @@ package fixenv import ( + "errors" "runtime" "sync" "testing" @@ -76,3 +77,89 @@ func TestCreateMainTestEnv(t *testing.T) { }) }) } + +func TestRunTests(t *testing.T) { + expectedReturnCode := 123 + + checkInitialized := func(t *testing.T) { + t.Helper() + + globalMutex.Lock() + defer globalMutex.Unlock() + + if _, ok := globalScopeInfo[packageScopeName]; !ok { + t.Fatal() + } + } + cleanGlobalState := func() { + globalMutex.Lock() + defer globalMutex.Unlock() + + delete(globalScopeInfo, packageScopeName) + } + + t.Run("without options", func(t *testing.T) { + m := &mTestsMock{ + returnCode: expectedReturnCode, + run: func() { + checkInitialized(t) + }, + } + + if res := RunTests(m); res != expectedReturnCode { + t.Fatalf("%v != %v", res, expectedReturnCode) + } + cleanGlobalState() + }) + t.Run("with options", func(t *testing.T) { + m := &mTestsMock{ + returnCode: expectedReturnCode, + run: func() { + checkInitialized(t) + lastPackageLevelVirtualTest.SkipNow() + }, + } + + called := false + RunTests(m, CreateMainTestEnvOpts{SkipNow: func() { + called = true + }}) + if !called { + t.Fatal() + } + cleanGlobalState() + }) + t.Run("with two options", func(t *testing.T) { + defer func() { + cleanGlobalState() + + rec := recover() + if !errors.Is(rec.(error), errTooManyOptionalArgs) { + t.Fatal(rec) + } + }() + m := &mTestsMock{ + run: func() { + checkInitialized(t) + }, + } + RunTests(m, CreateMainTestEnvOpts{}, CreateMainTestEnvOpts{}) + }) +} + +type mTestsMock struct { + runCalled bool + returnCode int + run func() +} + +func (r *mTestsMock) Run() (code int) { + r.runCalled = true + if r.run != nil { + r.run() + } + return r.returnCode +} + +// check interface implementation +var _ RunTestsI = &mTestsMock{}