diff --git a/.envrc b/.envrc index f8baba1..a94791f 100644 --- a/.envrc +++ b/.envrc @@ -4,4 +4,4 @@ export PATH="${WDP}/.tools:${PATH}" export GO111MODULE=on export CGO_ENABLED=1 -export TESTCASE_PP_DEBUG=TRUE +export TESTCASE_DEBUG=TRUE diff --git a/Spec_test.go b/Spec_test.go index 01bb547..0637881 100644 --- a/Spec_test.go +++ b/Spec_test.go @@ -13,6 +13,7 @@ import ( "go.llib.dev/testcase/assert" "go.llib.dev/testcase/internal/doubles" + "go.llib.dev/testcase/internal/environ" "go.llib.dev/testcase/internal/spechelper" "go.llib.dev/testcase/random" "go.llib.dev/testcase/sandbox" @@ -1361,8 +1362,8 @@ func TestSpec_Test_whenTestingTBIsGivenThatDoesNotSupportTBRunner_executesOnFini } func TestSpec_Test_testingTBNoTBRunner_ordered(t *testing.T) { - testcase.SetEnv(t, testcase.EnvKeySeed, "42") - testcase.SetEnv(t, testcase.EnvKeyOrdering, string(testcase.OrderingAsRandom)) + testcase.SetEnv(t, environ.KeySeed, "42") + testcase.SetEnv(t, environ.KeyOrdering, string(testcase.OrderingAsRandom)) testcase.NewSpec(t).Context("", func(s *testcase.Spec) { var out []int s.Test(``, func(t *testcase.T) { out = append(out, 1) }) diff --git a/T_test.go b/T_test.go index fba50ee..0c0e4cc 100644 --- a/T_test.go +++ b/T_test.go @@ -11,6 +11,7 @@ import ( "go.llib.dev/testcase/assert" "go.llib.dev/testcase/contracts" "go.llib.dev/testcase/internal/doubles" + "go.llib.dev/testcase/internal/environ" "go.llib.dev/testcase/sandbox" "go.llib.dev/testcase/random" @@ -349,7 +350,7 @@ func TestT_Random(t *testing.T) { } t.Run(`when environment value is set`, func(t *testing.T) { - testcase.SetEnv(t, testcase.EnvKeySeed, `42`) + testcase.SetEnv(t, environ.KeySeed, `42`) s := testcase.NewSpec(t) s.Test(``, func(t *testcase.T) { t.Must.NotEmpty(t.Random) diff --git a/TableTest_test.go b/TableTest_test.go index e2a11fa..9ee4eec 100644 --- a/TableTest_test.go +++ b/TableTest_test.go @@ -6,10 +6,11 @@ import ( "go.llib.dev/testcase" "go.llib.dev/testcase/assert" + "go.llib.dev/testcase/internal/environ" ) func TestTableTest_orderIsDeterministic(t *testing.T) { - t.Setenv(testcase.EnvKeySeed, "42") + t.Setenv(environ.KeySeed, "42") var cases = map[string]int{ "1": 1, diff --git a/env.go b/env.go deleted file mode 100644 index bbeb796..0000000 --- a/env.go +++ /dev/null @@ -1,14 +0,0 @@ -package testcase - -// EnvKeySeed is the environment variable key that will be checked for a pseudo random seed, -// which will be used to randomize the order of executions between test cases. -const EnvKeySeed = `TESTCASE_SEED` - -// EnvKeyOrdering is the environment variable key that will be checked for testCase determine -// what order of execution should be used between test cases in a testing group. -// The default sorting behavior is pseudo random based on an the seed. -// -// Mods: -// - defined: execute testCase in the order which they are being defined -// - random: pseudo random based ordering between tests. -const EnvKeyOrdering = `TESTCASE_ORDERING` diff --git a/internal/environ/environ.go b/internal/environ/environ.go new file mode 100644 index 0000000..0240956 --- /dev/null +++ b/internal/environ/environ.go @@ -0,0 +1,58 @@ +package environ + +import ( + "os" + "slices" + "strings" + + "go.llib.dev/testcase/internal" +) + +// KeySeed is the environment variable key that will be checked for a pseudo random seed, +// which will be used to randomize the order of executions between test cases. +const KeySeed = `TESTCASE_SEED` + +// KeyOrdering is the environment variable key that will be checked for testCase determine +// what order of execution should be used between test cases in a testing group. +// The default sorting behavior is pseudo random based on an the seed. +// +// Mods: +// - defined: execute testCase in the order which they are being defined +// - random: pseudo random based ordering between tests. +const KeyOrdering = `TESTCASE_ORDERING` +const KeyOrdering2 = `TESTCASE_ORDER` + +const KeyDebug = "TESTCASE_DEBUG" + +var acceptedKeys = []string{ + KeySeed, + KeyOrdering, + KeyOrdering2, + KeyDebug, +} + +func init() { CheckEnvKeys() } + +func CheckEnvKeys() { + var got bool + for _, envPair := range os.Environ() { + ekv := strings.SplitN(envPair, "=", 2) // best effort to split, but it might not be platform agnostic + if len(ekv) != 2 { + continue + } + key, _ := ekv[0], ekv[1] + + if !strings.HasPrefix(key, "TESTCASE_") { + continue + } + + if !slices.Contains(acceptedKeys, key) { + got = true + internal.Warn("unrecognised testcase variable:", key) + } + } + if got { + internal.Warn("check if you might have a typo.") + internal.Warn("accepted environment variables:", strings.Join(acceptedKeys, ", ")) + } +} diff --git a/internal/environ/environ_test.go b/internal/environ/environ_test.go new file mode 100644 index 0000000..9706638 --- /dev/null +++ b/internal/environ/environ_test.go @@ -0,0 +1,32 @@ +package environ_test + +import ( + "fmt" + "testing" + + "go.llib.dev/testcase/assert" + "go.llib.dev/testcase/internal" + "go.llib.dev/testcase/internal/env" + "go.llib.dev/testcase/internal/environ" + "go.llib.dev/testcase/random" +) + +func Test_checkEnvKeys(t *testing.T) { + t.Run("when invalid testcase env variable is present in the env", func(t *testing.T) { + out := internal.StubWarn(t) + rnd := random.New(random.CryptoSeed{}) + key := fmt.Sprintf("TESTCASE_%s", rnd.StringNC(rnd.IntB(0, 10), random.CharsetASCII())) + env.SetEnv(t, key, rnd.StringNC(5, random.CharsetASCII())) + environ.CheckEnvKeys() + assert.NotEmpty(t, out.String()) + assert.Contain(t, out.String(), key) + assert.Contain(t, out.String(), "typo") + }) + t.Run("when only valid env variables are present in the env", func(t *testing.T) { + out := internal.StubWarn(t) + env.SetEnv(t, environ.KeySeed, "123") + env.SetEnv(t, environ.KeyOrdering, "defined") + environ.CheckEnvKeys() + assert.Empty(t, out.String()) + }) +} diff --git a/internal/spechelper/ordering.go b/internal/spechelper/ordering.go index 104fc2a..b10bef9 100644 --- a/internal/spechelper/ordering.go +++ b/internal/spechelper/ordering.go @@ -5,9 +5,10 @@ import ( "go.llib.dev/testcase" "go.llib.dev/testcase/internal" + "go.llib.dev/testcase/internal/environ" ) func OrderAsDefined(tb testing.TB) { internal.SetupCacheFlush(tb) - testcase.SetEnv(tb, testcase.EnvKeyOrdering, string(testcase.OrderingAsDefined)) + testcase.SetEnv(tb, environ.KeyOrdering, string(testcase.OrderingAsDefined)) } diff --git a/internal/warn.go b/internal/warn.go new file mode 100644 index 0000000..4b69b07 --- /dev/null +++ b/internal/warn.go @@ -0,0 +1,24 @@ +package internal + +import ( + "bytes" + "fmt" + "io" + "os" + "testing" +) + +var warnOutput io.Writer = os.Stderr + +func Warn(vs ...any) { + out := append([]any{"[WARN]", "[TESTCASE]"}, vs...) + fmt.Fprintln(warnOutput, out...) +} + +func StubWarn(tb testing.TB) *bytes.Buffer { + original := warnOutput + tb.Cleanup(func() { warnOutput = original }) + var buf bytes.Buffer + warnOutput = &buf + return &buf +} diff --git a/ordering.go b/ordering.go index 7cd938c..5aaf0e5 100644 --- a/ordering.go +++ b/ordering.go @@ -8,6 +8,7 @@ import ( "testing" "go.llib.dev/testcase/internal" + "go.llib.dev/testcase/internal/environ" ) func newOrderer(tb testing.TB, seed int64) orderer { @@ -77,7 +78,7 @@ func getGlobalOrderMod(tb testing.TB) testOrderingMod { } func getOrderingModFromENV() testOrderingMod { - mod, ok := os.LookupEnv(EnvKeyOrdering) + mod, ok := os.LookupEnv(environ.KeyOrdering) if !ok { return OrderingAsRandom } diff --git a/ordering_test.go b/ordering_test.go index 9209d6d..168b64e 100644 --- a/ordering_test.go +++ b/ordering_test.go @@ -6,6 +6,8 @@ import ( "go.llib.dev/testcase/assert" "go.llib.dev/testcase/internal" + "go.llib.dev/testcase/internal/environ" + "go.llib.dev/testcase/random" ) var ord = Var[orderer]{ID: `orderer`} @@ -177,9 +179,13 @@ func TestNewOrderer(t *testing.T) { internal.SetupCacheFlush(t) }) + orderingEnvKey := Let[string](s, func(t *T) string { + return random.Pick(t.Random, environ.KeyOrdering, environ.KeyOrdering2) + }) + s.When(`mod is unknown`, func(s *Spec) { s.Before(func(t *T) { - SetEnv(t, EnvKeyOrdering, `unknown`) + SetEnv(t, orderingEnvKey.Get(t), `unknown`) }) s.Then(`it will panic`, func(t *T) { @@ -189,7 +195,7 @@ func TestNewOrderer(t *testing.T) { s.When(`mod is random`, func(s *Spec) { s.Before(func(t *T) { - SetEnv(t, EnvKeyOrdering, string(OrderingAsRandom)) + SetEnv(t, orderingEnvKey.Get(t), string(OrderingAsRandom)) }) s.Then(`random orderer provided`, func(t *T) { @@ -201,7 +207,7 @@ func TestNewOrderer(t *testing.T) { s.When(`mod set ordering as tests are defined`, func(s *Spec) { s.Before(func(t *T) { - SetEnv(t, EnvKeyOrdering, string(OrderingAsDefined)) + SetEnv(t, environ.KeyOrdering, string(OrderingAsDefined)) }) s.Then(`null orderer provided`, func(t *T) { diff --git a/pp/debug.go b/pp/debug.go index 8642ff4..8d24153 100644 --- a/pp/debug.go +++ b/pp/debug.go @@ -9,7 +9,7 @@ import ( var debug = false func init() { - v, ok := os.LookupEnv("TESTCASE_PP_DEBUG") + v, ok := os.LookupEnv("TESTCASE_DEBUG") if !ok { return } diff --git a/seed.go b/seed.go index 67c94b5..85857b1 100644 --- a/seed.go +++ b/seed.go @@ -2,18 +2,20 @@ package testcase import ( "fmt" - "go.llib.dev/testcase/random" "math/rand" "os" "strconv" "testing" "time" + "go.llib.dev/testcase/random" + "go.llib.dev/testcase/internal" + "go.llib.dev/testcase/internal/environ" ) func makeSeed() (int64, error) { - rawSeed, injectedRandomSeedIsSet := os.LookupEnv(EnvKeySeed) + rawSeed, injectedRandomSeedIsSet := os.LookupEnv(environ.KeySeed) if !injectedRandomSeedIsSet { salt := rand.New(random.CryptoSeed{}).Int63() base := time.Now().UnixNano() @@ -21,7 +23,7 @@ func makeSeed() (int64, error) { } seed, err := strconv.ParseInt(rawSeed, 10, 64) if err != nil { - return 0, fmt.Errorf("%s has invalid seed integer value: %s", EnvKeySeed, rawSeed) + return 0, fmt.Errorf("%s has invalid seed integer value: %s", environ.KeySeed, rawSeed) } return seed, nil } @@ -33,7 +35,7 @@ func seedForSpec(tb testing.TB) (_seed int64) { tb.Helper() if tb.Failed() { // Help developers to know the seed of the failed test execution. - internal.Log(tb, fmt.Sprintf(`%s=%d`, EnvKeySeed, _seed)) + internal.Log(tb, fmt.Sprintf(`%s=%d`, environ.KeySeed, _seed)) } }) }