package gobang import ( "encoding/json" "errors" "fmt" "log/slog" "os" "slices" "strings" "time" "uuid" ) func test_Heredoc() { TestStart("Heredoc()") Testing("only add newline on empty string", func() { TAssertEqual(Heredoc(""), "\n") }) Testing("remove leading newline", func() { given := Heredoc(` Start and end `) expected := ` Start and end ` TAssertEqual(given, expected) }) Testing("removes ALL tabs", func() { given := Heredoc(` Tab here> ", "", }) }) } func test_TAssertEqualS() { TestStart("TAssertEqualS()") Testing("noop for equal things", func() { TAssertEqualS(true, true, "ignored") }) Testing("fail with message on unequal", func() { var n int exitFn := func(val int) { n = val } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TAssertEqualS("a", []byte("a"), "custom string") testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n")[1:] TAssertEqual(n, 100) TAssertEqual(given, []string{ "message: custom string", "given != expected", "given: \"a\"", "expected: []byte{0x61}", "", }) }) } func test_TAssertEqualI() { TestStart("TAssertEqualI()") Testing("noop for equal slices", func() { TAssertEqualI([]string{}, []string{}) TAssertEqualI([]string{"", "a", "b"}, []string{"", "a", "b"}) }) Testing("fail when `given` has less elements", func() { var n int exitFn := func(val int) { n = val } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TAssertEqualI([]string{"a"}, []string{"a", "b", "c"}) testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n")[1:] TAssertEqual(n, 100) TAssertEqual(given, []string{ "expected has 2 more elements:", `[]string{"b", "c"}`, "", }) }) Testing("fail when `given` has more elements", func() { var n int exitFn := func(val int) { n = val } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TAssertEqualI([]int{1, 2, 3, 4}, []int{5, 6, 7}) testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n")[1:] TAssertEqual(n, 100) TAssertEqual(given, []string{ "given has 1 more elements:", "[]int{4}", "", }) }) Testing("fail when nth element is different", func() { var n int exitFn := func(val int) { n = val } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TAssertEqualI([]int{1, 2, 10, 4, 100}, []int{1, 2, 3, 4, 5}) testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n") given = slices.Concat(given[1:4], given[5:]) TAssertEqual(n, 100) TAssertEqual(given, []string{ "given != expected (i = 2)", "given: 10", "expected: 3", "given != expected (i = 4)", "given: 100", "expected: 5", "", }) }) } func test_TErrorIf() { TestStart("TErrorIf()") Testing("noop on nil value", func() { exitFn := func(val int) { panic(val) } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TErrorIf(nil) testExitFn = savedExitFn testOutput = savedOutput TAssertEqual(w.String(), "") }) Testing("simple dynamic message plus exit code", func() { myErr := errors.New("unexpected error") myStr := fmt.Sprintf("%#v", myErr) var n int exitFn := func(val int) { n = val } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TErrorIf(myErr) testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n")[1] TAssertEqual(n, 100) TAssertEqual(given, "Unexpected error: " + myStr) }) } func test_TErrorNil() { TestStart("TErrorNil()") Testing("noop on thruthy value", func() { exitFn := func(val int) { panic(val) } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TErrorNil(errors.New("some error")) testExitFn = savedExitFn testOutput = savedOutput TAssertEqual(w.String(), "") }) Testing("simple message with special exit code", func() { var n int exitFn := func(val int) { n = val } w := new(strings.Builder) savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = w TErrorNil(nil) testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n")[1] TAssertEqual(n, 100) TAssertEqual(given, "Expected error, got nil") }) } func test_TMust() { TestStart("TMust()") myErr := errors.New("a test error") fn := func(shouldError bool) (int, error) { if shouldError { return 0, myErr } else { return 10, nil } } Testing("noop when error is nil", func() { n := TMust(fn(false)) TAssertEqual(n, 10) }) Testing("exit on non nil", func() { var x int exitFn := func(val int) { x = val } var w strings.Builder savedExitFn := testExitFn savedOutput := testOutput testExitFn = exitFn testOutput = &w n := TMust(fn(true)) testExitFn = savedExitFn testOutput = savedOutput given := strings.Split(w.String(), "\n")[1] TAssertEqual(n, 0) TAssertEqual(x, 100) TAssertEqual(given, fmt.Sprintf("Unexpected error: %#v", myErr)) }) } func test_SetLoggerOutput() { TestStart("SetLoggerOutput()") savedLogger := slog.Default() Testing("the output JSON has data under \"src\"", func() { saved := SourceInfoSkip SourceInfoSkip = 3 s := new(strings.Builder) SetLoggerOutput(s) Info("", "") var data map[string]interface{} err := json.Unmarshal([]byte(s.String()), &data) TErrorIf(err) src := data["src"].(map[string]interface{}) file := src["file"].(string) const FILENAME = "tests/gobang.go" TAssertEqual(file[len(file) - len(FILENAME):], FILENAME) TAssertEqual( src["function"].(string), "gobang.test_SetLoggerOutput.func1", ) line := src["line"].(float64) TAssertEqual(line > 0, true) SourceInfoSkip = saved }) Testing("the output JSON has data under \"info\"", func() { s := new(strings.Builder) SetLoggerOutput(s) Info("", "") var data map[string]interface{} err := json.Unmarshal([]byte(s.String()), &data) TErrorIf(err) info := data["info"].(map[string]interface{}) TAssertEqual(int(info["pid" ].(float64)), os.Getpid()) TAssertEqual(int(info["ppid"].(float64)), os.Getppid()) _, err = uuid.FromString(info["puuid"].(string)) TErrorIf(err) }) Testing("we can add groups to the default logger", func() { s := new(strings.Builder) SetLoggerOutput(s, slog.Group("one", "key", "value")) Info("", "") var data map[string]interface{} err := json.Unmarshal([]byte(s.String()), &data) TErrorIf(err) TAssertEqual( data["one"].(map[string]interface{})["key"].(string), "value", ) }) Testing("the puuid is the same across calls to the logger", func() { s := new(strings.Builder) SetLoggerOutput(s) Info("", "first") Info("", "second") strs := strings.Split(s.String(), "\n") TAssertEqual(len(strs), 3) TAssertEqual(strs[2], "") log1 := strs[0] log2 := strs[1] var puuidFromString = func(str string) string { var data map[string]interface{} err := json.Unmarshal([]byte(str), &data) TErrorIf(err) return data["info"]. (map[string]interface{})["puuid"].(string) } puuid1 := puuidFromString(log1) puuid2 := puuidFromString(log2) TAssertEqual(puuid1, puuid2) uuid1, err := uuid.FromString(puuid1) TErrorIf(err) uuid2, err := uuid.FromString(puuid2) TErrorIf(err) TAssertEqual(uuid1, uuid2) }) slog.SetDefault(savedLogger) } func test_levelFromString() { TestStart("levelFromString()") values := []string { "NONE", "ERROR", "WARNING", "INFO", "DEBUG" } Testing("ok for expected values", func() { for _, s := range values { var fallback LogLevel = 123 theLevel := levelFromString(s, fallback) notFallback := theLevel >= LogLevel_None && theLevel <= LogLevel_Debug && theLevel != fallback TAssertEqual(notFallback, true) } }) Testing("fallback for unexpected values", func() { var fallback LogLevel = 123 rand, err := Random(10) TErrorIf(err) theLevel := levelFromString(string(rand), fallback) TAssertEqual(theLevel, fallback) }) } func test_setLogLevel() { TestStart("setLogLevel()") const NAME = "LOG_LEVEL" savedEnvvar := os.Getenv(NAME) savedValue := level var fallbackValue LogLevel = 123 Testing("noop when envvar is unset", func() { TErrorIf(os.Unsetenv(NAME)) level = fallbackValue setLogLevel() TAssertEqual(level, fallbackValue) }) Testing("noop when envvar is empty", func() { TErrorIf(os.Setenv(NAME, "")) level = fallbackValue setLogLevel() TAssertEqual(level, fallbackValue) }) Testing("update variable otherwise", func() { TErrorIf(os.Setenv(NAME, "DEBUG")) level = fallbackValue setLogLevel() TAssertEqual(level, LogLevel_Debug) }) TErrorIf(os.Setenv(NAME, savedEnvvar)) level = savedValue } func test_SetLevel() { TestStart("SetLevel()") savedValue := level Testing("the behaviour is a simple assignment", func() { levels := []LogLevel { LogLevel_None, LogLevel_Error, LogLevel_Warning, LogLevel_Info, LogLevel_Debug, } for _, l := range levels { SetLevel(l) TAssertEqual(l, level) } }) level = savedValue } func test_setMetric() { TestStart("setMetric()") const NAME = "NO_METRIC" savedEnvvar := os.Getenv(NAME) savedValue := emitMetric defer func() { TErrorIf(os.Setenv(NAME, savedEnvvar)) emitMetric = savedValue }() Testing("noop when envvar is unset", func() { TErrorIf(os.Unsetenv(NAME)) emitMetric = true setMetric() TAssertEqual(emitMetric, true) }) Testing("noop when envvar is empty", func() { TErrorIf(os.Setenv(NAME, "")) emitMetric = true setMetric() TAssertEqual(emitMetric, true) }) Testing("make variable false otherwise", func() { TErrorIf(os.Setenv(NAME, "1")) emitMetric = true setMetric() TAssertEqual(emitMetric, false) }) } func test_Fatal() { TestStart("Fatal()") Testing("error and panic", func() { savedLogger := slog.Default() savedLevel := level w := new(strings.Builder) SetLoggerOutput(w) level = LogLevel_Error defer func() { r := recover() TAssertEqual(r == nil, false) slog.SetDefault(savedLogger) level = savedLevel TAssertEqual(len(w.String()) == 0, false) }() Fatal(nil) TErrorNil(nil) }) } func test_FatalIf() { TestStart("FatalIf()") Testing("noop on nil", func() { FatalIf(nil) }) Testing("error and panic otherwise", func() { savedLogger := slog.Default() savedLevel := level w := new(strings.Builder) SetLoggerOutput(w) level = LogLevel_Error myErr := errors.New("the error") defer func() { r := recover() TAssertEqual(r, myErr) slog.SetDefault(savedLogger) level = savedLevel TAssertEqual(len(w.String()) == 0, false) }() FatalIf(myErr) TErrorNil(nil) }) } func test_Assert() { TestStart("Assert()") Testing("noop if true", func() { Assert(true, "ignored") }) Testing("error and panic if false", func() { savedLogger := slog.Default() savedLevel := level w := new(strings.Builder) SetLoggerOutput(w) level = LogLevel_Error const errMsg = "assert error message" defer func() { r := recover() TAssertEqual( r.(error).Error(), "assertion failed: assert error message", ) slog.SetDefault(savedLogger) level = savedLevel TAssertEqual(len(w.String()) == 0, false) }() Assert(1 == 2, errMsg) TErrorNil(nil) }) } func test_Unreachable() { TestStart("Unreachable()") Testing("always has error and panic", func() { savedLogger := slog.Default() savedLevel := level w := new(strings.Builder) SetLoggerOutput(w) level = LogLevel_Error defer func() { r := recover() TAssertEqual( r.(error).Error(), "assertion failed: Unreachable code was reached", ) slog.SetDefault(savedLogger) level = savedLevel TAssertEqual(len(w.String()) == 0, false) }() Unreachable() TErrorNil(nil) }) } func test_setHostname() { TestStart("setHostname()") Testing("it assigns the value to the global variable", func() { name, _ := os.Hostname() hostname = "" setHostname() TAssertEqual(hostname, name) }) } func MainTest() { Init() test_Heredoc() test_SetOf() test_Contains() test_MapIndexed() test_Map() test_Filter() test_ExitIf() test_PanicIf() test_Must() test_Clamp() test_ValidateSQLTablePrefix() test_WrapErrors() test_SomeError() test_SomeFnError() test_Random() test_sourceInfo() test_logArgs() test_anyArr() test_Debug() test_Info() test_Warning() test_Error() test_metric() test_Timed() test_MakeCounter() test_MakeGauge() test_showColour() test_TestStart() test_Testing() test_terr() test_TAssertEqual() test_TAssertEqualS() test_TAssertEqualI() test_TErrorIf() test_TErrorNil() test_TMust() test_SetLoggerOutput() test_levelFromString() test_setLogLevel() test_SetLevel() test_setMetric() test_Fatal() test_FatalIf() test_Assert() test_Unreachable() test_setHostname() }