package is_test import ( "errors" "fmt" "io/fs" "math/rand/v2" "reflect" "testing" "time" "olexsmir.xyz/x/is" ) func TestEqual(t *testing.T) { t.Run("equal", func(t *testing.T) { now := time.Now() val := 420 tests := map[string]struct{ got, expected any }{ "int": {69, 69}, "string": {"hello", "hello"}, "bool": {true, true}, "struct": {intType{1}, intType{1}}, "pointer": {&val, &val}, "nil": {nil, nil}, "nil pointer": {(*int)(nil), (*int)(nil)}, "nil map": {map[string]int(nil), map[string]int(nil)}, "nil chan": {(chan int)(nil), (chan int)(nil)}, "nil slice": {[]int(nil), []int(nil)}, "byte slice": {[]byte("abc"), []byte("abc")}, "int slice": {[]int{1, 2}, []int{1, 2}}, "string slice": {[]string{"a", "b"}, []string{"a", "b"}}, "empty map": {map[string]int{}, map[string]int{}}, "map": {map[string]int{"a": 1}, map[string]int{"a": 1}}, "time.Time": {now, now}, } for tname, tt := range tests { t.Run(tname, func(t *testing.T) { ft := &fakeT{} is.Equal(ft, tt.expected, tt.got) if ft.failed { t.Errorf("failed: %s", ft.msg) } }) } }) t.Run("non-equal", func(t *testing.T) { now := time.Now() val1, val2 := 69, 420 tests := map[string]struct { expected, got any expectedMsg string }{ "int": {228, 1337, `expected: 228, got: 1337`}, "string": {"hello", "world", `expected: "hello", got: "world"`}, "bool": {true, false, "expected: true, got: false"}, "struct": {intType{1}, intType{2}, `expected: is_test.intType{v:1}, got: is_test.intType{v:2}`}, "pointer": {&val1, &val2, ``}, "nil/non-nil": {nil, 2, `expected: , got: 2`}, "non-nil/nil": {2, nil, `expected: 2, got: `}, "nil slice/empty": {[]int(nil), []int{}, `expected: []int(nil), got: []int{}`}, "int slice": {[]int{1, 2}, []int{2, 1}, `expected: []int{1, 2}, got: []int{2, 1}`}, "byte slice": {[]byte("abc"), []byte("def"), `expected: []byte{0x61, 0x62, 0x63}, got: []byte{0x64, 0x65, 0x66}`}, "chan": {make(chan int), make(chan int), ``}, "string slice": {[]string{"a", "b"}, []string{"b", "a"}, `expected: []string{"a", "b"}, got: []string{"b", "a"}`}, "map": {map[string]int{"a": 1}, map[string]int{"a": 2}, `expected: map[string]int{"a":1}, got: map[string]int{"a":2}`}, "time.Time": {now, now.Add(time.Second), ``}, } for tname, tt := range tests { t.Run(tname, func(t *testing.T) { ft := &fakeT{} is.Equal(ft, tt.expected, tt.got) shouldHaveFailed(t, ft) if tt.expectedMsg != "" { expectMsg(t, ft, tt.expectedMsg) } }) } }) t.Run("implements equaler", func(t *testing.T) { ft := &fakeT{} val1, val2 := newRandomy(1), newRandomy(1) is.Equal(ft, val1, val2) if ft.failed { t.Errorf("%#v == %#v: should have passed", val1, val2) } }) t.Run("implements equaler, not empty", func(t *testing.T) { ft := &fakeT{} val1, val2 := newRandomy(1), newRandomy(2) is.Equal(ft, val1, val2) shouldHaveFailed(t, ft) }) t.Run("time.Time", func(t *testing.T) { ft := &fakeT{} val1 := time.Date(2022, 1, 3, 0, 0, 18, 0, time.UTC) val2 := time.Date(2022, 1, 3, 5, 0, 18, 0, time.FixedZone("UTC+5", 5*3600)) is.Equal(ft, val1, val1) if ft.failed { t.Errorf("%#v == %#v: should have passed", val1, val2) } }) } func TestErr(t *testing.T) { t.Run("expected nil, got nil", func(t *testing.T) { ft := &fakeT{} is.Err(ft, nil, nil) if ft.failed { t.Errorf("failed: %s", ft.msg) } }) t.Run("expected nil, got error", func(t *testing.T) { ft := &fakeT{} is.Err(ft, errors.New("an error"), nil) shouldBeFatal(t, ft) shouldHaveFailed(t, ft) expectMsg(t, ft, "unexpected error: an error") }) t.Run("expected error, got nil", func(t *testing.T) { ft := &fakeT{} is.Err(ft, nil, errors.New("err")) shouldHaveFailed(t, ft) expectMsg(t, ft, `got: , expected: error`) }) t.Run("expected error, got same error", func(t *testing.T) { ft := &fakeT{} err := errors.New("an error") is.Err(ft, err, err) if ft.failed { t.Errorf("failed: %s", ft.msg) } }) t.Run("expected error, got different error", func(t *testing.T) { ft := &fakeT{} err1 := errors.New("an error") err2 := errors.New("second error") is.Err(ft, err1, err2) shouldHaveFailed(t, ft) shouldBeFatal(t, ft) expectMsg(t, ft, `expected: *errors.errorString(an error), got: *errors.errorString(second error)`) }) t.Run("expected error, got error of different type", func(t *testing.T) { ft := &fakeT{} err1 := errors.New("an error") err2 := errType("my error") is.Err(ft, err1, err2) shouldHaveFailed(t, ft) shouldBeFatal(t, ft) expectMsg(t, ft, `expected: *errors.errorString(an error), got: is_test.errType(my error)`) }) t.Run("expected error, got wrapped error", func(t *testing.T) { ft := &fakeT{} err := errors.New("an error") werr := fmt.Errorf("wrapped: %w", err) is.Err(ft, werr, err) if ft.failed { t.Errorf("failed: %s", ft.msg) } }) t.Run("got error, string", func(t *testing.T) { ft := &fakeT{} is.Err(ft, errors.New("test string"), "test string") if ft.failed { t.Errorf("failed: %s", ft.msg) } }) t.Run("got error, partial string", func(t *testing.T) { ft := &fakeT{} is.Err(ft, errors.New("test string"), "string") if ft.failed { t.Errorf("failed: %s", ft.msg) } }) t.Run("got error, not containing string", func(t *testing.T) { ft := &fakeT{} is.Err(ft, errors.New("test string"), "asdfasdf") shouldHaveFailed(t, ft) shouldBeFatal(t, ft) expectMsg(t, ft, `expected: "test string", got: "asdfasdf"`) }) t.Run("got custom type, same types", func(t *testing.T) { ft := &fakeT{} err := errType("custom error") is.Err(ft, err, err) if ft.failed { t.Errorf("failed: %s", ft.msg) } }) t.Run("got custom type, different types", func(t *testing.T) { ft := &fakeT{} err := errType("custom error") is.Err(ft, err, reflect.TypeFor[*fs.PathError]()) shouldHaveFailed(t, ft) shouldBeFatal(t, ft) expectMsg(t, ft, `expected: *fs.PathError, got: is_test.errType`) }) t.Run("unexpected type", func(t *testing.T) { ft := &fakeT{} is.Err(ft, errors.New("asdf"), 0) shouldHaveFailed(t, ft) shouldBeFatal(t, ft) expectMsg(t, ft, `unexpected type: int`) }) } func expectMsg(t *testing.T, ft *fakeT, expectedMsg string) { t.Helper() if ft.msg != expectedMsg { t.Errorf("expected msg: %q; got: %q", expectedMsg, ft.msg) } } func shouldBeFatal(t *testing.T, ft *fakeT) { t.Helper() if !ft.fatal { t.Error("should be fatal") } } func shouldHaveFailed(t *testing.T, ft *fakeT) { t.Helper() if !ft.failed { t.Error("should have failed") } } // helper types --- type intType struct{ v int } type randomy struct { val int rnd float64 } func newRandomy(val int) randomy { return randomy{val, rand.Float64()} } func (n randomy) Equal(other randomy) bool { return n.val == other.val } type errType string func (e errType) Error() string { return string(e) } // mockT ---- type fakeT struct { testing.TB failed bool fatal bool msg string } func (m *fakeT) Helper() {} func (m *fakeT) Log(a ...any) { fmt.Println(a...) } func (m *fakeT) Fatal(args ...any) { m.fatal = true m.Error(args...) } func (m *fakeT) Fatalf(format string, args ...any) { m.fatal = true m.Errorf(format, args...) } func (m *fakeT) Error(args ...any) { m.failed = true m.msg = fmt.Sprint(args...) } func (m *fakeT) Errorf(format string, args ...any) { m.failed = true m.msg = fmt.Sprintf(format, args...) }