From f6b707a3a90f87010f669572a55bad389bdade8d Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Mon, 19 Jan 2026 19:01:16 +0100 Subject: [PATCH 01/10] test: refactored assertions tests Signed-off-by: Frederic BIDON --- internal/assertions/compare_test.go | 1083 ++++++++++----------------- internal/assertions/mock_test.go | 14 + internal/assertions/number_test.go | 934 ++++++++++++----------- internal/assertions/string_test.go | 6 +- 4 files changed, 890 insertions(+), 1147 deletions(-) diff --git a/internal/assertions/compare_test.go b/internal/assertions/compare_test.go index ac5e718e2..27b684582 100644 --- a/internal/assertions/compare_test.go +++ b/internal/assertions/compare_test.go @@ -6,549 +6,236 @@ package assertions import ( "bytes" "iter" - "runtime" "slices" - "strings" "testing" "time" ) -const pkg = "github.com/go-openapi/testify/v2/internal/assertions" - -//nolint:dupl // no this is not a duplicate: it just looks almost the same! -func TestCompareGreater(t *testing.T) { +func TestCompareErrorMessages(t *testing.T) { + // Error message validation t.Parallel() - t.Run("with basic input", func(t *testing.T) { + t.Run("with Greater", func(t *testing.T) { t.Parallel() mock := new(mockT) - if !Greater(mock, 2, 1) { - t.Error("Greater should return true") - } + Greater(mock, 1, 2) - if Greater(mock, 1, 1) { - t.Error("Greater should return false") + if !mock.Failed() { + t.Error("Expected test to fail but it passed") } - if Greater(mock, 1, 2) { - t.Error("Greater should return false") + errorMsg := mock.errorString() + if !Contains(t, errorMsg, `"1" is not greater than "2"`) { + t.Errorf("Error message should contain comparison details, got: %s", errorMsg) } }) - for currCase := range compareStrictlyGreaterCases() { - t.Run("should NOT be strictly greater, with expected error message", func(t *testing.T) { - t.Parallel() - - mock := &outputT{buf: bytes.NewBuffer(nil)} // check error report - False(t, Greater(mock, currCase.less, currCase.greater), - "expected %v NOT to be strictly greater than %v", - currCase.less, currCase.greater, - ) - - Contains(t, mock.buf.String(), currCase.msg) - Contains(t, mock.helpers, pkg+".Greater") - }) - - t.Run("should be strictly greater", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) // don't check output - True(t, Greater(mock, currCase.greater, currCase.less), - "expected %v to be strictly greater than %v", - currCase.less, currCase.greater, - ) - }) - } - - for currCase := range compareEqualCases() { - t.Run("equal values should NOT be strictly greater", func(t *testing.T) { - t.Parallel() + t.Run("with GreaterOrEqual", func(t *testing.T) { + mock := new(mockT) + GreaterOrEqual(mock, 1, 2) - mock := new(mockT) - False(t, Greater(mock, currCase.less, currCase.greater), - "expected (equal) %v NOT to be strictly greater than %v", - currCase.less, currCase.greater, - ) - }) - } -} + if !mock.Failed() { + t.Error("Expected test to fail but it passed") + } -func TestCompareGreaterOrEqual(t *testing.T) { - t.Parallel() + errorMsg := mock.errorString() + if !Contains(t, errorMsg, `"1" is not greater than or equal to "2"`) { + t.Errorf("Error message should contain comparison details, got: %s", errorMsg) + } + }) - t.Run("with basic input", func(t *testing.T) { + t.Run("with Less", func(t *testing.T) { t.Parallel() mock := new(mockT) - if !GreaterOrEqual(mock, 2, 1) { - t.Error("GreaterOrEqual should return true") - } + Less(mock, 2, 1) - if !GreaterOrEqual(mock, 1, 1) { - t.Error("GreaterOrEqual should return true") + if !mock.Failed() { + t.Error("Expected test to fail but it passed") } - if GreaterOrEqual(mock, 1, 2) { - t.Error("GreaterOrEqual should return false") + errorMsg := mock.errorString() + if !Contains(t, errorMsg, `"2" is not less than "1"`) { + t.Errorf("Error message should contain comparison details, got: %s", errorMsg) } }) - for currCase := range compareStrictlyGreaterCases() { - t.Run("should NOT be greater or equal, with expected error message", func(t *testing.T) { - t.Parallel() - - mock := &outputT{buf: bytes.NewBuffer(nil)} // check error report - - False(t, GreaterOrEqual(mock, currCase.less, currCase.greater), - "expected %v NOT to be greater than or equal to %v", - currCase.less, currCase.greater, - ) - - Contains(t, mock.buf.String(), strings.ReplaceAll(currCase.msg, "than", "than or equal to")) - Contains(t, mock.helpers, pkg+".GreaterOrEqual") - }) - - t.Run("should be greater or equal", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - - True(t, GreaterOrEqual(mock, currCase.greater, currCase.less), - "expected %v to be greater than or equal to %v", - currCase.less, currCase.greater, - ) - }) - } - - for currCase := range compareEqualCases() { - t.Run("equal values should be greater or equal", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - - True(t, GreaterOrEqual(mock, currCase.less, currCase.greater), - "expected (equal) %v to be greater than or equal to %v", - currCase.less, currCase.greater, - ) - }) - } -} - -//nolint:dupl // no this is not a duplicate: it just looks almost the same! -func TestCompareLess(t *testing.T) { - t.Parallel() - t.Run("with basic input", func(t *testing.T) { + t.Run("with LessOrEqual", func(t *testing.T) { t.Parallel() mock := new(mockT) + LessOrEqual(mock, 2, 1) - if !Less(mock, 1, 2) { - t.Error("Less should return true") - } - - if Less(mock, 1, 1) { - t.Error("Less should return false") + if !mock.Failed() { + t.Error("Expected test to fail but it passed") } - if Less(mock, 2, 1) { - t.Error("Less should return false") + errorMsg := mock.errorString() + if !Contains(t, errorMsg, `"2" is not less than or equal to "1"`) { + t.Errorf("Error message should contain comparison details, got: %s", errorMsg) } }) - for currCase := range compareStrictlyLessCases() { - t.Run("should NOT be stricly less, with expected error message", func(t *testing.T) { - t.Parallel() - - mock := &outputT{buf: bytes.NewBuffer(nil)} // check error report - False(t, Less(mock, currCase.greater, currCase.less), - "expected %v NOT to be stricly less than %v", - currCase.greater, currCase.less, - ) - - Contains(t, mock.buf.String(), currCase.msg) - Contains(t, mock.helpers, pkg+".Less") - }) - - t.Run("should be stricly less", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - - True(t, Less(mock, currCase.less, currCase.greater), - "expected %v be stricly less than %v", - currCase.less, currCase.greater, - ) - }) - } - - for currCase := range compareEqualCases() { - t.Run("equal values should NOT be strictly less", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - - True(t, GreaterOrEqual(mock, currCase.less, currCase.greater), - "expected (equal) %v NOT to be strictly less than %v", - currCase.less, currCase.greater, - ) - }) - } -} - -func TestCompareLessOrEqual(t *testing.T) { - t.Parallel() - - t.Run("with basic input", func(t *testing.T) { + t.Run("with Positive", func(t *testing.T) { t.Parallel() mock := new(mockT) + Positive(mock, -1) - if !LessOrEqual(mock, 1, 2) { - t.Error("LessOrEqual should return true") - } - - if !LessOrEqual(mock, 1, 1) { - t.Error("LessOrEqual should return true") + if !mock.Failed() { + t.Error("Expected test to fail but it passed") } - if LessOrEqual(mock, 2, 1) { - t.Error("LessOrEqual should return false") + errorMsg := mock.errorString() + if !Contains(t, errorMsg, `"-1" is not positive`) { + t.Errorf("Error message should contain sign check details, got: %s", errorMsg) } }) - for currCase := range compareStrictlyLessCases() { - t.Run("should NOT be less or equal, with expected error message", func(t *testing.T) { - t.Parallel() - - mock := &outputT{buf: bytes.NewBuffer(nil)} // check error report - - False(t, LessOrEqual(mock, currCase.greater, currCase.less), - "expected %v NOT to be less than or equal to %v", - currCase.less, currCase.greater, - ) - - Contains(t, mock.buf.String(), strings.ReplaceAll(currCase.msg, "than", "than or equal to")) - Contains(t, mock.helpers, pkg+".LessOrEqual") - }) - - t.Run("should be stricly less", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - True(t, LessOrEqual(mock, currCase.less, currCase.greater), - "expected %v to be less than or equal to %v", - currCase.less, currCase.greater, - ) - }) + t.Run("with Negative", func(t *testing.T) { + t.Parallel() - for currCase := range compareEqualCases() { - t.Run("equal values should be less or equal", func(t *testing.T) { - t.Parallel() + mock := new(mockT) + Negative(mock, 1) - mock := new(mockT) + if !mock.Failed() { + t.Error("Expected test to fail but it passed") + } - True(t, GreaterOrEqual(mock, currCase.less, currCase.greater), - "expected (equal) %v to be less than or equal to %v", - currCase.less, currCase.greater, - ) - }) + errorMsg := mock.errorString() + if !Contains(t, errorMsg, `"1" is not negative`) { + t.Errorf("Error message should contain sign check details, got: %s", errorMsg) } - } -} + }) -func TestCompareGreaterT(t *testing.T) { - t.Parallel() + t.Run("with forwarded args", func(t *testing.T) { + msgAndArgs := []any{"format %s %x", "this", 0xc001} + const expectedOutput = "format this c001\n" + + funcs := []func(t T){ + func(t T) { Greater(t, 1, 2, msgAndArgs...) }, + func(t T) { GreaterOrEqual(t, 1, 2, msgAndArgs...) }, + func(t T) { Less(t, 2, 1, msgAndArgs...) }, + func(t T) { LessOrEqual(t, 2, 1, msgAndArgs...) }, + func(t T) { Positive(t, 0, msgAndArgs...) }, + func(t T) { Negative(t, 0, msgAndArgs...) }, + } - for tc := range greaterTCases() { - t.Run(tc.name, tc.test) - } + for _, f := range funcs { + mock := &outputT{buf: bytes.NewBuffer(nil)} + f(mock) + Contains(t, mock.buf.String(), expectedOutput) + } + }) } -func TestCompareGreaterOrEqualT(t *testing.T) { +func TestCompareGreaterAndLess(t *testing.T) { t.Parallel() - for tc := range greaterOrEqualTCases() { - t.Run(tc.name, tc.test) + // Unified tests with all comparison functions, reflection-based or generic + for tc := range comparisonCases() { + t.Run(tc.name+"/unified", testAllComparison(tc)) } } -func TestCompareLessT(t *testing.T) { +func TestCompareGreaterAndLessT(t *testing.T) { t.Parallel() - for tc := range lessTCases() { - t.Run(tc.name, tc.test) + // Unified tests with all comparison functions + for tc := range comparisonCases() { + t.Run(tc.name, func(t *testing.T) { + // Dispatch to type-specific test based on the type of tc.less + switch tc.less.(type) { + case string: + testAllComparisonT[string](tc)(t) + case int: + testAllComparisonT[int](tc)(t) + case int8: + testAllComparisonT[int8](tc)(t) + case int16: + testAllComparisonT[int16](tc)(t) + case int32: + testAllComparisonT[int32](tc)(t) + case int64: + testAllComparisonT[int64](tc)(t) + case uint: + testAllComparisonT[uint](tc)(t) + case uint8: + testAllComparisonT[uint8](tc)(t) + case uint16: + testAllComparisonT[uint16](tc)(t) + case uint32: + testAllComparisonT[uint32](tc)(t) + case uint64: + testAllComparisonT[uint64](tc)(t) + case float32: + testAllComparisonT[float32](tc)(t) + case float64: + testAllComparisonT[float64](tc)(t) + case uintptr: + testAllComparisonT[uintptr](tc)(t) + case time.Time: + testAllComparisonT[time.Time](tc)(t) + case []byte: + testAllComparisonT[[]byte](tc)(t) + default: + // Custom types (like redefined uintptr) - skip, they're tested separately + t.Skip("custom types tested separately") + } + }) } -} - -func TestCompareLessOrEqualT(t *testing.T) { - t.Parallel() - for tc := range lessOrEqualTCases() { - t.Run(tc.name, tc.test) - } + // Additional type-specific tests + t.Run("custom int type", testGreaterTCustomInt()) } func TestComparePositiveT(t *testing.T) { t.Parallel() - for tc := range positiveTCases() { - t.Run(tc.name, tc.test) - } -} - -func TestCompareNegativeT(t *testing.T) { - t.Parallel() - - for tc := range negativeTCases() { - t.Run(tc.name, tc.test) + // Unified tests with both Positive and Negative functions + for tc := range signCases() { + t.Run(tc.name, func(t *testing.T) { + // Dispatch to type-specific test based on the type of tc.positive + switch tc.positive.(type) { + case int: + testAllSignT[int](tc)(t) + case int8: + testAllSignT[int8](tc)(t) + case int16: + testAllSignT[int16](tc)(t) + case int32: + testAllSignT[int32](tc)(t) + case int64: + testAllSignT[int64](tc)(t) + case float32: + testAllSignT[float32](tc)(t) + case float64: + testAllSignT[float64](tc)(t) + } + }) } } func TestComparePositive(t *testing.T) { t.Parallel() - mock := new(testing.T) - - if !Positive(mock, 1) { - t.Error("Positive should return true") - } - - if !Positive(mock, 1.23) { - t.Error("Positive should return true") - } - - if Positive(mock, -1) { - t.Error("Positive should return false") - } - - if Positive(mock, -1.23) { - t.Error("Positive should return false") - } - // Check error report - for currCase := range comparePositiveCases() { - out := &outputT{buf: bytes.NewBuffer(nil)} - False(t, Positive(out, currCase.e)) - Contains(t, out.buf.String(), currCase.msg) - Contains(t, out.helpers, pkg+".Positive") - } -} - -func TestCompareNegative(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - if !Negative(mock, -1) { - t.Error("Negative should return true") - } - - if !Negative(mock, -1.23) { - t.Error("Negative should return true") - } - - if Negative(mock, 1) { - t.Error("Negative should return false") - } - - if Negative(mock, 1.23) { - t.Error("Negative should return false") - } - - // Check error report - for currCase := range compareNegativeCases() { - out := &outputT{buf: bytes.NewBuffer(nil)} - False(t, Negative(out, currCase.e)) - Contains(t, out.buf.String(), currCase.msg) - Contains(t, out.helpers, pkg+".Negative") - } -} - -func TestCompareMsgAndArgsForwarding(t *testing.T) { - msgAndArgs := []any{"format %s %x", "this", 0xc001} - const expectedOutput = "format this c001\n" - - funcs := []func(t T){ - func(t T) { Greater(t, 1, 2, msgAndArgs...) }, - func(t T) { GreaterOrEqual(t, 1, 2, msgAndArgs...) }, - func(t T) { Less(t, 2, 1, msgAndArgs...) }, - func(t T) { LessOrEqual(t, 2, 1, msgAndArgs...) }, - func(t T) { Positive(t, 0, msgAndArgs...) }, - func(t T) { Negative(t, 0, msgAndArgs...) }, - } - - for _, f := range funcs { - out := &outputT{buf: bytes.NewBuffer(nil)} - f(out) - Contains(t, out.buf.String(), expectedOutput) + // Unified tests with both Positive and Negative functions + for tc := range signCases() { + t.Run(tc.name+"/unified", testAllSign(tc)) } } // genericTestCase wraps a test function with its name for table-driven tests of generic functions. +// Kept for compatibility with existing special-case tests. type genericTestCase struct { name string test func(*testing.T) } -// greaterTCases returns test cases for GreaterT with various Ordered types. -func greaterTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - {"int", testGreaterT[int](2, 1, 1, 2)}, - {"int8", testGreaterT[int8](2, 1, 1, 2)}, - {"int16", testGreaterT[int16](2, 1, 1, 2)}, - {"int32", testGreaterT[int32](2, 1, 1, 2)}, - {"int64", testGreaterT[int64](2, 1, 1, 2)}, - {"uint", testGreaterT[uint](2, 1, 1, 2)}, - {"uint8", testGreaterT[uint8](2, 1, 1, 2)}, - {"uint16", testGreaterT[uint16](2, 1, 1, 2)}, - {"uint32", testGreaterT[uint32](2, 1, 1, 2)}, - {"uint64", testGreaterT[uint64](2, 1, 1, 2)}, - {"float32", testGreaterT[float32](2.5, 1.5, 1.5, 2.5)}, - {"float64", testGreaterT[float64](2.5, 1.5, 1.5, 2.5)}, - {"string", testGreaterT[string]("b", "a", "a", "b")}, - {"uintptr", testGreaterT[uintptr](2, 1, 1, 2)}, - {"time.Time", testGreaterTTime()}, - {"[]byte", testGreaterTBytes()}, - {"custom int type", testGreaterTCustomInt()}, - }) -} - -// greaterOrEqualTCases returns test cases for GreaterOrEqualT with various Ordered types. -func greaterOrEqualTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - {"int", testGreaterOrEqualT[int](2, 1, 1, 1, 0, 1)}, - {"int8", testGreaterOrEqualT[int8](2, 1, 1, 1, 0, 1)}, - {"int16", testGreaterOrEqualT[int16](2, 1, 1, 1, 0, 1)}, - {"int32", testGreaterOrEqualT[int32](2, 1, 1, 1, 0, 1)}, - {"int64", testGreaterOrEqualT[int64](2, 1, 1, 1, 0, 1)}, - {"uint", testGreaterOrEqualT[uint](2, 1, 1, 1, 0, 1)}, - {"uint8", testGreaterOrEqualT[uint8](2, 1, 1, 1, 0, 1)}, - {"uint16", testGreaterOrEqualT[uint16](2, 1, 1, 1, 0, 1)}, - {"uint32", testGreaterOrEqualT[uint32](2, 1, 1, 1, 0, 1)}, - {"uint64", testGreaterOrEqualT[uint64](2, 1, 1, 1, 0, 1)}, - {"float32", testGreaterOrEqualT[float32](2.5, 1.5, 1.5, 1.5, 0.5, 1.5)}, - {"float64", testGreaterOrEqualT[float64](2.5, 1.5, 1.5, 1.5, 0.5, 1.5)}, - {"string", testGreaterOrEqualT[string]("b", "a", "a", "a", "a", "b")}, - {"uintptr", testGreaterOrEqualT[uintptr](2, 1, 1, 1, 0, 1)}, - {"time.Time", testGreaterOrEqualTTime()}, - {"[]byte", testGreaterOrEqualTBytes()}, - }) -} - -// lessTCases returns test cases for LessT with various Ordered types. -func lessTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - {"int", testLessT[int](1, 2, 2, 1)}, - {"int8", testLessT[int8](1, 2, 2, 1)}, - {"int16", testLessT[int16](1, 2, 2, 1)}, - {"int32", testLessT[int32](1, 2, 2, 1)}, - {"int64", testLessT[int64](1, 2, 2, 1)}, - {"uint", testLessT[uint](1, 2, 2, 1)}, - {"uint8", testLessT[uint8](1, 2, 2, 1)}, - {"uint16", testLessT[uint16](1, 2, 2, 1)}, - {"uint32", testLessT[uint32](1, 2, 2, 1)}, - {"uint64", testLessT[uint64](1, 2, 2, 1)}, - {"float32", testLessT[float32](1.5, 2.5, 2.5, 1.5)}, - {"float64", testLessT[float64](1.5, 2.5, 2.5, 1.5)}, - {"string", testLessT[string]("a", "b", "b", "a")}, - {"uintptr", testLessT[uintptr](1, 2, 2, 1)}, - {"time.Time", testLessTTime()}, - {"[]byte", testLessTBytes()}, - }) -} - -// lessOrEqualTCases returns test cases for LessOrEqualT with various Ordered types. -func lessOrEqualTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - {"int", testLessOrEqualT[int](1, 2, 1, 1, 2, 1)}, - {"int8", testLessOrEqualT[int8](1, 2, 1, 1, 2, 1)}, - {"int16", testLessOrEqualT[int16](1, 2, 1, 1, 2, 1)}, - {"int32", testLessOrEqualT[int32](1, 2, 1, 1, 2, 1)}, - {"int64", testLessOrEqualT[int64](1, 2, 1, 1, 2, 1)}, - {"uint", testLessOrEqualT[uint](1, 2, 1, 1, 2, 1)}, - {"uint8", testLessOrEqualT[uint8](1, 2, 1, 1, 2, 1)}, - {"uint16", testLessOrEqualT[uint16](1, 2, 1, 1, 2, 1)}, - {"uint32", testLessOrEqualT[uint32](1, 2, 1, 1, 2, 1)}, - {"uint64", testLessOrEqualT[uint64](1, 2, 1, 1, 2, 1)}, - {"float32", testLessOrEqualT[float32](1.5, 2.5, 1.5, 1.5, 2.5, 1.5)}, - {"float64", testLessOrEqualT[float64](1.5, 2.5, 1.5, 1.5, 2.5, 1.5)}, - {"string", testLessOrEqualT[string]("a", "b", "a", "a", "b", "a")}, - {"uintptr", testLessOrEqualT[uintptr](1, 2, 1, 1, 2, 1)}, - {"time.Time", testLessOrEqualTTime()}, - {"[]byte", testLessOrEqualTBytes()}, - }) -} - -// positiveTCases returns test cases for PositiveT with various SignedNumeric types. -func positiveTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - {"int", testPositiveT[int](1, -1)}, - {"int8", testPositiveT[int8](1, -1)}, - {"int16", testPositiveT[int16](1, -1)}, - {"int32", testPositiveT[int32](1, -1)}, - {"int64", testPositiveT[int64](1, -1)}, - {"float32", testPositiveT[float32](1.5, -1.5)}, - {"float64", testPositiveT[float64](1.5, -1.5)}, - {"zero is not positive", testPositiveTZero()}, - }) -} - -// negativeTCases returns test cases for NegativeT with various SignedNumeric types. -func negativeTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - {"int", testNegativeT[int](-1, 1)}, - {"int8", testNegativeT[int8](-1, 1)}, - {"int16", testNegativeT[int16](-1, 1)}, - {"int32", testNegativeT[int32](-1, 1)}, - {"int64", testNegativeT[int64](-1, 1)}, - {"float32", testNegativeT[float32](-1.5, 1.5)}, - {"float64", testNegativeT[float64](-1.5, 1.5)}, - {"zero is not negative", testNegativeTZero()}, - }) -} - -// Test helper functions for generic comparison assertions - -func testGreaterT[V Ordered](successE1, successE2, failE1, failE2 V) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - True(t, GreaterT(mock, successE1, successE2)) - False(t, GreaterT(mock, failE1, failE2)) - False(t, GreaterT(mock, successE1, successE1)) // equal values - } -} - -func testGreaterTTime() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - t0 := time.Now() - t1 := t0.Add(-time.Second) - - True(t, GreaterT(mock, t0, t1)) - False(t, GreaterT(mock, t1, t0)) - False(t, GreaterT(mock, t0, t0)) - } -} - -func testGreaterTBytes() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - True(t, GreaterT(mock, []byte{2}, []byte{1})) - False(t, GreaterT(mock, []byte{1}, []byte{2})) - False(t, GreaterT(mock, []byte{1}, []byte{1})) - } -} +// Special-case test helpers for types that need custom handling func testGreaterTCustomInt() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper return func(t *testing.T) { t.Parallel() mock := new(mockT) @@ -559,308 +246,324 @@ func testGreaterTCustomInt() func(*testing.T) { } } -func testGreaterOrEqualT[V Ordered](gtE1, gtE2, eqE1, eqE2, failE1, failE2 V) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) +// Unified test helpers for comparison functions - True(t, GreaterOrEqualT(mock, gtE1, gtE2)) // greater - True(t, GreaterOrEqualT(mock, eqE1, eqE2)) // equal - False(t, GreaterOrEqualT(mock, failE1, failE2)) // less - } +// comparisonTestCase represents a test case for comparison functions. +type comparisonTestCase struct { + name string + less any + greater any + equal bool // if true, less == greater (for testing equal values) } -func testGreaterOrEqualTTime() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - t0 := time.Now() - t1 := t0.Add(-time.Second) +// comparisonCases returns unified test data for all comparison functions. +func comparisonCases() iter.Seq[comparisonTestCase] { + type redefinedUintptr uintptr - True(t, GreaterOrEqualT(mock, t0, t1)) // greater - True(t, GreaterOrEqualT(mock, t0, t0)) // equal - False(t, GreaterOrEqualT(mock, t1, t0)) // less - } + return slices.Values([]comparisonTestCase{ + // Strict inequality cases + {name: "string", less: "a", greater: "b", equal: false}, + {name: "int", less: int(1), greater: int(2), equal: false}, + {name: "int8", less: int8(1), greater: int8(2), equal: false}, + {name: "int16", less: int16(1), greater: int16(2), equal: false}, + {name: "int32", less: int32(1), greater: int32(2), equal: false}, + {name: "int64", less: int64(1), greater: int64(2), equal: false}, + {name: "uint", less: uint(1), greater: uint(2), equal: false}, + {name: "uint8", less: uint8(1), greater: uint8(2), equal: false}, + {name: "uint16", less: uint16(1), greater: uint16(2), equal: false}, + {name: "uint32", less: uint32(1), greater: uint32(2), equal: false}, + {name: "uint64", less: uint64(1), greater: uint64(2), equal: false}, + {name: "float32", less: float32(1.23), greater: float32(2.34), equal: false}, + {name: "float64", less: float64(1.23), greater: float64(2.34), equal: false}, + {name: "uintptr", less: uintptr(1), greater: uintptr(2), equal: false}, + {name: "uintptr/9-10", less: uintptr(9), greater: uintptr(10), equal: false}, + {name: "redefined-uintptr", less: redefinedUintptr(9), greater: redefinedUintptr(10), equal: false}, + {name: "time.Time", less: time.Time{}, greater: time.Time{}.Add(time.Hour), equal: false}, + {name: "[]byte", less: []byte{1, 1}, greater: []byte{1, 2}, equal: false}, + + // Equality cases + {name: "string/equal", less: "a", greater: "a", equal: true}, + {name: "int/equal", less: int(1), greater: int(1), equal: true}, + {name: "int8/equal", less: int8(1), greater: int8(1), equal: true}, + {name: "int16/equal", less: int16(1), greater: int16(1), equal: true}, + {name: "int32/equal", less: int32(1), greater: int32(1), equal: true}, + {name: "int64/equal", less: int64(1), greater: int64(1), equal: true}, + {name: "uint/equal", less: uint(1), greater: uint(1), equal: true}, + {name: "uint8/equal", less: uint8(1), greater: uint8(1), equal: true}, + {name: "uint16/equal", less: uint16(1), greater: uint16(1), equal: true}, + {name: "uint32/equal", less: uint32(1), greater: uint32(1), equal: true}, + {name: "uint64/equal", less: uint64(1), greater: uint64(1), equal: true}, + {name: "float32/equal", less: float32(1.23), greater: float32(1.23), equal: true}, + {name: "float64/equal", less: float64(1.23), greater: float64(1.23), equal: true}, + {name: "uintptr/equal", less: uintptr(1), greater: uintptr(1), equal: true}, + {name: "time.Time/equal", less: time.Time{}, greater: time.Time{}, equal: true}, + {name: "[]byte/equal", less: []byte{1, 1}, greater: []byte{1, 1}, equal: true}, + }) } -func testGreaterOrEqualTBytes() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper +// testAllComparison tests all four comparison functions with the same test data. +func testAllComparison(tc comparisonTestCase) func(*testing.T) { return func(t *testing.T) { t.Parallel() - mock := new(mockT) - True(t, GreaterOrEqualT(mock, []byte{2}, []byte{1})) - True(t, GreaterOrEqualT(mock, []byte{1}, []byte{1})) - False(t, GreaterOrEqualT(mock, []byte{1}, []byte{2})) - } -} + if tc.equal { + // For equal values: + // - Greater and Less should return false + // - GreaterOrEqual and LessOrEqual should return true + t.Run("with equal values", func(t *testing.T) { + t.Run("Greater should fail", testComparison(Greater, tc.less, tc.greater, false)) + t.Run("GreaterOrEqual should pass", testComparison(GreaterOrEqual, tc.less, tc.greater, true)) + t.Run("Less should fail", testComparison(Less, tc.less, tc.greater, false)) + t.Run("LessOrEqual should pass", testComparison(LessOrEqual, tc.less, tc.greater, true)) + }) -func testLessT[V Ordered](successE1, successE2, failE1, failE2 V) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) + return + } - True(t, LessT(mock, successE1, successE2)) - False(t, LessT(mock, failE1, failE2)) - False(t, LessT(mock, successE1, successE1)) // equal values + // For strict inequality: + // Test both directions to verify the inverse relationships + t.Run("with strict inequality", func(t *testing.T) { + t.Run("Greater", func(t *testing.T) { + t.Run("should pass (greater > less)", testComparison(Greater, tc.greater, tc.less, true)) + t.Run("should fail (less > greater)", testComparison(Greater, tc.less, tc.greater, false)) + }) + t.Run("GreaterOrEqual", func(t *testing.T) { + t.Run("should pass (greater >= less)", testComparison(GreaterOrEqual, tc.greater, tc.less, true)) + t.Run("should fail (less >= greater)", testComparison(GreaterOrEqual, tc.less, tc.greater, false)) + }) + t.Run("Less", func(t *testing.T) { + t.Run("should pass (less < greater)", testComparison(Less, tc.less, tc.greater, true)) + t.Run("should fail (greater < less)", testComparison(Less, tc.greater, tc.less, false)) + }) + t.Run("LessOrEqual", func(t *testing.T) { + t.Run("should pass (less <= greater)", testComparison(LessOrEqual, tc.less, tc.greater, true)) + t.Run("should fail (greater <= less)", testComparison(LessOrEqual, tc.greater, tc.less, false)) + }) + }) } } -func testLessTTime() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper +// testComparison is a helper that tests a comparison function. +func testComparison(cmp func(T, any, any, ...any) bool, e1, e2 any, shouldPass bool) func(*testing.T) { return func(t *testing.T) { t.Parallel() - mock := new(mockT) - t0 := time.Now() - t1 := t0.Add(time.Second) + mock := new(mockT) + result := cmp(mock, e1, e2) - True(t, LessT(mock, t0, t1)) - False(t, LessT(mock, t1, t0)) - False(t, LessT(mock, t0, t0)) - } -} + if shouldPass { + True(t, result) + False(t, mock.Failed()) -func testLessTBytes() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) + return + } - True(t, LessT(mock, []byte{1}, []byte{2})) - False(t, LessT(mock, []byte{2}, []byte{1})) - False(t, LessT(mock, []byte{1}, []byte{1})) + False(t, result) + True(t, mock.Failed()) } } -func testLessOrEqualT[V Ordered](ltE1, ltE2, eqE1, eqE2, failE1, failE2 V) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper +// testAllComparisonT tests all four generic comparison functions with the same test data. +// +//nolint:thelper // linter false positive: this is not a helper +func testAllComparisonT[V Ordered](tc comparisonTestCase) func(*testing.T) { return func(t *testing.T) { t.Parallel() - mock := new(mockT) - True(t, LessOrEqualT(mock, ltE1, ltE2)) // less - True(t, LessOrEqualT(mock, eqE1, eqE2)) // equal - False(t, LessOrEqualT(mock, failE1, failE2)) // greater - } -} + // Type assert the values + less, ok1 := tc.less.(V) + greater, ok2 := tc.greater.(V) + if !ok1 || !ok2 { + t.Fatalf("type mismatch in testcase: expected %T, got less=%T, greater=%T", *new(V), tc.less, tc.greater) + } -func testLessOrEqualTTime() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) + if tc.equal { + // For equal values: + // - GreaterT and LessT should return false + // - GreaterOrEqualT and LessOrEqualT should return true + t.Run("with equal values", func(t *testing.T) { + t.Run("GreaterT should fail", testComparisonT(GreaterT[V], less, greater, false)) + t.Run("GreaterOrEqualT should pass", testComparisonT(GreaterOrEqualT[V], less, greater, true)) + t.Run("LessT should fail", testComparisonT(LessT[V], less, greater, false)) + t.Run("LessOrEqualT should pass", testComparisonT(LessOrEqualT[V], less, greater, true)) + }) - t0 := time.Now() - t1 := t0.Add(time.Second) + return + } - True(t, LessOrEqualT(mock, t0, t1)) // less - True(t, LessOrEqualT(mock, t0, t0)) // equal - False(t, LessOrEqualT(mock, t1, t0)) // greater + // For strict inequality: + // Test both directions to verify the inverse relationships + t.Run("with strict inequality", func(t *testing.T) { + t.Run("GreaterT", func(t *testing.T) { + t.Run("should pass (greater > less)", testComparisonT(GreaterT[V], greater, less, true)) + t.Run("should fail (less > greater)", testComparisonT(GreaterT[V], less, greater, false)) + }) + t.Run("GreaterOrEqualT", func(t *testing.T) { + t.Run("should pass (greater >= less)", testComparisonT(GreaterOrEqualT[V], greater, less, true)) + t.Run("should fail (less >= greater)", testComparisonT(GreaterOrEqualT[V], less, greater, false)) + }) + t.Run("LessT", func(t *testing.T) { + t.Run("should pass (less < greater)", testComparisonT(LessT[V], less, greater, true)) + t.Run("should fail (greater < less)", testComparisonT(LessT[V], greater, less, false)) + }) + t.Run("LessOrEqualT", func(t *testing.T) { + t.Run("should pass (less <= greater)", testComparisonT(LessOrEqualT[V], less, greater, true)) + t.Run("should fail (greater <= less)", testComparisonT(LessOrEqualT[V], greater, less, false)) + }) + }) } } -func testLessOrEqualTBytes() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper +// testComparisonT is a helper that tests a generic comparison function. +func testComparisonT[V Ordered](cmp func(T, V, V, ...any) bool, e1, e2 V, shouldPass bool) func(*testing.T) { return func(t *testing.T) { t.Parallel() + mock := new(mockT) + result := cmp(mock, e1, e2) - True(t, LessOrEqualT(mock, []byte{1}, []byte{2})) - True(t, LessOrEqualT(mock, []byte{1}, []byte{1})) - False(t, LessOrEqualT(mock, []byte{2}, []byte{1})) - } -} + if shouldPass { + True(t, result) + False(t, mock.Failed()) -func testPositiveT[V SignedNumeric](positive, negative V) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) + return + } - True(t, PositiveT(mock, positive)) - False(t, PositiveT(mock, negative)) + False(t, result) + True(t, mock.Failed()) } } -func testPositiveTZero() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) +// Unified test helpers for sign functions (Positive/Negative) - False(t, PositiveT(mock, 0)) - False(t, PositiveT(mock, 0.0)) - } +// signTestCase represents a test case for Positive/Negative functions. +type signTestCase struct { + name string + positive any + negative any + isZero bool // if true, positive is zero (both Positive and Negative should fail) } -func testNegativeT[V SignedNumeric](negative, positive V) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { - t.Parallel() - mock := new(mockT) +// signCases returns unified test data for Positive/Negative functions. +func signCases() iter.Seq[signTestCase] { + return slices.Values([]signTestCase{ + {name: "int", positive: int(1), negative: int(-1), isZero: false}, + {name: "int8", positive: int8(1), negative: int8(-1), isZero: false}, + {name: "int16", positive: int16(1), negative: int16(-1), isZero: false}, + {name: "int32", positive: int32(1), negative: int32(-1), isZero: false}, + {name: "int64", positive: int64(1), negative: int64(-1), isZero: false}, + {name: "float32", positive: float32(1.5), negative: float32(-1.5), isZero: false}, + {name: "float64", positive: float64(1.5), negative: float64(-1.5), isZero: false}, - True(t, NegativeT(mock, negative)) - False(t, NegativeT(mock, positive)) - } + // Zero cases - both Positive and Negative should fail + {name: "int/zero", positive: int(0), negative: int(0), isZero: true}, + {name: "float64/zero", positive: float64(0.0), negative: float64(0.0), isZero: true}, + }) } -func testNegativeTZero() func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper +// testAllSign tests both Positive and Negative functions with the same test data. +func testAllSign(tc signTestCase) func(*testing.T) { return func(t *testing.T) { t.Parallel() - mock := new(mockT) - False(t, NegativeT(mock, 0)) - False(t, NegativeT(mock, 0.0)) - } -} + if tc.isZero { + // Zero should fail both Positive and Negative + t.Run("zero should fail both", func(t *testing.T) { + t.Run("Positive should fail", testSign(Positive, tc.positive, false)) + t.Run("Negative should fail", testSign(Negative, tc.positive, false)) + }) -// callerName gives the function name (qualified with a package path) -// for the caller after skip frames (where 0 means the current function). -func callerName(skip int) string { - // Make room for the skip PC. - var pc [1]uintptr - n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName - if n == 0 { - panic("testing: zero callers found") + return + } + + // Test positive and negative values + t.Run("with positive/negative values", func(t *testing.T) { + t.Run("Positive", func(t *testing.T) { + t.Run("should pass (positive value)", testSign(Positive, tc.positive, true)) + t.Run("should fail (negative value)", testSign(Positive, tc.negative, false)) + }) + t.Run("Negative", func(t *testing.T) { + t.Run("should pass (negative value)", testSign(Negative, tc.negative, true)) + t.Run("should fail (positive value)", testSign(Negative, tc.positive, false)) + }) + }) } - frames := runtime.CallersFrames(pc[:n]) - frame, _ := frames.Next() - return frame.Function } -type compareFixture struct { - less any - greater any - msg string -} +// testSign is a helper that tests a sign function. +func testSign(sign func(T, any, ...any) bool, e any, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() -func compareStrictlyGreaterCases() iter.Seq[compareFixture] { - const genMsg = `"1" is not greater than "2"` + mock := new(mockT) + result := sign(mock, e) - type redefinedUintptr uintptr + if shouldPass { + True(t, result) + False(t, mock.Failed()) - return slices.Values( - []compareFixture{ - {less: "a", greater: "b", msg: `"a" is not greater than "b"`}, - {less: int(1), greater: int(2), msg: genMsg}, - {less: int8(1), greater: int8(2), msg: genMsg}, - {less: int16(1), greater: int16(2), msg: genMsg}, - {less: int32(1), greater: int32(2), msg: genMsg}, - {less: int64(1), greater: int64(2), msg: genMsg}, - {less: uint8(1), greater: uint8(2), msg: genMsg}, - {less: uint16(1), greater: uint16(2), msg: genMsg}, - {less: uint32(1), greater: uint32(2), msg: genMsg}, - {less: uint64(1), greater: uint64(2), msg: genMsg}, - {less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than "2.34"`}, - {less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than "2.34"`}, - {less: uintptr(1), greater: uintptr(2), msg: genMsg}, - {less: uintptr(9), greater: uintptr(10), msg: `"9" is not greater than "10"`}, - {less: redefinedUintptr(9), greater: redefinedUintptr(10), msg: `"9" is not greater than "10"`}, - {less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than "0001-01-01 01:00:00 +0000 UTC"`}, - {less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than "[1 2]"`}, - }, - ) -} + return + } -func compareStrictlyLessCases() iter.Seq[compareFixture] { - const genMsg = `"2" is not less than "1"` - - return slices.Values( - []compareFixture{ - {less: "a", greater: "b", msg: `"b" is not less than "a"`}, - {less: int(1), greater: int(2), msg: genMsg}, - {less: int8(1), greater: int8(2), msg: genMsg}, - {less: int16(1), greater: int16(2), msg: genMsg}, - {less: int32(1), greater: int32(2), msg: genMsg}, - {less: int64(1), greater: int64(2), msg: genMsg}, - {less: uint8(1), greater: uint8(2), msg: genMsg}, - {less: uint16(1), greater: uint16(2), msg: genMsg}, - {less: uint32(1), greater: uint32(2), msg: genMsg}, - {less: uint64(1), greater: uint64(2), msg: genMsg}, - {less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than "1.23"`}, - {less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than "1.23"`}, - {less: uintptr(1), greater: uintptr(2), msg: genMsg}, - {less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than "0001-01-01 00:00:00 +0000 UTC"`}, - {less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than "[1 1]"`}, - }, - ) + False(t, result) + True(t, mock.Failed()) + } } -func compareEqualCases() iter.Seq[compareFixture] { - // This iterator produces equal-values to check edge cases with strict comparisons. - // The message cannot be used for error message checks. - return func(yield func(compareFixture) bool) { - for greater := range compareStrictlyGreaterCases() { - greater.msg = "" - equal1 := greater - equal1.less = equal1.greater - if !yield(equal1) { - return - } +// testAllSignT tests both PositiveT and NegativeT functions with the same test data. +// +//nolint:thelper // linter false positive: this is not a helper +func testAllSignT[V SignedNumeric](tc signTestCase) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() - equal2 := greater - equal2.greater = equal2.less - if !yield(equal2) { - return - } + // Type assert the values + positive, ok1 := tc.positive.(V) + negative, ok2 := tc.negative.(V) + if !ok1 || !ok2 { + t.Fatalf("type mismatch: expected %T, got positive=%T, negative=%T", *new(V), tc.positive, tc.negative) } - for less := range compareStrictlyLessCases() { - less.msg = "" - equal1 := less - equal1.less = equal1.greater - if !yield(equal1) { - return - } + if tc.isZero { + // Zero should fail both PositiveT and NegativeT + t.Run("zero should fail both", func(t *testing.T) { + t.Run("PositiveT should fail", testSignT(PositiveT[V], positive, false)) + t.Run("NegativeT should fail", testSignT(NegativeT[V], positive, false)) + }) - equal2 := less - equal2.greater = equal2.less - if !yield(equal2) { - return - } + return } - } -} -type compareTestCase struct { - e any - msg string + // Test positive and negative values + t.Run("with positive/negative values", func(t *testing.T) { + t.Run("PositiveT", func(t *testing.T) { + t.Run("should pass (positive value)", testSignT(PositiveT[V], positive, true)) + t.Run("should fail (negative value)", testSignT(PositiveT[V], negative, false)) + }) + t.Run("NegativeT", func(t *testing.T) { + t.Run("should pass (negative value)", testSignT(NegativeT[V], negative, true)) + t.Run("should fail (positive value)", testSignT(NegativeT[V], positive, false)) + }) + }) + } } -type comparePositiveCase = compareTestCase +// testSignT is a helper that tests a generic sign function. +func testSignT[V SignedNumeric](sign func(T, V, ...any) bool, e V, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() -type compareNegativeCase = compareTestCase + mock := new(mockT) + result := sign(mock, e) -func comparePositiveCases() iter.Seq[comparePositiveCase] { - const genMsg = `"-1" is not positive` + if shouldPass { + True(t, result) + False(t, mock.Failed()) - return slices.Values([]comparePositiveCase{ - {e: int(-1), msg: genMsg}, - {e: int8(-1), msg: genMsg}, - {e: int16(-1), msg: genMsg}, - {e: int32(-1), msg: genMsg}, - {e: int64(-1), msg: genMsg}, - {e: float32(-1.23), msg: `"-1.23" is not positive`}, - {e: float64(-1.23), msg: `"-1.23" is not positive`}, - }) -} + return + } -func compareNegativeCases() iter.Seq[compareNegativeCase] { - const genMsg = `"1" is not negative` - - return slices.Values([]compareNegativeCase{ - {e: int(1), msg: genMsg}, - {e: int8(1), msg: genMsg}, - {e: int16(1), msg: genMsg}, - {e: int32(1), msg: genMsg}, - {e: int64(1), msg: genMsg}, - {e: float32(1.23), msg: `"1.23" is not negative`}, - {e: float64(1.23), msg: `"1.23" is not negative`}, - }) + False(t, result) + True(t, mock.Failed()) + } } diff --git a/internal/assertions/mock_test.go b/internal/assertions/mock_test.go index 5112bd52a..625989c7e 100644 --- a/internal/assertions/mock_test.go +++ b/internal/assertions/mock_test.go @@ -241,6 +241,20 @@ func parseLabeledOutput(output string) []labeledContent { return contents } +// callerName gives the function name (qualified with a package path) +// for the caller after skip frames (where 0 means the current function). +func callerName(skip int) string { + // Make room for the skip PC. + var pc [1]uintptr + n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName + if n == 0 { + panic("testing: zero callers found") + } + frames := runtime.CallersFrames(pc[:n]) + frame, _ := frames.Next() + return frame.Function +} + type testCase struct { expected any actual any diff --git a/internal/assertions/number_test.go b/internal/assertions/number_test.go index ee2a414e5..ebf5e4144 100644 --- a/internal/assertions/number_test.go +++ b/internal/assertions/number_test.go @@ -4,103 +4,33 @@ package assertions import ( - "fmt" "iter" "math" "slices" "testing" - "time" ) func TestNumberInDelta(t *testing.T) { t.Parallel() - t.Run("with simple input", func(t *testing.T) { + t.Run("InDelta specific (type conversion)", func(t *testing.T) { t.Parallel() - mock := new(testing.T) - - True(t, InDelta(mock, 1.001, 1, 0.01), "|1.001 - 1| <= 0.01") - True(t, InDelta(mock, 1, 1.001, 0.01), "|1 - 1.001| <= 0.01") - True(t, InDelta(mock, 1, 2, 1), "|1 - 2| <= 1") - False(t, InDelta(mock, 1, 2, 0.5), "Expected |1 - 2| <= 0.5 to fail") - False(t, InDelta(mock, 2, 1, 0.5), "Expected |2 - 1| <= 0.5 to fail") False(t, InDelta(mock, "", nil, 1), "Expected non numerals to fail") }) - t.Run("with edge cases", func(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - - False(t, InDelta(mock, 42, math.NaN(), 0.01), "Expected NaN for actual to fail") - False(t, InDelta(mock, math.NaN(), 42, 0.01), "Expected NaN for expected to fail") - True(t, InDelta(mock, math.NaN(), math.NaN(), 0.01), "Expected NaN for both to pass") - }) - - t.Run("should be within delta", func(t *testing.T) { - for tc := range numberInDeltaCases() { - t.Run(fmt.Sprintf("%f - %f", tc.a, tc.b), func(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - - True(t, InDelta(mock, tc.a, tc.b, tc.delta), "Expected |%V - %V| <= %v", tc.a, tc.b, tc.delta) - }) - } - }) -} - -func TestNumberInDeltaT(t *testing.T) { - t.Parallel() - - t.Run("with simple input", func(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - - True(t, InDeltaT(mock, 1.001, 1, 0.01), "|1.001 - 1| <= 0.01") - True(t, InDeltaT(mock, 1, 1.001, 0.01), "|1 - 1.001| <= 0.01") - True(t, InDeltaT(mock, 1, 2, 1), "|1 - 2| <= 1") - False(t, InDeltaT[float32](mock, 1, 2, 0.5), "Expected |1 - 2| <= 0.5 to fail") - False(t, InDeltaT(mock, 2, 1, 0.5), "Expected |2 - 1| <= 0.5 to fail") - }) - - t.Run("with edge cases", func(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - - False(t, InDeltaT(mock, 42, math.NaN(), 0.01), "Expected NaN for actual to fail") - False(t, InDeltaT(mock, math.NaN(), 42, 0.01), "Expected NaN for expected to fail") - True(t, InDeltaT(mock, math.NaN(), math.NaN(), 0.01), "Expected NaN for both to pass") - }) - - for tc := range deltaTCases() { + // run all test cases with both InDelta and InDeltaT + for tc := range deltaCases() { t.Run(tc.name, tc.test) } } func TestNumberInDeltaSlice(t *testing.T) { t.Parallel() - mock := new(testing.T) - True(t, InDeltaSlice(mock, - []float64{1.001, math.NaN(), 0.999}, - []float64{1, math.NaN(), 1}, - 0.1), "{1.001, NaN, 0.009} is element-wise close to {1, NaN, 1} in delta=0.1") - - True(t, InDeltaSlice(mock, - []float64{1, math.NaN(), 2}, - []float64{0, math.NaN(), 3}, - 1), "{1, NaN, 2} is element-wise close to {0, NaN, 3} in delta=1") - - False(t, InDeltaSlice(mock, - []float64{1, math.NaN(), 2}, - []float64{0, math.NaN(), 3}, - 0.1), "{1, NaN, 2} is not element-wise close to {0, NaN, 3} in delta=0.1") - - False(t, InDeltaSlice(mock, "", nil, 1), "Expected non numeral slices to fail") + for tc := range deltaSliceCases() { + t.Run(tc.name, tc.test) + } } func TestNumberInDeltaMapValues(t *testing.T) { @@ -115,226 +45,287 @@ func TestNumberInDeltaMapValues(t *testing.T) { func TestNumberInEpsilon(t *testing.T) { t.Parallel() - for tc := range numberInEpsilonTrueCases() { - t.Run("with InEpsilon true", func(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - True(t, - InEpsilon(mock, tc.a, tc.b, tc.epsilon, - "Expected %V and %V to have a relative difference of %v", - tc.a, tc.b, tc.epsilon, - ), - "test: %q", tc, - ) - }) - } - - for tc := range numberInEpsilonFalseCases() { - t.Run("with InEpsilon false", func(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - False(t, - InEpsilon(mock, tc.a, tc.b, tc.epsilon, - "Expected %V and %V to have a relative difference of %v", - tc.a, tc.b, tc.epsilon, - ), - "test: %q", tc, - ) - }) + // run all test cases with both InEpsilon and InEpsilonT + for tc := range epsilonCases() { + t.Run(tc.name, tc.test) } } func TestNumberInEpsilonSlice(t *testing.T) { t.Parallel() - mock := new(testing.T) - True(t, InEpsilonSlice(mock, - []float64{2.2, math.NaN(), 2.0}, - []float64{2.1, math.NaN(), 2.1}, - 0.06), "{2.2, NaN, 2.0} is element-wise close to {2.1, NaN, 2.1} in epsilon=0.06") - - False(t, InEpsilonSlice(mock, - []float64{2.2, 2.0}, - []float64{2.1, 2.1}, - 0.04), "{2.2, 2.0} is not element-wise close to {2.1, 2.1} in epsilon=0.04") - - False(t, InEpsilonSlice(mock, "", nil, 1), "Expected expected non-slices to fail (nil)") - False(t, InEpsilonSlice(mock, nil, "", 1), "Expected actual non-slices to fail (nil)") - False(t, InEpsilonSlice(mock, 1, []int{}, 1), "Expected expected non-slices to fail") - False(t, InEpsilonSlice(mock, []int{}, 1, 1), "Expected actual non-slices to fail") - False(t, InEpsilonSlice(mock, []string{}, []int{}, 1), "Expected expected non-numeral slices to fail") - False(t, InEpsilonSlice(mock, []int{}, []string{}, 1), "Expected actual non-numeral slices to fail") + for tc := range epsilonSliceCases() { + t.Run(tc.name, tc.test) + } } -type numberInDeltaCase struct { - a, b any - delta float64 +func TestInDeltaTErrorMessage(t *testing.T) { + t.Parallel() + + mock := new(mockT) + + // Test that error message shows correct difference + InDeltaT(mock, 10, 1, 5) + + if !mock.Failed() { + t.Error("Expected test to fail but it passed") + } + + // Verify the error message contains the actual difference (9) + errorMsg := mock.errorString() + if !Contains(t, errorMsg, "difference was 9") { + t.Errorf("Error message should contain 'difference was 9', got: %s", errorMsg) + } } -func numberInDeltaCases() iter.Seq[numberInDeltaCase] { - type myFloat float32 - - return slices.Values([]numberInDeltaCase{ - {uint(2), uint(1), 1}, - {uint8(2), uint8(1), 1}, - {uint16(2), uint16(1), 1}, - {uint32(2), uint32(1), 1}, - {uint64(2), uint64(1), 1}, - {int(2), int(1), 1}, - {int8(2), int8(1), 1}, - {int16(2), int16(1), 1}, - {int32(2), int32(1), 1}, - {int64(2), int64(1), 1}, - {float32(2), float32(1), 1}, - {float64(2), float64(1), 1}, - {myFloat(2), myFloat(1), 1}, +func TestInEpsilonTErrorMessage(t *testing.T) { + t.Parallel() + + t.Run("relative error message", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + + // Test relative error: 100 vs 110 has 10% error, exceeds 5% epsilon + InEpsilonT(mock, 100.0, 110.0, 0.05) + + if !mock.Failed() { + t.Error("Expected test to fail but it passed") + } + + // Verify the error message contains relative error + errorMsg := mock.errorString() + if !Contains(t, errorMsg, "Relative error is too high") { + t.Errorf("Error message should contain 'Relative error is too high', got: %s", errorMsg) + } + // Should show actual relative error of 0.1 (10%) + if !Contains(t, errorMsg, "0.1") { + t.Errorf("Error message should contain '0.1' (10%% relative error), got: %s", errorMsg) + } }) -} -func testDeltaT[Number Measurable](a, b, delta Number, success bool) func(*testing.T) { - //nolint:thelper // linter false positive: this is not a helper - return func(t *testing.T) { + t.Run("absolute error message for zero expected", func(t *testing.T) { t.Parallel() mock := new(mockT) - if success { - True(t, InDeltaT(mock, a, b, delta)) - return + // Test absolute error: expected=0, actual=0.5, epsilon=0.1 + InEpsilonT(mock, 0.0, 0.5, 0.1) + + if !mock.Failed() { + t.Error("Expected test to fail but it passed") } - False(t, InDeltaT(mock, a, b, delta)) - } + // Verify the error message mentions absolute error + errorMsg := mock.errorString() + if !Contains(t, errorMsg, "Expected value is zero, using absolute error comparison") { + t.Errorf("Error message should mention absolute error comparison, got: %s", errorMsg) + } + // Should show actual absolute difference of 0.5 + if !Contains(t, errorMsg, "0.5") { + t.Errorf("Error message should contain '0.5' (absolute difference), got: %s", errorMsg) + } + }) } -func deltaTCases() iter.Seq[genericTestCase] { +// Helper functions and test data for InDelta/InDeltaT + +func deltaCases() iter.Seq[genericTestCase] { return slices.Values([]genericTestCase{ + // Simple input cases + {"simple/within-delta-1.001-1-0.01", testAllDelta(1.001, 1.0, 0.01, true)}, + {"simple/within-delta-1-1.001-0.01", testAllDelta(1.0, 1.001, 0.01, true)}, + {"simple/within-delta-1-2-1", testAllDelta(1.0, 2.0, 1.0, true)}, + {"simple/exceeds-delta-1-2-0.5", testAllDelta(1.0, 2.0, 0.5, false)}, + {"simple/exceeds-delta-2-1-0.5", testAllDelta(2.0, 1.0, 0.5, false)}, + + // Edge cases - NaN + {"edge/nan-for-actual", testAllDelta(42.0, math.NaN(), 0.01, false)}, + {"edge/nan-for-expected", testAllDelta(math.NaN(), 42.0, 0.01, false)}, + {"edge/nan-for-both", testAllDelta(math.NaN(), math.NaN(), 0.01, true)}, + // All integer types - basic success cases - {"int/success", testDeltaT[int](2, 1, 1, true)}, - {"int8/success", testDeltaT[int8](2, 1, 1, true)}, - {"int16/success", testDeltaT[int16](2, 1, 1, true)}, - {"int32/success", testDeltaT[int32](2, 1, 1, true)}, - {"int64/success", testDeltaT[int64](2, 1, 1, true)}, - {"uint/success", testDeltaT[uint](2, 1, 1, true)}, - {"uint8/success", testDeltaT[uint8](2, 1, 1, true)}, - {"uint16/success", testDeltaT[uint16](2, 1, 1, true)}, - {"uint32/success", testDeltaT[uint32](2, 1, 1, true)}, - {"uint64/success", testDeltaT[uint64](2, 1, 1, true)}, - {"float32/success", testDeltaT[float32](2.0, 1.0, 1.0, true)}, - {"float64/success", testDeltaT[float64](2.0, 1.0, 1.0, true)}, + {"int/success", testAllDelta(int(2), int(1), int(1), true)}, + {"int8/success", testAllDelta(int8(2), int8(1), int8(1), true)}, + {"int16/success", testAllDelta(int16(2), int16(1), int16(1), true)}, + {"int32/success", testAllDelta(int32(2), int32(1), int32(1), true)}, + {"int64/success", testAllDelta(int64(2), int64(1), int64(1), true)}, + {"uint/success", testAllDelta(uint(2), uint(1), uint(1), true)}, + {"uint8/success", testAllDelta(uint8(2), uint8(1), uint8(1), true)}, + {"uint16/success", testAllDelta(uint16(2), uint16(1), uint16(1), true)}, + {"uint32/success", testAllDelta(uint32(2), uint32(1), uint32(1), true)}, + {"uint64/success", testAllDelta(uint64(2), uint64(1), uint64(1), true)}, + {"float32/success", testAllDelta(float32(2.0), float32(1.0), float32(1.0), true)}, + {"float64/success", testAllDelta(2.0, 1.0, 1.0, true)}, // Basic failure cases - {"int/failure", testDeltaT[int](10, 1, 5, false)}, - {"uint/failure", testDeltaT[uint](10, 1, 5, false)}, - {"float64/failure", testDeltaT[float64](10.0, 1.0, 5.0, false)}, + {"int/failure", testAllDelta(int(10), int(1), int(5), false)}, + {"uint/failure", testAllDelta(uint(10), uint(1), uint(5), false)}, + {"float64/failure", testAllDelta(10.0, 1.0, 5.0, false)}, // Exact match (zero delta) - {"int/exact", testDeltaT[int](5, 5, 0, true)}, - {"uint/exact", testDeltaT[uint](5, 5, 0, true)}, - {"float64/exact", testDeltaT[float64](5.0, 5.0, 0.0, true)}, + {"int/exact", testAllDelta(int(5), int(5), int(0), true)}, + {"uint/exact", testAllDelta(uint(5), uint(5), uint(0), true)}, + {"float64/exact", testAllDelta(5.0, 5.0, 0.0, true)}, // Zero values - {"int/zero", testDeltaT[int](0, 0, 0, true)}, - {"uint/zero", testDeltaT[uint](0, 0, 0, true)}, - {"float64/zero", testDeltaT[float64](0.0, 0.0, 0.0, true)}, - {"int/near-zero", testDeltaT[int](1, 0, 1, true)}, - {"float64/near-zero", testDeltaT[float64](0.01, 0.0, 0.02, true)}, + {"int/zero", testAllDelta(int(0), int(0), int(0), true)}, + {"uint/zero", testAllDelta(uint(0), uint(0), uint(0), true)}, + {"float64/zero", testAllDelta(0.0, 0.0, 0.0, true)}, + {"int/near-zero", testAllDelta(int(1), int(0), int(1), true)}, + {"float64/near-zero", testAllDelta(0.01, 0.0, 0.02, true)}, // Negative numbers - {"int/negative", testDeltaT[int](-5, -4, 2, true)}, - {"int/negative-fail", testDeltaT[int](-10, -1, 5, false)}, - {"float64/negative", testDeltaT[float64](-5.0, -4.0, 2.0, true)}, + {"int/negative", testAllDelta(int(-5), int(-4), int(2), true)}, + {"int/negative-fail", testAllDelta(int(-10), int(-1), int(5), false)}, + {"float64/negative", testAllDelta(-5.0, -4.0, 2.0, true)}, // Mixed positive/negative - {"int/mixed", testDeltaT[int](5, -5, 10, true)}, - {"int/mixed-fail", testDeltaT[int](5, -5, 9, false)}, - {"float64/mixed", testDeltaT[float64](5.0, -5.0, 10.0, true)}, + {"int/mixed", testAllDelta(int(5), int(-5), int(10), true)}, + {"int/mixed-fail", testAllDelta(int(5), int(-5), int(9), false)}, + {"float64/mixed", testAllDelta(5.0, -5.0, 10.0, true)}, // Unsigned integer edge cases (overflow protection) - {"uint/expected-greater", testDeltaT[uint](100, 50, 60, true)}, - {"uint/actual-greater", testDeltaT[uint](50, 100, 60, true)}, - {"uint8/max-value", testDeltaT[uint8](255, 250, 10, true)}, - {"uint8/max-value-fail", testDeltaT[uint8](255, 250, 4, false)}, - {"uint16/large-diff", testDeltaT[uint16](60000, 40000, 25000, true)}, - {"uint32/large-diff", testDeltaT[uint32](4000000000, 3000000000, 1000000001, true)}, - {"uint64/large-diff", testDeltaT[uint64](10000000000, 5000000000, 5000000001, true)}, + {"uint/expected-greater", testAllDelta(uint(100), uint(50), uint(60), true)}, + {"uint/actual-greater", testAllDelta(uint(50), uint(100), uint(60), true)}, + {"uint8/max-value", testAllDelta(uint8(255), uint8(250), uint8(10), true)}, + {"uint8/max-value-fail", testAllDelta(uint8(255), uint8(250), uint8(4), false)}, + {"uint16/large-diff", testAllDelta(uint16(60000), uint16(40000), uint16(25000), true)}, + {"uint32/large-diff", testAllDelta(uint32(4000000000), uint32(3000000000), uint32(1000000001), true)}, + {"uint64/large-diff", testAllDelta(uint64(10000000000), uint64(5000000000), uint64(5000000001), true)}, // Boundary testing for unsigned (expected > actual path) - {"uint8/boundary-expected-gt-actual", testDeltaT[uint8](200, 100, 100, true)}, - {"uint8/boundary-expected-gt-actual-fail", testDeltaT[uint8](200, 100, 99, false)}, + {"uint8/boundary-expected-gt-actual", testAllDelta(uint8(200), uint8(100), uint8(100), true)}, + {"uint8/boundary-expected-gt-actual-fail", testAllDelta(uint8(200), uint8(100), uint8(99), false)}, // Boundary testing for unsigned (actual > expected path) - {"uint8/boundary-actual-gt-expected", testDeltaT[uint8](100, 200, 100, true)}, - {"uint8/boundary-actual-gt-expected-fail", testDeltaT[uint8](100, 200, 99, false)}, + {"uint8/boundary-actual-gt-expected", testAllDelta(uint8(100), uint8(200), uint8(100), true)}, + {"uint8/boundary-actual-gt-expected-fail", testAllDelta(uint8(100), uint8(200), uint8(99), false)}, // Float32 NaN cases - {"float32/both-nan", testDeltaT[float32](float32(math.NaN()), float32(math.NaN()), 1.0, true)}, - {"float32/expected-nan", testDeltaT[float32](float32(math.NaN()), 1.0, 1.0, false)}, - {"float32/actual-nan", testDeltaT[float32](1.0, float32(math.NaN()), 1.0, false)}, + {"float32/both-nan", testAllDelta(float32(math.NaN()), float32(math.NaN()), float32(1.0), true)}, + {"float32/expected-nan", testAllDelta(float32(math.NaN()), float32(1.0), float32(1.0), false)}, + {"float32/actual-nan", testAllDelta(float32(1.0), float32(math.NaN()), float32(1.0), false)}, // Float64 NaN cases - {"float64/both-nan", testDeltaT[float64](math.NaN(), math.NaN(), 1.0, true)}, - {"float64/expected-nan", testDeltaT[float64](math.NaN(), 1.0, 1.0, false)}, - {"float64/actual-nan", testDeltaT[float64](1.0, math.NaN(), 1.0, false)}, + {"float64/both-nan", testAllDelta(math.NaN(), math.NaN(), 1.0, true)}, + {"float64/expected-nan", testAllDelta(math.NaN(), 1.0, 1.0, false)}, + {"float64/actual-nan", testAllDelta(1.0, math.NaN(), 1.0, false)}, // Float32 +Inf cases - {"float32/both-plus-inf", testDeltaT[float32](float32(math.Inf(1)), float32(math.Inf(1)), 1.0, true)}, - {"float32/expected-plus-inf-actual-minus-inf", testDeltaT[float32](float32(math.Inf(1)), float32(math.Inf(-1)), 1.0, false)}, - {"float32/expected-plus-inf-actual-finite", testDeltaT[float32](float32(math.Inf(1)), 1.0, 1.0, false)}, - {"float32/expected-finite-actual-plus-inf", testDeltaT[float32](1.0, float32(math.Inf(1)), 1.0, false)}, + {"float32/both-plus-inf", testAllDelta(float32(math.Inf(1)), float32(math.Inf(1)), float32(1.0), true)}, + {"float32/expected-plus-inf-actual-minus-inf", testAllDelta(float32(math.Inf(1)), float32(math.Inf(-1)), float32(1.0), false)}, + {"float32/expected-plus-inf-actual-finite", testAllDelta(float32(math.Inf(1)), float32(1.0), float32(1.0), false)}, + {"float32/expected-finite-actual-plus-inf", testAllDelta(float32(1.0), float32(math.Inf(1)), float32(1.0), false)}, // Float64 +Inf cases - {"float64/both-plus-inf", testDeltaT[float64](math.Inf(1), math.Inf(1), 1.0, true)}, - {"float64/expected-plus-inf-actual-minus-inf", testDeltaT[float64](math.Inf(1), math.Inf(-1), 1.0, false)}, - {"float64/expected-plus-inf-actual-finite", testDeltaT[float64](math.Inf(1), 1.0, 1.0, false)}, - {"float64/expected-finite-actual-plus-inf", testDeltaT[float64](1.0, math.Inf(1), 1.0, false)}, + {"float64/both-plus-inf", testAllDelta(math.Inf(1), math.Inf(1), 1.0, true)}, + {"float64/expected-plus-inf-actual-minus-inf", testAllDelta(math.Inf(1), math.Inf(-1), 1.0, false)}, + {"float64/expected-plus-inf-actual-finite", testAllDelta(math.Inf(1), 1.0, 1.0, false)}, + {"float64/expected-finite-actual-plus-inf", testAllDelta(1.0, math.Inf(1), 1.0, false)}, // Float32 -Inf cases - {"float32/both-minus-inf", testDeltaT[float32](float32(math.Inf(-1)), float32(math.Inf(-1)), 1.0, true)}, - {"float32/expected-minus-inf-actual-plus-inf", testDeltaT[float32](float32(math.Inf(-1)), float32(math.Inf(1)), 1.0, false)}, - {"float32/expected-minus-inf-actual-finite", testDeltaT[float32](float32(math.Inf(-1)), 1.0, 1.0, false)}, - {"float32/expected-finite-actual-minus-inf", testDeltaT[float32](1.0, float32(math.Inf(-1)), 1.0, false)}, + {"float32/both-minus-inf", testAllDelta(float32(math.Inf(-1)), float32(math.Inf(-1)), float32(1.0), true)}, + {"float32/expected-minus-inf-actual-plus-inf", testAllDelta(float32(math.Inf(-1)), float32(math.Inf(1)), float32(1.0), false)}, + {"float32/expected-minus-inf-actual-finite", testAllDelta(float32(math.Inf(-1)), float32(1.0), float32(1.0), false)}, + {"float32/expected-finite-actual-minus-inf", testAllDelta(float32(1.0), float32(math.Inf(-1)), float32(1.0), false)}, // Float64 -Inf cases - {"float64/both-minus-inf", testDeltaT[float64](math.Inf(-1), math.Inf(-1), 1.0, true)}, - {"float64/expected-minus-inf-actual-plus-inf", testDeltaT[float64](math.Inf(-1), math.Inf(1), 1.0, false)}, - {"float64/expected-minus-inf-actual-finite", testDeltaT[float64](math.Inf(-1), 1.0, 1.0, false)}, - {"float64/expected-finite-actual-minus-inf", testDeltaT[float64](1.0, math.Inf(-1), 1.0, false)}, + {"float64/both-minus-inf", testAllDelta(math.Inf(-1), math.Inf(-1), 1.0, true)}, + {"float64/expected-minus-inf-actual-plus-inf", testAllDelta(math.Inf(-1), math.Inf(1), 1.0, false)}, + {"float64/expected-minus-inf-actual-finite", testAllDelta(math.Inf(-1), 1.0, 1.0, false)}, + {"float64/expected-finite-actual-minus-inf", testAllDelta(1.0, math.Inf(-1), 1.0, false)}, // Delta validation - NaN delta - {"float64/delta-nan", testDeltaT[float64](1.0, 1.0, math.NaN(), false)}, - {"float32/delta-nan", testDeltaT[float32](1.0, 1.0, float32(math.NaN()), false)}, + {"float64/delta-nan", testAllDelta(1.0, 1.0, math.NaN(), false)}, + {"float32/delta-nan", testAllDelta(float32(1.0), float32(1.0), float32(math.NaN()), false)}, // Delta validation - Inf delta - {"float64/delta-plus-inf", testDeltaT[float64](1.0, 1.0, math.Inf(1), false)}, - {"float64/delta-minus-inf", testDeltaT[float64](1.0, 1.0, math.Inf(-1), false)}, - {"float32/delta-plus-inf", testDeltaT[float32](1.0, 1.0, float32(math.Inf(1)), false)}, - {"float32/delta-minus-inf", testDeltaT[float32](1.0, 1.0, float32(math.Inf(-1)), false)}, + {"float64/delta-plus-inf", testAllDelta(1.0, 1.0, math.Inf(1), false)}, + {"float64/delta-minus-inf", testAllDelta(1.0, 1.0, math.Inf(-1), false)}, + {"float32/delta-plus-inf", testAllDelta(float32(1.0), float32(1.0), float32(math.Inf(1)), false)}, + {"float32/delta-minus-inf", testAllDelta(float32(1.0), float32(1.0), float32(math.Inf(-1)), false)}, // Very small deltas (precision testing) - {"float64/small-delta", testDeltaT[float64](1.0, 1.0000001, 0.000001, true)}, - {"float64/small-delta-fail", testDeltaT[float64](1.0, 1.0000001, 0.00000001, false)}, - {"float32/small-delta", testDeltaT[float32](1.0, 1.00001, 0.0001, true)}, - {"float32/small-delta-fail", testDeltaT[float32](1.0, 1.00001, 0.00001, false)}, + {"float64/small-delta", testAllDelta(1.0, 1.0000001, 0.000001, true)}, + {"float64/small-delta-fail", testAllDelta(1.0, 1.0000001, 0.00000001, false)}, + {"float32/small-delta", testAllDelta(float32(1.0), float32(1.00001), float32(0.0001), true)}, + {"float32/small-delta-fail", testAllDelta(float32(1.0), float32(1.00001), float32(0.00001), false)}, // Large values - {"int64/large-values", testDeltaT[int64](9223372036854775800, 9223372036854775700, 200, true)}, - {"uint64/large-values", testDeltaT[uint64](18446744073709551600, 18446744073709551500, 200, true)}, - {"float64/large-values", testDeltaT[float64](1e308, 1e308-1e307, 2e307, true)}, + {"int64/large-values", testAllDelta(int64(9223372036854775800), int64(9223372036854775700), int64(200), true)}, + {"uint64/large-values", testAllDelta(uint64(18446744073709551600), uint64(18446744073709551500), uint64(200), true)}, + {"float64/large-values", testAllDelta(1e308, 1e308-1e307, 2e307, true)}, // Edge case: delta is zero with different values - {"int/zero-delta-different", testDeltaT[int](5, 6, 0, false)}, - {"float64/zero-delta-different", testDeltaT[float64](5.0, 5.00001, 0.0, false)}, + {"int/zero-delta-different", testAllDelta(int(5), int(6), int(0), false)}, + {"float64/zero-delta-different", testAllDelta(5.0, 5.00001, 0.0, false)}, // Commutative property (order shouldn't matter) - {"int/commutative-1", testDeltaT[int](10, 5, 6, true)}, - {"int/commutative-2", testDeltaT[int](5, 10, 6, true)}, - {"float64/commutative-1", testDeltaT[float64](10.0, 5.0, 6.0, true)}, - {"float64/commutative-2", testDeltaT[float64](5.0, 10.0, 6.0, true)}, + {"int/commutative-1", testAllDelta(int(10), int(5), int(6), true)}, + {"int/commutative-2", testAllDelta(int(5), int(10), int(6), true)}, + {"float64/commutative-1", testAllDelta(10.0, 5.0, 6.0, true)}, + {"float64/commutative-2", testAllDelta(5.0, 10.0, 6.0, true)}, }) } +// testAllDelta tests both InDelta and InDeltaT with the same input. +// +//nolint:thelper // linter false positive: this is not a helper +func testAllDelta[Number Measurable](expected, actual, delta Number, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + if shouldPass { + t.Run("should pass", func(t *testing.T) { + t.Run("with InDelta", testDelta(expected, actual, delta, true)) + t.Run("with InDeltaT", testDeltaT(expected, actual, delta, true)) + }) + } else { + t.Run("should fail", func(t *testing.T) { + t.Run("with InDelta", testDelta(expected, actual, delta, false)) + t.Run("with InDeltaT", testDeltaT(expected, actual, delta, false)) + }) + } + } +} + +func testDelta[Number Measurable](expected, actual, delta Number, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + // InDelta requires delta as float64, so convert it + result := InDelta(mock, expected, actual, float64(delta)) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + } else { + False(t, result) + True(t, mock.Failed()) + } + } +} + +func testDeltaT[Number Measurable](expected, actual, delta Number, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := InDeltaT(mock, expected, actual, delta) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + } else { + False(t, result) + True(t, mock.Failed()) + } + } +} + +// Helper functions and test data for InDeltaMapValues + type numberInDeltaMapCase struct { title string expect any @@ -454,282 +445,317 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { }) } -type numberInEpsilonCase struct { - a, b any - epsilon float64 -} +// Helper functions and test data for InEpsilon/InEpsilonT -func numberInEpsilonTrueCases() iter.Seq[numberInEpsilonCase] { - return slices.Values([]numberInEpsilonCase{ - {uint8(2), uint16(2), .001}, - {2.1, 2.2, 0.1}, - {2.2, 2.1, 0.1}, - {-2.1, -2.2, 0.1}, - {-2.2, -2.1, 0.1}, - {uint64(100), uint8(101), 0.01}, - {0.1, -0.1, 2}, - {0.1, 0, 2}, - {math.NaN(), math.NaN(), 1}, - {math.Inf(1), math.Inf(1), 1}, - {math.Inf(-1), math.Inf(-1), 1}, - {time.Second, time.Second + time.Millisecond, 0.002}, - {0, 0.1, 2}, // works: fall back to absolute error - }) -} +func epsilonCases() iter.Seq[genericTestCase] { + return slices.Values([]genericTestCase{ + // Simple input cases + {"simple/1pct-error-within-2pct-epsilon", testAllEpsilon(100.0, 101.0, 0.02, true)}, + {"simple/5pct-error-exceeds-2pct-epsilon", testAllEpsilon(100.0, 105.0, 0.02, false)}, + {"simple/exact-match-zero-epsilon", testAllEpsilon(100.0, 100.0, 0.0, true)}, -func numberInEpsilonFalseCases() iter.Seq[numberInEpsilonCase] { - return slices.Values([]numberInEpsilonCase{ - {uint8(2), int16(-2), .001}, - {uint64(100), uint8(102), 0.01}, - {2.1, 2.2, 0.001}, - {2.2, 2.1, 0.001}, - {2.1, -2.2, 1}, - {2.1, "bla-bla", 0}, - {0.1, -0.1, 1.99}, - {0, 0.1, 0.01}, // works (and fails): fall back to absolute error - {time.Second, time.Second + 10*time.Millisecond, 0.002}, - {math.NaN(), 0, 1}, - {0, math.NaN(), 1}, - {0, 0, math.NaN()}, - {math.Inf(1), 1, 1}, - {math.Inf(-1), 1, 1}, - {1, math.Inf(1), 1}, - {1, math.Inf(-1), 1}, - {math.Inf(1), math.Inf(-1), 1}, - {math.Inf(-1), math.Inf(1), 1}, - }) -} + // Edge cases - NaN + {"edge/nan-for-actual", testAllEpsilon(42.0, math.NaN(), 0.01, false)}, + {"edge/nan-for-expected", testAllEpsilon(math.NaN(), 42.0, 0.01, false)}, + {"edge/nan-for-both", testAllEpsilon(math.NaN(), math.NaN(), 0.01, true)}, -func TestNumberInEpsilonT(t *testing.T) { - t.Parallel() + // Edge cases - zero expected (uses absolute error) + {"edge/zero-expected-within", testAllEpsilon(0.0, 0.009, 0.01, true)}, + {"edge/zero-expected-exceeds", testAllEpsilon(0.0, 0.011, 0.01, false)}, - t.Run("with simple input", func(t *testing.T) { - t.Parallel() + // All numeric types - basic success cases (12 cases) + {"int/success", testAllEpsilon(int(100), int(101), 0.02, true)}, // 1% error < 2% epsilon + {"int8/success", testAllEpsilon(int8(100), int8(101), 0.02, true)}, // 1% error < 2% epsilon + {"int16/success", testAllEpsilon(int16(100), int16(101), 0.02, true)}, // 1% error < 2% epsilon + {"int32/success", testAllEpsilon(int32(100), int32(101), 0.02, true)}, // 1% error < 2% epsilon + {"int64/success", testAllEpsilon(int64(100), int64(101), 0.02, true)}, // 1% error < 2% epsilon + {"uint/success", testAllEpsilon(uint(100), uint(101), 0.02, true)}, // 1% error < 2% epsilon + {"uint8/success", testAllEpsilon(uint8(100), uint8(101), 0.02, true)}, // 1% error < 2% epsilon + {"uint16/success", testAllEpsilon(uint16(100), uint16(101), 0.02, true)}, // 1% error < 2% epsilon + {"uint32/success", testAllEpsilon(uint32(100), uint32(101), 0.02, true)}, // 1% error < 2% epsilon + {"uint64/success", testAllEpsilon(uint64(100), uint64(101), 0.02, true)}, // 1% error < 2% epsilon + {"float32/success", testAllEpsilon(float32(100.0), float32(101.0), 0.02, true)}, // 1% error < 2% epsilon + {"float64/success", testAllEpsilon(100.0, 101.0, 0.02, true)}, // 1% error < 2% epsilon - mock := new(testing.T) + // Basic failure cases (3 cases) + {"int/failure", testAllEpsilon(int(100), int(110), 0.05, false)}, // 10% error > 5% epsilon + {"uint/failure", testAllEpsilon(uint(100), uint(110), 0.05, false)}, // 10% error > 5% epsilon + {"float64/failure", testAllEpsilon(100.0, 110.0, 0.05, false)}, // 10% error > 5% epsilon - // Basic relative error: 100 vs 101 has 1% error, within 2% epsilon - True(t, InEpsilonT(mock, 100.0, 101.0, 0.02), "1% error should be within 2% epsilon") - // 100 vs 105 has 5% error, exceeds 2% epsilon - False(t, InEpsilonT(mock, 100.0, 105.0, 0.02), "5% error should exceed 2% epsilon") - // Exact match always passes - True(t, InEpsilonT(mock, 100, 100, 0.0), "Exact match should pass even with 0 epsilon") - }) + // Exact match (3 cases) + {"int/exact", testAllEpsilon(int(100), int(100), 0.0, true)}, // Exact match + {"uint/exact", testAllEpsilon(uint(100), uint(100), 0.0, true)}, // Exact match + {"float64/exact", testAllEpsilon(100.0, 100.0, 0.0, true)}, // Exact match - t.Run("with edge cases", func(t *testing.T) { - t.Parallel() + // Zero expected value - uses absolute error (8 cases) + {"int/both-zero", testAllEpsilon(int(0), int(0), 0.01, true)}, // Both zero + {"uint/both-zero", testAllEpsilon(uint(0), uint(0), 0.01, true)}, // Both zero + {"float64/both-zero", testAllEpsilon(0.0, 0.0, 0.01, true)}, // Both zero + {"float64/zero-expected-within", testAllEpsilon(0.0, 0.009, 0.01, true)}, // |0.009| <= 0.01 + {"float64/zero-expected-at-boundary", testAllEpsilon(0.0, 0.01, 0.01, true)}, // |0.01| <= 0.01 + {"float64/zero-expected-exceed", testAllEpsilon(0.0, 0.011, 0.01, false)}, // |0.011| > 0.01 + {"float64/zero-expected-large", testAllEpsilon(0.0, 100.0, 0.01, false)}, // |100| > 0.01 + {"int/zero-expected-negative", testAllEpsilon(int(0), int(-5), 10.0, true)}, // |-5| <= 10 (absolute) - mock := new(testing.T) + // Near-zero values (2 cases) + {"float64/near-zero-success", testAllEpsilon(0.001, 0.00101, 0.02, true)}, // 1% error < 2% + {"float64/near-zero-failure", testAllEpsilon(0.001, 0.00110, 0.05, false)}, // 10% error > 5% - // NaN cases - False(t, InEpsilonT(mock, 42.0, math.NaN(), 0.01), "Expected NaN for actual to fail") - False(t, InEpsilonT(mock, math.NaN(), 42.0, 0.01), "Expected NaN for expected to fail") - True(t, InEpsilonT(mock, math.NaN(), math.NaN(), 0.01), "Expected NaN for both to pass") + // Negative numbers (3 cases) + {"int/negative", testAllEpsilon(int(-100), int(-101), 0.02, true)}, // 1% error < 2% + {"int/negative-fail", testAllEpsilon(int(-100), int(-110), 0.05, false)}, // 10% error > 5% + {"float64/negative", testAllEpsilon(-100.0, -101.0, 0.02, true)}, // 1% error < 2% - // Zero expected value uses absolute error - True(t, InEpsilonT(mock, 0.0, 0.009, 0.01), "Zero expected: |0.009| <= 0.01 should pass") - False(t, InEpsilonT(mock, 0.0, 0.011, 0.01), "Zero expected: |0.011| > 0.01 should fail") - }) + // Mixed positive/negative (3 cases) + {"int/mixed-small", testAllEpsilon(int(100), int(-100), 2.1, true)}, // 200% error <= 210% epsilon + {"int/mixed-fail", testAllEpsilon(int(100), int(-100), 1.9, false)}, // 200% error > 190% epsilon + {"float64/mixed", testAllEpsilon(100.0, -100.0, 2.1, true)}, // 200% error <= 210% epsilon - for tc := range epsilonTCases() { - t.Run(tc.name, tc.test) - } -} + // Float32 NaN cases (3 cases) + {"float32/both-nan", testAllEpsilon(float32(math.NaN()), float32(math.NaN()), 0.01, true)}, + {"float32/expected-nan", testAllEpsilon(float32(math.NaN()), float32(42.0), 0.01, false)}, + {"float32/actual-nan", testAllEpsilon(float32(42.0), float32(math.NaN()), 0.01, false)}, -func TestInDeltaTErrorMessage(t *testing.T) { - t.Parallel() + // Float64 NaN cases (3 cases) + {"float64/both-nan", testAllEpsilon(math.NaN(), math.NaN(), 0.01, true)}, + {"float64/expected-nan", testAllEpsilon(math.NaN(), 42.0, 0.01, false)}, + {"float64/actual-nan", testAllEpsilon(42.0, math.NaN(), 0.01, false)}, - mock := new(mockT) + // Float32 +Inf cases (4 cases) + {"float32/both-plus-inf", testAllEpsilon(float32(math.Inf(1)), float32(math.Inf(1)), 0.01, true)}, + {"float32/expected-plus-inf-actual-minus-inf", testAllEpsilon(float32(math.Inf(1)), float32(math.Inf(-1)), 0.01, false)}, + {"float32/expected-plus-inf-actual-finite", testAllEpsilon(float32(math.Inf(1)), float32(100.0), 0.01, false)}, + {"float32/expected-finite-actual-plus-inf", testAllEpsilon(float32(100.0), float32(math.Inf(1)), 0.01, false)}, - // Test that error message shows correct difference - InDeltaT(mock, 10, 1, 5) + // Float64 +Inf cases (4 cases) + {"float64/both-plus-inf", testAllEpsilon(math.Inf(1), math.Inf(1), 0.01, true)}, + {"float64/expected-plus-inf-actual-minus-inf", testAllEpsilon(math.Inf(1), math.Inf(-1), 0.01, false)}, + {"float64/expected-plus-inf-actual-finite", testAllEpsilon(math.Inf(1), 100.0, 0.01, false)}, + {"float64/expected-finite-actual-plus-inf", testAllEpsilon(100.0, math.Inf(1), 0.01, false)}, - if !mock.Failed() { - t.Error("Expected test to fail but it passed") - } + // Float32 -Inf cases (4 cases) + {"float32/both-minus-inf", testAllEpsilon(float32(math.Inf(-1)), float32(math.Inf(-1)), 0.01, true)}, + {"float32/expected-minus-inf-actual-plus-inf", testAllEpsilon(float32(math.Inf(-1)), float32(math.Inf(1)), 0.01, false)}, + {"float32/expected-minus-inf-actual-finite", testAllEpsilon(float32(math.Inf(-1)), float32(100.0), 0.01, false)}, + {"float32/expected-finite-actual-minus-inf", testAllEpsilon(float32(100.0), float32(math.Inf(-1)), 0.01, false)}, - // Verify the error message contains the actual difference (9) - errorMsg := mock.errorString() - if !Contains(t, errorMsg, "difference was 9") { - t.Errorf("Error message should contain 'difference was 9', got: %s", errorMsg) - } -} + // Float64 -Inf cases (4 cases) + {"float64/both-minus-inf", testAllEpsilon(math.Inf(-1), math.Inf(-1), 0.01, true)}, + {"float64/expected-minus-inf-actual-plus-inf", testAllEpsilon(math.Inf(-1), math.Inf(1), 0.01, false)}, + {"float64/expected-minus-inf-actual-finite", testAllEpsilon(math.Inf(-1), 100.0, 0.01, false)}, + {"float64/expected-finite-actual-minus-inf", testAllEpsilon(100.0, math.Inf(-1), 0.01, false)}, -func TestInEpsilonTErrorMessage(t *testing.T) { - t.Parallel() + // Epsilon validation (6 cases) + {"float64/epsilon-negative", testAllEpsilon(100.0, 100.0, -0.01, false)}, // Negative epsilon + {"float64/epsilon-nan", testAllEpsilon(100.0, 100.0, math.NaN(), false)}, // NaN epsilon + {"float32/epsilon-nan", testAllEpsilon(float32(100.0), float32(100.0), math.NaN(), false)}, // NaN epsilon + {"float64/epsilon-plus-inf", testAllEpsilon(100.0, 100.0, math.Inf(1), false)}, // +Inf epsilon + {"float64/epsilon-minus-inf", testAllEpsilon(100.0, 100.0, math.Inf(-1), false)}, // -Inf epsilon + {"float32/epsilon-plus-inf", testAllEpsilon(float32(100.0), float32(100.0), math.Inf(1), false)}, // +Inf epsilon - t.Run("relative error message", func(t *testing.T) { - t.Parallel() + // Precision testing (4 cases) + {"float64/small-epsilon-pass", testAllEpsilon(1.0, 1.000001, 0.00001, true)}, // Very small error + {"float64/small-epsilon-fail", testAllEpsilon(1.0, 1.000011, 0.00001, false)}, // Exceeds small epsilon + {"float32/small-epsilon-pass", testAllEpsilon(float32(1.0), float32(1.000001), 0.00001, true)}, // Very small error + {"float32/small-epsilon-fail", testAllEpsilon(float32(1.0), float32(1.000011), 0.00001, false)}, // Exceeds small epsilon - mock := new(mockT) + // Large values (3 cases) + {"int64/large-values", testAllEpsilon(int64(1000000000), int64(1010000000), 0.02, true)}, // 1% error < 2% + {"uint64/large-values", testAllEpsilon(uint64(1000000000), uint64(1010000000), 0.02, true)}, // 1% error < 2% + {"float64/large-values", testAllEpsilon(1e15, 1.01e15, 0.02, true)}, // 1% error < 2% - // Test relative error: 100 vs 110 has 10% error, exceeds 5% epsilon - InEpsilonT(mock, 100.0, 110.0, 0.05) + // Edge cases (4 cases) + {"int/zero-epsilon-same", testAllEpsilon(int(100), int(100), 0.0, true)}, // Zero epsilon, exact match + {"float64/zero-epsilon-different", testAllEpsilon(100.0, 100.1, 0.0, false)}, // Zero epsilon, different + {"int/large-epsilon", testAllEpsilon(int(100), int(200), 1.5, true)}, // 100% error < 150% epsilon + {"float64/boundary", testAllEpsilon(100.0, 102.0, 0.02, true)}, // Exactly 2% error with 2% epsilon + }) +} - if !mock.Failed() { - t.Error("Expected test to fail but it passed") - } +// testAllEpsilon tests both InEpsilon and InEpsilonT with the same input. +// +//nolint:thelper // linter false positive: this is not a helper +func testAllEpsilon[Number Measurable](expected, actual Number, epsilon float64, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() - // Verify the error message contains relative error - errorMsg := mock.errorString() - if !Contains(t, errorMsg, "Relative error is too high") { - t.Errorf("Error message should contain 'Relative error is too high', got: %s", errorMsg) - } - // Should show actual relative error of 0.1 (10%) - if !Contains(t, errorMsg, "0.1") { - t.Errorf("Error message should contain '0.1' (10%% relative error), got: %s", errorMsg) + if shouldPass { + t.Run("should pass", func(t *testing.T) { + t.Run("with InEpsilon", testEpsilon(expected, actual, epsilon, true)) + t.Run("with InEpsilonT", testEpsilonT(expected, actual, epsilon, true)) + }) + } else { + t.Run("should fail", func(t *testing.T) { + t.Run("with InEpsilon", testEpsilon(expected, actual, epsilon, false)) + t.Run("with InEpsilonT", testEpsilonT(expected, actual, epsilon, false)) + }) } - }) + } +} - t.Run("absolute error message for zero expected", func(t *testing.T) { +func testEpsilon[Number Measurable](expected, actual Number, epsilon float64, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { t.Parallel() mock := new(mockT) + result := InEpsilon(mock, expected, actual, epsilon) - // Test absolute error: expected=0, actual=0.5, epsilon=0.1 - InEpsilonT(mock, 0.0, 0.5, 0.1) - - if !mock.Failed() { - t.Error("Expected test to fail but it passed") - } - - // Verify the error message mentions absolute error - errorMsg := mock.errorString() - if !Contains(t, errorMsg, "Expected value is zero, using absolute error comparison") { - t.Errorf("Error message should mention absolute error comparison, got: %s", errorMsg) - } - // Should show actual absolute difference of 0.5 - if !Contains(t, errorMsg, "0.5") { - t.Errorf("Error message should contain '0.5' (absolute difference), got: %s", errorMsg) + if shouldPass { + True(t, result) + False(t, mock.Failed()) + } else { + False(t, result) + True(t, mock.Failed()) } - }) + } } -func epsilonTCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - // All numeric types - basic success cases (12 cases) - {"int/success", testEpsilonT[int](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"int8/success", testEpsilonT[int8](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"int16/success", testEpsilonT[int16](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"int32/success", testEpsilonT[int32](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"int64/success", testEpsilonT[int64](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"uint/success", testEpsilonT[uint](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"uint8/success", testEpsilonT[uint8](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"uint16/success", testEpsilonT[uint16](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"uint32/success", testEpsilonT[uint32](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"uint64/success", testEpsilonT[uint64](100, 101, 0.02, true)}, // 1% error < 2% epsilon - {"float32/success", testEpsilonT[float32](100.0, 101.0, 0.02, true)}, // 1% error < 2% epsilon - {"float64/success", testEpsilonT[float64](100.0, 101.0, 0.02, true)}, // 1% error < 2% epsilon - - // Basic failure cases (3 cases) - {"int/failure", testEpsilonT[int](100, 110, 0.05, false)}, // 10% error > 5% epsilon - {"uint/failure", testEpsilonT[uint](100, 110, 0.05, false)}, // 10% error > 5% epsilon - {"float64/failure", testEpsilonT[float64](100.0, 110.0, 0.05, false)}, // 10% error > 5% epsilon - - // Exact match (3 cases) - {"int/exact", testEpsilonT[int](100, 100, 0.0, true)}, // Exact match - {"uint/exact", testEpsilonT[uint](100, 100, 0.0, true)}, // Exact match - {"float64/exact", testEpsilonT[float64](100.0, 100.0, 0.0, true)}, // Exact match - - // Zero expected value - uses absolute error (8 cases) - {"int/both-zero", testEpsilonT[int](0, 0, 0.01, true)}, // Both zero - {"uint/both-zero", testEpsilonT[uint](0, 0, 0.01, true)}, // Both zero - {"float64/both-zero", testEpsilonT[float64](0.0, 0.0, 0.01, true)}, // Both zero - {"float64/zero-expected-within", testEpsilonT[float64](0.0, 0.009, 0.01, true)}, // |0.009| <= 0.01 - {"float64/zero-expected-at-boundary", testEpsilonT[float64](0.0, 0.01, 0.01, true)}, // |0.01| <= 0.01 - {"float64/zero-expected-exceed", testEpsilonT[float64](0.0, 0.011, 0.01, false)}, // |0.011| > 0.01 - {"float64/zero-expected-large", testEpsilonT[float64](0.0, 100.0, 0.01, false)}, // |100| > 0.01 - {"int/zero-expected-negative", testEpsilonT[int](0, -5, 10, true)}, // |-5| <= 10 (absolute) +func testEpsilonT[Number Measurable](expected, actual Number, epsilon float64, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() - // Near-zero values (2 cases) - {"float64/near-zero-success", testEpsilonT[float64](0.001, 0.00101, 0.02, true)}, // 1% error < 2% - {"float64/near-zero-failure", testEpsilonT[float64](0.001, 0.00110, 0.05, false)}, // 10% error > 5% + mock := new(mockT) + result := InEpsilonT(mock, expected, actual, epsilon) - // Negative numbers (3 cases) - {"int/negative", testEpsilonT[int](-100, -101, 0.02, true)}, // 1% error < 2% - {"int/negative-fail", testEpsilonT[int](-100, -110, 0.05, false)}, // 10% error > 5% - {"float64/negative", testEpsilonT[float64](-100.0, -101.0, 0.02, true)}, // 1% error < 2% + if shouldPass { + True(t, result) + False(t, mock.Failed()) + } else { + False(t, result) + True(t, mock.Failed()) + } + } +} - // Mixed positive/negative (3 cases) - {"int/mixed-small", testEpsilonT[int](100, -100, 2.1, true)}, // 200% error <= 210% epsilon - {"int/mixed-fail", testEpsilonT[int](100, -100, 1.9, false)}, // 200% error > 190% epsilon - {"float64/mixed", testEpsilonT[float64](100.0, -100.0, 2.1, true)}, // 200% error <= 210% epsilon +// Helper functions and test data for InDeltaSlice - // Float32 NaN cases (3 cases) - {"float32/both-nan", testEpsilonT[float32](float32(math.NaN()), float32(math.NaN()), 0.01, true)}, - {"float32/expected-nan", testEpsilonT[float32](float32(math.NaN()), 42.0, 0.01, false)}, - {"float32/actual-nan", testEpsilonT[float32](42.0, float32(math.NaN()), 0.01, false)}, +func deltaSliceCases() iter.Seq[genericTestCase] { + return slices.Values([]genericTestCase{ + // Success cases - slices are element-wise within delta + { + "within-delta-with-nan", + testDeltaSlice( + []float64{1.001, math.NaN(), 0.999}, + []float64{1, math.NaN(), 1}, + 0.1, + true, + ), + }, + { + "within-delta-1.0", + testDeltaSlice( + []float64{1, math.NaN(), 2}, + []float64{0, math.NaN(), 3}, + 1, + true, + ), + }, - // Float64 NaN cases (3 cases) - {"float64/both-nan", testEpsilonT[float64](math.NaN(), math.NaN(), 0.01, true)}, - {"float64/expected-nan", testEpsilonT[float64](math.NaN(), 42.0, 0.01, false)}, - {"float64/actual-nan", testEpsilonT[float64](42.0, math.NaN(), 0.01, false)}, + // Failure cases - slices are not element-wise within delta + { + "not-within-delta", + testDeltaSlice( + []float64{1, math.NaN(), 2}, + []float64{0, math.NaN(), 3}, + 0.1, + false, + ), + }, - // Float32 +Inf cases (4 cases) - {"float32/both-plus-inf", testEpsilonT[float32](float32(math.Inf(1)), float32(math.Inf(1)), 0.01, true)}, - {"float32/expected-plus-inf-actual-minus-inf", testEpsilonT[float32](float32(math.Inf(1)), float32(math.Inf(-1)), 0.01, false)}, - {"float32/expected-plus-inf-actual-finite", testEpsilonT[float32](float32(math.Inf(1)), 100.0, 0.01, false)}, - {"float32/expected-finite-actual-plus-inf", testEpsilonT[float32](100.0, float32(math.Inf(1)), 0.01, false)}, + // Edge cases - invalid inputs + { + "invalid-non-slice-inputs", + testDeltaSlice("", nil, 1, false), + }, + }) +} - // Float64 +Inf cases (4 cases) - {"float64/both-plus-inf", testEpsilonT[float64](math.Inf(1), math.Inf(1), 0.01, true)}, - {"float64/expected-plus-inf-actual-minus-inf", testEpsilonT[float64](math.Inf(1), math.Inf(-1), 0.01, false)}, - {"float64/expected-plus-inf-actual-finite", testEpsilonT[float64](math.Inf(1), 100.0, 0.01, false)}, - {"float64/expected-finite-actual-plus-inf", testEpsilonT[float64](100.0, math.Inf(1), 0.01, false)}, +//nolint:thelper // linter false positive: this is not a helper +func testDeltaSlice(expected, actual any, delta float64, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() - // Float32 -Inf cases (4 cases) - {"float32/both-minus-inf", testEpsilonT[float32](float32(math.Inf(-1)), float32(math.Inf(-1)), 0.01, true)}, - {"float32/expected-minus-inf-actual-plus-inf", testEpsilonT[float32](float32(math.Inf(-1)), float32(math.Inf(1)), 0.01, false)}, - {"float32/expected-minus-inf-actual-finite", testEpsilonT[float32](float32(math.Inf(-1)), 100.0, 0.01, false)}, - {"float32/expected-finite-actual-minus-inf", testEpsilonT[float32](100.0, float32(math.Inf(-1)), 0.01, false)}, + mock := new(mockT) + result := InDeltaSlice(mock, expected, actual, delta) - // Float64 -Inf cases (4 cases) - {"float64/both-minus-inf", testEpsilonT[float64](math.Inf(-1), math.Inf(-1), 0.01, true)}, - {"float64/expected-minus-inf-actual-plus-inf", testEpsilonT[float64](math.Inf(-1), math.Inf(1), 0.01, false)}, - {"float64/expected-minus-inf-actual-finite", testEpsilonT[float64](math.Inf(-1), 100.0, 0.01, false)}, - {"float64/expected-finite-actual-minus-inf", testEpsilonT[float64](100.0, math.Inf(-1), 0.01, false)}, + if shouldPass { + True(t, result) + False(t, mock.Failed()) + } else { + False(t, result) + True(t, mock.Failed()) + } + } +} - // Epsilon validation (6 cases) - {"float64/epsilon-negative", testEpsilonT[float64](100.0, 100.0, -0.01, false)}, // Negative epsilon - {"float64/epsilon-nan", testEpsilonT[float64](100.0, 100.0, math.NaN(), false)}, // NaN epsilon - {"float32/epsilon-nan", testEpsilonT[float32](100.0, 100.0, math.NaN(), false)}, // NaN epsilon - {"float64/epsilon-plus-inf", testEpsilonT[float64](100.0, 100.0, math.Inf(1), false)}, // +Inf epsilon - {"float64/epsilon-minus-inf", testEpsilonT[float64](100.0, 100.0, math.Inf(-1), false)}, // -Inf epsilon - {"float32/epsilon-plus-inf", testEpsilonT[float32](100.0, 100.0, math.Inf(1), false)}, // +Inf epsilon +// Helper functions and test data for InEpsilonSlice - // Precision testing (4 cases) - {"float64/small-epsilon-pass", testEpsilonT[float64](1.0, 1.000001, 0.00001, true)}, // Very small error - {"float64/small-epsilon-fail", testEpsilonT[float64](1.0, 1.000011, 0.00001, false)}, // Exceeds small epsilon - {"float32/small-epsilon-pass", testEpsilonT[float32](1.0, 1.000001, 0.00001, true)}, // Very small error - {"float32/small-epsilon-fail", testEpsilonT[float32](1.0, 1.000011, 0.00001, false)}, // Exceeds small epsilon +func epsilonSliceCases() iter.Seq[genericTestCase] { + return slices.Values([]genericTestCase{ + // Success cases - slices are element-wise within epsilon + { + "within-epsilon-with-nan", + testEpsilonSlice( + []float64{2.2, math.NaN(), 2.0}, + []float64{2.1, math.NaN(), 2.1}, + 0.06, + true, + ), + }, - // Large values (3 cases) - {"int64/large-values", testEpsilonT[int64](1000000000, 1010000000, 0.02, true)}, // 1% error < 2% - {"uint64/large-values", testEpsilonT[uint64](1000000000, 1010000000, 0.02, true)}, // 1% error < 2% - {"float64/large-values", testEpsilonT[float64](1e15, 1.01e15, 0.02, true)}, // 1% error < 2% + // Failure cases - slices are not element-wise within epsilon + { + "not-within-epsilon", + testEpsilonSlice( + []float64{2.2, 2.0}, + []float64{2.1, 2.1}, + 0.04, + false, + ), + }, - // Edge cases (4 cases) - {"int/zero-epsilon-same", testEpsilonT[int](100, 100, 0.0, true)}, // Zero epsilon, exact match - {"float64/zero-epsilon-different", testEpsilonT[float64](100.0, 100.1, 0.0, false)}, // Zero epsilon, different - {"int/large-epsilon", testEpsilonT[int](100, 200, 1.5, true)}, // 100% error < 150% epsilon - {"float64/boundary", testEpsilonT[float64](100.0, 102.0, 0.02, true)}, // Exactly 2% error with 2% epsilon + // Edge cases - invalid inputs + { + "invalid-expected-nil", + testEpsilonSlice("", nil, 1, false), + }, + { + "invalid-actual-nil", + testEpsilonSlice(nil, "", 1, false), + }, + { + "invalid-expected-not-slice", + testEpsilonSlice(1, []int{}, 1, false), + }, + { + "invalid-actual-not-slice", + testEpsilonSlice([]int{}, 1, 1, false), + }, + { + "invalid-expected-non-numeric-slice", + testEpsilonSlice([]string{}, []int{}, 1, false), + }, + { + "invalid-actual-non-numeric-slice", + testEpsilonSlice([]int{}, []string{}, 1, false), + }, }) } //nolint:thelper // linter false positive: this is not a helper -func testEpsilonT[Number Measurable](expected, actual Number, epsilon float64, shouldPass bool) func(*testing.T) { +func testEpsilonSlice(expected, actual any, epsilon float64, shouldPass bool) func(*testing.T) { return func(t *testing.T) { t.Parallel() - mock := &mockT{} - result := InEpsilonT(mock, expected, actual, epsilon) + mock := new(mockT) + result := InEpsilonSlice(mock, expected, actual, epsilon) if shouldPass { - True(t, result, "Expected InEpsilonT(%v, %v, %v) to pass", expected, actual, epsilon) - False(t, mock.Failed(), "Mock should not have failed") + True(t, result) + False(t, mock.Failed()) } else { - False(t, result, "Expected InEpsilonT(%v, %v, %v) to fail", expected, actual, epsilon) - True(t, mock.Failed(), "Mock should have failed") + False(t, result) + True(t, mock.Failed()) } } } diff --git a/internal/assertions/string_test.go b/internal/assertions/string_test.go index 3ab379f2e..a5fd202de 100644 --- a/internal/assertions/string_test.go +++ b/internal/assertions/string_test.go @@ -10,7 +10,7 @@ import ( "testing" ) -func TestStringEqual(t *testing.T) { +func TestStringEqualAndRegexp(t *testing.T) { t.Parallel() i := 0 @@ -23,7 +23,7 @@ func TestStringEqual(t *testing.T) { } } -func TestStringEqualFormatting(t *testing.T) { +func TestStringEqualAndRegexpFormatting(t *testing.T) { t.Parallel() i := 0 @@ -78,7 +78,7 @@ func TestStringRegexp(t *testing.T) { }) }) - t.Run("with fmt.Sprint conversion", func(t *testing.T) { + t.Run("with fmt.Sprint conversion (edge case)", func(t *testing.T) { t.Parallel() const ( From cd40600f9ae6fc6763a84a834ae137a98ee01e17 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Mon, 19 Jan 2026 20:36:48 +0100 Subject: [PATCH 02/10] feat: added 17 more generic assertions Signed-off-by: Frederic BIDON --- internal/assertions/collection.go | 197 ++++++++++++++++++++++++++++++ internal/assertions/compare.go | 1 + internal/assertions/equal.go | 196 ++++++++++++++++++++++------- internal/assertions/file.go | 12 +- internal/assertions/file_test.go | 26 ++-- internal/assertions/generics.go | 2 +- internal/assertions/order.go | 129 ++++++++++++++++++- 7 files changed, 497 insertions(+), 66 deletions(-) diff --git a/internal/assertions/collection.go b/internal/assertions/collection.go index c27b8dc6d..81e03684d 100644 --- a/internal/assertions/collection.go +++ b/internal/assertions/collection.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "reflect" + "slices" "strings" ) @@ -77,6 +78,78 @@ func Contains(t T, s, contains any, msgAndArgs ...any) bool { return true } +// StringContainsT asserts that a string contains the specified substring. +// +// Strings may be go strings or []byte. +// +// # Usage +// +// assertions.StringContainsT(t, "Hello World", "World") +// +// # Examples +// +// success: "AB", "A" +// failure: "AB", "C" +func StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + if !strings.Contains(string(str), string(substring)) { + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", str), substring), msgAndArgs...) + } + + return true +} + +// SliceContainsT asserts that the specified slice contains a comparable element. +// +// # Usage +// +// assertions.SliceContainsT(t, []{"Hello","World"}, "World") +// +// # Examples +// +// success: []string{"A","B"}, "A" +// failure: []string{"A","B"}, "C" +func SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + if !slices.Contains(s, element) { + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", s), element), msgAndArgs...) + } + + return true +} + +// MapContainsT asserts that the specified map contains a key. +// +// # Usage +// +// assertions.MapContainsT(t, map[string]string{"Hello": "x","World": "y"}, "World") +// +// # Examples +// +// success: map[string]string{"A": "B"}, "A" +// failure: map[string]string{"A": "B"}, "C" +func MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + _, ok := m[key] + if !ok { + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", m), key), msgAndArgs...) + } + + return true +} + // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // @@ -107,6 +180,78 @@ func NotContains(t T, s, contains any, msgAndArgs ...any) bool { return true } +// StringNotContainsT asserts that a string does not contain the specified substring. +// +// Strings may be go strings or []byte. +// +// # Usage +// +// assertions.StringNotContainsT(t, "Hello World", "hi") +// +// # Examples +// +// success: "AB", "C" +// failure: "AB", "A" +func StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + if strings.Contains(string(str), string(substring)) { + return Fail(t, fmt.Sprintf("%s should not contain %#v", truncatingFormat("%#v", str), substring), msgAndArgs...) + } + + return true +} + +// SliceNotContainsT asserts that the specified slice does not contain a comparable element. +// +// # Usage +// +// assertions.SliceNotContainsT(t, []{"Hello","World"}, "hi") +// +// # Examples +// +// success: []string{"A","B"}, "C" +// failure: []string{"A","B"}, "A" +func SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + if slices.Contains(s, element) { + return Fail(t, fmt.Sprintf("%s should not contain %#v", truncatingFormat("%#v", s), element), msgAndArgs...) + } + + return true +} + +// MapNotContainsT asserts that the specified map does not contain a key. +// +// # Usage +// +// assertions.MapNotContainsT(t, map[string]string{"Hello": "x","World": "y"}, "hi") +// +// # Examples +// +// success: map[string]string{"A": "B"}, "C" +// failure: map[string]string{"A": "B"}, "A" +func MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + _, ok := m[key] + if ok { + return Fail(t, fmt.Sprintf("%s should not contain %#v", truncatingFormat("%#v", m), key), msgAndArgs...) + } + + return true +} + // Subset asserts that the list (array, slice, or map) contains all elements // given in the subset (array, slice, or map). // @@ -129,6 +274,7 @@ func Subset(t T, list, subset any, msgAndArgs ...any) (ok bool) { if h, ok := t.(H); ok { h.Helper() } + if subset == nil { return true // we consider nil to be equal to the nil set } @@ -185,6 +331,31 @@ func Subset(t T, list, subset any, msgAndArgs ...any) (ok bool) { return true } +// SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset. +// +// # Usage +// +// assertions.SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) +// +// # Examples +// +// success: []int{1, 2, 3}, []int{1, 2} +// failure: []int{1, 2, 3}, []int{4, 5} +func SliceSubsetT[Slice ~[]E, E comparable](t T, list, subset Slice, msgAndArgs ...any) (ok bool) { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + for _, element := range subset { + if !slices.Contains(list, element) { + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", list), element), msgAndArgs...) + } + } + + return true +} + // NotSubset asserts that the list (array, slice, or map) does NOT contain all // elements given in the subset (array, slice, or map). // Map elements are key-value pairs unless compared with an array or slice where @@ -247,6 +418,7 @@ func NotSubset(t T, list, subset any, msgAndArgs ...any) (ok bool) { } subsetList = reflect.ValueOf(keys) } + for i := range subsetList.Len() { element := subsetList.Index(i).Interface() ok, found := containsElement(list, element) @@ -261,6 +433,31 @@ func NotSubset(t T, list, subset any, msgAndArgs ...any) (ok bool) { return Fail(t, fmt.Sprintf("%s is a subset of %s", truncatingFormat("%q", subset), truncatingFormat("%q", list)), msgAndArgs...) } +// SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset. +// +// # Usage +// +// assertions.SliceNotSubsetT(t, []int{1, 2, 3}, []int{1, 4}) +// +// # Examples +// +// success: []int{1, 2, 3}, []int{4, 5} +// failure: []int{1, 2, 3}, []int{1, 2} +func SliceNotSubsetT[Slice ~[]E, E comparable](t T, list, subset Slice, msgAndArgs ...any) (ok bool) { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + for _, element := range subset { + if !slices.Contains(list, element) { + return true + } + } + + return Fail(t, fmt.Sprintf("%s is a subset of %s", truncatingFormat("%q", subset), truncatingFormat("%q", list)), msgAndArgs...) +} + // ElementsMatch asserts that the specified listA(array, slice...) is equal to specified // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. diff --git a/internal/assertions/compare.go b/internal/assertions/compare.go index 8561ebcfe..70be4da82 100644 --- a/internal/assertions/compare.go +++ b/internal/assertions/compare.go @@ -500,6 +500,7 @@ func convertReflectValue[V any](obj any, value reflect.Value) V { //nolint:iretu if !ok { converted, ok = value.Convert(reflect.TypeFor[V]()).Interface().(V) if !ok { + // should never get there panic("internal error: expected that reflect.Value.Convert yields its target type") } } diff --git a/internal/assertions/equal.go b/internal/assertions/equal.go index 554bbb9ca..8f61e7a1c 100644 --- a/internal/assertions/equal.go +++ b/internal/assertions/equal.go @@ -32,28 +32,93 @@ func Equal(t T, expected, actual any, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } + if err := validateEqualArgs(expected, actual); err != nil { return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", expected, actual, err), msgAndArgs...) } if !ObjectsAreEqual(expected, actual) { - diff := diff(expected, actual) - expectedStr, actualStr := formatUnequalValues(expected, actual) + return failWithDiff(t, expected, actual, msgAndArgs...) + } - if colors.Enabled() { - expectedStr = colors.ExpectedColorizer()(expectedStr) - actualStr = colors.ActualColorizer()(actualStr) - } + return true +} + +// EqualT asserts that two objects of the same comparable type are equal. +// +// Pointer variable equality is determined based on the equality of the memory addresses (unlike [Equal], but like [Same]). +// +// Functions, slices and maps are not comparable. See also [ComparisonOperators]. +// +// If you need to compare values of non-comparable types, or compare pointers by the value they point to, +// use [Equal] instead. +// +// # Usage +// +// assertions.EqualT(t, 123, 123) +// +// # Examples +// +// success: 123, 123 +// failure: 123, 456 +// +// [ComparisonOperators]: https://go.dev/ref/spec#Comparison_operators. +func EqualT[V comparable](t T, expected, actual V, msgAndArgs ...any) bool { + // Domain: equality + if expected != actual { + return failWithDiff(t, expected, actual, msgAndArgs...) + } + + return true +} + +// NotEqual asserts that the specified values are NOT equal. +// +// # Usage +// +// assertions.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +// +// # Examples +// +// success: 123, 456 +// failure: 123, 123 +func NotEqual(t T, expected, actual any, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", + expected, actual, err), msgAndArgs...) + } + + if ObjectsAreEqual(expected, actual) { + return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) + } + + return true +} - return Fail(t, - fmt.Sprintf("Not equal: \n"+ - "expected: %s\n"+ - "actual : %s%s", - expectedStr, - actualStr, diff), - msgAndArgs..., - ) +// NotEqualT asserts that the specified values of the same comparable type are NOT equal. +// +// See [EqualT]. +// +// # Usage +// +// assertions.NotEqualT(t, obj1, obj2) +// +// # Examples +// +// success: 123, 456 +// failure: 123, 123 +func NotEqualT[V comparable](t T, expected, actual V, msgAndArgs ...any) bool { + // Domain: equality + if expected == actual { + return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) } return true @@ -93,6 +158,31 @@ func Same(t T, expected, actual any, msgAndArgs ...any) bool { return true } +// SameT asserts that two pointers of the same type reference the same object. +// +// # Usage +// +// assertions.SameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, staticVarPtr +// failure: &staticVar, ptr("static string") +func SameT[P any](t T, expected, actual *P, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + + if expected != actual { + return Fail(t, fmt.Sprintf("Not same: \n"+ + "expected: %[2]s (%[1]T)(%[1]p)\n"+ + "actual : %[4]s (%[3]T)(%[3]p)", expected, truncatingFormat("%#v", expected), actual, truncatingFormat("%#v", actual)), msgAndArgs...) + } + + return true +} + // NotSame asserts that two pointers do not reference the same object. // // Both arguments must be pointer variables. Pointer variable sameness is @@ -126,6 +216,33 @@ func NotSame(t T, expected, actual any, msgAndArgs ...any) bool { return true } +// NotSameT asserts that two pointers do not reference the same object. +// +// See [SameT] +// +// # Usage +// +// assertions.NotSameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, ptr("static string") +// failure: &staticVar, staticVarPtr +func NotSameT[P any](t T, expected, actual *P, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + + if expected == actual { + return Fail(t, fmt.Sprintf( + "Expected and actual point to the same object: %p %s", + expected, truncatingFormat("%#v", expected)), msgAndArgs...) + } + + return true +} + // EqualValues asserts that two objects are equal or convertible to the larger // type and equal. // @@ -324,57 +441,50 @@ func NotEmpty(t T, object any, msgAndArgs ...any) bool { return pass } -// NotEqual asserts that the specified values are NOT equal. +// NotEqualValues asserts that two objects are not equal even when converted to the same type. // // # Usage // -// assertions.NotEqual(t, obj1, obj2) -// -// Pointer variable equality is determined based on the equality of the -// referenced values (as opposed to the memory addresses). +// assertions.NotEqualValues(t, obj1, obj2) // // # Examples // -// success: 123, 456 -// failure: 123, 123 -func NotEqual(t T, expected, actual any, msgAndArgs ...any) bool { +// success: uint32(123), int32(456) +// failure: uint32(123), int32(123) +func NotEqualValues(t T, expected, actual any, msgAndArgs ...any) bool { // Domain: equality if h, ok := t.(H); ok { h.Helper() } - if err := validateEqualArgs(expected, actual); err != nil { - return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", - expected, actual, err), msgAndArgs...) - } - if ObjectsAreEqual(expected, actual) { + if ObjectsAreEqualValues(expected, actual) { return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) } return true } -// NotEqualValues asserts that two objects are not equal even when converted to the same type. -// -// # Usage -// -// assertions.NotEqualValues(t, obj1, obj2) -// -// # Examples -// -// success: uint32(123), int32(456) -// failure: uint32(123), int32(123) -func NotEqualValues(t T, expected, actual any, msgAndArgs ...any) bool { - // Domain: equality +func failWithDiff(t T, expected, actual any, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - if ObjectsAreEqualValues(expected, actual) { - return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) + diff := diff(expected, actual) + expectedStr, actualStr := formatUnequalValues(expected, actual) + + if colors.Enabled() { + expectedStr = colors.ExpectedColorizer()(expectedStr) + actualStr = colors.ActualColorizer()(actualStr) } - return true + return Fail(t, + fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", + expectedStr, + actualStr, diff), + msgAndArgs..., + ) } // isNil checks if a specified object is nil or not, without Failing. @@ -409,7 +519,7 @@ func validateEqualArgs(expected, actual any) error { return nil } -// samePointers checks if two generic interface objects are pointers of the same +// samePointers checks if two arbitrary interface objects are pointers of the same // type pointing to the same object. // // It returns two values: same indicating if they are the same type and point to the same object, diff --git a/internal/assertions/file.go b/internal/assertions/file.go index 086b06e72..4efe7ad08 100644 --- a/internal/assertions/file.go +++ b/internal/assertions/file.go @@ -38,18 +38,18 @@ func FileExists(t T, path string, msgAndArgs ...any) bool { return true } -// NoFileExists checks whether a file does not exist in a given path. It fails +// FileNotExists checks whether a file does not exist in a given path. It fails // if the path points to an existing _file_ only. // // # Usage // -// assertions.NoFileExists(t, "path/to/file") +// assertions.FileNotExists(t, "path/to/file") // // # Examples // // success: filepath.Join(testDataPath(),"non_existing_file") // failure: filepath.Join(testDataPath(),"existing_file") -func NoFileExists(t T, path string, msgAndArgs ...any) bool { +func FileNotExists(t T, path string, msgAndArgs ...any) bool { // Domain: file if h, ok := t.(H); ok { h.Helper() @@ -93,18 +93,18 @@ func DirExists(t T, path string, msgAndArgs ...any) bool { return true } -// NoDirExists checks whether a directory does not exist in the given path. +// DirNotExists checks whether a directory does not exist in the given path. // It fails if the path points to an existing _directory_ only. // // # Usage // -// assertions.NoDirExists(t, "path/to/directory") +// assertions.DirNotExists(t, "path/to/directory") // // # Examples // // success: filepath.Join(testDataPath(),"non_existing_dir") // failure: filepath.Join(testDataPath(),"existing_dir") -func NoDirExists(t T, path string, msgAndArgs ...any) bool { +func DirNotExists(t T, path string, msgAndArgs ...any) bool { // Domain: file if h, ok := t.(H); ok { h.Helper() diff --git a/internal/assertions/file_test.go b/internal/assertions/file_test.go index cf94abd26..1067c055c 100644 --- a/internal/assertions/file_test.go +++ b/internal/assertions/file_test.go @@ -30,25 +30,25 @@ func TestFileExists(t *testing.T) { True(t, FileExists(mock, link)) } -func TestFileNoFileExists(t *testing.T) { +func TestFileFileNotExists(t *testing.T) { t.Parallel() mock := new(testing.T) - False(t, NoFileExists(mock, filepath.Join("testdata", "existing_file"))) + False(t, FileNotExists(mock, filepath.Join("testdata", "existing_file"))) mock = new(testing.T) - True(t, NoFileExists(mock, "non_existent_file")) + True(t, FileNotExists(mock, "non_existent_file")) mock = new(testing.T) - True(t, NoFileExists(mock, filepath.Join("testdata", "existing_dir"))) + True(t, FileNotExists(mock, filepath.Join("testdata", "existing_dir"))) link := getTempSymlinkPath(t, filepath.Join("testdata", "existing_file")) mock = new(testing.T) - False(t, NoFileExists(mock, link)) + False(t, FileNotExists(mock, link)) link = getTempSymlinkPath(t, "non_existent_file") mock = new(testing.T) - False(t, NoFileExists(mock, link)) + False(t, FileNotExists(mock, link)) } func TestFileDirExists(t *testing.T) { @@ -72,25 +72,25 @@ func TestFileDirExists(t *testing.T) { False(t, DirExists(mock, link)) } -func TestFileNoDirExists(t *testing.T) { +func TestFileDirNotExists(t *testing.T) { t.Parallel() mock := new(testing.T) - True(t, NoDirExists(mock, filepath.Join("testdata", "existing_file"))) + True(t, DirNotExists(mock, filepath.Join("testdata", "existing_file"))) mock = new(testing.T) - True(t, NoDirExists(mock, "non_existent_dir")) + True(t, DirNotExists(mock, "non_existent_dir")) mock = new(testing.T) - False(t, NoDirExists(mock, filepath.Join("testdata", "existing_dir"))) + False(t, DirNotExists(mock, filepath.Join("testdata", "existing_dir"))) link := getTempSymlinkPath(t, filepath.Join("testdata", "existing_file")) mock = new(testing.T) - True(t, NoDirExists(mock, link)) + True(t, DirNotExists(mock, link)) link = getTempSymlinkPath(t, "non_existent_dir") mock = new(testing.T) - True(t, NoDirExists(mock, link)) + True(t, DirNotExists(mock, link)) } func TestFileEmpty(t *testing.T) { @@ -146,7 +146,7 @@ func TestFileNotEmpty(t *testing.T) { link = getTempSymlinkPath(t, "non_existent_file") mock = new(testing.T) - False(t, NoFileExists(mock, link)) + False(t, FileNotExists(mock, link)) } func getTempSymlinkPath(t *testing.T, file string) string { diff --git a/internal/assertions/generics.go b/internal/assertions/generics.go index 14dd82ca7..659d8a966 100644 --- a/internal/assertions/generics.go +++ b/internal/assertions/generics.go @@ -24,7 +24,7 @@ type ( // Ordered is a standard ordered type (i.e. types that support "<": [cmp.Ordered]) plus []byte and [time.Time]. // - // This is used by [GreaterT], [GreaterOrEqualT], [LessT], and [LessOrEqualT]. + // This is used by [GreaterT], [GreaterOrEqualT], [LessT], [LessOrEqualT], [IsIncreasingT], [IsDecreasingT]. // // NOTE: since [time.Time] is a struct, custom types which redeclare [time.Time] are not supported. Ordered interface { diff --git a/internal/assertions/order.go b/internal/assertions/order.go index c44b83666..c9484e4e5 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -6,9 +6,10 @@ package assertions import ( "fmt" "reflect" + "slices" ) -// IsIncreasing asserts that the collection is increasing. +// IsIncreasing asserts that the collection is strictly increasing. // // # Usage // @@ -28,6 +29,32 @@ func IsIncreasing(t T, object any, msgAndArgs ...any) bool { return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } +// IsIncreasingT asserts that a slice of [Ordered] is strictly increasing. +// +// # Usage +// +// assertions.IsIncreasingT(t, []int{1, 2, 3}) +// assertions.IsIncreasingT(t, []float{1, 2}) +// assertions.IsIncreasingT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 2, 3} +// failure: []int{1, 1, 2} +func IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + // Domain: ordering + if h, ok := t.(H); ok { + h.Helper() + } + + isIncreasing := slices.IsSortedFunc(collection, compareStrictOrdered) + if !isIncreasing { + return Fail(t, "should be increasing", msgAndArgs...) + } + + return true +} + // IsNonIncreasing asserts that the collection is not increasing. // // # Usage @@ -48,7 +75,33 @@ func IsNonIncreasing(t T, object any, msgAndArgs ...any) bool { return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } -// IsDecreasing asserts that the collection is decreasing. +// IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. +// +// # Usage +// +// assertions.IsNonIncreasing(t, []int{2, 1, 1}) +// assertions.IsNonIncreasing(t, []float{2, 1}) +// assertions.IsNonIncreasing(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{2, 1, 1} +// failure: []int{1, 2, 3} +func IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + // Domain: ordering + if h, ok := t.(H); ok { + h.Helper() + } + + isIncreasing := slices.IsSortedFunc(collection, compareStrictOrdered) + if isIncreasing { + return Fail(t, "should not be increasing", msgAndArgs...) + } + + return true +} + +// IsDecreasing asserts that the collection is strictly decreasing. // // # Usage // @@ -68,7 +121,33 @@ func IsDecreasing(t T, object any, msgAndArgs ...any) bool { return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } -// IsNonDecreasing asserts that the collection is not decreasing. +// IsDecreasingT asserts that a slice of [Ordered] is strictly decreasing. +// +// # Usage +// +// assertions.IsDecreasingT(t, []int{2, 1, 0}) +// assertions.IsDecreasingT(t, []float{2, 1}) +// assertions.IsDecreasingT(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{3, 2, 1} +// failure: []int{1, 2, 3} +func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + // Domain: ordering + if h, ok := t.(H); ok { + h.Helper() + } + + isDecreasing := slices.IsSortedFunc(collection, reverseCompareStrictOrdered) + if !isDecreasing { + return Fail(t, "should be decreasing", msgAndArgs...) + } + + return true +} + +// IsNonDecreasing asserts that the collection is not strictly decreasing. // // # Usage // @@ -88,6 +167,32 @@ func IsNonDecreasing(t T, object any, msgAndArgs ...any) bool { return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } +// IsNonDecreasingT asserts that a slice of [Ordered] is not decreasing. +// +// # Usage +// +// assertions.IsNonDecreasingT(t, []int{1, 1, 2}) +// assertions.IsNonDecreasingT(t, []float{1, 2}) +// assertions.IsNonDecreasingT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 1, 2} +// failure: []int{2, 1, 1} +func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + // Domain: ordering + if h, ok := t.(H); ok { + h.Helper() + } + + isDecreasing := slices.IsSortedFunc(collection, reverseCompareStrictOrdered) + if isDecreasing { + return Fail(t, "should not be decreasing", msgAndArgs...) + } + + return true +} + // isOrdered checks that collection contains orderable elements. func isOrdered(t T, object any, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...any) bool { objKind := reflect.TypeOf(object).Kind() @@ -126,3 +231,21 @@ func isOrdered(t T, object any, allowedComparesResults []compareResult, failMess return true } + +func compareStrictOrdered[E Ordered](a, b E) int { + v := compareOrderedWithAny[E](a, b) + if v == 0 { + return -1 + } + + return v +} + +func reverseCompareStrictOrdered[E Ordered](a, b E) int { + v := compareOrderedWithAny[E](b, a) + if v == 0 { + return 1 + } + + return v +} From c80324f16ca02f9114cfe9bd33251cea906dd28f Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Mon, 19 Jan 2026 22:57:14 +0100 Subject: [PATCH 03/10] feat: completed the addition of generics Signed-off-by: Frederic BIDON --- internal/assertions/collection_test.go | 438 +++++++++++++++++++++++++ internal/assertions/equal_test.go | 430 +++++++++++++++++++++++- internal/assertions/order.go | 62 +++- internal/assertions/order_impl_test.go | 52 +++ internal/assertions/order_test.go | 420 ++++++++++++++++++++++++ 5 files changed, 1399 insertions(+), 3 deletions(-) create mode 100644 internal/assertions/order_impl_test.go diff --git a/internal/assertions/collection_test.go b/internal/assertions/collection_test.go index 871607e42..c4a6871f5 100644 --- a/internal/assertions/collection_test.go +++ b/internal/assertions/collection_test.go @@ -734,3 +734,441 @@ func testElementsMatchTStructPair() (matchTest, notMatchTest func(*testing.T)) { } return matchTest, notMatchTest } + +// Generic Contains function tests + +type containsTestCase struct { + name string + container any + element any + shouldPass bool +} + +func stringContainsTCases() iter.Seq[containsTestCase] { + return slices.Values([]containsTestCase{ + // Success cases + {name: "string/contains", container: "hello world", element: "world", shouldPass: true}, + {name: "string/contains-start", container: "hello world", element: "hello", shouldPass: true}, + {name: "string/contains-middle", container: "hello world", element: "lo wo", shouldPass: true}, + {name: "[]byte/contains", container: []byte("hello"), element: []byte("ell"), shouldPass: true}, + + // Failure cases + {name: "string/not-contains", container: "hello world", element: "xyz", shouldPass: false}, + {name: "string/case-sensitive", container: "hello world", element: "WORLD", shouldPass: false}, + {name: "[]byte/not-contains", container: []byte("hello"), element: []byte("xyz"), shouldPass: false}, + }) +} + +func sliceContainsTCases() iter.Seq[containsTestCase] { + return slices.Values([]containsTestCase{ + // Success cases + {name: "int/contains", container: []int{1, 2, 3}, element: 2, shouldPass: true}, + {name: "int/contains-first", container: []int{1, 2, 3}, element: 1, shouldPass: true}, + {name: "int/contains-last", container: []int{1, 2, 3}, element: 3, shouldPass: true}, + {name: "string/contains", container: []string{"a", "b", "c"}, element: "b", shouldPass: true}, + {name: "float64/contains", container: []float64{1.1, 2.2, 3.3}, element: 2.2, shouldPass: true}, + + // Failure cases + {name: "int/not-contains", container: []int{1, 2, 3}, element: 5, shouldPass: false}, + {name: "string/not-contains", container: []string{"a", "b", "c"}, element: "d", shouldPass: false}, + {name: "empty-slice", container: []int{}, element: 1, shouldPass: false}, + }) +} + +func mapContainsTCases() iter.Seq[containsTestCase] { + return slices.Values([]containsTestCase{ + // Success cases + {name: "string-int/has-key", container: map[string]int{"a": 1, "b": 2}, element: "a", shouldPass: true}, + {name: "int-string/has-key", container: map[int]string{1: "one", 2: "two"}, element: 1, shouldPass: true}, + {name: "string-string/has-key", container: map[string]string{"x": "y"}, element: "x", shouldPass: true}, + + // Failure cases + {name: "string-int/no-key", container: map[string]int{"a": 1, "b": 2}, element: "c", shouldPass: false}, + {name: "int-string/no-key", container: map[int]string{1: "one", 2: "two"}, element: 3, shouldPass: false}, + {name: "empty-map", container: map[string]int{}, element: "a", shouldPass: false}, + }) +} + +func TestStringContainsT(t *testing.T) { + t.Parallel() + + for tc := range stringContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Type dispatch for string or []byte + switch container := tc.container.(type) { + case string: + element, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string element but got %T", tc.element) + } + testStringContainsT(StringContainsT[string, string], container, element, tc.shouldPass)(t) + case []byte: + element, ok := tc.element.([]byte) + if !ok { + t.Fatalf("invalid test case: requires []byte element but got %T", tc.element) + } + testStringContainsT(StringContainsT[[]byte, []byte], container, element, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +func TestStringNotContainsT(t *testing.T) { + t.Parallel() + + for tc := range stringContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotContains + shouldPass := !tc.shouldPass + + // Type dispatch for string or []byte + switch container := tc.container.(type) { + case string: + element, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string element but got %T", tc.element) + } + testStringContainsT(StringNotContainsT[string, string], container, element, shouldPass)(t) + case []byte: + element, ok := tc.element.([]byte) + if !ok { + t.Fatalf("invalid test case: requires []byte element but got %T", tc.element) + } + testStringContainsT(StringNotContainsT[[]byte, []byte], container, element, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +func TestSliceContainsT(t *testing.T) { + t.Parallel() + + for tc := range sliceContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Type dispatch + switch container := tc.container.(type) { + case []int: + element, ok := tc.element.(int) + if !ok { + t.Fatalf("invalid test case: requires int element but got %T", tc.element) + } + testSliceContainsT(SliceContainsT[[]int, int], container, element, tc.shouldPass)(t) + case []string: + element, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string element but got %T", tc.element) + } + testSliceContainsT(SliceContainsT[[]string, string], container, element, tc.shouldPass)(t) + case []float64: + element, ok := tc.element.(float64) + if !ok { + t.Fatalf("invalid test case: requires float64 element but got %T", tc.element) + } + testSliceContainsT(SliceContainsT[[]float64, float64], container, element, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +func TestSliceNotContainsT(t *testing.T) { + t.Parallel() + + for tc := range sliceContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotContains + shouldPass := !tc.shouldPass + + // Type dispatch + switch container := tc.container.(type) { + case []int: + element, ok := tc.element.(int) + if !ok { + t.Fatalf("invalid test case: requires int element but got %T", tc.element) + } + testSliceContainsT(SliceNotContainsT[[]int, int], container, element, shouldPass)(t) + case []string: + element, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string element but got %T", tc.element) + } + testSliceContainsT(SliceNotContainsT[[]string, string], container, element, shouldPass)(t) + case []float64: + element, ok := tc.element.(float64) + if !ok { + t.Fatalf("invalid test case: requires float64 element but got %T", tc.element) + } + testSliceContainsT(SliceNotContainsT[[]float64, float64], container, element, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +func TestMapContainsT(t *testing.T) { + t.Parallel() + + for tc := range mapContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Type dispatch for different map types + switch container := tc.container.(type) { + case map[string]int: + key, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string key but got %T", tc.element) + } + testMapContainsT(MapContainsT[map[string]int, string, int], container, key, tc.shouldPass)(t) + case map[int]string: + key, ok := tc.element.(int) + if !ok { + t.Fatalf("invalid test case: requires int key but got %T", tc.element) + } + testMapContainsT(MapContainsT[map[int]string, int, string], container, key, tc.shouldPass)(t) + case map[string]string: + key, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string key but got %T", tc.element) + } + testMapContainsT(MapContainsT[map[string]string, string, string], container, key, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +func TestMapNotContainsT(t *testing.T) { + t.Parallel() + + for tc := range mapContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotContains + shouldPass := !tc.shouldPass + + // Type dispatch for different map types + switch container := tc.container.(type) { + case map[string]int: + key, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string key but got %T", tc.element) + } + testMapContainsT(MapNotContainsT[map[string]int, string, int], container, key, shouldPass)(t) + case map[int]string: + key, ok := tc.element.(int) + if !ok { + t.Fatalf("invalid test case: requires int key but got %T", tc.element) + } + testMapContainsT(MapNotContainsT[map[int]string, int, string], container, key, shouldPass)(t) + case map[string]string: + key, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string key but got %T", tc.element) + } + testMapContainsT(MapNotContainsT[map[string]string, string, string], container, key, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testStringContainsT[ADoc, EDoc Text]( + fn func(T, ADoc, EDoc, ...any) bool, + container ADoc, + element EDoc, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, container, element) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testSliceContainsT[Slice ~[]E, E comparable]( + fn func(T, Slice, E, ...any) bool, + slice Slice, + element E, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, slice, element) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testMapContainsT[Map ~map[K]V, K comparable, V any]( + fn func(T, Map, K, ...any) bool, + m Map, + key K, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, m, key) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} + +// Generic Subset function tests + +type subsetTestCase struct { + name string + list any + subset any + shouldPass bool +} + +func sliceSubsetTCases() iter.Seq[subsetTestCase] { + return slices.Values([]subsetTestCase{ + // Success cases + {name: "int/proper-subset", list: []int{1, 2, 3, 4, 5}, subset: []int{2, 4}, shouldPass: true}, + {name: "int/equal-sets", list: []int{1, 2, 3}, subset: []int{1, 2, 3}, shouldPass: true}, + {name: "int/empty-subset", list: []int{1, 2, 3}, subset: []int{}, shouldPass: true}, + {name: "string/subset", list: []string{"a", "b", "c", "d"}, subset: []string{"b", "d"}, shouldPass: true}, + {name: "float64/subset", list: []float64{1.1, 2.2, 3.3}, subset: []float64{2.2}, shouldPass: true}, + + // Failure cases + {name: "int/not-subset", list: []int{1, 2, 3}, subset: []int{4, 5}, shouldPass: false}, + {name: "int/partial-subset", list: []int{1, 2, 3}, subset: []int{2, 4}, shouldPass: false}, + {name: "string/not-subset", list: []string{"a", "b"}, subset: []string{"c"}, shouldPass: false}, + }) +} + +func TestSliceSubsetT(t *testing.T) { + t.Parallel() + + for tc := range sliceSubsetTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Type dispatch + switch list := tc.list.(type) { + case []int: + subset, ok := tc.subset.([]int) + if !ok { + t.Fatalf("invalid test case: requires []int subset but got %T", tc.subset) + } + testSubsetT(SliceSubsetT[[]int, int], list, subset, tc.shouldPass)(t) + case []string: + subset, ok := tc.subset.([]string) + if !ok { + t.Fatalf("invalid test case: requires []string subset but got %T", tc.subset) + } + testSubsetT(SliceSubsetT[[]string, string], list, subset, tc.shouldPass)(t) + case []float64: + subset, ok := tc.subset.([]float64) + if !ok { + t.Fatalf("invalid test case: requires []float64 subset but got %T", tc.subset) + } + testSubsetT(SliceSubsetT[[]float64, float64], list, subset, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", list) + } + }) + } +} + +func TestSliceNotSubsetT(t *testing.T) { + t.Parallel() + + for tc := range sliceSubsetTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotSubset + shouldPass := !tc.shouldPass + + // Type dispatch + switch list := tc.list.(type) { + case []int: + subset, ok := tc.subset.([]int) + if !ok { + t.Fatalf("invalid test case: requires []int subset but got %T", tc.subset) + } + testSubsetT(SliceNotSubsetT[[]int, int], list, subset, shouldPass)(t) + case []string: + subset, ok := tc.subset.([]string) + if !ok { + t.Fatalf("invalid test case: requires []string subset but got %T", tc.subset) + } + testSubsetT(SliceNotSubsetT[[]string, string], list, subset, shouldPass)(t) + case []float64: + subset, ok := tc.subset.([]float64) + if !ok { + t.Fatalf("invalid test case: requires []float64 subset but got %T", tc.subset) + } + testSubsetT(SliceNotSubsetT[[]float64, float64], list, subset, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", list) + } + }) + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testSubsetT[Slice ~[]E, E comparable]( + fn func(T, Slice, Slice, ...any) bool, + list, subset Slice, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, list, subset) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} diff --git a/internal/assertions/equal_test.go b/internal/assertions/equal_test.go index d5a6ebae9..d21ce76ed 100644 --- a/internal/assertions/equal_test.go +++ b/internal/assertions/equal_test.go @@ -369,6 +369,22 @@ func TestEqualValuePanics(t *testing.T) { } } +func TestEqualT(t *testing.T) { + t.Parallel() + + for tc := range equalTCases() { + t.Run(tc.name, testAllEqualT(tc)) + } +} + +func TestNotEqualT(t *testing.T) { + t.Parallel() + + for tc := range equalTCases() { + t.Run(tc.name, testAllNotEqualT(tc)) + } +} + type panicCase struct { name string value1 any @@ -744,7 +760,6 @@ func equalEmptyCases() iter.Seq[equalEmptyCase] { "foo\t\n" + "\t\n", }, - { name: "non-printable character is not empty", value: "\u00a0", // NO-BREAK SPACE UNICODE CHARACTER @@ -752,7 +767,6 @@ func equalEmptyCases() iter.Seq[equalEmptyCase] { // Proposal for enhancement: here you cannot figure out what is expected expectedErrMsg: "Should be empty, but was \u00a0\n", }, - // Here we are testing there is no error message on success { name: "Empty string is empty", @@ -860,3 +874,415 @@ func equalBytesCases() iter.Seq[equalBytesCase] { {nil, make([]byte, 0)}, }) } + +// Generic Equal function tests + +type equalTTestCase struct { + name string + expected any + actual any + shouldPass bool +} + +func equalTCases() iter.Seq[equalTTestCase] { + return slices.Values([]equalTTestCase{ + // Success cases - equal values + {name: "int/equal", expected: 42, actual: 42, shouldPass: true}, + {name: "int8/equal", expected: int8(10), actual: int8(10), shouldPass: true}, + {name: "int16/equal", expected: int16(100), actual: int16(100), shouldPass: true}, + {name: "int32/equal", expected: int32(1000), actual: int32(1000), shouldPass: true}, + {name: "int64/equal", expected: int64(10000), actual: int64(10000), shouldPass: true}, + {name: "uint/equal", expected: uint(42), actual: uint(42), shouldPass: true}, + {name: "uint8/equal", expected: uint8(10), actual: uint8(10), shouldPass: true}, + {name: "uint16/equal", expected: uint16(100), actual: uint16(100), shouldPass: true}, + {name: "uint32/equal", expected: uint32(1000), actual: uint32(1000), shouldPass: true}, + {name: "uint64/equal", expected: uint64(10000), actual: uint64(10000), shouldPass: true}, + {name: "string/equal", expected: "hello", actual: "hello", shouldPass: true}, + {name: "float32/equal", expected: float32(3.14), actual: float32(3.14), shouldPass: true}, + {name: "float64/equal", expected: 3.14, actual: 3.14, shouldPass: true}, + {name: "bool/true", expected: true, actual: true, shouldPass: true}, + {name: "bool/false", expected: false, actual: false, shouldPass: true}, + + // Failure cases - not equal + {name: "int/not-equal", expected: 42, actual: 43, shouldPass: false}, + {name: "string/not-equal", expected: "hello", actual: "world", shouldPass: false}, + {name: "bool/not-equal", expected: true, actual: false, shouldPass: false}, + {name: "float64/not-equal", expected: 3.14, actual: 2.71, shouldPass: false}, + }) +} + +func testAllEqualT(tc equalTTestCase) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + // Type dispatch + switch expected := tc.expected.(type) { + case int: + actual, ok := tc.actual.(int) + if !ok { + t.Fatalf("invalid test case: requires int but got %T", tc.actual) + } + testEqualT(EqualT[int], expected, actual, tc.shouldPass)(t) + case int8: + actual, ok := tc.actual.(int8) + if !ok { + t.Fatalf("invalid test case: requires int8 but got %T", tc.actual) + } + testEqualT(EqualT[int8], expected, actual, tc.shouldPass)(t) + case int16: + actual, ok := tc.actual.(int16) + if !ok { + t.Fatalf("invalid test case: requires int16 but got %T", tc.actual) + } + testEqualT(EqualT[int16], expected, actual, tc.shouldPass)(t) + case int32: + actual, ok := tc.actual.(int32) + if !ok { + t.Fatalf("invalid test case: requires int32 but got %T", tc.actual) + } + testEqualT(EqualT[int32], expected, actual, tc.shouldPass)(t) + case int64: + actual, ok := tc.actual.(int64) + if !ok { + t.Fatalf("invalid test case: requires int64 but got %T", tc.actual) + } + testEqualT(EqualT[int64], expected, actual, tc.shouldPass)(t) + case uint: + actual, ok := tc.actual.(uint) + if !ok { + t.Fatalf("invalid test case: requires uint but got %T", tc.actual) + } + testEqualT(EqualT[uint], expected, actual, tc.shouldPass)(t) + case uint8: + actual, ok := tc.actual.(uint8) + if !ok { + t.Fatalf("invalid test case: requires uint8 but got %T", tc.actual) + } + testEqualT(EqualT[uint8], expected, actual, tc.shouldPass)(t) + case uint16: + actual, ok := tc.actual.(uint16) + if !ok { + t.Fatalf("invalid test case: requires uint16 but got %T", tc.actual) + } + testEqualT(EqualT[uint16], expected, actual, tc.shouldPass)(t) + case uint32: + actual, ok := tc.actual.(uint32) + if !ok { + t.Fatalf("invalid test case: requires uint32 but got %T", tc.actual) + } + testEqualT(EqualT[uint32], expected, actual, tc.shouldPass)(t) + case uint64: + actual, ok := tc.actual.(uint64) + if !ok { + t.Fatalf("invalid test case: requires uint64 but got %T", tc.actual) + } + testEqualT(EqualT[uint64], expected, actual, tc.shouldPass)(t) + case string: + actual, ok := tc.actual.(string) + if !ok { + t.Fatalf("invalid test case: requires string but got %T", tc.actual) + } + testEqualT(EqualT[string], expected, actual, tc.shouldPass)(t) + case float32: + actual, ok := tc.actual.(float32) + if !ok { + t.Fatalf("invalid test case: requires float32 but got %T", tc.actual) + } + testEqualT(EqualT[float32], expected, actual, tc.shouldPass)(t) + case float64: + actual, ok := tc.actual.(float64) + if !ok { + t.Fatalf("invalid test case: requires float64 but got %T", tc.actual) + } + testEqualT(EqualT[float64], expected, actual, tc.shouldPass)(t) + case bool: + actual, ok := tc.actual.(bool) + if !ok { + t.Fatalf("invalid test case: requires bool but got %T", tc.actual) + } + testEqualT(EqualT[bool], expected, actual, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", expected) + } + } +} + +func testAllNotEqualT(tc equalTTestCase) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotEqual + shouldPass := !tc.shouldPass + + // Type dispatch + switch expected := tc.expected.(type) { + case int: + actual, ok := tc.actual.(int) + if !ok { + t.Fatalf("invalid test case: requires int but got %T", tc.actual) + } + testEqualT(NotEqualT[int], expected, actual, shouldPass)(t) + case int8: + actual, ok := tc.actual.(int8) + if !ok { + t.Fatalf("invalid test case: requires int8 but got %T", tc.actual) + } + testEqualT(NotEqualT[int8], expected, actual, shouldPass)(t) + case int16: + actual, ok := tc.actual.(int16) + if !ok { + t.Fatalf("invalid test case: requires int16 but got %T", tc.actual) + } + testEqualT(NotEqualT[int16], expected, actual, shouldPass)(t) + case int32: + actual, ok := tc.actual.(int32) + if !ok { + t.Fatalf("invalid test case: requires int32 but got %T", tc.actual) + } + testEqualT(NotEqualT[int32], expected, actual, shouldPass)(t) + case int64: + actual, ok := tc.actual.(int64) + if !ok { + t.Fatalf("invalid test case: requires int64 but got %T", tc.actual) + } + testEqualT(NotEqualT[int64], expected, actual, shouldPass)(t) + case uint: + actual, ok := tc.actual.(uint) + if !ok { + t.Fatalf("invalid test case: requires uint but got %T", tc.actual) + } + testEqualT(NotEqualT[uint], expected, actual, shouldPass)(t) + case uint8: + actual, ok := tc.actual.(uint8) + if !ok { + t.Fatalf("invalid test case: requires uint8 but got %T", tc.actual) + } + testEqualT(NotEqualT[uint8], expected, actual, shouldPass)(t) + case uint16: + actual, ok := tc.actual.(uint16) + if !ok { + t.Fatalf("invalid test case: requires uint16 but got %T", tc.actual) + } + testEqualT(NotEqualT[uint16], expected, actual, shouldPass)(t) + case uint32: + actual, ok := tc.actual.(uint32) + if !ok { + t.Fatalf("invalid test case: requires uint32 but got %T", tc.actual) + } + testEqualT(NotEqualT[uint32], expected, actual, shouldPass)(t) + case uint64: + actual, ok := tc.actual.(uint64) + if !ok { + t.Fatalf("invalid test case: requires uint64 but got %T", tc.actual) + } + testEqualT(NotEqualT[uint64], expected, actual, shouldPass)(t) + case string: + actual, ok := tc.actual.(string) + if !ok { + t.Fatalf("invalid test case: requires string but got %T", tc.actual) + } + testEqualT(NotEqualT[string], expected, actual, shouldPass)(t) + case float32: + actual, ok := tc.actual.(float32) + if !ok { + t.Fatalf("invalid test case: requires float32 but got %T", tc.actual) + } + testEqualT(NotEqualT[float32], expected, actual, shouldPass)(t) + case float64: + actual, ok := tc.actual.(float64) + if !ok { + t.Fatalf("invalid test case: requires float64 but got %T", tc.actual) + } + testEqualT(NotEqualT[float64], expected, actual, shouldPass)(t) + case bool: + actual, ok := tc.actual.(bool) + if !ok { + t.Fatalf("invalid test case: requires bool but got %T", tc.actual) + } + testEqualT(NotEqualT[bool], expected, actual, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", expected) + } + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testEqualT[V comparable]( + fn func(T, V, V, ...any) bool, + expected, actual V, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, expected, actual) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} + +// Generic Same function tests + +type sameTTestCase struct { + name string + makeValues func() (expected, actual any) // Function to create fresh pointers + shouldPass bool +} + +func sameTCases() iter.Seq[sameTTestCase] { + return slices.Values([]sameTTestCase{ + // Success cases - same pointer + { + name: "int/same-pointer", + makeValues: func() (any, any) { + x := 42 + return &x, &x + }, + shouldPass: true, + }, + { + name: "string/same-pointer", + makeValues: func() (any, any) { + s := "hello" + return &s, &s + }, + shouldPass: true, + }, + { + name: "float64/same-pointer", + makeValues: func() (any, any) { + f := 3.14 + return &f, &f + }, + shouldPass: true, + }, + + // Failure cases - different pointers (even with same value) + { + name: "int/different-pointers-same-value", + makeValues: func() (any, any) { + x, y := 42, 42 + return &x, &y + }, + shouldPass: false, + }, + { + name: "string/different-pointers", + makeValues: func() (any, any) { + s1, s2 := "hello", "world" + return &s1, &s2 + }, + shouldPass: false, + }, + { + name: "float64/different-pointers-same-value", + makeValues: func() (any, any) { + f1, f2 := 3.14, 3.14 + return &f1, &f2 + }, + shouldPass: false, + }, + }) +} + +func TestSameT(t *testing.T) { + t.Parallel() + + for tc := range sameTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + expected, actual := tc.makeValues() + + // Type dispatch based on pointer type + switch exp := expected.(type) { + case *int: + act, ok := actual.(*int) + if !ok { + t.Fatalf("invalid test case: requires *int but got %T", actual) + } + testSameT(SameT[int], exp, act, tc.shouldPass)(t) + case *string: + act, ok := actual.(*string) + if !ok { + t.Fatalf("invalid test case: requires *string but got %T", actual) + } + testSameT(SameT[string], exp, act, tc.shouldPass)(t) + case *float64: + act, ok := actual.(*float64) + if !ok { + t.Fatalf("invalid test case: requires *float64 but got %T", actual) + } + testSameT(SameT[float64], exp, act, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", exp) + } + }) + } +} + +func TestNotSameT(t *testing.T) { + t.Parallel() + + for tc := range sameTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotSame + shouldPass := !tc.shouldPass + + expected, actual := tc.makeValues() + + // Type dispatch based on pointer type + switch exp := expected.(type) { + case *int: + act, ok := actual.(*int) + if !ok { + t.Fatalf("invalid test case: requires *int but got %T", actual) + } + testSameT(NotSameT[int], exp, act, shouldPass)(t) + case *string: + act, ok := actual.(*string) + if !ok { + t.Fatalf("invalid test case: requires *string but got %T", actual) + } + testSameT(NotSameT[string], exp, act, shouldPass)(t) + case *float64: + act, ok := actual.(*float64) + if !ok { + t.Fatalf("invalid test case: requires *float64 but got %T", actual) + } + testSameT(NotSameT[float64], exp, act, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", exp) + } + }) + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testSameT[P any]( + fn func(T, *P, *P, ...any) bool, + expected, actual *P, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, expected, actual) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} diff --git a/internal/assertions/order.go b/internal/assertions/order.go index c9484e4e5..e07f49548 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -55,6 +55,60 @@ func IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m return true } +// SortedT asserts that the slice of [Ordered] is sorted (i.e. non-strictly increasing). +// +// Unlike [IsIncreasingT], it accepts elements to be equal. +// +// # Usage +// +// assertions.SortedT(t, []int{1, 2, 3}) +// assertions.SortedT(t, []float{1, 2}) +// assertions.SortedT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 1, 3} +// failure: []int{1, 4, 2} +func SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + // Domain: ordering + if h, ok := t.(H); ok { + h.Helper() + } + isSorted := slices.IsSortedFunc(collection, compareOrdered) + if !isSorted { + return Fail(t, "should be sorted", msgAndArgs...) + } + + return true +} + +// NotSortedT asserts that the slice of [Ordered] is NOT sorted (i.e. non-strictly increasing). +// +// Unlike [IsDecreasingT], it accepts slices that are neither increasing nor decreasing. +// +// # Usage +// +// assertions.NotSortedT(t, []int{3, 2, 3}) +// assertions.NotSortedT(t, []float{2, 1}) +// assertions.NotSortedT(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{3, 1, 3} +// failure: []int{1, 4, 8} +func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + // Domain: ordering + if h, ok := t.(H); ok { + h.Helper() + } + isSorted := slices.IsSortedFunc(collection, compareOrdered) + if isSorted { + return Fail(t, "should not be sorted", msgAndArgs...) + } + + return true +} + // IsNonIncreasing asserts that the collection is not increasing. // // # Usage @@ -241,10 +295,16 @@ func compareStrictOrdered[E Ordered](a, b E) int { return v } +func compareOrdered[E Ordered](a, b E) int { + v := compareOrderedWithAny[E](a, b) + + return v +} + func reverseCompareStrictOrdered[E Ordered](a, b E) int { v := compareOrderedWithAny[E](b, a) if v == 0 { - return 1 + return -1 } return v diff --git a/internal/assertions/order_impl_test.go b/internal/assertions/order_impl_test.go new file mode 100644 index 000000000..a84222005 --- /dev/null +++ b/internal/assertions/order_impl_test.go @@ -0,0 +1,52 @@ +package assertions + +import "testing" + +func TestOrderUnexportedImplementationDetails(t *testing.T) { + t.Parallel() + + t.Run("compareStrictOrdered", testCompareStrictOrdered) + t.Run("reverseCompareStrictOrdered", testReverseCompareStrictOrdered) +} + +func testCompareStrictOrdered(t *testing.T) { + t.Parallel() + + // Expectations: + // a < b : -1 + // a == b : -1 (attention standard cmp.Compare yield 0) + // a > b : 1 + res := compareStrictOrdered(1, 0) + if res != 1 { + t.Fatalf("expected 1 > 0") + } + res = compareStrictOrdered(0, 0) + if res != -1 { + t.Fatalf("expected !(0 > 0)") + } + res = compareStrictOrdered(0, 1) + if res != -1 { + t.Fatalf("expected !(0 > 1)") + } +} + +func testReverseCompareStrictOrdered(t *testing.T) { + t.Parallel() + + // Expectations: + // a < b : 1 + // a == b : -1 (attention standard cmp.Compare yield 0) + // a > b : -1 + res := reverseCompareStrictOrdered(1, 0) + if res != -1 { + t.Fatalf("expected !(1 < 0)") + } + res = reverseCompareStrictOrdered(0, 0) + if res != -1 { + t.Fatalf("expected !(0 < 0)") + } + res = reverseCompareStrictOrdered(0, 1) + if res != 1 { + t.Fatalf("expected 0 < 1)") + } +} diff --git a/internal/assertions/order_test.go b/internal/assertions/order_test.go index 25e370e88..7822708fb 100644 --- a/internal/assertions/order_test.go +++ b/internal/assertions/order_test.go @@ -39,6 +39,8 @@ func TestOrderIsIncreasing(t *testing.T) { // Check error report for currCase := range decreasingFixtures() { t.Run(fmt.Sprintf("%#v", currCase.collection), func(t *testing.T) { + t.Parallel() + out := &outputT{buf: bytes.NewBuffer(nil)} False(t, IsIncreasing(out, currCase.collection)) Contains(t, out.buf.String(), currCase.msg) @@ -242,3 +244,421 @@ func decreasingFixtures2() iter.Seq[orderedFixture] { }, ) } + +// Tests for generic ordering functions + +type orderTestCase struct { + name string + collection any + shouldPass bool +} + +func orderIncreasingCases() iter.Seq[orderTestCase] { + return slices.Values([]orderTestCase{ + // Success cases - strictly increasing + {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: true}, + {name: "int8/increasing", collection: []int8{1, 2, 3}, shouldPass: true}, + {name: "int16/increasing", collection: []int16{1, 2, 3}, shouldPass: true}, + {name: "int32/increasing", collection: []int32{1, 2, 3}, shouldPass: true}, + {name: "int64/increasing", collection: []int64{1, 2, 3}, shouldPass: true}, + {name: "uint/increasing", collection: []uint{1, 2, 3}, shouldPass: true}, + {name: "uint8/increasing", collection: []uint8{1, 2, 3}, shouldPass: true}, + {name: "uint16/increasing", collection: []uint16{1, 2, 3}, shouldPass: true}, + {name: "uint32/increasing", collection: []uint32{1, 2, 3}, shouldPass: true}, + {name: "uint64/increasing", collection: []uint64{1, 2, 3}, shouldPass: true}, + {name: "float32/increasing", collection: []float32{1.1, 2.2, 3.3}, shouldPass: true}, + {name: "float64/increasing", collection: []float64{1.1, 2.2, 3.3}, shouldPass: true}, + {name: "string/increasing", collection: []string{"a", "b", "c"}, shouldPass: true}, + + // Failure cases - not strictly increasing (equal or decreasing) + {name: "int/equal", collection: []int{1, 1, 2}, shouldPass: false}, + {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: false}, + {name: "float64/equal", collection: []float64{1.1, 1.1, 2.2}, shouldPass: false}, + {name: "string/equal", collection: []string{"a", "a", "b"}, shouldPass: false}, + }) +} + +func orderDecreasingCases() iter.Seq[orderTestCase] { + return slices.Values([]orderTestCase{ + // Success cases - strictly decreasing + {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: true}, + {name: "int8/decreasing", collection: []int8{3, 2, 1}, shouldPass: true}, + {name: "int16/decreasing", collection: []int16{3, 2, 1}, shouldPass: true}, + {name: "int32/decreasing", collection: []int32{3, 2, 1}, shouldPass: true}, + {name: "int64/decreasing", collection: []int64{3, 2, 1}, shouldPass: true}, + {name: "uint/decreasing", collection: []uint{3, 2, 1}, shouldPass: true}, + {name: "uint8/decreasing", collection: []uint8{3, 2, 1}, shouldPass: true}, + {name: "uint16/decreasing", collection: []uint16{3, 2, 1}, shouldPass: true}, + {name: "uint32/decreasing", collection: []uint32{3, 2, 1}, shouldPass: true}, + {name: "uint64/decreasing", collection: []uint64{3, 2, 1}, shouldPass: true}, + {name: "float32/decreasing", collection: []float32{3.3, 2.2, 1.1}, shouldPass: true}, + {name: "float64/decreasing", collection: []float64{3.3, 2.2, 1.1}, shouldPass: true}, + {name: "string/decreasing", collection: []string{"c", "b", "a"}, shouldPass: true}, + + // Failure cases - not strictly decreasing (equal or increasing) + {name: "int/equal", collection: []int{2, 1, 1}, shouldPass: false}, + {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: false}, + {name: "float64/equal", collection: []float64{2.2, 1.1, 1.1}, shouldPass: false}, + {name: "string/equal", collection: []string{"b", "a", "a"}, shouldPass: false}, + }) +} + +func orderNonIncreasingCases() iter.Seq[orderTestCase] { + return slices.Values([]orderTestCase{ + // Success cases - decreasing or equal (not increasing) + {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: true}, + {name: "int/with-equal", collection: []int{3, 2, 2, 1}, shouldPass: true}, + {name: "int/all-equal", collection: []int{2, 2, 2}, shouldPass: true}, + {name: "int8/decreasing", collection: []int8{3, 2, 1}, shouldPass: true}, + {name: "int16/decreasing", collection: []int16{3, 2, 1}, shouldPass: true}, + {name: "int32/decreasing", collection: []int32{3, 2, 1}, shouldPass: true}, + {name: "int64/decreasing", collection: []int64{3, 2, 1}, shouldPass: true}, + {name: "uint/decreasing", collection: []uint{3, 2, 1}, shouldPass: true}, + {name: "uint8/decreasing", collection: []uint8{3, 2, 1}, shouldPass: true}, + {name: "uint16/decreasing", collection: []uint16{3, 2, 1}, shouldPass: true}, + {name: "uint32/decreasing", collection: []uint32{3, 2, 1}, shouldPass: true}, + {name: "uint64/decreasing", collection: []uint64{3, 2, 1}, shouldPass: true}, + {name: "float32/decreasing", collection: []float32{3.3, 2.2, 1.1}, shouldPass: true}, + {name: "float64/decreasing", collection: []float64{3.3, 2.2, 1.1}, shouldPass: true}, + {name: "float64/with-equal", collection: []float64{3.3, 2.2, 2.2}, shouldPass: true}, + {name: "string/decreasing", collection: []string{"c", "b", "a"}, shouldPass: true}, + {name: "string/with-equal", collection: []string{"c", "b", "b"}, shouldPass: true}, + + // Failure cases - increasing + {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: false}, + {name: "float64/increasing", collection: []float64{1.1, 2.2, 3.3}, shouldPass: false}, + {name: "string/increasing", collection: []string{"a", "b", "c"}, shouldPass: false}, + }) +} + +func orderNonDecreasingCases() iter.Seq[orderTestCase] { + return slices.Values([]orderTestCase{ + // Success cases - increasing or equal (not decreasing) + {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: true}, + {name: "int/with-equal", collection: []int{1, 2, 2, 3}, shouldPass: true}, + {name: "int/all-equal", collection: []int{2, 2, 2}, shouldPass: true}, + {name: "int8/increasing", collection: []int8{1, 2, 3}, shouldPass: true}, + {name: "int16/increasing", collection: []int16{1, 2, 3}, shouldPass: true}, + {name: "int32/increasing", collection: []int32{1, 2, 3}, shouldPass: true}, + {name: "int64/increasing", collection: []int64{1, 2, 3}, shouldPass: true}, + {name: "uint/increasing", collection: []uint{1, 2, 3}, shouldPass: true}, + {name: "uint8/increasing", collection: []uint8{1, 2, 3}, shouldPass: true}, + {name: "uint16/increasing", collection: []uint16{1, 2, 3}, shouldPass: true}, + {name: "uint32/increasing", collection: []uint32{1, 2, 3}, shouldPass: true}, + {name: "uint64/increasing", collection: []uint64{1, 2, 3}, shouldPass: true}, + {name: "float32/increasing", collection: []float32{1.1, 2.2, 3.3}, shouldPass: true}, + {name: "float64/increasing", collection: []float64{1.1, 2.2, 3.3}, shouldPass: true}, + {name: "float64/with-equal", collection: []float64{1.1, 2.2, 2.2}, shouldPass: true}, + {name: "string/increasing", collection: []string{"a", "b", "c"}, shouldPass: true}, + {name: "string/with-equal", collection: []string{"a", "b", "b"}, shouldPass: true}, + + // Failure cases - decreasing + {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: false}, + {name: "float64/decreasing", collection: []float64{3.3, 2.2, 1.1}, shouldPass: false}, + {name: "string/decreasing", collection: []string{"c", "b", "a"}, shouldPass: false}, + }) +} + +func TestOrderIsIncreasingT(t *testing.T) { + t.Parallel() + + for tc := range orderIncreasingCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Dispatch based on type + switch coll := tc.collection.(type) { + case []int: + testOrderingT(IsIncreasingT[[]int, int], coll, tc.shouldPass)(t) + case []int8: + testOrderingT(IsIncreasingT[[]int8, int8], coll, tc.shouldPass)(t) + case []int16: + testOrderingT(IsIncreasingT[[]int16, int16], coll, tc.shouldPass)(t) + case []int32: + testOrderingT(IsIncreasingT[[]int32, int32], coll, tc.shouldPass)(t) + case []int64: + testOrderingT(IsIncreasingT[[]int64, int64], coll, tc.shouldPass)(t) + case []uint: + testOrderingT(IsIncreasingT[[]uint, uint], coll, tc.shouldPass)(t) + case []uint8: + testOrderingT(IsIncreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) + case []uint16: + testOrderingT(IsIncreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) + case []uint32: + testOrderingT(IsIncreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) + case []uint64: + testOrderingT(IsIncreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) + case []float32: + testOrderingT(IsIncreasingT[[]float32, float32], coll, tc.shouldPass)(t) + case []float64: + testOrderingT(IsIncreasingT[[]float64, float64], coll, tc.shouldPass)(t) + case []string: + testOrderingT(IsIncreasingT[[]string, string], coll, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", coll) + } + }) + } +} + +func TestOrderIsDecreasingT(t *testing.T) { + t.Parallel() + + for tc := range orderDecreasingCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Dispatch based on type + switch coll := tc.collection.(type) { + case []int: + testOrderingT(IsDecreasingT[[]int, int], coll, tc.shouldPass)(t) + case []int8: + testOrderingT(IsDecreasingT[[]int8, int8], coll, tc.shouldPass)(t) + case []int16: + testOrderingT(IsDecreasingT[[]int16, int16], coll, tc.shouldPass)(t) + case []int32: + testOrderingT(IsDecreasingT[[]int32, int32], coll, tc.shouldPass)(t) + case []int64: + testOrderingT(IsDecreasingT[[]int64, int64], coll, tc.shouldPass)(t) + case []uint: + testOrderingT(IsDecreasingT[[]uint, uint], coll, tc.shouldPass)(t) + case []uint8: + testOrderingT(IsDecreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) + case []uint16: + testOrderingT(IsDecreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) + case []uint32: + testOrderingT(IsDecreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) + case []uint64: + testOrderingT(IsDecreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) + case []float32: + testOrderingT(IsDecreasingT[[]float32, float32], coll, tc.shouldPass)(t) + case []float64: + testOrderingT(IsDecreasingT[[]float64, float64], coll, tc.shouldPass)(t) + case []string: + testOrderingT(IsDecreasingT[[]string, string], coll, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", coll) + } + }) + } +} + +func TestOrderIsNonIncreasingT(t *testing.T) { + t.Parallel() + + for tc := range orderNonIncreasingCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Dispatch based on type + switch coll := tc.collection.(type) { + case []int: + testOrderingT(IsNonIncreasingT[[]int, int], coll, tc.shouldPass)(t) + case []int8: + testOrderingT(IsNonIncreasingT[[]int8, int8], coll, tc.shouldPass)(t) + case []int16: + testOrderingT(IsNonIncreasingT[[]int16, int16], coll, tc.shouldPass)(t) + case []int32: + testOrderingT(IsNonIncreasingT[[]int32, int32], coll, tc.shouldPass)(t) + case []int64: + testOrderingT(IsNonIncreasingT[[]int64, int64], coll, tc.shouldPass)(t) + case []uint: + testOrderingT(IsNonIncreasingT[[]uint, uint], coll, tc.shouldPass)(t) + case []uint8: + testOrderingT(IsNonIncreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) + case []uint16: + testOrderingT(IsNonIncreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) + case []uint32: + testOrderingT(IsNonIncreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) + case []uint64: + testOrderingT(IsNonIncreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) + case []float32: + testOrderingT(IsNonIncreasingT[[]float32, float32], coll, tc.shouldPass)(t) + case []float64: + testOrderingT(IsNonIncreasingT[[]float64, float64], coll, tc.shouldPass)(t) + case []string: + testOrderingT(IsNonIncreasingT[[]string, string], coll, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", coll) + } + }) + } +} + +func TestOrderIsNonDecreasingT(t *testing.T) { + t.Parallel() + + for tc := range orderNonDecreasingCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Dispatch based on type + switch coll := tc.collection.(type) { + case []int: + testOrderingT(IsNonDecreasingT[[]int, int], coll, tc.shouldPass)(t) + case []int8: + testOrderingT(IsNonDecreasingT[[]int8, int8], coll, tc.shouldPass)(t) + case []int16: + testOrderingT(IsNonDecreasingT[[]int16, int16], coll, tc.shouldPass)(t) + case []int32: + testOrderingT(IsNonDecreasingT[[]int32, int32], coll, tc.shouldPass)(t) + case []int64: + testOrderingT(IsNonDecreasingT[[]int64, int64], coll, tc.shouldPass)(t) + case []uint: + testOrderingT(IsNonDecreasingT[[]uint, uint], coll, tc.shouldPass)(t) + case []uint8: + testOrderingT(IsNonDecreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) + case []uint16: + testOrderingT(IsNonDecreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) + case []uint32: + testOrderingT(IsNonDecreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) + case []uint64: + testOrderingT(IsNonDecreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) + case []float32: + testOrderingT(IsNonDecreasingT[[]float32, float32], coll, tc.shouldPass)(t) + case []float64: + testOrderingT(IsNonDecreasingT[[]float64, float64], coll, tc.shouldPass)(t) + case []string: + testOrderingT(IsNonDecreasingT[[]string, string], coll, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", coll) + } + }) + } +} + +func sortedTCases() iter.Seq[orderTestCase] { + return slices.Values([]orderTestCase{ + // Success cases - sorted (non-strictly increasing, allows equal values) + {name: "int/sorted-increasing", collection: []int{1, 2, 3}, shouldPass: true}, + {name: "int/sorted-with-equal", collection: []int{1, 1, 2, 3}, shouldPass: true}, + {name: "int/sorted-all-equal", collection: []int{2, 2, 2}, shouldPass: true}, + {name: "int8/sorted", collection: []int8{1, 2, 3}, shouldPass: true}, + {name: "int16/sorted", collection: []int16{1, 2, 3}, shouldPass: true}, + {name: "int32/sorted", collection: []int32{1, 2, 3}, shouldPass: true}, + {name: "int64/sorted", collection: []int64{1, 2, 3}, shouldPass: true}, + {name: "uint/sorted", collection: []uint{1, 2, 3}, shouldPass: true}, + {name: "uint8/sorted", collection: []uint8{1, 2, 3}, shouldPass: true}, + {name: "uint16/sorted", collection: []uint16{1, 2, 3}, shouldPass: true}, + {name: "uint32/sorted", collection: []uint32{1, 2, 3}, shouldPass: true}, + {name: "uint64/sorted", collection: []uint64{1, 2, 3}, shouldPass: true}, + {name: "float32/sorted", collection: []float32{1.1, 2.2, 3.3}, shouldPass: true}, + {name: "float64/sorted", collection: []float64{1.1, 2.2, 3.3}, shouldPass: true}, + {name: "string/sorted", collection: []string{"a", "b", "c"}, shouldPass: true}, + {name: "string/sorted-with-equal", collection: []string{"a", "a", "b"}, shouldPass: true}, + + // Failure cases - not sorted + {name: "int/unsorted", collection: []int{1, 4, 2}, shouldPass: false}, + {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: false}, + {name: "float64/unsorted", collection: []float64{1.1, 3.3, 2.2}, shouldPass: false}, + {name: "string/unsorted", collection: []string{"b", "a", "c"}, shouldPass: false}, + }) +} + +func TestSortedT(t *testing.T) { + t.Parallel() + + for tc := range sortedTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Dispatch based on type + switch coll := tc.collection.(type) { + case []int: + testOrderingT(SortedT[[]int, int], coll, tc.shouldPass)(t) + case []int8: + testOrderingT(SortedT[[]int8, int8], coll, tc.shouldPass)(t) + case []int16: + testOrderingT(SortedT[[]int16, int16], coll, tc.shouldPass)(t) + case []int32: + testOrderingT(SortedT[[]int32, int32], coll, tc.shouldPass)(t) + case []int64: + testOrderingT(SortedT[[]int64, int64], coll, tc.shouldPass)(t) + case []uint: + testOrderingT(SortedT[[]uint, uint], coll, tc.shouldPass)(t) + case []uint8: + testOrderingT(SortedT[[]uint8, uint8], coll, tc.shouldPass)(t) + case []uint16: + testOrderingT(SortedT[[]uint16, uint16], coll, tc.shouldPass)(t) + case []uint32: + testOrderingT(SortedT[[]uint32, uint32], coll, tc.shouldPass)(t) + case []uint64: + testOrderingT(SortedT[[]uint64, uint64], coll, tc.shouldPass)(t) + case []float32: + testOrderingT(SortedT[[]float32, float32], coll, tc.shouldPass)(t) + case []float64: + testOrderingT(SortedT[[]float64, float64], coll, tc.shouldPass)(t) + case []string: + testOrderingT(SortedT[[]string, string], coll, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", coll) + } + }) + } +} + +func TestNotSortedT(t *testing.T) { + t.Parallel() + + for tc := range sortedTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Invert shouldPass for NotSorted + shouldPass := !tc.shouldPass + + // Dispatch based on type + switch coll := tc.collection.(type) { + case []int: + testOrderingT(NotSortedT[[]int, int], coll, shouldPass)(t) + case []int8: + testOrderingT(NotSortedT[[]int8, int8], coll, shouldPass)(t) + case []int16: + testOrderingT(NotSortedT[[]int16, int16], coll, shouldPass)(t) + case []int32: + testOrderingT(NotSortedT[[]int32, int32], coll, shouldPass)(t) + case []int64: + testOrderingT(NotSortedT[[]int64, int64], coll, shouldPass)(t) + case []uint: + testOrderingT(NotSortedT[[]uint, uint], coll, shouldPass)(t) + case []uint8: + testOrderingT(NotSortedT[[]uint8, uint8], coll, shouldPass)(t) + case []uint16: + testOrderingT(NotSortedT[[]uint16, uint16], coll, shouldPass)(t) + case []uint32: + testOrderingT(NotSortedT[[]uint32, uint32], coll, shouldPass)(t) + case []uint64: + testOrderingT(NotSortedT[[]uint64, uint64], coll, shouldPass)(t) + case []float32: + testOrderingT(NotSortedT[[]float32, float32], coll, shouldPass)(t) + case []float64: + testOrderingT(NotSortedT[[]float64, float64], coll, shouldPass)(t) + case []string: + testOrderingT(NotSortedT[[]string, string], coll, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", coll) + } + }) + } +} + +//nolint:thelper // linter false positive: these are not helpers +func testOrderingT[OrderedSlice ~[]E, E Ordered]( + fn func(T, OrderedSlice, ...any) bool, + collection OrderedSlice, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, collection) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} From 9bca8b778034fb7d873e45e20fafef048d9fbaae Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Mon, 19 Jan 2026 23:01:13 +0100 Subject: [PATCH 04/10] chore: regenerated API and doc Signed-off-by: Frederic BIDON --- assert/assert_assertions.go | 465 +++++++++++++++++++++--- assert/assert_assertions_test.go | 520 ++++++++++++++++++++++++--- assert/assert_examples_test.go | 178 +++++++++- assert/assert_format.go | 222 ++++++++++-- assert/assert_format_test.go | 520 ++++++++++++++++++++++++--- assert/assert_forward.go | 82 ++--- assert/assert_forward_test.go | 222 ++++++------ assert/assert_helpers.go | 2 +- assert/assert_helpers_test.go | 2 +- assert/assert_types.go | 4 +- codegen/internal/model/model.go | 2 +- docs/doc-site/api/_index.md | 10 +- docs/doc-site/api/boolean.md | 4 +- docs/doc-site/api/collection.md | 396 ++++++++++++++++++++- docs/doc-site/api/common.md | 4 +- docs/doc-site/api/comparison.md | 4 +- docs/doc-site/api/condition.md | 4 +- docs/doc-site/api/equality.md | 224 +++++++++++- docs/doc-site/api/error.md | 4 +- docs/doc-site/api/file.md | 138 +++---- docs/doc-site/api/http.md | 4 +- docs/doc-site/api/json.md | 4 +- docs/doc-site/api/number.md | 4 +- docs/doc-site/api/ordering.md | 315 +++++++++++++++- docs/doc-site/api/panic.md | 4 +- docs/doc-site/api/string.md | 4 +- docs/doc-site/api/testing.md | 4 +- docs/doc-site/api/time.md | 4 +- docs/doc-site/api/type.md | 4 +- docs/doc-site/api/yaml.md | 4 +- internal/assertions/order.go | 4 +- require/require_assertions.go | 553 ++++++++++++++++++++++++++--- require/require_assertions_test.go | 420 ++++++++++++++++++++-- require/require_examples_test.go | 178 +++++++++- require/require_format.go | 296 +++++++++++++-- require/require_format_test.go | 420 ++++++++++++++++++++-- require/require_forward.go | 114 +++--- require/require_forward_test.go | 202 +++++------ require/require_helpers.go | 2 +- require/require_helpers_test.go | 2 +- require/require_types.go | 4 +- 41 files changed, 4770 insertions(+), 783 deletions(-) diff --git a/assert/assert_assertions.go b/assert/assert_assertions.go index 1b8325f24..536b50104 100644 --- a/assert/assert_assertions.go +++ b/assert/assert_assertions.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -76,6 +76,26 @@ func DirExists(t T, path string, msgAndArgs ...any) bool { return assertions.DirExists(t, path, msgAndArgs...) } +// DirNotExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +// +// # Usage +// +// assertions.DirNotExists(t, "path/to/directory") +// +// # Examples +// +// success: filepath.Join(testDataPath(),"non_existing_dir") +// failure: filepath.Join(testDataPath(),"existing_dir") +// +// Upon failure, the test [T] is marked as failed and continues execution. +func DirNotExists(t T, path string, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.DirNotExists(t, path, msgAndArgs...) +} + // ElementsMatch asserts that the specified listA(array, slice...) is equal to specified // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. @@ -218,6 +238,34 @@ func EqualExportedValues(t T, expected any, actual any, msgAndArgs ...any) bool return assertions.EqualExportedValues(t, expected, actual, msgAndArgs...) } +// EqualT asserts that two objects of the same comparable type are equal. +// +// Pointer variable equality is determined based on the equality of the memory addresses (unlike [Equal], but like [Same]). +// +// Functions, slices and maps are not comparable. See also [ComparisonOperators]. +// +// If you need to compare values of non-comparable types, or compare pointers by the value they point to, +// use [Equal] instead. +// +// # Usage +// +// assertions.EqualT(t, 123, 123) +// +// # Examples +// +// success: 123, 123 +// failure: 123, 456 +// +// Upon failure, the test [T] is marked as failed and continues execution. +// +// [ComparisonOperators]: https://go.dev/ref/spec#Comparison_operators. +func EqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.EqualT(t, expected, actual, msgAndArgs...) +} + // EqualValues asserts that two objects are equal or convertible to the larger // type and equal. // @@ -558,6 +606,26 @@ func FileNotEmpty(t T, path string, msgAndArgs ...any) bool { return assertions.FileNotEmpty(t, path, msgAndArgs...) } +// FileNotExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +// +// # Usage +// +// assertions.FileNotExists(t, "path/to/file") +// +// # Examples +// +// success: filepath.Join(testDataPath(),"non_existing_file") +// failure: filepath.Join(testDataPath(),"existing_file") +// +// Upon failure, the test [T] is marked as failed and continues execution. +func FileNotExists(t T, path string, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.FileNotExists(t, path, msgAndArgs...) +} + // Greater asserts that the first element is strictly greater than the second. // // Both elements must be of the same type in the [reflect.Kind] sense. @@ -1014,7 +1082,7 @@ func InEpsilonT[Number Measurable](t T, expected Number, actual Number, epsilon return assertions.InEpsilonT(t, expected, actual, epsilon, msgAndArgs...) } -// IsDecreasing asserts that the collection is decreasing. +// IsDecreasing asserts that the collection is strictly decreasing. // // # Usage // @@ -1035,7 +1103,28 @@ func IsDecreasing(t T, object any, msgAndArgs ...any) bool { return assertions.IsDecreasing(t, object, msgAndArgs...) } -// IsIncreasing asserts that the collection is increasing. +// IsDecreasingT asserts that a slice of [Ordered] is strictly decreasing. +// +// # Usage +// +// assertions.IsDecreasingT(t, []int{2, 1, 0}) +// assertions.IsDecreasingT(t, []float{2, 1}) +// assertions.IsDecreasingT(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{3, 2, 1} +// failure: []int{1, 2, 3} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsDecreasingT(t, collection, msgAndArgs...) +} + +// IsIncreasing asserts that the collection is strictly increasing. // // # Usage // @@ -1056,7 +1145,28 @@ func IsIncreasing(t T, object any, msgAndArgs ...any) bool { return assertions.IsIncreasing(t, object, msgAndArgs...) } -// IsNonDecreasing asserts that the collection is not decreasing. +// IsIncreasingT asserts that a slice of [Ordered] is strictly increasing. +// +// # Usage +// +// assertions.IsIncreasingT(t, []int{1, 2, 3}) +// assertions.IsIncreasingT(t, []float{1, 2}) +// assertions.IsIncreasingT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 2, 3} +// failure: []int{1, 1, 2} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsIncreasingT(t, collection, msgAndArgs...) +} + +// IsNonDecreasing asserts that the collection is not strictly decreasing. // // # Usage // @@ -1067,7 +1177,7 @@ func IsIncreasing(t T, object any, msgAndArgs ...any) bool { // # Examples // // success: []int{1, 1, 2} -// failure: []int{2, 1, 1} +// failure: []int{2, 1, 0} // // Upon failure, the test [T] is marked as failed and continues execution. func IsNonDecreasing(t T, object any, msgAndArgs ...any) bool { @@ -1077,6 +1187,27 @@ func IsNonDecreasing(t T, object any, msgAndArgs ...any) bool { return assertions.IsNonDecreasing(t, object, msgAndArgs...) } +// IsNonDecreasingT asserts that a slice of [Ordered] is not decreasing. +// +// # Usage +// +// assertions.IsNonDecreasingT(t, []int{1, 1, 2}) +// assertions.IsNonDecreasingT(t, []float{1, 2}) +// assertions.IsNonDecreasingT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 1, 2} +// failure: []int{2, 1, 0} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsNonDecreasingT(t, collection, msgAndArgs...) +} + // IsNonIncreasing asserts that the collection is not increasing. // // # Usage @@ -1098,6 +1229,27 @@ func IsNonIncreasing(t T, object any, msgAndArgs ...any) bool { return assertions.IsNonIncreasing(t, object, msgAndArgs...) } +// IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. +// +// # Usage +// +// assertions.IsNonIncreasing(t, []int{2, 1, 1}) +// assertions.IsNonIncreasing(t, []float{2, 1}) +// assertions.IsNonIncreasing(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{2, 1, 1} +// failure: []int{1, 2, 3} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsNonIncreasingT(t, collection, msgAndArgs...) +} + // IsNotType asserts that the specified objects are not of the same type. // // # Usage @@ -1359,6 +1511,44 @@ func LessT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs ...any return assertions.LessT(t, e1, e2, msgAndArgs...) } +// MapContainsT asserts that the specified map contains a key. +// +// # Usage +// +// assertions.MapContainsT(t, map[string]string{"Hello": "x","World": "y"}, "World") +// +// # Examples +// +// success: map[string]string{"A": "B"}, "A" +// failure: map[string]string{"A": "B"}, "C" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.MapContainsT(t, m, key, msgAndArgs...) +} + +// MapNotContainsT asserts that the specified map does not contain a key. +// +// # Usage +// +// assertions.MapNotContainsT(t, map[string]string{"Hello": "x","World": "y"}, "hi") +// +// # Examples +// +// success: map[string]string{"A": "B"}, "C" +// failure: map[string]string{"A": "B"}, "A" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.MapNotContainsT(t, m, key, msgAndArgs...) +} + // Negative asserts that the specified element is strictly negative. // // # Usage @@ -1450,26 +1640,6 @@ func Nil(t T, object any, msgAndArgs ...any) bool { return assertions.Nil(t, object, msgAndArgs...) } -// NoDirExists checks whether a directory does not exist in the given path. -// It fails if the path points to an existing _directory_ only. -// -// # Usage -// -// assertions.NoDirExists(t, "path/to/directory") -// -// # Examples -// -// success: filepath.Join(testDataPath(),"non_existing_dir") -// failure: filepath.Join(testDataPath(),"existing_dir") -// -// Upon failure, the test [T] is marked as failed and continues execution. -func NoDirExists(t T, path string, msgAndArgs ...any) bool { - if h, ok := t.(H); ok { - h.Helper() - } - return assertions.NoDirExists(t, path, msgAndArgs...) -} - // NoError asserts that a function returned a nil error (ie. no error). // // # Usage @@ -1492,26 +1662,6 @@ func NoError(t T, err error, msgAndArgs ...any) bool { return assertions.NoError(t, err, msgAndArgs...) } -// NoFileExists checks whether a file does not exist in a given path. It fails -// if the path points to an existing _file_ only. -// -// # Usage -// -// assertions.NoFileExists(t, "path/to/file") -// -// # Examples -// -// success: filepath.Join(testDataPath(),"non_existing_file") -// failure: filepath.Join(testDataPath(),"existing_file") -// -// Upon failure, the test [T] is marked as failed and continues execution. -func NoFileExists(t T, path string, msgAndArgs ...any) bool { - if h, ok := t.(H); ok { - h.Helper() - } - return assertions.NoFileExists(t, path, msgAndArgs...) -} - // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // @@ -1625,6 +1775,27 @@ func NotEqual(t T, expected any, actual any, msgAndArgs ...any) bool { return assertions.NotEqual(t, expected, actual, msgAndArgs...) } +// NotEqualT asserts that the specified values of the same comparable type are NOT equal. +// +// See [EqualT]. +// +// # Usage +// +// assertions.NotEqualT(t, obj1, obj2) +// +// # Examples +// +// success: 123, 456 +// failure: 123, 123 +// +// Upon failure, the test [T] is marked as failed and continues execution. +func NotEqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.NotEqualT(t, expected, actual, msgAndArgs...) +} + // NotEqualValues asserts that two objects are not equal even when converted to the same type. // // # Usage @@ -1830,6 +2001,50 @@ func NotSame(t T, expected any, actual any, msgAndArgs ...any) bool { return assertions.NotSame(t, expected, actual, msgAndArgs...) } +// NotSameT asserts that two pointers do not reference the same object. +// +// See [SameT] +// +// # Usage +// +// assertions.NotSameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, ptr("static string") +// failure: &staticVar, staticVarPtr +// +// Upon failure, the test [T] is marked as failed and continues execution. +func NotSameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.NotSameT(t, expected, actual, msgAndArgs...) +} + +// NotSortedT asserts that the slice of [Ordered] is NOT sorted (i.e. non-strictly increasing). +// +// Unlike [IsDecreasingT], it accepts slices that are neither increasing nor decreasing. +// +// # Usage +// +// assertions.NotSortedT(t, []int{3, 2, 3}) +// assertions.NotSortedT(t, []float{2, 1}) +// assertions.NotSortedT(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{3, 1, 3} +// failure: []int{1, 4, 8} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.NotSortedT(t, collection, msgAndArgs...) +} + // NotSubset asserts that the list (array, slice, or map) does NOT contain all // elements given in the subset (array, slice, or map). // Map elements are key-value pairs unless compared with an array or slice where @@ -2039,6 +2254,166 @@ func Same(t T, expected any, actual any, msgAndArgs ...any) bool { return assertions.Same(t, expected, actual, msgAndArgs...) } +// SameT asserts that two pointers of the same type reference the same object. +// +// # Usage +// +// assertions.SameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, staticVarPtr +// failure: &staticVar, ptr("static string") +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SameT(t, expected, actual, msgAndArgs...) +} + +// SliceContainsT asserts that the specified slice contains a comparable element. +// +// # Usage +// +// assertions.SliceContainsT(t, []{"Hello","World"}, "World") +// +// # Examples +// +// success: []string{"A","B"}, "A" +// failure: []string{"A","B"}, "C" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceContainsT(t, s, element, msgAndArgs...) +} + +// SliceNotContainsT asserts that the specified slice does not contain a comparable element. +// +// # Usage +// +// assertions.SliceNotContainsT(t, []{"Hello","World"}, "hi") +// +// # Examples +// +// success: []string{"A","B"}, "C" +// failure: []string{"A","B"}, "A" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceNotContainsT(t, s, element, msgAndArgs...) +} + +// SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset. +// +// # Usage +// +// assertions.SliceNotSubsetT(t, []int{1, 2, 3}, []int{1, 4}) +// +// # Examples +// +// success: []int{1, 2, 3}, []int{4, 5} +// failure: []int{1, 2, 3}, []int{1, 2} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceNotSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool) { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceNotSubsetT(t, list, subset, msgAndArgs...) +} + +// SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset. +// +// # Usage +// +// assertions.SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) +// +// # Examples +// +// success: []int{1, 2, 3}, []int{1, 2} +// failure: []int{1, 2, 3}, []int{4, 5} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool) { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceSubsetT(t, list, subset, msgAndArgs...) +} + +// SortedT asserts that the slice of [Ordered] is sorted (i.e. non-strictly increasing). +// +// Unlike [IsIncreasingT], it accepts elements to be equal. +// +// # Usage +// +// assertions.SortedT(t, []int{1, 2, 3}) +// assertions.SortedT(t, []float{1, 2}) +// assertions.SortedT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 1, 3} +// failure: []int{1, 4, 2} +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SortedT(t, collection, msgAndArgs...) +} + +// StringContainsT asserts that a string contains the specified substring. +// +// Strings may be go strings or []byte. +// +// # Usage +// +// assertions.StringContainsT(t, "Hello World", "World") +// +// # Examples +// +// success: "AB", "A" +// failure: "AB", "C" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.StringContainsT(t, str, substring, msgAndArgs...) +} + +// StringNotContainsT asserts that a string does not contain the specified substring. +// +// Strings may be go strings or []byte. +// +// # Usage +// +// assertions.StringNotContainsT(t, "Hello World", "hi") +// +// # Examples +// +// success: "AB", "C" +// failure: "AB", "A" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.StringNotContainsT(t, str, substring, msgAndArgs...) +} + // Subset asserts that the list (array, slice, or map) contains all elements // given in the subset (array, slice, or map). // diff --git a/assert/assert_assertions_test.go b/assert/assert_assertions_test.go index c0a723785..a64110c3d 100644 --- a/assert/assert_assertions_test.go +++ b/assert/assert_assertions_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -89,6 +89,30 @@ func TestDirExists(t *testing.T) { }) } +func TestDirNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := DirNotExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + if !result { + t.Error("DirNotExists should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := DirNotExists(mock, filepath.Join(testDataPath(), "existing_dir")) + if result { + t.Error("DirNotExists should return false on failure") + } + if !mock.failed { + t.Error("DirNotExists should mark test as failed") + } + }) +} + func TestElementsMatch(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -233,6 +257,30 @@ func TestEqualExportedValues(t *testing.T) { }) } +func TestEqualT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := EqualT(t, 123, 123) + if !result { + t.Error("EqualT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := EqualT(mock, 123, 456) + if result { + t.Error("EqualT should return false on failure") + } + if !mock.failed { + t.Error("EqualT should mark test as failed") + } + }) +} + func TestEqualValues(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -577,6 +625,30 @@ func TestFileNotEmpty(t *testing.T) { }) } +func TestFileNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := FileNotExists(t, filepath.Join(testDataPath(), "non_existing_file")) + if !result { + t.Error("FileNotExists should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := FileNotExists(mock, filepath.Join(testDataPath(), "existing_file")) + if result { + t.Error("FileNotExists should return false on failure") + } + if !mock.failed { + t.Error("FileNotExists should mark test as failed") + } + }) +} + func TestGreater(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1033,6 +1105,30 @@ func TestIsDecreasing(t *testing.T) { }) } +func TestIsDecreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsDecreasingT(t, []int{3, 2, 1}) + if !result { + t.Error("IsDecreasingT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsDecreasingT(mock, []int{1, 2, 3}) + if result { + t.Error("IsDecreasingT should return false on failure") + } + if !mock.failed { + t.Error("IsDecreasingT should mark test as failed") + } + }) +} + func TestIsIncreasing(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1057,6 +1153,30 @@ func TestIsIncreasing(t *testing.T) { }) } +func TestIsIncreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsIncreasingT(t, []int{1, 2, 3}) + if !result { + t.Error("IsIncreasingT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsIncreasingT(mock, []int{1, 1, 2}) + if result { + t.Error("IsIncreasingT should return false on failure") + } + if !mock.failed { + t.Error("IsIncreasingT should mark test as failed") + } + }) +} + func TestIsNonDecreasing(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1071,7 +1191,7 @@ func TestIsNonDecreasing(t *testing.T) { t.Parallel() mock := new(mockT) - result := IsNonDecreasing(mock, []int{2, 1, 1}) + result := IsNonDecreasing(mock, []int{2, 1, 0}) if result { t.Error("IsNonDecreasing should return false on failure") } @@ -1081,6 +1201,30 @@ func TestIsNonDecreasing(t *testing.T) { }) } +func TestIsNonDecreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsNonDecreasingT(t, []int{1, 1, 2}) + if !result { + t.Error("IsNonDecreasingT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsNonDecreasingT(mock, []int{2, 1, 0}) + if result { + t.Error("IsNonDecreasingT should return false on failure") + } + if !mock.failed { + t.Error("IsNonDecreasingT should mark test as failed") + } + }) +} + func TestIsNonIncreasing(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1105,6 +1249,30 @@ func TestIsNonIncreasing(t *testing.T) { }) } +func TestIsNonIncreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsNonIncreasingT(t, []int{2, 1, 1}) + if !result { + t.Error("IsNonIncreasingT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsNonIncreasingT(mock, []int{1, 2, 3}) + if result { + t.Error("IsNonIncreasingT should return false on failure") + } + if !mock.failed { + t.Error("IsNonIncreasingT should mark test as failed") + } + }) +} + func TestIsNotType(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1369,13 +1537,13 @@ func TestLessT(t *testing.T) { }) } -func TestNegative(t *testing.T) { +func TestMapContainsT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := Negative(t, -1) + result := MapContainsT(t, map[string]string{"A": "B"}, "A") if !result { - t.Error("Negative should return true on success") + t.Error("MapContainsT should return true on success") } }) @@ -1383,23 +1551,23 @@ func TestNegative(t *testing.T) { t.Parallel() mock := new(mockT) - result := Negative(mock, 1) + result := MapContainsT(mock, map[string]string{"A": "B"}, "C") if result { - t.Error("Negative should return false on failure") + t.Error("MapContainsT should return false on failure") } if !mock.failed { - t.Error("Negative should mark test as failed") + t.Error("MapContainsT should mark test as failed") } }) } -func TestNegativeT(t *testing.T) { +func TestMapNotContainsT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NegativeT(t, -1) + result := MapNotContainsT(t, map[string]string{"A": "B"}, "C") if !result { - t.Error("NegativeT should return true on success") + t.Error("MapNotContainsT should return true on success") } }) @@ -1407,23 +1575,23 @@ func TestNegativeT(t *testing.T) { t.Parallel() mock := new(mockT) - result := NegativeT(mock, 1) + result := MapNotContainsT(mock, map[string]string{"A": "B"}, "A") if result { - t.Error("NegativeT should return false on failure") + t.Error("MapNotContainsT should return false on failure") } if !mock.failed { - t.Error("NegativeT should mark test as failed") + t.Error("MapNotContainsT should mark test as failed") } }) } -func TestNever(t *testing.T) { +func TestNegative(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := Never(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond) + result := Negative(t, -1) if !result { - t.Error("Never should return true on success") + t.Error("Negative should return true on success") } }) @@ -1431,23 +1599,23 @@ func TestNever(t *testing.T) { t.Parallel() mock := new(mockT) - result := Never(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond) + result := Negative(mock, 1) if result { - t.Error("Never should return false on failure") + t.Error("Negative should return false on failure") } if !mock.failed { - t.Error("Never should mark test as failed") + t.Error("Negative should mark test as failed") } }) } -func TestNil(t *testing.T) { +func TestNegativeT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := Nil(t, nil) + result := NegativeT(t, -1) if !result { - t.Error("Nil should return true on success") + t.Error("NegativeT should return true on success") } }) @@ -1455,23 +1623,23 @@ func TestNil(t *testing.T) { t.Parallel() mock := new(mockT) - result := Nil(mock, "not nil") + result := NegativeT(mock, 1) if result { - t.Error("Nil should return false on failure") + t.Error("NegativeT should return false on failure") } if !mock.failed { - t.Error("Nil should mark test as failed") + t.Error("NegativeT should mark test as failed") } }) } -func TestNoDirExists(t *testing.T) { +func TestNever(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NoDirExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + result := Never(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond) if !result { - t.Error("NoDirExists should return true on success") + t.Error("Never should return true on success") } }) @@ -1479,23 +1647,23 @@ func TestNoDirExists(t *testing.T) { t.Parallel() mock := new(mockT) - result := NoDirExists(mock, filepath.Join(testDataPath(), "existing_dir")) + result := Never(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond) if result { - t.Error("NoDirExists should return false on failure") + t.Error("Never should return false on failure") } if !mock.failed { - t.Error("NoDirExists should mark test as failed") + t.Error("Never should mark test as failed") } }) } -func TestNoError(t *testing.T) { +func TestNil(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NoError(t, nil) + result := Nil(t, nil) if !result { - t.Error("NoError should return true on success") + t.Error("Nil should return true on success") } }) @@ -1503,23 +1671,23 @@ func TestNoError(t *testing.T) { t.Parallel() mock := new(mockT) - result := NoError(mock, ErrTest) + result := Nil(mock, "not nil") if result { - t.Error("NoError should return false on failure") + t.Error("Nil should return false on failure") } if !mock.failed { - t.Error("NoError should mark test as failed") + t.Error("Nil should mark test as failed") } }) } -func TestNoFileExists(t *testing.T) { +func TestNoError(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NoFileExists(t, filepath.Join(testDataPath(), "non_existing_file")) + result := NoError(t, nil) if !result { - t.Error("NoFileExists should return true on success") + t.Error("NoError should return true on success") } }) @@ -1527,12 +1695,12 @@ func TestNoFileExists(t *testing.T) { t.Parallel() mock := new(mockT) - result := NoFileExists(mock, filepath.Join(testDataPath(), "existing_file")) + result := NoError(mock, ErrTest) if result { - t.Error("NoFileExists should return false on failure") + t.Error("NoError should return false on failure") } if !mock.failed { - t.Error("NoFileExists should mark test as failed") + t.Error("NoError should mark test as failed") } }) } @@ -1657,6 +1825,30 @@ func TestNotEqual(t *testing.T) { }) } +func TestNotEqualT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := NotEqualT(t, 123, 456) + if !result { + t.Error("NotEqualT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := NotEqualT(mock, 123, 123) + if result { + t.Error("NotEqualT should return false on failure") + } + if !mock.failed { + t.Error("NotEqualT should mark test as failed") + } + }) +} + func TestNotEqualValues(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1897,6 +2089,54 @@ func TestNotSame(t *testing.T) { }) } +func TestNotSameT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := NotSameT(t, &staticVar, ptr("static string")) + if !result { + t.Error("NotSameT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := NotSameT(mock, &staticVar, staticVarPtr) + if result { + t.Error("NotSameT should return false on failure") + } + if !mock.failed { + t.Error("NotSameT should mark test as failed") + } + }) +} + +func TestNotSortedT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := NotSortedT(t, []int{3, 1, 3}) + if !result { + t.Error("NotSortedT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := NotSortedT(mock, []int{1, 4, 8}) + if result { + t.Error("NotSortedT should return false on failure") + } + if !mock.failed { + t.Error("NotSortedT should mark test as failed") + } + }) +} + func TestNotSubset(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2137,6 +2377,198 @@ func TestSame(t *testing.T) { }) } +func TestSameT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SameT(t, &staticVar, staticVarPtr) + if !result { + t.Error("SameT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SameT(mock, &staticVar, ptr("static string")) + if result { + t.Error("SameT should return false on failure") + } + if !mock.failed { + t.Error("SameT should mark test as failed") + } + }) +} + +func TestSliceContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceContainsT(t, []string{"A", "B"}, "A") + if !result { + t.Error("SliceContainsT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceContainsT(mock, []string{"A", "B"}, "C") + if result { + t.Error("SliceContainsT should return false on failure") + } + if !mock.failed { + t.Error("SliceContainsT should mark test as failed") + } + }) +} + +func TestSliceNotContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceNotContainsT(t, []string{"A", "B"}, "C") + if !result { + t.Error("SliceNotContainsT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceNotContainsT(mock, []string{"A", "B"}, "A") + if result { + t.Error("SliceNotContainsT should return false on failure") + } + if !mock.failed { + t.Error("SliceNotContainsT should mark test as failed") + } + }) +} + +func TestSliceNotSubsetT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceNotSubsetT(t, []int{1, 2, 3}, []int{4, 5}) + if !result { + t.Error("SliceNotSubsetT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceNotSubsetT(mock, []int{1, 2, 3}, []int{1, 2}) + if result { + t.Error("SliceNotSubsetT should return false on failure") + } + if !mock.failed { + t.Error("SliceNotSubsetT should mark test as failed") + } + }) +} + +func TestSliceSubsetT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) + if !result { + t.Error("SliceSubsetT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceSubsetT(mock, []int{1, 2, 3}, []int{4, 5}) + if result { + t.Error("SliceSubsetT should return false on failure") + } + if !mock.failed { + t.Error("SliceSubsetT should mark test as failed") + } + }) +} + +func TestSortedT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SortedT(t, []int{1, 1, 3}) + if !result { + t.Error("SortedT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SortedT(mock, []int{1, 4, 2}) + if result { + t.Error("SortedT should return false on failure") + } + if !mock.failed { + t.Error("SortedT should mark test as failed") + } + }) +} + +func TestStringContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := StringContainsT(t, "AB", "A") + if !result { + t.Error("StringContainsT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := StringContainsT(mock, "AB", "C") + if result { + t.Error("StringContainsT should return false on failure") + } + if !mock.failed { + t.Error("StringContainsT should mark test as failed") + } + }) +} + +func TestStringNotContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := StringNotContainsT(t, "AB", "C") + if !result { + t.Error("StringNotContainsT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := StringNotContainsT(mock, "AB", "A") + if result { + t.Error("StringNotContainsT should return false on failure") + } + if !mock.failed { + t.Error("StringNotContainsT should mark test as failed") + } + }) +} + func TestSubset(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/assert/assert_examples_test.go b/assert/assert_examples_test.go index 203648f5b..db1ff083f 100644 --- a/assert/assert_examples_test.go +++ b/assert/assert_examples_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert_test @@ -45,6 +45,14 @@ func ExampleDirExists() { // Output: success: true } +func ExampleDirNotExists() { + t := new(testing.T) + success := assert.DirNotExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleElementsMatch() { t := new(testing.T) success := assert.ElementsMatch(t, []int{1, 3, 2, 3}, []int{1, 3, 3, 2}) @@ -93,6 +101,14 @@ func ExampleEqualExportedValues() { // Output: success: true } +func ExampleEqualT() { + t := new(testing.T) + success := assert.EqualT(t, 123, 123) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleEqualValues() { t := new(testing.T) success := assert.EqualValues(t, uint32(123), int32(123)) @@ -209,6 +225,14 @@ func ExampleFileNotEmpty() { // Output: success: true } +func ExampleFileNotExists() { + t := new(testing.T) + success := assert.FileNotExists(t, filepath.Join(testDataPath(), "non_existing_file")) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleGreater() { t := new(testing.T) success := assert.Greater(t, 2, 1) @@ -361,6 +385,14 @@ func ExampleIsDecreasing() { // Output: success: true } +func ExampleIsDecreasingT() { + t := new(testing.T) + success := assert.IsDecreasingT(t, []int{3, 2, 1}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleIsIncreasing() { t := new(testing.T) success := assert.IsIncreasing(t, []int{1, 2, 3}) @@ -369,6 +401,14 @@ func ExampleIsIncreasing() { // Output: success: true } +func ExampleIsIncreasingT() { + t := new(testing.T) + success := assert.IsIncreasingT(t, []int{1, 2, 3}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleIsNonDecreasing() { t := new(testing.T) success := assert.IsNonDecreasing(t, []int{1, 1, 2}) @@ -377,6 +417,14 @@ func ExampleIsNonDecreasing() { // Output: success: true } +func ExampleIsNonDecreasingT() { + t := new(testing.T) + success := assert.IsNonDecreasingT(t, []int{1, 1, 2}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleIsNonIncreasing() { t := new(testing.T) success := assert.IsNonIncreasing(t, []int{2, 1, 1}) @@ -385,6 +433,14 @@ func ExampleIsNonIncreasing() { // Output: success: true } +func ExampleIsNonIncreasingT() { + t := new(testing.T) + success := assert.IsNonIncreasingT(t, []int{2, 1, 1}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleIsNotType() { t := new(testing.T) success := assert.IsNotType(t, int32(123), int64(456)) @@ -473,59 +529,59 @@ func ExampleLessT() { // Output: success: true } -func ExampleNegative() { +func ExampleMapContainsT() { t := new(testing.T) - success := assert.Negative(t, -1) + success := assert.MapContainsT(t, map[string]string{"A": "B"}, "A") fmt.Printf("success: %t\n", success) // Output: success: true } -func ExampleNegativeT() { +func ExampleMapNotContainsT() { t := new(testing.T) - success := assert.NegativeT(t, -1) + success := assert.MapNotContainsT(t, map[string]string{"A": "B"}, "C") fmt.Printf("success: %t\n", success) // Output: success: true } -func ExampleNever() { +func ExampleNegative() { t := new(testing.T) - success := assert.Never(t, func() bool { - return false - }, 100*time.Millisecond, 20*time.Millisecond) + success := assert.Negative(t, -1) fmt.Printf("success: %t\n", success) // Output: success: true } -func ExampleNil() { +func ExampleNegativeT() { t := new(testing.T) - success := assert.Nil(t, nil) + success := assert.NegativeT(t, -1) fmt.Printf("success: %t\n", success) // Output: success: true } -func ExampleNoDirExists() { +func ExampleNever() { t := new(testing.T) - success := assert.NoDirExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + success := assert.Never(t, func() bool { + return false + }, 100*time.Millisecond, 20*time.Millisecond) fmt.Printf("success: %t\n", success) // Output: success: true } -func ExampleNoError() { +func ExampleNil() { t := new(testing.T) - success := assert.NoError(t, nil) + success := assert.Nil(t, nil) fmt.Printf("success: %t\n", success) // Output: success: true } -func ExampleNoFileExists() { +func ExampleNoError() { t := new(testing.T) - success := assert.NoFileExists(t, filepath.Join(testDataPath(), "non_existing_file")) + success := assert.NoError(t, nil) fmt.Printf("success: %t\n", success) // Output: success: true @@ -571,6 +627,14 @@ func ExampleNotEqual() { // Output: success: true } +func ExampleNotEqualT() { + t := new(testing.T) + success := assert.NotEqualT(t, 123, 456) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleNotEqualValues() { t := new(testing.T) success := assert.NotEqualValues(t, uint32(123), int32(456)) @@ -652,6 +716,22 @@ func ExampleNotSame() { // Output: success: true } +func ExampleNotSameT() { + t := new(testing.T) + success := assert.NotSameT(t, &staticVar, ptr("static string")) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleNotSortedT() { + t := new(testing.T) + success := assert.NotSortedT(t, []int{3, 1, 3}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleNotSubset() { t := new(testing.T) success := assert.NotSubset(t, []int{1, 2, 3}, []int{4, 5}) @@ -738,6 +818,70 @@ func ExampleSame() { // Output: success: true } +func ExampleSameT() { + t := new(testing.T) + success := assert.SameT(t, &staticVar, staticVarPtr) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleSliceContainsT() { + t := new(testing.T) + success := assert.SliceContainsT(t, []string{"A", "B"}, "A") + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleSliceNotContainsT() { + t := new(testing.T) + success := assert.SliceNotContainsT(t, []string{"A", "B"}, "C") + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleSliceNotSubsetT() { + t := new(testing.T) + success := assert.SliceNotSubsetT(t, []int{1, 2, 3}, []int{4, 5}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleSliceSubsetT() { + t := new(testing.T) + success := assert.SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleSortedT() { + t := new(testing.T) + success := assert.SortedT(t, []int{1, 1, 3}) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleStringContainsT() { + t := new(testing.T) + success := assert.StringContainsT(t, "AB", "A") + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleStringNotContainsT() { + t := new(testing.T) + success := assert.StringNotContainsT(t, "AB", "C") + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleSubset() { t := new(testing.T) success := assert.Subset(t, []int{1, 2, 3}, []int{1, 2}) diff --git a/assert/assert_format.go b/assert/assert_format.go index d5ca7852b..8735e53c3 100644 --- a/assert/assert_format.go +++ b/assert/assert_format.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -45,6 +45,16 @@ func DirExistsf(t T, path string, msg string, args ...any) bool { return assertions.DirExists(t, path, forwardArgs(msg, args)) } +// DirNotExistsf is the same as [DirNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func DirNotExistsf(t T, path string, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.DirNotExists(t, path, forwardArgs(msg, args)) +} + // ElementsMatchf is the same as [ElementsMatch], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -105,6 +115,16 @@ func EqualExportedValuesf(t T, expected any, actual any, msg string, args ...any return assertions.EqualExportedValues(t, expected, actual, forwardArgs(msg, args)) } +// EqualTf is the same as [EqualT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func EqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.EqualT(t, expected, actual, forwardArgs(msg, args)) +} + // EqualValuesf is the same as [EqualValues], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -255,6 +275,16 @@ func FileNotEmptyf(t T, path string, msg string, args ...any) bool { return assertions.FileNotEmpty(t, path, forwardArgs(msg, args)) } +// FileNotExistsf is the same as [FileNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func FileNotExistsf(t T, path string, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.FileNotExists(t, path, forwardArgs(msg, args)) +} + // Greaterf is the same as [Greater], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -445,6 +475,16 @@ func IsDecreasingf(t T, object any, msg string, args ...any) bool { return assertions.IsDecreasing(t, object, forwardArgs(msg, args)) } +// IsDecreasingTf is the same as [IsDecreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsDecreasingT(t, collection, forwardArgs(msg, args)) +} + // IsIncreasingf is the same as [IsIncreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -455,6 +495,16 @@ func IsIncreasingf(t T, object any, msg string, args ...any) bool { return assertions.IsIncreasing(t, object, forwardArgs(msg, args)) } +// IsIncreasingTf is the same as [IsIncreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsIncreasingT(t, collection, forwardArgs(msg, args)) +} + // IsNonDecreasingf is the same as [IsNonDecreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -465,6 +515,16 @@ func IsNonDecreasingf(t T, object any, msg string, args ...any) bool { return assertions.IsNonDecreasing(t, object, forwardArgs(msg, args)) } +// IsNonDecreasingTf is the same as [IsNonDecreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsNonDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsNonDecreasingT(t, collection, forwardArgs(msg, args)) +} + // IsNonIncreasingf is the same as [IsNonIncreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -475,6 +535,16 @@ func IsNonIncreasingf(t T, object any, msg string, args ...any) bool { return assertions.IsNonIncreasing(t, object, forwardArgs(msg, args)) } +// IsNonIncreasingTf is the same as [IsNonIncreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsNonIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsNonIncreasingT(t, collection, forwardArgs(msg, args)) +} + // IsNotTypef is the same as [IsNotType], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -585,6 +655,26 @@ func LessTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg string, args return assertions.LessT(t, e1, e2, forwardArgs(msg, args)) } +// MapContainsTf is the same as [MapContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func MapContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.MapContainsT(t, m, key, forwardArgs(msg, args)) +} + +// MapNotContainsTf is the same as [MapNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func MapNotContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.MapNotContainsT(t, m, key, forwardArgs(msg, args)) +} + // Negativef is the same as [Negative], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -625,16 +715,6 @@ func Nilf(t T, object any, msg string, args ...any) bool { return assertions.Nil(t, object, forwardArgs(msg, args)) } -// NoDirExistsf is the same as [NoDirExists], but it accepts a format msg string to format arguments like [fmt.Printf]. -// -// Upon failure, the test [T] is marked as failed and continues execution. -func NoDirExistsf(t T, path string, msg string, args ...any) bool { - if h, ok := t.(H); ok { - h.Helper() - } - return assertions.NoDirExists(t, path, forwardArgs(msg, args)) -} - // NoErrorf is the same as [NoError], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -645,16 +725,6 @@ func NoErrorf(t T, err error, msg string, args ...any) bool { return assertions.NoError(t, err, forwardArgs(msg, args)) } -// NoFileExistsf is the same as [NoFileExists], but it accepts a format msg string to format arguments like [fmt.Printf]. -// -// Upon failure, the test [T] is marked as failed and continues execution. -func NoFileExistsf(t T, path string, msg string, args ...any) bool { - if h, ok := t.(H); ok { - h.Helper() - } - return assertions.NoFileExists(t, path, forwardArgs(msg, args)) -} - // NotContainsf is the same as [NotContains], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -705,6 +775,16 @@ func NotEqualf(t T, expected any, actual any, msg string, args ...any) bool { return assertions.NotEqual(t, expected, actual, forwardArgs(msg, args)) } +// NotEqualTf is the same as [NotEqualT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func NotEqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.NotEqualT(t, expected, actual, forwardArgs(msg, args)) +} + // NotEqualValuesf is the same as [NotEqualValues], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -805,6 +885,26 @@ func NotSamef(t T, expected any, actual any, msg string, args ...any) bool { return assertions.NotSame(t, expected, actual, forwardArgs(msg, args)) } +// NotSameTf is the same as [NotSameT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func NotSameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.NotSameT(t, expected, actual, forwardArgs(msg, args)) +} + +// NotSortedTf is the same as [NotSortedT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func NotSortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.NotSortedT(t, collection, forwardArgs(msg, args)) +} + // NotSubsetf is the same as [NotSubset], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -905,6 +1005,86 @@ func Samef(t T, expected any, actual any, msg string, args ...any) bool { return assertions.Same(t, expected, actual, forwardArgs(msg, args)) } +// SameTf is the same as [SameT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SameT(t, expected, actual, forwardArgs(msg, args)) +} + +// SliceContainsTf is the same as [SliceContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceContainsT(t, s, element, forwardArgs(msg, args)) +} + +// SliceNotContainsTf is the same as [SliceNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceNotContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceNotContainsT(t, s, element, forwardArgs(msg, args)) +} + +// SliceNotSubsetTf is the same as [SliceNotSubsetT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceNotSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) (ok bool) { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceNotSubsetT(t, list, subset, forwardArgs(msg, args)) +} + +// SliceSubsetTf is the same as [SliceSubsetT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SliceSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) (ok bool) { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SliceSubsetT(t, list, subset, forwardArgs(msg, args)) +} + +// SortedTf is the same as [SortedT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SortedT(t, collection, forwardArgs(msg, args)) +} + +// StringContainsTf is the same as [StringContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func StringContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.StringContainsT(t, str, substring, forwardArgs(msg, args)) +} + +// StringNotContainsTf is the same as [StringNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func StringNotContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.StringNotContainsT(t, str, substring, forwardArgs(msg, args)) +} + // Subsetf is the same as [Subset], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. diff --git a/assert/assert_format_test.go b/assert/assert_format_test.go index c9fdfcb89..8799db103 100644 --- a/assert/assert_format_test.go +++ b/assert/assert_format_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -89,6 +89,30 @@ func TestDirExistsf(t *testing.T) { }) } +func TestDirNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := DirNotExistsf(t, filepath.Join(testDataPath(), "non_existing_dir"), "test message") + if !result { + t.Error("DirNotExistsf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := DirNotExistsf(mock, filepath.Join(testDataPath(), "existing_dir"), "test message") + if result { + t.Error("DirNotExistsf should return false on failure") + } + if !mock.failed { + t.Error("DirNotExists should mark test as failed") + } + }) +} + func TestElementsMatchf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -233,6 +257,30 @@ func TestEqualExportedValuesf(t *testing.T) { }) } +func TestEqualTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := EqualTf(t, 123, 123, "test message") + if !result { + t.Error("EqualTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := EqualTf(mock, 123, 456, "test message") + if result { + t.Error("EqualTf should return false on failure") + } + if !mock.failed { + t.Error("EqualT should mark test as failed") + } + }) +} + func TestEqualValuesf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -577,6 +625,30 @@ func TestFileNotEmptyf(t *testing.T) { }) } +func TestFileNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := FileNotExistsf(t, filepath.Join(testDataPath(), "non_existing_file"), "test message") + if !result { + t.Error("FileNotExistsf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := FileNotExistsf(mock, filepath.Join(testDataPath(), "existing_file"), "test message") + if result { + t.Error("FileNotExistsf should return false on failure") + } + if !mock.failed { + t.Error("FileNotExists should mark test as failed") + } + }) +} + func TestGreaterf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1033,6 +1105,30 @@ func TestIsDecreasingf(t *testing.T) { }) } +func TestIsDecreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsDecreasingTf(t, []int{3, 2, 1}, "test message") + if !result { + t.Error("IsDecreasingTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsDecreasingTf(mock, []int{1, 2, 3}, "test message") + if result { + t.Error("IsDecreasingTf should return false on failure") + } + if !mock.failed { + t.Error("IsDecreasingT should mark test as failed") + } + }) +} + func TestIsIncreasingf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1057,6 +1153,30 @@ func TestIsIncreasingf(t *testing.T) { }) } +func TestIsIncreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsIncreasingTf(t, []int{1, 2, 3}, "test message") + if !result { + t.Error("IsIncreasingTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsIncreasingTf(mock, []int{1, 1, 2}, "test message") + if result { + t.Error("IsIncreasingTf should return false on failure") + } + if !mock.failed { + t.Error("IsIncreasingT should mark test as failed") + } + }) +} + func TestIsNonDecreasingf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1071,7 +1191,7 @@ func TestIsNonDecreasingf(t *testing.T) { t.Parallel() mock := new(mockT) - result := IsNonDecreasingf(mock, []int{2, 1, 1}, "test message") + result := IsNonDecreasingf(mock, []int{2, 1, 0}, "test message") if result { t.Error("IsNonDecreasingf should return false on failure") } @@ -1081,6 +1201,30 @@ func TestIsNonDecreasingf(t *testing.T) { }) } +func TestIsNonDecreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsNonDecreasingTf(t, []int{1, 1, 2}, "test message") + if !result { + t.Error("IsNonDecreasingTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsNonDecreasingTf(mock, []int{2, 1, 0}, "test message") + if result { + t.Error("IsNonDecreasingTf should return false on failure") + } + if !mock.failed { + t.Error("IsNonDecreasingT should mark test as failed") + } + }) +} + func TestIsNonIncreasingf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1105,6 +1249,30 @@ func TestIsNonIncreasingf(t *testing.T) { }) } +func TestIsNonIncreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsNonIncreasingTf(t, []int{2, 1, 1}, "test message") + if !result { + t.Error("IsNonIncreasingTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsNonIncreasingTf(mock, []int{1, 2, 3}, "test message") + if result { + t.Error("IsNonIncreasingTf should return false on failure") + } + if !mock.failed { + t.Error("IsNonIncreasingT should mark test as failed") + } + }) +} + func TestIsNotTypef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1369,13 +1537,13 @@ func TestLessTf(t *testing.T) { }) } -func TestNegativef(t *testing.T) { +func TestMapContainsTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := Negativef(t, -1, "test message") + result := MapContainsTf(t, map[string]string{"A": "B"}, "A", "test message") if !result { - t.Error("Negativef should return true on success") + t.Error("MapContainsTf should return true on success") } }) @@ -1383,23 +1551,23 @@ func TestNegativef(t *testing.T) { t.Parallel() mock := new(mockT) - result := Negativef(mock, 1, "test message") + result := MapContainsTf(mock, map[string]string{"A": "B"}, "C", "test message") if result { - t.Error("Negativef should return false on failure") + t.Error("MapContainsTf should return false on failure") } if !mock.failed { - t.Error("Negative should mark test as failed") + t.Error("MapContainsT should mark test as failed") } }) } -func TestNegativeTf(t *testing.T) { +func TestMapNotContainsTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NegativeTf(t, -1, "test message") + result := MapNotContainsTf(t, map[string]string{"A": "B"}, "C", "test message") if !result { - t.Error("NegativeTf should return true on success") + t.Error("MapNotContainsTf should return true on success") } }) @@ -1407,23 +1575,23 @@ func TestNegativeTf(t *testing.T) { t.Parallel() mock := new(mockT) - result := NegativeTf(mock, 1, "test message") + result := MapNotContainsTf(mock, map[string]string{"A": "B"}, "A", "test message") if result { - t.Error("NegativeTf should return false on failure") + t.Error("MapNotContainsTf should return false on failure") } if !mock.failed { - t.Error("NegativeT should mark test as failed") + t.Error("MapNotContainsT should mark test as failed") } }) } -func TestNeverf(t *testing.T) { +func TestNegativef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := Neverf(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond, "test message") + result := Negativef(t, -1, "test message") if !result { - t.Error("Neverf should return true on success") + t.Error("Negativef should return true on success") } }) @@ -1431,23 +1599,23 @@ func TestNeverf(t *testing.T) { t.Parallel() mock := new(mockT) - result := Neverf(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond, "test message") + result := Negativef(mock, 1, "test message") if result { - t.Error("Neverf should return false on failure") + t.Error("Negativef should return false on failure") } if !mock.failed { - t.Error("Never should mark test as failed") + t.Error("Negative should mark test as failed") } }) } -func TestNilf(t *testing.T) { +func TestNegativeTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := Nilf(t, nil, "test message") + result := NegativeTf(t, -1, "test message") if !result { - t.Error("Nilf should return true on success") + t.Error("NegativeTf should return true on success") } }) @@ -1455,23 +1623,23 @@ func TestNilf(t *testing.T) { t.Parallel() mock := new(mockT) - result := Nilf(mock, "not nil", "test message") + result := NegativeTf(mock, 1, "test message") if result { - t.Error("Nilf should return false on failure") + t.Error("NegativeTf should return false on failure") } if !mock.failed { - t.Error("Nil should mark test as failed") + t.Error("NegativeT should mark test as failed") } }) } -func TestNoDirExistsf(t *testing.T) { +func TestNeverf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NoDirExistsf(t, filepath.Join(testDataPath(), "non_existing_dir"), "test message") + result := Neverf(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond, "test message") if !result { - t.Error("NoDirExistsf should return true on success") + t.Error("Neverf should return true on success") } }) @@ -1479,23 +1647,23 @@ func TestNoDirExistsf(t *testing.T) { t.Parallel() mock := new(mockT) - result := NoDirExistsf(mock, filepath.Join(testDataPath(), "existing_dir"), "test message") + result := Neverf(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond, "test message") if result { - t.Error("NoDirExistsf should return false on failure") + t.Error("Neverf should return false on failure") } if !mock.failed { - t.Error("NoDirExists should mark test as failed") + t.Error("Never should mark test as failed") } }) } -func TestNoErrorf(t *testing.T) { +func TestNilf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NoErrorf(t, nil, "test message") + result := Nilf(t, nil, "test message") if !result { - t.Error("NoErrorf should return true on success") + t.Error("Nilf should return true on success") } }) @@ -1503,23 +1671,23 @@ func TestNoErrorf(t *testing.T) { t.Parallel() mock := new(mockT) - result := NoErrorf(mock, ErrTest, "test message") + result := Nilf(mock, "not nil", "test message") if result { - t.Error("NoErrorf should return false on failure") + t.Error("Nilf should return false on failure") } if !mock.failed { - t.Error("NoError should mark test as failed") + t.Error("Nil should mark test as failed") } }) } -func TestNoFileExistsf(t *testing.T) { +func TestNoErrorf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - result := NoFileExistsf(t, filepath.Join(testDataPath(), "non_existing_file"), "test message") + result := NoErrorf(t, nil, "test message") if !result { - t.Error("NoFileExistsf should return true on success") + t.Error("NoErrorf should return true on success") } }) @@ -1527,12 +1695,12 @@ func TestNoFileExistsf(t *testing.T) { t.Parallel() mock := new(mockT) - result := NoFileExistsf(mock, filepath.Join(testDataPath(), "existing_file"), "test message") + result := NoErrorf(mock, ErrTest, "test message") if result { - t.Error("NoFileExistsf should return false on failure") + t.Error("NoErrorf should return false on failure") } if !mock.failed { - t.Error("NoFileExists should mark test as failed") + t.Error("NoError should mark test as failed") } }) } @@ -1657,6 +1825,30 @@ func TestNotEqualf(t *testing.T) { }) } +func TestNotEqualTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := NotEqualTf(t, 123, 456, "test message") + if !result { + t.Error("NotEqualTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := NotEqualTf(mock, 123, 123, "test message") + if result { + t.Error("NotEqualTf should return false on failure") + } + if !mock.failed { + t.Error("NotEqualT should mark test as failed") + } + }) +} + func TestNotEqualValuesf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1897,6 +2089,54 @@ func TestNotSamef(t *testing.T) { }) } +func TestNotSameTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := NotSameTf(t, &staticVar, ptr("static string"), "test message") + if !result { + t.Error("NotSameTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := NotSameTf(mock, &staticVar, staticVarPtr, "test message") + if result { + t.Error("NotSameTf should return false on failure") + } + if !mock.failed { + t.Error("NotSameT should mark test as failed") + } + }) +} + +func TestNotSortedTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := NotSortedTf(t, []int{3, 1, 3}, "test message") + if !result { + t.Error("NotSortedTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := NotSortedTf(mock, []int{1, 4, 8}, "test message") + if result { + t.Error("NotSortedTf should return false on failure") + } + if !mock.failed { + t.Error("NotSortedT should mark test as failed") + } + }) +} + func TestNotSubsetf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2137,6 +2377,198 @@ func TestSamef(t *testing.T) { }) } +func TestSameTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SameTf(t, &staticVar, staticVarPtr, "test message") + if !result { + t.Error("SameTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SameTf(mock, &staticVar, ptr("static string"), "test message") + if result { + t.Error("SameTf should return false on failure") + } + if !mock.failed { + t.Error("SameT should mark test as failed") + } + }) +} + +func TestSliceContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceContainsTf(t, []string{"A", "B"}, "A", "test message") + if !result { + t.Error("SliceContainsTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceContainsTf(mock, []string{"A", "B"}, "C", "test message") + if result { + t.Error("SliceContainsTf should return false on failure") + } + if !mock.failed { + t.Error("SliceContainsT should mark test as failed") + } + }) +} + +func TestSliceNotContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceNotContainsTf(t, []string{"A", "B"}, "C", "test message") + if !result { + t.Error("SliceNotContainsTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceNotContainsTf(mock, []string{"A", "B"}, "A", "test message") + if result { + t.Error("SliceNotContainsTf should return false on failure") + } + if !mock.failed { + t.Error("SliceNotContainsT should mark test as failed") + } + }) +} + +func TestSliceNotSubsetTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceNotSubsetTf(t, []int{1, 2, 3}, []int{4, 5}, "test message") + if !result { + t.Error("SliceNotSubsetTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceNotSubsetTf(mock, []int{1, 2, 3}, []int{1, 2}, "test message") + if result { + t.Error("SliceNotSubsetTf should return false on failure") + } + if !mock.failed { + t.Error("SliceNotSubsetT should mark test as failed") + } + }) +} + +func TestSliceSubsetTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SliceSubsetTf(t, []int{1, 2, 3}, []int{1, 2}, "test message") + if !result { + t.Error("SliceSubsetTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SliceSubsetTf(mock, []int{1, 2, 3}, []int{4, 5}, "test message") + if result { + t.Error("SliceSubsetTf should return false on failure") + } + if !mock.failed { + t.Error("SliceSubsetT should mark test as failed") + } + }) +} + +func TestSortedTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SortedTf(t, []int{1, 1, 3}, "test message") + if !result { + t.Error("SortedTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SortedTf(mock, []int{1, 4, 2}, "test message") + if result { + t.Error("SortedTf should return false on failure") + } + if !mock.failed { + t.Error("SortedT should mark test as failed") + } + }) +} + +func TestStringContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := StringContainsTf(t, "AB", "A", "test message") + if !result { + t.Error("StringContainsTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := StringContainsTf(mock, "AB", "C", "test message") + if result { + t.Error("StringContainsTf should return false on failure") + } + if !mock.failed { + t.Error("StringContainsT should mark test as failed") + } + }) +} + +func TestStringNotContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := StringNotContainsTf(t, "AB", "C", "test message") + if !result { + t.Error("StringNotContainsTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := StringNotContainsTf(mock, "AB", "A", "test message") + if result { + t.Error("StringNotContainsTf should return false on failure") + } + if !mock.failed { + t.Error("StringNotContainsT should mark test as failed") + } + }) +} + func TestSubsetf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/assert/assert_forward.go b/assert/assert_forward.go index 5a5edeed9..c0ac599ce 100644 --- a/assert/assert_forward.go +++ b/assert/assert_forward.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -91,6 +91,26 @@ func (a *Assertions) DirExistsf(path string, msg string, args ...any) bool { return assertions.DirExists(a.t, path, forwardArgs(msg, args)) } +// DirNotExists is the same as [DirNotExists], as a method rather than a package-level function. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func (a *Assertions) DirNotExists(path string, msgAndArgs ...any) bool { + if h, ok := a.t.(H); ok { + h.Helper() + } + return assertions.DirNotExists(a.t, path, msgAndArgs...) +} + +// DirNotExistsf is the same as [Assertions.DirNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func (a *Assertions) DirNotExistsf(path string, msg string, args ...any) bool { + if h, ok := a.t.(H); ok { + h.Helper() + } + return assertions.DirNotExists(a.t, path, forwardArgs(msg, args)) +} + // ElementsMatch is the same as [ElementsMatch], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -471,6 +491,26 @@ func (a *Assertions) FileNotEmptyf(path string, msg string, args ...any) bool { return assertions.FileNotEmpty(a.t, path, forwardArgs(msg, args)) } +// FileNotExists is the same as [FileNotExists], as a method rather than a package-level function. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func (a *Assertions) FileNotExists(path string, msgAndArgs ...any) bool { + if h, ok := a.t.(H); ok { + h.Helper() + } + return assertions.FileNotExists(a.t, path, msgAndArgs...) +} + +// FileNotExistsf is the same as [Assertions.FileNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func (a *Assertions) FileNotExistsf(path string, msg string, args ...any) bool { + if h, ok := a.t.(H); ok { + h.Helper() + } + return assertions.FileNotExists(a.t, path, forwardArgs(msg, args)) +} + // Greater is the same as [Greater], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -1051,26 +1091,6 @@ func (a *Assertions) Nilf(object any, msg string, args ...any) bool { return assertions.Nil(a.t, object, forwardArgs(msg, args)) } -// NoDirExists is the same as [NoDirExists], as a method rather than a package-level function. -// -// Upon failure, the test [T] is marked as failed and continues execution. -func (a *Assertions) NoDirExists(path string, msgAndArgs ...any) bool { - if h, ok := a.t.(H); ok { - h.Helper() - } - return assertions.NoDirExists(a.t, path, msgAndArgs...) -} - -// NoDirExistsf is the same as [Assertions.NoDirExists], but it accepts a format msg string to format arguments like [fmt.Printf]. -// -// Upon failure, the test [T] is marked as failed and continues execution. -func (a *Assertions) NoDirExistsf(path string, msg string, args ...any) bool { - if h, ok := a.t.(H); ok { - h.Helper() - } - return assertions.NoDirExists(a.t, path, forwardArgs(msg, args)) -} - // NoError is the same as [NoError], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -1091,26 +1111,6 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...any) bool { return assertions.NoError(a.t, err, forwardArgs(msg, args)) } -// NoFileExists is the same as [NoFileExists], as a method rather than a package-level function. -// -// Upon failure, the test [T] is marked as failed and continues execution. -func (a *Assertions) NoFileExists(path string, msgAndArgs ...any) bool { - if h, ok := a.t.(H); ok { - h.Helper() - } - return assertions.NoFileExists(a.t, path, msgAndArgs...) -} - -// NoFileExistsf is the same as [Assertions.NoFileExists], but it accepts a format msg string to format arguments like [fmt.Printf]. -// -// Upon failure, the test [T] is marked as failed and continues execution. -func (a *Assertions) NoFileExistsf(path string, msg string, args ...any) bool { - if h, ok := a.t.(H); ok { - h.Helper() - } - return assertions.NoFileExists(a.t, path, forwardArgs(msg, args)) -} - // NotContains is the same as [NotContains], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and continues execution. diff --git a/assert/assert_forward_test.go b/assert/assert_forward_test.go index c52c70a98..b1c31a274 100644 --- a/assert/assert_forward_test.go +++ b/assert/assert_forward_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -179,6 +179,60 @@ func TestAssertionsDirExistsf(t *testing.T) { }) } +func TestAssertionsDirNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + result := a.DirNotExists(filepath.Join(testDataPath(), "non_existing_dir")) + if !result { + t.Error("Assertions.DirNotExists should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + a := New(mock) + result := a.DirNotExists(filepath.Join(testDataPath(), "existing_dir")) + if result { + t.Error("Assertions.DirNotExists should return false on failure") + } + if !mock.failed { + t.Error("DirNotExists should mark test as failed") + } + }) +} + +func TestAssertionsDirNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + result := a.DirNotExistsf(filepath.Join(testDataPath(), "non_existing_dir"), "test message") + if !result { + t.Error("Assertions.DirNotExists should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + a := New(mock) + result := a.DirNotExistsf(filepath.Join(testDataPath(), "existing_dir"), "test message") + if result { + t.Error("Assertions.DirNotExists should return false on failure") + } + if !mock.failed { + t.Error("Assertions.DirNotExists should mark test as failed") + } + }) +} + func TestAssertionsElementsMatch(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1168,6 +1222,60 @@ func TestAssertionsFileNotEmptyf(t *testing.T) { }) } +func TestAssertionsFileNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + result := a.FileNotExists(filepath.Join(testDataPath(), "non_existing_file")) + if !result { + t.Error("Assertions.FileNotExists should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + a := New(mock) + result := a.FileNotExists(filepath.Join(testDataPath(), "existing_file")) + if result { + t.Error("Assertions.FileNotExists should return false on failure") + } + if !mock.failed { + t.Error("FileNotExists should mark test as failed") + } + }) +} + +func TestAssertionsFileNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + result := a.FileNotExistsf(filepath.Join(testDataPath(), "non_existing_file"), "test message") + if !result { + t.Error("Assertions.FileNotExists should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + a := New(mock) + result := a.FileNotExistsf(filepath.Join(testDataPath(), "existing_file"), "test message") + if result { + t.Error("Assertions.FileNotExists should return false on failure") + } + if !mock.failed { + t.Error("Assertions.FileNotExists should mark test as failed") + } + }) +} + func TestAssertionsGreater(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2049,7 +2157,7 @@ func TestAssertionsIsNonDecreasing(t *testing.T) { mock := new(mockT) a := New(mock) - result := a.IsNonDecreasing([]int{2, 1, 1}) + result := a.IsNonDecreasing([]int{2, 1, 0}) if result { t.Error("Assertions.IsNonDecreasing should return false on failure") } @@ -2076,7 +2184,7 @@ func TestAssertionsIsNonDecreasingf(t *testing.T) { mock := new(mockT) a := New(mock) - result := a.IsNonDecreasingf([]int{2, 1, 1}, "test message") + result := a.IsNonDecreasingf([]int{2, 1, 0}, "test message") if result { t.Error("Assertions.IsNonDecreasing should return false on failure") } @@ -2734,60 +2842,6 @@ func TestAssertionsNilf(t *testing.T) { }) } -func TestAssertionsNoDirExists(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - result := a.NoDirExists(filepath.Join(testDataPath(), "non_existing_dir")) - if !result { - t.Error("Assertions.NoDirExists should return true on success") - } - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - a := New(mock) - result := a.NoDirExists(filepath.Join(testDataPath(), "existing_dir")) - if result { - t.Error("Assertions.NoDirExists should return false on failure") - } - if !mock.failed { - t.Error("NoDirExists should mark test as failed") - } - }) -} - -func TestAssertionsNoDirExistsf(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - result := a.NoDirExistsf(filepath.Join(testDataPath(), "non_existing_dir"), "test message") - if !result { - t.Error("Assertions.NoDirExists should return true on success") - } - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - a := New(mock) - result := a.NoDirExistsf(filepath.Join(testDataPath(), "existing_dir"), "test message") - if result { - t.Error("Assertions.NoDirExists should return false on failure") - } - if !mock.failed { - t.Error("Assertions.NoDirExists should mark test as failed") - } - }) -} - func TestAssertionsNoError(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2842,60 +2896,6 @@ func TestAssertionsNoErrorf(t *testing.T) { }) } -func TestAssertionsNoFileExists(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - result := a.NoFileExists(filepath.Join(testDataPath(), "non_existing_file")) - if !result { - t.Error("Assertions.NoFileExists should return true on success") - } - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - a := New(mock) - result := a.NoFileExists(filepath.Join(testDataPath(), "existing_file")) - if result { - t.Error("Assertions.NoFileExists should return false on failure") - } - if !mock.failed { - t.Error("NoFileExists should mark test as failed") - } - }) -} - -func TestAssertionsNoFileExistsf(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - result := a.NoFileExistsf(filepath.Join(testDataPath(), "non_existing_file"), "test message") - if !result { - t.Error("Assertions.NoFileExists should return true on success") - } - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - a := New(mock) - result := a.NoFileExistsf(filepath.Join(testDataPath(), "existing_file"), "test message") - if result { - t.Error("Assertions.NoFileExists should return false on failure") - } - if !mock.failed { - t.Error("Assertions.NoFileExists should mark test as failed") - } - }) -} - func TestAssertionsNotContains(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/assert/assert_helpers.go b/assert/assert_helpers.go index b4216c990..b045c686b 100644 --- a/assert/assert_helpers.go +++ b/assert/assert_helpers.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert diff --git a/assert/assert_helpers_test.go b/assert/assert_helpers_test.go index e687cc1ce..f4530b215 100644 --- a/assert/assert_helpers_test.go +++ b/assert/assert_helpers_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert diff --git a/assert/assert_types.go b/assert/assert_types.go index 038328fe6..0962f8dfb 100644 --- a/assert/assert_types.go +++ b/assert/assert_types.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package assert @@ -57,7 +57,7 @@ type ( // Ordered is a standard ordered type (i.e. types that support "<": [cmp.Ordered]) plus []byte and [time.Time]. // - // This is used by [GreaterT], [GreaterOrEqualT], [LessT], and [LessOrEqualT]. + // This is used by [GreaterT], [GreaterOrEqualT], [LessT], [LessOrEqualT], [IsIncreasingT], [IsDecreasingT]. // // NOTE: since [time.Time] is a struct, custom types which redeclare [time.Time] are not supported. Ordered = assertions.Ordered diff --git a/codegen/internal/model/model.go b/codegen/internal/model/model.go index c04237f1b..f436b2f88 100644 --- a/codegen/internal/model/model.go +++ b/codegen/internal/model/model.go @@ -122,7 +122,7 @@ func (f Function) GenericName(suffixes ...string) string { for i, p := range f.TypeParams[1:] { w.WriteString(", ") w.WriteString(p.Name) - if len(f.TypeParams) <= i+1+1 || f.TypeParams[i+1].Constraint != p.Constraint { + if len(f.TypeParams) <= i+1+1 || f.TypeParams[i+1+1].Constraint != p.Constraint { w.WriteByte(' ') w.WriteString(p.Constraint) } diff --git a/docs/doc-site/api/_index.md b/docs/doc-site/api/_index.md index f01a44070..68dbaa2e3 100644 --- a/docs/doc-site/api/_index.md +++ b/docs/doc-site/api/_index.md @@ -6,7 +6,7 @@ description: | Find the assertion function you need for your data. weight: 1 -modified: 2026-01-18 +modified: 2026-01-19 --- **Go testing assertions for the rest of us** @@ -33,16 +33,16 @@ Each domain contains assertions regrouped by their use case (e.g. http, json, er --- - [Boolean](./boolean.md) - Asserting Boolean Values (4) -- [Collection](./collection.md) - Asserting Slices And Maps (9) +- [Collection](./collection.md) - Asserting Slices And Maps (17) - [Comparison](./comparison.md) - Comparing Ordered Values (12) - [Condition](./condition.md) - Expressing Assertions Using Conditions (4) -- [Equality](./equality.md) - Asserting Two Things Are Equal (12) +- [Equality](./equality.md) - Asserting Two Things Are Equal (16) - [Error](./error.md) - Asserting Errors (8) - [File](./file.md) - Asserting OS Files (6) - [Http](./http.md) - Asserting HTTP Response And Body (7) - [Json](./json.md) - Asserting JSON Documents (3) - [Number](./number.md) - Asserting Numbers (7) -- [Ordering](./ordering.md) - Asserting How Collections Are Ordered (4) +- [Ordering](./ordering.md) - Asserting How Collections Are Ordered (10) - [Panic](./panic.md) - Asserting A Panic Behavior (4) - [String](./string.md) - Asserting Strings (4) - [Testing](./testing.md) - Mimicks Methods From The Testing Standard Library (2) @@ -67,5 +67,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/boolean.md b/docs/doc-site/api/boolean.md index 01e28abbb..a0ac92d26 100644 --- a/docs/doc-site/api/boolean.md +++ b/docs/doc-site/api/boolean.md @@ -1,7 +1,7 @@ --- title: "Boolean" description: "Asserting Boolean Values" -modified: 2026-01-18 +modified: 2026-01-19 weight: 1 domains: - "boolean" @@ -235,5 +235,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/collection.md b/docs/doc-site/api/collection.md index 7d3dbae1d..6f8d20088 100644 --- a/docs/doc-site/api/collection.md +++ b/docs/doc-site/api/collection.md @@ -1,7 +1,7 @@ --- title: "Collection" description: "Asserting Slices And Maps" -modified: 2026-01-18 +modified: 2026-01-19 weight: 2 domains: - "collection" @@ -14,6 +14,10 @@ keywords: - "ElementsMatchTf" - "Len" - "Lenf" + - "MapContainsT" + - "MapContainsTf" + - "MapNotContainsT" + - "MapNotContainsTf" - "NotContains" - "NotContainsf" - "NotElementsMatch" @@ -22,6 +26,18 @@ keywords: - "NotElementsMatchTf" - "NotSubset" - "NotSubsetf" + - "SliceContainsT" + - "SliceContainsTf" + - "SliceNotContainsT" + - "SliceNotContainsTf" + - "SliceNotSubsetT" + - "SliceNotSubsetTf" + - "SliceSubsetT" + - "SliceSubsetTf" + - "StringContainsT" + - "StringContainsTf" + - "StringNotContainsT" + - "StringNotContainsTf" - "Subset" - "Subsetf" --- @@ -35,7 +51,7 @@ Asserting Slices And Maps _All links point to _ -This domain exposes 9 functionalities. +This domain exposes 17 functionalities. Generic assertions are marked with a {{% icon icon="star" color=orange %}} ```tree @@ -43,10 +59,18 @@ Generic assertions are marked with a {{% icon icon="star" color=orange %}} - [ElementsMatch](#elementsmatch) | angles-right - [ElementsMatchT[E comparable]](#elementsmatchte-comparable) | star | orange - [Len](#len) | angles-right +- [MapContainsT[Map ~map[K]V, K comparable, V any]](#mapcontainstmap-~mapkv-k-comparable-v-any) | star | orange +- [MapNotContainsT[Map ~map[K]V, K comparable, V any]](#mapnotcontainstmap-~mapkv-k-comparable-v-any) | star | orange - [NotContains](#notcontains) | angles-right - [NotElementsMatch](#notelementsmatch) | angles-right - [NotElementsMatchT[E comparable]](#notelementsmatchte-comparable) | star | orange - [NotSubset](#notsubset) | angles-right +- [SliceContainsT[Slice ~[]E, E comparable]](#slicecontainstslice-~e-e-comparable) | star | orange +- [SliceNotContainsT[Slice ~[]E, E comparable]](#slicenotcontainstslice-~e-e-comparable) | star | orange +- [SliceNotSubsetT[Slice ~[]E, E comparable]](#slicenotsubsettslice-~e-e-comparable) | star | orange +- [SliceSubsetT[Slice ~[]E, E comparable]](#slicesubsettslice-~e-e-comparable) | star | orange +- [StringContainsT[ADoc, EDoc Text]](#stringcontainstadoc-edoc-text) | star | orange +- [StringNotContainsT[ADoc, EDoc Text]](#stringnotcontainstadoc-edoc-text) | star | orange - [Subset](#subset) | angles-right ``` @@ -96,7 +120,7 @@ specified substring or element. |--|--| | [`assertions.Contains(t T, s any, contains any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Contains) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Contains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L63) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Contains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L64) {{% /tab %}} {{< /tabs >}} @@ -145,7 +169,7 @@ the number of appearances of each of them in both lists should match. |--|--| | [`assertions.ElementsMatch(t T, listA any, listB any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#ElementsMatch) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L276) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L473) {{% /tab %}} {{< /tabs >}} @@ -190,7 +214,7 @@ the number of appearances of each of them in both lists should match. |--|--| | [`assertions.ElementsMatchT(t T, listA []E, listB []E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#ElementsMatchT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L349) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L546) {{% /tab %}} {{< /tabs >}} @@ -245,13 +269,99 @@ See also [reflect.Len](https://pkg.go.dev/reflect#Len). |--|--| | [`assertions.Len(t T, object any, length int, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Len) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Len](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L31) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Len](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L32) > **Note** > (proposals) this does not currently support iterators, or collection objects that have a Len() method. {{% /tab %}} {{< /tabs >}} +### MapContainsT[Map ~map[K]V, K comparable, V any] {{% icon icon="star" color=orange %}}{#mapcontainstmap-~mapkv-k-comparable-v-any} + +MapContainsT asserts that the specified map contains a key. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.MapContainsT(t, map[string]string{"Hello": "x","World": "y"}, "World") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: map[string]string{"A": "B"}, "A" + failure: map[string]string{"A": "B"}, "C" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#MapContainsT) | package-level function | +| [`assert.MapContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#MapContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#MapContainsT) | package-level function | +| [`require.MapContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#MapContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.MapContainsT(t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#MapContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#MapContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L139) +{{% /tab %}} +{{< /tabs >}} + +### MapNotContainsT[Map ~map[K]V, K comparable, V any] {{% icon icon="star" color=orange %}}{#mapnotcontainstmap-~mapkv-k-comparable-v-any} + +MapNotContainsT asserts that the specified map does not contain a key. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.MapNotContainsT(t, map[string]string{"Hello": "x","World": "y"}, "hi") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: map[string]string{"A": "B"}, "C" + failure: map[string]string{"A": "B"}, "A" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#MapNotContainsT) | package-level function | +| [`assert.MapNotContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#MapNotContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#MapNotContainsT) | package-level function | +| [`require.MapNotContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#MapNotContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.MapNotContainsT(t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#MapNotContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#MapNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L241) +{{% /tab %}} +{{< /tabs >}} + ### NotContains{#notcontains} NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the @@ -298,7 +408,7 @@ specified substring or element. |--|--| | [`assertions.NotContains(t T, s any, contains any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotContains) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotContains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L93) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotContains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L166) {{% /tab %}} {{< /tabs >}} @@ -350,7 +460,7 @@ This is an inverse of ElementsMatch. |--|--| | [`assertions.NotElementsMatch(t T, listA any, listB any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatch) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L313) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L510) {{% /tab %}} {{< /tabs >}} @@ -398,7 +508,7 @@ This is an inverse of ElementsMatch. |--|--| | [`assertions.NotElementsMatchT(t T, listA []E, listB []E, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatchT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L385) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L582) {{% /tab %}} {{< /tabs >}} @@ -451,7 +561,269 @@ only the map key is evaluated. |--|--| | [`assertions.NotSubset(t T, list any, subset any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotSubset) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSubset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L204) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSubset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L375) +{{% /tab %}} +{{< /tabs >}} + +### SliceContainsT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicecontainstslice-~e-e-comparable} + +SliceContainsT asserts that the specified slice contains a comparable element. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SliceContainsT(t, []{"Hello","World"}, "World") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []string{"A","B"}, "A" + failure: []string{"A","B"}, "C" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceContainsT) | package-level function | +| [`assert.SliceContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceContainsT) | package-level function | +| [`require.SliceContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SliceContainsT(t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L116) +{{% /tab %}} +{{< /tabs >}} + +### SliceNotContainsT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicenotcontainstslice-~e-e-comparable} + +SliceNotContainsT asserts that the specified slice does not contain a comparable element. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SliceNotContainsT(t, []{"Hello","World"}, "hi") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []string{"A","B"}, "C" + failure: []string{"A","B"}, "A" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceNotContainsT) | package-level function | +| [`assert.SliceNotContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceNotContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceNotContainsT) | package-level function | +| [`require.SliceNotContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceNotContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SliceNotContainsT(t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceNotContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L218) +{{% /tab %}} +{{< /tabs >}} + +### SliceNotSubsetT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicenotsubsettslice-~e-e-comparable} + +SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SliceNotSubsetT(t, []int{1, 2, 3}, []int{1, 4}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{1, 2, 3}, []int{4, 5} + failure: []int{1, 2, 3}, []int{1, 2} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SliceNotSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceNotSubsetT) | package-level function | +| [`assert.SliceNotSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceNotSubsetTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SliceNotSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceNotSubsetT) | package-level function | +| [`require.SliceNotSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceNotSubsetTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SliceNotSubsetT(t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceNotSubsetT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceNotSubsetT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L446) +{{% /tab %}} +{{< /tabs >}} + +### SliceSubsetT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicesubsettslice-~e-e-comparable} + +SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{1, 2, 3}, []int{1, 2} + failure: []int{1, 2, 3}, []int{4, 5} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SliceSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceSubsetT) | package-level function | +| [`assert.SliceSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SliceSubsetTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SliceSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceSubsetT) | package-level function | +| [`require.SliceSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SliceSubsetTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SliceSubsetT(t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceSubsetT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceSubsetT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L344) +{{% /tab %}} +{{< /tabs >}} + +### StringContainsT[ADoc, EDoc Text] {{% icon icon="star" color=orange %}}{#stringcontainstadoc-edoc-text} + +StringContainsT asserts that a string contains the specified substring. + +Strings may be go strings or []byte. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.StringContainsT(t, "Hello World", "World") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: "AB", "A" + failure: "AB", "C" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#StringContainsT) | package-level function | +| [`assert.StringContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#StringContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#StringContainsT) | package-level function | +| [`require.StringContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#StringContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.StringContainsT(t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#StringContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#StringContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L93) +{{% /tab %}} +{{< /tabs >}} + +### StringNotContainsT[ADoc, EDoc Text] {{% icon icon="star" color=orange %}}{#stringnotcontainstadoc-edoc-text} + +StringNotContainsT asserts that a string does not contain the specified substring. + +Strings may be go strings or []byte. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.StringNotContainsT(t, "Hello World", "hi") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: "AB", "C" + failure: "AB", "A" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#StringNotContainsT) | package-level function | +| [`assert.StringNotContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#StringNotContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#StringNotContainsT) | package-level function | +| [`require.StringNotContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#StringNotContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.StringNotContainsT(t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#StringNotContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#StringNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L195) {{% /tab %}} {{< /tabs >}} @@ -505,7 +877,7 @@ only the map key is evaluated. |--|--| | [`assertions.Subset(t T, list any, subset any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Subset) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Subset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L127) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Subset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L272) {{% /tab %}} {{< /tabs >}} @@ -525,5 +897,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/common.md b/docs/doc-site/api/common.md index d700fd0e8..b4825ffd3 100644 --- a/docs/doc-site/api/common.md +++ b/docs/doc-site/api/common.md @@ -1,7 +1,7 @@ --- title: "Common" description: "Other Uncategorized Helpers" -modified: 2026-01-18 +modified: 2026-01-19 weight: 18 domains: - "common" @@ -160,5 +160,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/comparison.md b/docs/doc-site/api/comparison.md index c6da96545..c479629ed 100644 --- a/docs/doc-site/api/comparison.md +++ b/docs/doc-site/api/comparison.md @@ -1,7 +1,7 @@ --- title: "Comparison" description: "Comparing Ordered Values" -modified: 2026-01-18 +modified: 2026-01-19 weight: 3 domains: - "comparison" @@ -689,5 +689,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/condition.md b/docs/doc-site/api/condition.md index 4faddbaac..8c5952d06 100644 --- a/docs/doc-site/api/condition.md +++ b/docs/doc-site/api/condition.md @@ -1,7 +1,7 @@ --- title: "Condition" description: "Expressing Assertions Using Conditions" -modified: 2026-01-18 +modified: 2026-01-19 weight: 4 domains: - "condition" @@ -294,5 +294,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/equality.md b/docs/doc-site/api/equality.md index 76b5715aa..7aea7b71b 100644 --- a/docs/doc-site/api/equality.md +++ b/docs/doc-site/api/equality.md @@ -1,7 +1,7 @@ --- title: "Equality" description: "Asserting Two Things Are Equal" -modified: 2026-01-18 +modified: 2026-01-19 weight: 5 domains: - "equality" @@ -12,6 +12,8 @@ keywords: - "Equalf" - "EqualExportedValues" - "EqualExportedValuesf" + - "EqualT" + - "EqualTf" - "EqualValues" - "EqualValuesf" - "Exactly" @@ -22,14 +24,20 @@ keywords: - "NotEmptyf" - "NotEqual" - "NotEqualf" + - "NotEqualT" + - "NotEqualTf" - "NotEqualValues" - "NotEqualValuesf" - "NotNil" - "NotNilf" - "NotSame" - "NotSamef" + - "NotSameT" + - "NotSameTf" - "Same" - "Samef" + - "SameT" + - "SameTf" --- Asserting Two Things Are Equal @@ -41,21 +49,26 @@ Asserting Two Things Are Equal _All links point to _ -This domain exposes 12 functionalities. +This domain exposes 16 functionalities. +Generic assertions are marked with a {{% icon icon="star" color=orange %}} ```tree - [Empty](#empty) | angles-right - [Equal](#equal) | angles-right - [EqualExportedValues](#equalexportedvalues) | angles-right +- [EqualT[V comparable]](#equaltv-comparable) | star | orange - [EqualValues](#equalvalues) | angles-right - [Exactly](#exactly) | angles-right - [Nil](#nil) | angles-right - [NotEmpty](#notempty) | angles-right - [NotEqual](#notequal) | angles-right +- [NotEqualT[V comparable]](#notequaltv-comparable) | star | orange - [NotEqualValues](#notequalvalues) | angles-right - [NotNil](#notnil) | angles-right - [NotSame](#notsame) | angles-right +- [NotSameT[P any]](#notsametp-any) | star | orange - [Same](#same) | angles-right +- [SameT[P any]](#sametp-any) | star | orange ``` ### Empty{#empty} @@ -111,7 +124,7 @@ Pointer values are "empty" if the pointer is nil or if the pointed value is "emp |--|--| | [`assertions.Empty(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Empty) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Empty](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L289) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Empty](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L406) {{% /tab %}} {{< /tabs >}} @@ -217,7 +230,57 @@ that could potentially differ. |--|--| | [`assertions.EqualExportedValues(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#EqualExportedValues) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualExportedValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L174) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualExportedValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L291) +{{% /tab %}} +{{< /tabs >}} + +### EqualT[V comparable] {{% icon icon="star" color=orange %}}{#equaltv-comparable} + +EqualT asserts that two objects of the same comparable type are equal. + +Pointer variable equality is determined based on the equality of the memory addresses (unlike [Equal], but like [Same]). + +Functions, slices and maps are not comparable. See also [ComparisonOperators](https://go.dev/ref/spec#Comparison_operators.). + +If you need to compare values of non-comparable types, or compare pointers by the value they point to, +use [Equal] instead. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.EqualT(t, 123, 123) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: 123, 123 + failure: 123, 456 +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.EqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#EqualT) | package-level function | +| [`assert.EqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#EqualTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.EqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#EqualT) | package-level function | +| [`require.EqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#EqualTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.EqualT(t T, expected V, actual V, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#EqualT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualT](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L67) {{% /tab %}} {{< /tabs >}} @@ -265,7 +328,7 @@ type and equal. |--|--| | [`assertions.EqualValues(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#EqualValues) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L140) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L257) {{% /tab %}} {{< /tabs >}} @@ -312,7 +375,7 @@ Exactly asserts that two objects are equal in value and type. |--|--| | [`assertions.Exactly(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Exactly) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Exactly](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L211) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Exactly](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L328) {{% /tab %}} {{< /tabs >}} @@ -359,7 +422,7 @@ Nil asserts that the specified object is nil. |--|--| | [`assertions.Nil(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Nil) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Nil](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L258) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Nil](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L375) {{% /tab %}} {{< /tabs >}} @@ -408,7 +471,7 @@ NotEmpty asserts that the specified object is NOT [Empty]. |--|--| | [`assertions.NotEmpty(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotEmpty) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEmpty](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L314) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEmpty](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L431) {{% /tab %}} {{< /tabs >}} @@ -457,7 +520,52 @@ referenced values (as opposed to the memory addresses). |--|--| | [`assertions.NotEqual(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotEqual) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqual](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L340) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqual](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L89) +{{% /tab %}} +{{< /tabs >}} + +### NotEqualT[V comparable] {{% icon icon="star" color=orange %}}{#notequaltv-comparable} + +NotEqualT asserts that the specified values of the same comparable type are NOT equal. + +See [EqualT]. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.NotEqualT(t, obj1, obj2) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: 123, 456 + failure: 123, 123 +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.NotEqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NotEqualT) | package-level function | +| [`assert.NotEqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NotEqualTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.NotEqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NotEqualT) | package-level function | +| [`require.NotEqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NotEqualTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.NotEqualT(t T, expected V, actual V, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotEqualT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqualT](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L118) {{% /tab %}} {{< /tabs >}} @@ -504,7 +612,7 @@ NotEqualValues asserts that two objects are not equal even when converted to the |--|--| | [`assertions.NotEqualValues(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotEqualValues) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqualValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L367) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqualValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L454) {{% /tab %}} {{< /tabs >}} @@ -551,7 +659,7 @@ assertions.NotNil(t, err) |--|--| | [`assertions.NotNil(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotNil) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotNil](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L237) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotNil](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L354) {{% /tab %}} {{< /tabs >}} @@ -601,7 +709,52 @@ determined based on the equality of both type and value. |--|--| | [`assertions.NotSame(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotSame) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSame](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L109) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSame](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L199) +{{% /tab %}} +{{< /tabs >}} + +### NotSameT[P any] {{% icon icon="star" color=orange %}}{#notsametp-any} + +NotSameT asserts that two pointers do not reference the same object. + +See [SameT] + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.NotSameT(t, ptr1, ptr2) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: &staticVar, ptr("static string") + failure: &staticVar, staticVarPtr +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.NotSameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NotSameT) | package-level function | +| [`assert.NotSameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NotSameTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.NotSameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NotSameT) | package-level function | +| [`require.NotSameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NotSameTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.NotSameT(t T, expected *P, actual *P, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotSameT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSameT](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L231) {{% /tab %}} {{< /tabs >}} @@ -651,7 +804,50 @@ determined based on the equality of both type and value. |--|--| | [`assertions.Same(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Same) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Same](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L75) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Same](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L140) +{{% /tab %}} +{{< /tabs >}} + +### SameT[P any] {{% icon icon="star" color=orange %}}{#sametp-any} + +SameT asserts that two pointers of the same type reference the same object. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SameT(t, ptr1, ptr2) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: &staticVar, staticVarPtr + failure: &staticVar, ptr("static string") +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SameT) | package-level function | +| [`assert.SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SameTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SameT) | package-level function | +| [`require.SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SameTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SameT(t T, expected *P, actual *P, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SameT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SameT](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L171) {{% /tab %}} {{< /tabs >}} @@ -671,5 +867,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/error.md b/docs/doc-site/api/error.md index c4ab3eaf4..0fec5605d 100644 --- a/docs/doc-site/api/error.md +++ b/docs/doc-site/api/error.md @@ -1,7 +1,7 @@ --- title: "Error" description: "Asserting Errors" -modified: 2026-01-18 +modified: 2026-01-19 weight: 6 domains: - "error" @@ -453,5 +453,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/file.md b/docs/doc-site/api/file.md index 100930ad8..588f6f368 100644 --- a/docs/doc-site/api/file.md +++ b/docs/doc-site/api/file.md @@ -1,23 +1,23 @@ --- title: "File" description: "Asserting OS Files" -modified: 2026-01-18 +modified: 2026-01-19 weight: 7 domains: - "file" keywords: - "DirExists" - "DirExistsf" + - "DirNotExists" + - "DirNotExistsf" - "FileEmpty" - "FileEmptyf" - "FileExists" - "FileExistsf" - "FileNotEmpty" - "FileNotEmptyf" - - "NoDirExists" - - "NoDirExistsf" - - "NoFileExists" - - "NoFileExistsf" + - "FileNotExists" + - "FileNotExistsf" --- Asserting OS Files @@ -33,11 +33,11 @@ This domain exposes 6 functionalities. ```tree - [DirExists](#direxists) | angles-right +- [DirNotExists](#dirnotexists) | angles-right - [FileEmpty](#fileempty) | angles-right - [FileExists](#fileexists) | angles-right - [FileNotEmpty](#filenotempty) | angles-right -- [NoDirExists](#nodirexists) | angles-right -- [NoFileExists](#nofileexists) | angles-right +- [FileNotExists](#filenotexists) | angles-right ``` ### DirExists{#direxists} @@ -88,6 +88,54 @@ if the path is a file rather a directory or there is an error checking whether i {{% /tab %}} {{< /tabs >}} +### DirNotExists{#dirnotexists} + +DirNotExists checks whether a directory does not exist in the given path. +It fails if the path points to an existing _directory_ only. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.DirNotExists(t, "path/to/directory") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: filepath.Join(testDataPath(),"non_existing_dir") + failure: filepath.Join(testDataPath(),"existing_dir") +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.DirNotExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#DirNotExists) | package-level function | +| [`assert.DirNotExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#DirNotExistsf) | formatted variant | +| [`assert.(*Assertions).DirNotExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.DirNotExists) | method variant | +| [`assert.(*Assertions).DirNotExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.DirNotExistsf) | method formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.DirNotExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#DirNotExists) | package-level function | +| [`require.DirNotExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#DirNotExistsf) | formatted variant | +| [`require.(*Assertions).DirNotExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.DirNotExists) | method variant | +| [`require.(*Assertions).DirNotExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.DirNotExistsf) | method formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.DirNotExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#DirNotExists) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#DirNotExists](https://github.com/go-openapi/testify/blob/master/internal/assertions/file.go#L107) +{{% /tab %}} +{{< /tabs >}} + ### FileEmpty{#fileempty} FileEmpty checks whether a file exists in the given path and is empty. @@ -232,64 +280,16 @@ It fails if the file is empty, if the path points to a directory or there is an {{% /tab %}} {{< /tabs >}} -### NoDirExists{#nodirexists} - -NoDirExists checks whether a directory does not exist in the given path. -It fails if the path points to an existing _directory_ only. - -{{% expand title="Examples" %}} -{{< tabs >}} -{{% tab title="Usage" %}} -```go - assertions.NoDirExists(t, "path/to/directory") -``` -{{< /tab >}} -{{% tab title="Examples" %}} -```go - success: filepath.Join(testDataPath(),"non_existing_dir") - failure: filepath.Join(testDataPath(),"existing_dir") -``` -{{< /tab >}} -{{< /tabs >}} -{{% /expand %}} - -{{< tabs >}} -{{% tab title="assert" style="secondary" %}} -| Signature | Usage | -|--|--| -| [`assert.NoDirExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NoDirExists) | package-level function | -| [`assert.NoDirExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NoDirExistsf) | formatted variant | -| [`assert.(*Assertions).NoDirExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.NoDirExists) | method variant | -| [`assert.(*Assertions).NoDirExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.NoDirExistsf) | method formatted variant | -{{% /tab %}} -{{% tab title="require" style="secondary" %}} -| Signature | Usage | -|--|--| -| [`require.NoDirExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NoDirExists) | package-level function | -| [`require.NoDirExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NoDirExistsf) | formatted variant | -| [`require.(*Assertions).NoDirExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.NoDirExists) | method variant | -| [`require.(*Assertions).NoDirExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.NoDirExistsf) | method formatted variant | -{{% /tab %}} - -{{% tab title="internal" style="accent" icon="wrench" %}} -| Signature | Usage | -|--|--| -| [`assertions.NoDirExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NoDirExists) | internal implementation | - -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NoDirExists](https://github.com/go-openapi/testify/blob/master/internal/assertions/file.go#L107) -{{% /tab %}} -{{< /tabs >}} - -### NoFileExists{#nofileexists} +### FileNotExists{#filenotexists} -NoFileExists checks whether a file does not exist in a given path. It fails +FileNotExists checks whether a file does not exist in a given path. It fails if the path points to an existing _file_ only. {{% expand title="Examples" %}} {{< tabs >}} {{% tab title="Usage" %}} ```go - assertions.NoFileExists(t, "path/to/file") + assertions.FileNotExists(t, "path/to/file") ``` {{< /tab >}} {{% tab title="Examples" %}} @@ -305,26 +305,26 @@ if the path points to an existing _file_ only. {{% tab title="assert" style="secondary" %}} | Signature | Usage | |--|--| -| [`assert.NoFileExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NoFileExists) | package-level function | -| [`assert.NoFileExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NoFileExistsf) | formatted variant | -| [`assert.(*Assertions).NoFileExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.NoFileExists) | method variant | -| [`assert.(*Assertions).NoFileExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.NoFileExistsf) | method formatted variant | +| [`assert.FileNotExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#FileNotExists) | package-level function | +| [`assert.FileNotExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#FileNotExistsf) | formatted variant | +| [`assert.(*Assertions).FileNotExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.FileNotExists) | method variant | +| [`assert.(*Assertions).FileNotExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Assertions.FileNotExistsf) | method formatted variant | {{% /tab %}} {{% tab title="require" style="secondary" %}} | Signature | Usage | |--|--| -| [`require.NoFileExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NoFileExists) | package-level function | -| [`require.NoFileExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NoFileExistsf) | formatted variant | -| [`require.(*Assertions).NoFileExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.NoFileExists) | method variant | -| [`require.(*Assertions).NoFileExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.NoFileExistsf) | method formatted variant | +| [`require.FileNotExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#FileNotExists) | package-level function | +| [`require.FileNotExistsf(t T, path string, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#FileNotExistsf) | formatted variant | +| [`require.(*Assertions).FileNotExists(path string) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.FileNotExists) | method variant | +| [`require.(*Assertions).FileNotExistsf(path string, msg string, args ..any)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#Assertions.FileNotExistsf) | method formatted variant | {{% /tab %}} {{% tab title="internal" style="accent" icon="wrench" %}} | Signature | Usage | |--|--| -| [`assertions.NoFileExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NoFileExists) | internal implementation | +| [`assertions.FileNotExists(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#FileNotExists) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NoFileExists](https://github.com/go-openapi/testify/blob/master/internal/assertions/file.go#L52) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#FileNotExists](https://github.com/go-openapi/testify/blob/master/internal/assertions/file.go#L52) {{% /tab %}} {{< /tabs >}} @@ -344,5 +344,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/http.md b/docs/doc-site/api/http.md index cb54d08af..213705eb6 100644 --- a/docs/doc-site/api/http.md +++ b/docs/doc-site/api/http.md @@ -1,7 +1,7 @@ --- title: "Http" description: "Asserting HTTP Response And Body" -modified: 2026-01-18 +modified: 2026-01-19 weight: 8 domains: - "http" @@ -382,5 +382,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/json.md b/docs/doc-site/api/json.md index 420a38712..5f73c697d 100644 --- a/docs/doc-site/api/json.md +++ b/docs/doc-site/api/json.md @@ -1,7 +1,7 @@ --- title: "Json" description: "Asserting JSON Documents" -modified: 2026-01-18 +modified: 2026-01-19 weight: 9 domains: - "json" @@ -197,5 +197,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/number.md b/docs/doc-site/api/number.md index 608708ffb..80ece2f2f 100644 --- a/docs/doc-site/api/number.md +++ b/docs/doc-site/api/number.md @@ -1,7 +1,7 @@ --- title: "Number" description: "Asserting Numbers" -modified: 2026-01-18 +modified: 2026-01-19 weight: 10 domains: - "number" @@ -443,5 +443,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/ordering.md b/docs/doc-site/api/ordering.md index a14f974a1..2b6139a8a 100644 --- a/docs/doc-site/api/ordering.md +++ b/docs/doc-site/api/ordering.md @@ -1,19 +1,31 @@ --- title: "Ordering" description: "Asserting How Collections Are Ordered" -modified: 2026-01-18 +modified: 2026-01-19 weight: 11 domains: - "ordering" keywords: - "IsDecreasing" - "IsDecreasingf" + - "IsDecreasingT" + - "IsDecreasingTf" - "IsIncreasing" - "IsIncreasingf" + - "IsIncreasingT" + - "IsIncreasingTf" - "IsNonDecreasing" - "IsNonDecreasingf" + - "IsNonDecreasingT" + - "IsNonDecreasingTf" - "IsNonIncreasing" - "IsNonIncreasingf" + - "IsNonIncreasingT" + - "IsNonIncreasingTf" + - "NotSortedT" + - "NotSortedTf" + - "SortedT" + - "SortedTf" --- Asserting How Collections Are Ordered @@ -25,18 +37,25 @@ Asserting How Collections Are Ordered _All links point to _ -This domain exposes 4 functionalities. +This domain exposes 10 functionalities. +Generic assertions are marked with a {{% icon icon="star" color=orange %}} ```tree - [IsDecreasing](#isdecreasing) | angles-right +- [IsDecreasingT[OrderedSlice ~[]E, E Ordered]](#isdecreasingtorderedslice-~e-e-ordered) | star | orange - [IsIncreasing](#isincreasing) | angles-right +- [IsIncreasingT[OrderedSlice ~[]E, E Ordered]](#isincreasingtorderedslice-~e-e-ordered) | star | orange - [IsNonDecreasing](#isnondecreasing) | angles-right +- [IsNonDecreasingT[OrderedSlice ~[]E, E Ordered]](#isnondecreasingtorderedslice-~e-e-ordered) | star | orange - [IsNonIncreasing](#isnonincreasing) | angles-right +- [IsNonIncreasingT[OrderedSlice ~[]E, E Ordered]](#isnonincreasingtorderedslice-~e-e-ordered) | star | orange +- [NotSortedT[OrderedSlice ~[]E, E Ordered]](#notsortedtorderedslice-~e-e-ordered) | star | orange +- [SortedT[OrderedSlice ~[]E, E Ordered]](#sortedtorderedslice-~e-e-ordered) | star | orange ``` ### IsDecreasing{#isdecreasing} -IsDecreasing asserts that the collection is decreasing. +IsDecreasing asserts that the collection is strictly decreasing. {{% expand title="Examples" %}} {{< tabs >}} @@ -79,13 +98,58 @@ IsDecreasing asserts that the collection is decreasing. |--|--| | [`assertions.IsDecreasing(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsDecreasing) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsDecreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L63) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsDecreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L170) +{{% /tab %}} +{{< /tabs >}} + +### IsDecreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isdecreasingtorderedslice-~e-e-ordered} + +IsDecreasingT asserts that a slice of [Ordered] is strictly decreasing. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.IsDecreasingT(t, []int{2, 1, 0}) + assertions.IsDecreasingT(t, []float{2, 1}) + assertions.IsDecreasingT(t, []string{"b", "a"}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{3, 2, 1} + failure: []int{1, 2, 3} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsDecreasingT) | package-level function | +| [`assert.IsDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsDecreasingTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsDecreasingT) | package-level function | +| [`require.IsDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsDecreasingTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.IsDecreasingT(t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsDecreasingT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsDecreasingT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L190) {{% /tab %}} {{< /tabs >}} ### IsIncreasing{#isincreasing} -IsIncreasing asserts that the collection is increasing. +IsIncreasing asserts that the collection is strictly increasing. {{% expand title="Examples" %}} {{< tabs >}} @@ -128,13 +192,58 @@ IsIncreasing asserts that the collection is increasing. |--|--| | [`assertions.IsIncreasing(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsIncreasing) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsIncreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L23) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsIncreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L24) +{{% /tab %}} +{{< /tabs >}} + +### IsIncreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isincreasingtorderedslice-~e-e-ordered} + +IsIncreasingT asserts that a slice of [Ordered] is strictly increasing. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.IsIncreasingT(t, []int{1, 2, 3}) + assertions.IsIncreasingT(t, []float{1, 2}) + assertions.IsIncreasingT(t, []string{"a", "b"}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{1, 2, 3} + failure: []int{1, 1, 2} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsIncreasingT) | package-level function | +| [`assert.IsIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsIncreasingTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsIncreasingT) | package-level function | +| [`require.IsIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsIncreasingTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.IsIncreasingT(t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsIncreasingT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsIncreasingT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L44) {{% /tab %}} {{< /tabs >}} ### IsNonDecreasing{#isnondecreasing} -IsNonDecreasing asserts that the collection is not decreasing. +IsNonDecreasing asserts that the collection is not strictly decreasing. {{% expand title="Examples" %}} {{< tabs >}} @@ -148,7 +257,7 @@ IsNonDecreasing asserts that the collection is not decreasing. {{% tab title="Examples" %}} ```go success: []int{1, 1, 2} - failure: []int{2, 1, 1} + failure: []int{2, 1, 0} ``` {{< /tab >}} {{< /tabs >}} @@ -177,7 +286,52 @@ IsNonDecreasing asserts that the collection is not decreasing. |--|--| | [`assertions.IsNonDecreasing(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasing) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L83) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L216) +{{% /tab %}} +{{< /tabs >}} + +### IsNonDecreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isnondecreasingtorderedslice-~e-e-ordered} + +IsNonDecreasingT asserts that a slice of [Ordered] is not decreasing. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.IsNonDecreasingT(t, []int{1, 1, 2}) + assertions.IsNonDecreasingT(t, []float{1, 2}) + assertions.IsNonDecreasingT(t, []string{"a", "b"}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{1, 1, 2} + failure: []int{2, 1, 0} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsNonDecreasingT) | package-level function | +| [`assert.IsNonDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsNonDecreasingTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsNonDecreasingT) | package-level function | +| [`require.IsNonDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsNonDecreasingTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.IsNonDecreasingT(t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasingT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasingT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L236) {{% /tab %}} {{< /tabs >}} @@ -226,7 +380,146 @@ IsNonIncreasing asserts that the collection is not increasing. |--|--| | [`assertions.IsNonIncreasing(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsNonIncreasing) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonIncreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L43) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonIncreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L124) +{{% /tab %}} +{{< /tabs >}} + +### IsNonIncreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isnonincreasingtorderedslice-~e-e-ordered} + +IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.IsNonIncreasing(t, []int{2, 1, 1}) + assertions.IsNonIncreasing(t, []float{2, 1}) + assertions.IsNonIncreasing(t, []string{"b", "a"}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{2, 1, 1} + failure: []int{1, 2, 3} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsNonIncreasingT) | package-level function | +| [`assert.IsNonIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsNonIncreasingTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsNonIncreasingT) | package-level function | +| [`require.IsNonIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsNonIncreasingTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.IsNonIncreasingT(t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsNonIncreasingT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonIncreasingT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L144) +{{% /tab %}} +{{< /tabs >}} + +### NotSortedT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#notsortedtorderedslice-~e-e-ordered} + +NotSortedT asserts that the slice of [Ordered] is NOT sorted (i.e. non-strictly increasing). + +Unlike [IsDecreasingT], it accepts slices that are neither increasing nor decreasing. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.NotSortedT(t, []int{3, 2, 3}) + assertions.NotSortedT(t, []float{2, 1}) + assertions.NotSortedT(t, []string{"b", "a"}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{3, 1, 3} + failure: []int{1, 4, 8} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NotSortedT) | package-level function | +| [`assert.NotSortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#NotSortedTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NotSortedT) | package-level function | +| [`require.NotSortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#NotSortedTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.NotSortedT(t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotSortedT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSortedT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L99) +{{% /tab %}} +{{< /tabs >}} + +### SortedT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#sortedtorderedslice-~e-e-ordered} + +SortedT asserts that the slice of [Ordered] is sorted (i.e. non-strictly increasing). + +Unlike [IsIncreasingT], it accepts elements to be equal. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SortedT(t, []int{1, 2, 3}) + assertions.SortedT(t, []float{1, 2}) + assertions.SortedT(t, []string{"a", "b"}) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: []int{1, 1, 3} + failure: []int{1, 4, 2} +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SortedT) | package-level function | +| [`assert.SortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SortedTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SortedT) | package-level function | +| [`require.SortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SortedTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SortedT(t T, collection OrderedSlice, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SortedT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SortedT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L72) {{% /tab %}} {{< /tabs >}} @@ -246,5 +539,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/panic.md b/docs/doc-site/api/panic.md index 88dd74bfa..a08e06852 100644 --- a/docs/doc-site/api/panic.md +++ b/docs/doc-site/api/panic.md @@ -1,7 +1,7 @@ --- title: "Panic" description: "Asserting A Panic Behavior" -modified: 2026-01-18 +modified: 2026-01-19 weight: 12 domains: - "panic" @@ -241,5 +241,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/string.md b/docs/doc-site/api/string.md index 0a98d70f9..695e85c5b 100644 --- a/docs/doc-site/api/string.md +++ b/docs/doc-site/api/string.md @@ -1,7 +1,7 @@ --- title: "String" description: "Asserting Strings" -modified: 2026-01-18 +modified: 2026-01-19 weight: 13 domains: - "string" @@ -241,5 +241,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/testing.md b/docs/doc-site/api/testing.md index 8edd73bcd..79475dc39 100644 --- a/docs/doc-site/api/testing.md +++ b/docs/doc-site/api/testing.md @@ -1,7 +1,7 @@ --- title: "Testing" description: "Mimicks Methods From The Testing Standard Library" -modified: 2026-01-18 +modified: 2026-01-19 weight: 14 domains: - "testing" @@ -136,5 +136,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/time.md b/docs/doc-site/api/time.md index 4c0f8fca9..1f38010b5 100644 --- a/docs/doc-site/api/time.md +++ b/docs/doc-site/api/time.md @@ -1,7 +1,7 @@ --- title: "Time" description: "Asserting Times And Durations" -modified: 2026-01-18 +modified: 2026-01-19 weight: 15 domains: - "time" @@ -138,5 +138,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/type.md b/docs/doc-site/api/type.md index 56fbddbc1..32525ae5d 100644 --- a/docs/doc-site/api/type.md +++ b/docs/doc-site/api/type.md @@ -1,7 +1,7 @@ --- title: "Type" description: "Asserting Types Rather Than Values" -modified: 2026-01-18 +modified: 2026-01-19 weight: 16 domains: - "type" @@ -444,5 +444,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/docs/doc-site/api/yaml.md b/docs/doc-site/api/yaml.md index 1fe014f07..3cac1a41c 100644 --- a/docs/doc-site/api/yaml.md +++ b/docs/doc-site/api/yaml.md @@ -1,7 +1,7 @@ --- title: "Yaml" description: "Asserting Yaml Documents" -modified: 2026-01-18 +modified: 2026-01-19 weight: 17 domains: - "yaml" @@ -202,5 +202,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] --> diff --git a/internal/assertions/order.go b/internal/assertions/order.go index e07f49548..e18560feb 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -212,7 +212,7 @@ func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m // # Examples // // success: []int{1, 1, 2} -// failure: []int{2, 1, 1} +// failure: []int{2, 1, 0} func IsNonDecreasing(t T, object any, msgAndArgs ...any) bool { // Domain: ordering if h, ok := t.(H); ok { @@ -232,7 +232,7 @@ func IsNonDecreasing(t T, object any, msgAndArgs ...any) bool { // # Examples // // success: []int{1, 1, 2} -// failure: []int{2, 1, 1} +// failure: []int{2, 1, 0} func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) bool { // Domain: ordering if h, ok := t.(H); ok { diff --git a/require/require_assertions.go b/require/require_assertions.go index 6e7036f77..6a796fe39 100644 --- a/require/require_assertions.go +++ b/require/require_assertions.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -88,6 +88,30 @@ func DirExists(t T, path string, msgAndArgs ...any) { t.FailNow() } +// DirNotExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +// +// # Usage +// +// assertions.DirNotExists(t, "path/to/directory") +// +// # Examples +// +// success: filepath.Join(testDataPath(),"non_existing_dir") +// failure: filepath.Join(testDataPath(),"existing_dir") +// +// Upon failure, the test [T] is marked as failed and stops execution. +func DirNotExists(t T, path string, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.DirNotExists(t, path, msgAndArgs...) { + return + } + + t.FailNow() +} + // ElementsMatch asserts that the specified listA(array, slice...) is equal to specified // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. @@ -254,6 +278,38 @@ func EqualExportedValues(t T, expected any, actual any, msgAndArgs ...any) { t.FailNow() } +// EqualT asserts that two objects of the same comparable type are equal. +// +// Pointer variable equality is determined based on the equality of the memory addresses (unlike [Equal], but like [Same]). +// +// Functions, slices and maps are not comparable. See also [ComparisonOperators]. +// +// If you need to compare values of non-comparable types, or compare pointers by the value they point to, +// use [Equal] instead. +// +// # Usage +// +// assertions.EqualT(t, 123, 123) +// +// # Examples +// +// success: 123, 123 +// failure: 123, 456 +// +// Upon failure, the test [T] is marked as failed and stops execution. +// +// [ComparisonOperators]: https://go.dev/ref/spec#Comparison_operators. +func EqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.EqualT(t, expected, actual, msgAndArgs...) { + return + } + + t.FailNow() +} + // EqualValues asserts that two objects are equal or convertible to the larger // type and equal. // @@ -650,6 +706,30 @@ func FileNotEmpty(t T, path string, msgAndArgs ...any) { t.FailNow() } +// FileNotExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +// +// # Usage +// +// assertions.FileNotExists(t, "path/to/file") +// +// # Examples +// +// success: filepath.Join(testDataPath(),"non_existing_file") +// failure: filepath.Join(testDataPath(),"existing_file") +// +// Upon failure, the test [T] is marked as failed and stops execution. +func FileNotExists(t T, path string, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.FileNotExists(t, path, msgAndArgs...) { + return + } + + t.FailNow() +} + // Greater asserts that the first element is strictly greater than the second. // // Both elements must be of the same type in the [reflect.Kind] sense. @@ -1178,7 +1258,7 @@ func InEpsilonT[Number Measurable](t T, expected Number, actual Number, epsilon t.FailNow() } -// IsDecreasing asserts that the collection is decreasing. +// IsDecreasing asserts that the collection is strictly decreasing. // // # Usage // @@ -1203,7 +1283,32 @@ func IsDecreasing(t T, object any, msgAndArgs ...any) { t.FailNow() } -// IsIncreasing asserts that the collection is increasing. +// IsDecreasingT asserts that a slice of [Ordered] is strictly decreasing. +// +// # Usage +// +// assertions.IsDecreasingT(t, []int{2, 1, 0}) +// assertions.IsDecreasingT(t, []float{2, 1}) +// assertions.IsDecreasingT(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{3, 2, 1} +// failure: []int{1, 2, 3} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsDecreasingT(t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + +// IsIncreasing asserts that the collection is strictly increasing. // // # Usage // @@ -1228,7 +1333,32 @@ func IsIncreasing(t T, object any, msgAndArgs ...any) { t.FailNow() } -// IsNonDecreasing asserts that the collection is not decreasing. +// IsIncreasingT asserts that a slice of [Ordered] is strictly increasing. +// +// # Usage +// +// assertions.IsIncreasingT(t, []int{1, 2, 3}) +// assertions.IsIncreasingT(t, []float{1, 2}) +// assertions.IsIncreasingT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 2, 3} +// failure: []int{1, 1, 2} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsIncreasingT(t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + +// IsNonDecreasing asserts that the collection is not strictly decreasing. // // # Usage // @@ -1239,7 +1369,7 @@ func IsIncreasing(t T, object any, msgAndArgs ...any) { // # Examples // // success: []int{1, 1, 2} -// failure: []int{2, 1, 1} +// failure: []int{2, 1, 0} // // Upon failure, the test [T] is marked as failed and stops execution. func IsNonDecreasing(t T, object any, msgAndArgs ...any) { @@ -1253,6 +1383,31 @@ func IsNonDecreasing(t T, object any, msgAndArgs ...any) { t.FailNow() } +// IsNonDecreasingT asserts that a slice of [Ordered] is not decreasing. +// +// # Usage +// +// assertions.IsNonDecreasingT(t, []int{1, 1, 2}) +// assertions.IsNonDecreasingT(t, []float{1, 2}) +// assertions.IsNonDecreasingT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 1, 2} +// failure: []int{2, 1, 0} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsNonDecreasingT(t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + // IsNonIncreasing asserts that the collection is not increasing. // // # Usage @@ -1278,6 +1433,31 @@ func IsNonIncreasing(t T, object any, msgAndArgs ...any) { t.FailNow() } +// IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. +// +// # Usage +// +// assertions.IsNonIncreasing(t, []int{2, 1, 1}) +// assertions.IsNonIncreasing(t, []float{2, 1}) +// assertions.IsNonIncreasing(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{2, 1, 1} +// failure: []int{1, 2, 3} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsNonIncreasingT(t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + // IsNotType asserts that the specified objects are not of the same type. // // # Usage @@ -1583,6 +1763,52 @@ func LessT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs ...any t.FailNow() } +// MapContainsT asserts that the specified map contains a key. +// +// # Usage +// +// assertions.MapContainsT(t, map[string]string{"Hello": "x","World": "y"}, "World") +// +// # Examples +// +// success: map[string]string{"A": "B"}, "A" +// failure: map[string]string{"A": "B"}, "C" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.MapContainsT(t, m, key, msgAndArgs...) { + return + } + + t.FailNow() +} + +// MapNotContainsT asserts that the specified map does not contain a key. +// +// # Usage +// +// assertions.MapNotContainsT(t, map[string]string{"Hello": "x","World": "y"}, "hi") +// +// # Examples +// +// success: map[string]string{"A": "B"}, "C" +// failure: map[string]string{"A": "B"}, "A" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.MapNotContainsT(t, m, key, msgAndArgs...) { + return + } + + t.FailNow() +} + // Negative asserts that the specified element is strictly negative. // // # Usage @@ -1690,30 +1916,6 @@ func Nil(t T, object any, msgAndArgs ...any) { t.FailNow() } -// NoDirExists checks whether a directory does not exist in the given path. -// It fails if the path points to an existing _directory_ only. -// -// # Usage -// -// assertions.NoDirExists(t, "path/to/directory") -// -// # Examples -// -// success: filepath.Join(testDataPath(),"non_existing_dir") -// failure: filepath.Join(testDataPath(),"existing_dir") -// -// Upon failure, the test [T] is marked as failed and stops execution. -func NoDirExists(t T, path string, msgAndArgs ...any) { - if h, ok := t.(H); ok { - h.Helper() - } - if assertions.NoDirExists(t, path, msgAndArgs...) { - return - } - - t.FailNow() -} - // NoError asserts that a function returned a nil error (ie. no error). // // # Usage @@ -1740,30 +1942,6 @@ func NoError(t T, err error, msgAndArgs ...any) { t.FailNow() } -// NoFileExists checks whether a file does not exist in a given path. It fails -// if the path points to an existing _file_ only. -// -// # Usage -// -// assertions.NoFileExists(t, "path/to/file") -// -// # Examples -// -// success: filepath.Join(testDataPath(),"non_existing_file") -// failure: filepath.Join(testDataPath(),"existing_file") -// -// Upon failure, the test [T] is marked as failed and stops execution. -func NoFileExists(t T, path string, msgAndArgs ...any) { - if h, ok := t.(H); ok { - h.Helper() - } - if assertions.NoFileExists(t, path, msgAndArgs...) { - return - } - - t.FailNow() -} - // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // @@ -1897,6 +2075,31 @@ func NotEqual(t T, expected any, actual any, msgAndArgs ...any) { t.FailNow() } +// NotEqualT asserts that the specified values of the same comparable type are NOT equal. +// +// See [EqualT]. +// +// # Usage +// +// assertions.NotEqualT(t, obj1, obj2) +// +// # Examples +// +// success: 123, 456 +// failure: 123, 123 +// +// Upon failure, the test [T] is marked as failed and stops execution. +func NotEqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.NotEqualT(t, expected, actual, msgAndArgs...) { + return + } + + t.FailNow() +} + // NotEqualValues asserts that two objects are not equal even when converted to the same type. // // # Usage @@ -2142,6 +2345,58 @@ func NotSame(t T, expected any, actual any, msgAndArgs ...any) { t.FailNow() } +// NotSameT asserts that two pointers do not reference the same object. +// +// See [SameT] +// +// # Usage +// +// assertions.NotSameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, ptr("static string") +// failure: &staticVar, staticVarPtr +// +// Upon failure, the test [T] is marked as failed and stops execution. +func NotSameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.NotSameT(t, expected, actual, msgAndArgs...) { + return + } + + t.FailNow() +} + +// NotSortedT asserts that the slice of [Ordered] is NOT sorted (i.e. non-strictly increasing). +// +// Unlike [IsDecreasingT], it accepts slices that are neither increasing nor decreasing. +// +// # Usage +// +// assertions.NotSortedT(t, []int{3, 2, 3}) +// assertions.NotSortedT(t, []float{2, 1}) +// assertions.NotSortedT(t, []string{"b", "a"}) +// +// # Examples +// +// success: []int{3, 1, 3} +// failure: []int{1, 4, 8} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.NotSortedT(t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + // NotSubset asserts that the list (array, slice, or map) does NOT contain all // elements given in the subset (array, slice, or map). // Map elements are key-value pairs unless compared with an array or slice where @@ -2391,6 +2646,198 @@ func Same(t T, expected any, actual any, msgAndArgs ...any) { t.FailNow() } +// SameT asserts that two pointers of the same type reference the same object. +// +// # Usage +// +// assertions.SameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, staticVarPtr +// failure: &staticVar, ptr("static string") +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SameT(t, expected, actual, msgAndArgs...) { + return + } + + t.FailNow() +} + +// SliceContainsT asserts that the specified slice contains a comparable element. +// +// # Usage +// +// assertions.SliceContainsT(t, []{"Hello","World"}, "World") +// +// # Examples +// +// success: []string{"A","B"}, "A" +// failure: []string{"A","B"}, "C" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceContainsT(t, s, element, msgAndArgs...) { + return + } + + t.FailNow() +} + +// SliceNotContainsT asserts that the specified slice does not contain a comparable element. +// +// # Usage +// +// assertions.SliceNotContainsT(t, []{"Hello","World"}, "hi") +// +// # Examples +// +// success: []string{"A","B"}, "C" +// failure: []string{"A","B"}, "A" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceNotContainsT(t, s, element, msgAndArgs...) { + return + } + + t.FailNow() +} + +// SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset. +// +// # Usage +// +// assertions.SliceNotSubsetT(t, []int{1, 2, 3}, []int{1, 4}) +// +// # Examples +// +// success: []int{1, 2, 3}, []int{4, 5} +// failure: []int{1, 2, 3}, []int{1, 2} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceNotSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceNotSubsetT(t, list, subset, msgAndArgs...) { + return + } + + t.FailNow() +} + +// SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset. +// +// # Usage +// +// assertions.SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) +// +// # Examples +// +// success: []int{1, 2, 3}, []int{1, 2} +// failure: []int{1, 2, 3}, []int{4, 5} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceSubsetT(t, list, subset, msgAndArgs...) { + return + } + + t.FailNow() +} + +// SortedT asserts that the slice of [Ordered] is sorted (i.e. non-strictly increasing). +// +// Unlike [IsIncreasingT], it accepts elements to be equal. +// +// # Usage +// +// assertions.SortedT(t, []int{1, 2, 3}) +// assertions.SortedT(t, []float{1, 2}) +// assertions.SortedT(t, []string{"a", "b"}) +// +// # Examples +// +// success: []int{1, 1, 3} +// failure: []int{1, 4, 2} +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SortedT(t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + +// StringContainsT asserts that a string contains the specified substring. +// +// Strings may be go strings or []byte. +// +// # Usage +// +// assertions.StringContainsT(t, "Hello World", "World") +// +// # Examples +// +// success: "AB", "A" +// failure: "AB", "C" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.StringContainsT(t, str, substring, msgAndArgs...) { + return + } + + t.FailNow() +} + +// StringNotContainsT asserts that a string does not contain the specified substring. +// +// Strings may be go strings or []byte. +// +// # Usage +// +// assertions.StringNotContainsT(t, "Hello World", "hi") +// +// # Examples +// +// success: "AB", "C" +// failure: "AB", "A" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.StringNotContainsT(t, str, substring, msgAndArgs...) { + return + } + + t.FailNow() +} + // Subset asserts that the list (array, slice, or map) contains all elements // given in the subset (array, slice, or map). // diff --git a/require/require_assertions_test.go b/require/require_assertions_test.go index 5436ee3fd..a487b0bb2 100644 --- a/require/require_assertions_test.go +++ b/require/require_assertions_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -77,6 +77,26 @@ func TestDirExists(t *testing.T) { }) } +func TestDirNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + DirNotExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + DirNotExists(mock, filepath.Join(testDataPath(), "existing_dir")) + // require functions don't return a value + if !mock.failed { + t.Error("DirNotExists should call FailNow()") + } + }) +} + func TestElementsMatch(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -197,6 +217,26 @@ func TestEqualExportedValues(t *testing.T) { }) } +func TestEqualT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + EqualT(t, 123, 123) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + EqualT(mock, 123, 456) + // require functions don't return a value + if !mock.failed { + t.Error("EqualT should call FailNow()") + } + }) +} + func TestEqualValues(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -485,6 +525,26 @@ func TestFileNotEmpty(t *testing.T) { }) } +func TestFileNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + FileNotExists(t, filepath.Join(testDataPath(), "non_existing_file")) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + FileNotExists(mock, filepath.Join(testDataPath(), "existing_file")) + // require functions don't return a value + if !mock.failed { + t.Error("FileNotExists should call FailNow()") + } + }) +} + func TestGreater(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -865,6 +925,26 @@ func TestIsDecreasing(t *testing.T) { }) } +func TestIsDecreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsDecreasingT(t, []int{3, 2, 1}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsDecreasingT(mock, []int{1, 2, 3}) + // require functions don't return a value + if !mock.failed { + t.Error("IsDecreasingT should call FailNow()") + } + }) +} + func TestIsIncreasing(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -885,6 +965,26 @@ func TestIsIncreasing(t *testing.T) { }) } +func TestIsIncreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsIncreasingT(t, []int{1, 2, 3}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsIncreasingT(mock, []int{1, 1, 2}) + // require functions don't return a value + if !mock.failed { + t.Error("IsIncreasingT should call FailNow()") + } + }) +} + func TestIsNonDecreasing(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -897,7 +997,7 @@ func TestIsNonDecreasing(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - IsNonDecreasing(mock, []int{2, 1, 1}) + IsNonDecreasing(mock, []int{2, 1, 0}) // require functions don't return a value if !mock.failed { t.Error("IsNonDecreasing should call FailNow()") @@ -905,6 +1005,26 @@ func TestIsNonDecreasing(t *testing.T) { }) } +func TestIsNonDecreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsNonDecreasingT(t, []int{1, 1, 2}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsNonDecreasingT(mock, []int{2, 1, 0}) + // require functions don't return a value + if !mock.failed { + t.Error("IsNonDecreasingT should call FailNow()") + } + }) +} + func TestIsNonIncreasing(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -925,6 +1045,26 @@ func TestIsNonIncreasing(t *testing.T) { }) } +func TestIsNonIncreasingT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsNonIncreasingT(t, []int{2, 1, 1}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsNonIncreasingT(mock, []int{1, 2, 3}) + // require functions don't return a value + if !mock.failed { + t.Error("IsNonIncreasingT should call FailNow()") + } + }) +} + func TestIsNotType(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1145,11 +1285,11 @@ func TestLessT(t *testing.T) { }) } -func TestNegative(t *testing.T) { +func TestMapContainsT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - Negative(t, -1) + MapContainsT(t, map[string]string{"A": "B"}, "A") // require functions don't return a value }) @@ -1157,19 +1297,19 @@ func TestNegative(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - Negative(mock, 1) + MapContainsT(mock, map[string]string{"A": "B"}, "C") // require functions don't return a value if !mock.failed { - t.Error("Negative should call FailNow()") + t.Error("MapContainsT should call FailNow()") } }) } -func TestNegativeT(t *testing.T) { +func TestMapNotContainsT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NegativeT(t, -1) + MapNotContainsT(t, map[string]string{"A": "B"}, "C") // require functions don't return a value }) @@ -1177,19 +1317,19 @@ func TestNegativeT(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NegativeT(mock, 1) + MapNotContainsT(mock, map[string]string{"A": "B"}, "A") // require functions don't return a value if !mock.failed { - t.Error("NegativeT should call FailNow()") + t.Error("MapNotContainsT should call FailNow()") } }) } -func TestNever(t *testing.T) { +func TestNegative(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - Never(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond) + Negative(t, -1) // require functions don't return a value }) @@ -1197,19 +1337,19 @@ func TestNever(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - Never(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond) + Negative(mock, 1) // require functions don't return a value if !mock.failed { - t.Error("Never should call FailNow()") + t.Error("Negative should call FailNow()") } }) } -func TestNil(t *testing.T) { +func TestNegativeT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - Nil(t, nil) + NegativeT(t, -1) // require functions don't return a value }) @@ -1217,19 +1357,19 @@ func TestNil(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - Nil(mock, "not nil") + NegativeT(mock, 1) // require functions don't return a value if !mock.failed { - t.Error("Nil should call FailNow()") + t.Error("NegativeT should call FailNow()") } }) } -func TestNoDirExists(t *testing.T) { +func TestNever(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NoDirExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + Never(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond) // require functions don't return a value }) @@ -1237,19 +1377,19 @@ func TestNoDirExists(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NoDirExists(mock, filepath.Join(testDataPath(), "existing_dir")) + Never(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond) // require functions don't return a value if !mock.failed { - t.Error("NoDirExists should call FailNow()") + t.Error("Never should call FailNow()") } }) } -func TestNoError(t *testing.T) { +func TestNil(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NoError(t, nil) + Nil(t, nil) // require functions don't return a value }) @@ -1257,19 +1397,19 @@ func TestNoError(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NoError(mock, ErrTest) + Nil(mock, "not nil") // require functions don't return a value if !mock.failed { - t.Error("NoError should call FailNow()") + t.Error("Nil should call FailNow()") } }) } -func TestNoFileExists(t *testing.T) { +func TestNoError(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NoFileExists(t, filepath.Join(testDataPath(), "non_existing_file")) + NoError(t, nil) // require functions don't return a value }) @@ -1277,10 +1417,10 @@ func TestNoFileExists(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NoFileExists(mock, filepath.Join(testDataPath(), "existing_file")) + NoError(mock, ErrTest) // require functions don't return a value if !mock.failed { - t.Error("NoFileExists should call FailNow()") + t.Error("NoError should call FailNow()") } }) } @@ -1385,6 +1525,26 @@ func TestNotEqual(t *testing.T) { }) } +func TestNotEqualT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + NotEqualT(t, 123, 456) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + NotEqualT(mock, 123, 123) + // require functions don't return a value + if !mock.failed { + t.Error("NotEqualT should call FailNow()") + } + }) +} + func TestNotEqualValues(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1585,6 +1745,46 @@ func TestNotSame(t *testing.T) { }) } +func TestNotSameT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + NotSameT(t, &staticVar, ptr("static string")) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + NotSameT(mock, &staticVar, staticVarPtr) + // require functions don't return a value + if !mock.failed { + t.Error("NotSameT should call FailNow()") + } + }) +} + +func TestNotSortedT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + NotSortedT(t, []int{3, 1, 3}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + NotSortedT(mock, []int{1, 4, 8}) + // require functions don't return a value + if !mock.failed { + t.Error("NotSortedT should call FailNow()") + } + }) +} + func TestNotSubset(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1785,6 +1985,166 @@ func TestSame(t *testing.T) { }) } +func TestSameT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SameT(t, &staticVar, staticVarPtr) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SameT(mock, &staticVar, ptr("static string")) + // require functions don't return a value + if !mock.failed { + t.Error("SameT should call FailNow()") + } + }) +} + +func TestSliceContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceContainsT(t, []string{"A", "B"}, "A") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceContainsT(mock, []string{"A", "B"}, "C") + // require functions don't return a value + if !mock.failed { + t.Error("SliceContainsT should call FailNow()") + } + }) +} + +func TestSliceNotContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceNotContainsT(t, []string{"A", "B"}, "C") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceNotContainsT(mock, []string{"A", "B"}, "A") + // require functions don't return a value + if !mock.failed { + t.Error("SliceNotContainsT should call FailNow()") + } + }) +} + +func TestSliceNotSubsetT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceNotSubsetT(t, []int{1, 2, 3}, []int{4, 5}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceNotSubsetT(mock, []int{1, 2, 3}, []int{1, 2}) + // require functions don't return a value + if !mock.failed { + t.Error("SliceNotSubsetT should call FailNow()") + } + }) +} + +func TestSliceSubsetT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceSubsetT(mock, []int{1, 2, 3}, []int{4, 5}) + // require functions don't return a value + if !mock.failed { + t.Error("SliceSubsetT should call FailNow()") + } + }) +} + +func TestSortedT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SortedT(t, []int{1, 1, 3}) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SortedT(mock, []int{1, 4, 2}) + // require functions don't return a value + if !mock.failed { + t.Error("SortedT should call FailNow()") + } + }) +} + +func TestStringContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + StringContainsT(t, "AB", "A") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + StringContainsT(mock, "AB", "C") + // require functions don't return a value + if !mock.failed { + t.Error("StringContainsT should call FailNow()") + } + }) +} + +func TestStringNotContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + StringNotContainsT(t, "AB", "C") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + StringNotContainsT(mock, "AB", "A") + // require functions don't return a value + if !mock.failed { + t.Error("StringNotContainsT should call FailNow()") + } + }) +} + func TestSubset(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/require/require_examples_test.go b/require/require_examples_test.go index 083d4860f..e1c56cfa6 100644 --- a/require/require_examples_test.go +++ b/require/require_examples_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require_test @@ -46,6 +46,14 @@ func ExampleDirExists() { // Output: passed } +func ExampleDirNotExists() { + t := new(testing.T) + require.DirNotExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + fmt.Println("passed") + + // Output: passed +} + func ExampleElementsMatch() { t := new(testing.T) require.ElementsMatch(t, []int{1, 3, 2, 3}, []int{1, 3, 3, 2}) @@ -94,6 +102,14 @@ func ExampleEqualExportedValues() { // Output: passed } +func ExampleEqualT() { + t := new(testing.T) + require.EqualT(t, 123, 123) + fmt.Println("passed") + + // Output: passed +} + func ExampleEqualValues() { t := new(testing.T) require.EqualValues(t, uint32(123), int32(123)) @@ -210,6 +226,14 @@ func ExampleFileNotEmpty() { // Output: passed } +func ExampleFileNotExists() { + t := new(testing.T) + require.FileNotExists(t, filepath.Join(testDataPath(), "non_existing_file")) + fmt.Println("passed") + + // Output: passed +} + func ExampleGreater() { t := new(testing.T) require.Greater(t, 2, 1) @@ -362,6 +386,14 @@ func ExampleIsDecreasing() { // Output: passed } +func ExampleIsDecreasingT() { + t := new(testing.T) + require.IsDecreasingT(t, []int{3, 2, 1}) + fmt.Println("passed") + + // Output: passed +} + func ExampleIsIncreasing() { t := new(testing.T) require.IsIncreasing(t, []int{1, 2, 3}) @@ -370,6 +402,14 @@ func ExampleIsIncreasing() { // Output: passed } +func ExampleIsIncreasingT() { + t := new(testing.T) + require.IsIncreasingT(t, []int{1, 2, 3}) + fmt.Println("passed") + + // Output: passed +} + func ExampleIsNonDecreasing() { t := new(testing.T) require.IsNonDecreasing(t, []int{1, 1, 2}) @@ -378,6 +418,14 @@ func ExampleIsNonDecreasing() { // Output: passed } +func ExampleIsNonDecreasingT() { + t := new(testing.T) + require.IsNonDecreasingT(t, []int{1, 1, 2}) + fmt.Println("passed") + + // Output: passed +} + func ExampleIsNonIncreasing() { t := new(testing.T) require.IsNonIncreasing(t, []int{2, 1, 1}) @@ -386,6 +434,14 @@ func ExampleIsNonIncreasing() { // Output: passed } +func ExampleIsNonIncreasingT() { + t := new(testing.T) + require.IsNonIncreasingT(t, []int{2, 1, 1}) + fmt.Println("passed") + + // Output: passed +} + func ExampleIsNotType() { t := new(testing.T) require.IsNotType(t, int32(123), int64(456)) @@ -474,59 +530,59 @@ func ExampleLessT() { // Output: passed } -func ExampleNegative() { +func ExampleMapContainsT() { t := new(testing.T) - require.Negative(t, -1) + require.MapContainsT(t, map[string]string{"A": "B"}, "A") fmt.Println("passed") // Output: passed } -func ExampleNegativeT() { +func ExampleMapNotContainsT() { t := new(testing.T) - require.NegativeT(t, -1) + require.MapNotContainsT(t, map[string]string{"A": "B"}, "C") fmt.Println("passed") // Output: passed } -func ExampleNever() { +func ExampleNegative() { t := new(testing.T) - require.Never(t, func() bool { - return false - }, 100*time.Millisecond, 20*time.Millisecond) + require.Negative(t, -1) fmt.Println("passed") // Output: passed } -func ExampleNil() { +func ExampleNegativeT() { t := new(testing.T) - require.Nil(t, nil) + require.NegativeT(t, -1) fmt.Println("passed") // Output: passed } -func ExampleNoDirExists() { +func ExampleNever() { t := new(testing.T) - require.NoDirExists(t, filepath.Join(testDataPath(), "non_existing_dir")) + require.Never(t, func() bool { + return false + }, 100*time.Millisecond, 20*time.Millisecond) fmt.Println("passed") // Output: passed } -func ExampleNoError() { +func ExampleNil() { t := new(testing.T) - require.NoError(t, nil) + require.Nil(t, nil) fmt.Println("passed") // Output: passed } -func ExampleNoFileExists() { +func ExampleNoError() { t := new(testing.T) - require.NoFileExists(t, filepath.Join(testDataPath(), "non_existing_file")) + require.NoError(t, nil) fmt.Println("passed") // Output: passed @@ -572,6 +628,14 @@ func ExampleNotEqual() { // Output: passed } +func ExampleNotEqualT() { + t := new(testing.T) + require.NotEqualT(t, 123, 456) + fmt.Println("passed") + + // Output: passed +} + func ExampleNotEqualValues() { t := new(testing.T) require.NotEqualValues(t, uint32(123), int32(456)) @@ -653,6 +717,22 @@ func ExampleNotSame() { // Output: passed } +func ExampleNotSameT() { + t := new(testing.T) + require.NotSameT(t, &staticVar, ptr("static string")) + fmt.Println("passed") + + // Output: passed +} + +func ExampleNotSortedT() { + t := new(testing.T) + require.NotSortedT(t, []int{3, 1, 3}) + fmt.Println("passed") + + // Output: passed +} + func ExampleNotSubset() { t := new(testing.T) require.NotSubset(t, []int{1, 2, 3}, []int{4, 5}) @@ -739,6 +819,70 @@ func ExampleSame() { // Output: passed } +func ExampleSameT() { + t := new(testing.T) + require.SameT(t, &staticVar, staticVarPtr) + fmt.Println("passed") + + // Output: passed +} + +func ExampleSliceContainsT() { + t := new(testing.T) + require.SliceContainsT(t, []string{"A", "B"}, "A") + fmt.Println("passed") + + // Output: passed +} + +func ExampleSliceNotContainsT() { + t := new(testing.T) + require.SliceNotContainsT(t, []string{"A", "B"}, "C") + fmt.Println("passed") + + // Output: passed +} + +func ExampleSliceNotSubsetT() { + t := new(testing.T) + require.SliceNotSubsetT(t, []int{1, 2, 3}, []int{4, 5}) + fmt.Println("passed") + + // Output: passed +} + +func ExampleSliceSubsetT() { + t := new(testing.T) + require.SliceSubsetT(t, []int{1, 2, 3}, []int{1, 2}) + fmt.Println("passed") + + // Output: passed +} + +func ExampleSortedT() { + t := new(testing.T) + require.SortedT(t, []int{1, 1, 3}) + fmt.Println("passed") + + // Output: passed +} + +func ExampleStringContainsT() { + t := new(testing.T) + require.StringContainsT(t, "AB", "A") + fmt.Println("passed") + + // Output: passed +} + +func ExampleStringNotContainsT() { + t := new(testing.T) + require.StringNotContainsT(t, "AB", "C") + fmt.Println("passed") + + // Output: passed +} + func ExampleSubset() { t := new(testing.T) require.Subset(t, []int{1, 2, 3}, []int{1, 2}) diff --git a/require/require_format.go b/require/require_format.go index cbf3cfe93..19af5302c 100644 --- a/require/require_format.go +++ b/require/require_format.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -57,6 +57,20 @@ func DirExistsf(t T, path string, msg string, args ...any) { t.FailNow() } +// DirNotExistsf is the same as [DirNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func DirNotExistsf(t T, path string, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.DirNotExists(t, path, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // ElementsMatchf is the same as [ElementsMatch], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -141,6 +155,20 @@ func EqualExportedValuesf(t T, expected any, actual any, msg string, args ...any t.FailNow() } +// EqualTf is the same as [EqualT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func EqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.EqualT(t, expected, actual, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // EqualValuesf is the same as [EqualValues], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -347,6 +375,20 @@ func FileNotEmptyf(t T, path string, msg string, args ...any) { t.FailNow() } +// FileNotExistsf is the same as [FileNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func FileNotExistsf(t T, path string, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.FileNotExists(t, path, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // Greaterf is the same as [Greater], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -613,6 +655,20 @@ func IsDecreasingf(t T, object any, msg string, args ...any) { t.FailNow() } +// IsDecreasingTf is the same as [IsDecreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsDecreasingT(t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // IsIncreasingf is the same as [IsIncreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -627,6 +683,20 @@ func IsIncreasingf(t T, object any, msg string, args ...any) { t.FailNow() } +// IsIncreasingTf is the same as [IsIncreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsIncreasingT(t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // IsNonDecreasingf is the same as [IsNonDecreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -641,6 +711,20 @@ func IsNonDecreasingf(t T, object any, msg string, args ...any) { t.FailNow() } +// IsNonDecreasingTf is the same as [IsNonDecreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsNonDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsNonDecreasingT(t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // IsNonIncreasingf is the same as [IsNonIncreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -655,6 +739,20 @@ func IsNonIncreasingf(t T, object any, msg string, args ...any) { t.FailNow() } +// IsNonIncreasingTf is the same as [IsNonIncreasingT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsNonIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsNonIncreasingT(t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // IsNotTypef is the same as [IsNotType], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -809,98 +907,98 @@ func LessTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg string, args t.FailNow() } -// Negativef is the same as [Negative], but it accepts a format msg string to format arguments like [fmt.Printf]. +// MapContainsTf is the same as [MapContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func Negativef(t T, e any, msg string, args ...any) { +func MapContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.Negative(t, e, forwardArgs(msg, args)) { + if assertions.MapContainsT(t, m, key, forwardArgs(msg, args)) { return } t.FailNow() } -// NegativeTf is the same as [NegativeT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// MapNotContainsTf is the same as [MapNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func NegativeTf[SignedNumber SignedNumeric](t T, e SignedNumber, msg string, args ...any) { +func MapNotContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NegativeT(t, e, forwardArgs(msg, args)) { + if assertions.MapNotContainsT(t, m, key, forwardArgs(msg, args)) { return } t.FailNow() } -// Neverf is the same as [Never], but it accepts a format msg string to format arguments like [fmt.Printf]. +// Negativef is the same as [Negative], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func Neverf(t T, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...any) { +func Negativef(t T, e any, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.Never(t, condition, waitFor, tick, forwardArgs(msg, args)) { + if assertions.Negative(t, e, forwardArgs(msg, args)) { return } t.FailNow() } -// Nilf is the same as [Nil], but it accepts a format msg string to format arguments like [fmt.Printf]. +// NegativeTf is the same as [NegativeT], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func Nilf(t T, object any, msg string, args ...any) { +func NegativeTf[SignedNumber SignedNumeric](t T, e SignedNumber, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.Nil(t, object, forwardArgs(msg, args)) { + if assertions.NegativeT(t, e, forwardArgs(msg, args)) { return } t.FailNow() } -// NoDirExistsf is the same as [NoDirExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// Neverf is the same as [Never], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func NoDirExistsf(t T, path string, msg string, args ...any) { +func Neverf(t T, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NoDirExists(t, path, forwardArgs(msg, args)) { + if assertions.Never(t, condition, waitFor, tick, forwardArgs(msg, args)) { return } t.FailNow() } -// NoErrorf is the same as [NoError], but it accepts a format msg string to format arguments like [fmt.Printf]. +// Nilf is the same as [Nil], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func NoErrorf(t T, err error, msg string, args ...any) { +func Nilf(t T, object any, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NoError(t, err, forwardArgs(msg, args)) { + if assertions.Nil(t, object, forwardArgs(msg, args)) { return } t.FailNow() } -// NoFileExistsf is the same as [NoFileExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// NoErrorf is the same as [NoError], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. -func NoFileExistsf(t T, path string, msg string, args ...any) { +func NoErrorf(t T, err error, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NoFileExists(t, path, forwardArgs(msg, args)) { + if assertions.NoError(t, err, forwardArgs(msg, args)) { return } @@ -977,6 +1075,20 @@ func NotEqualf(t T, expected any, actual any, msg string, args ...any) { t.FailNow() } +// NotEqualTf is the same as [NotEqualT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func NotEqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.NotEqualT(t, expected, actual, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // NotEqualValuesf is the same as [NotEqualValues], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -1117,6 +1229,34 @@ func NotSamef(t T, expected any, actual any, msg string, args ...any) { t.FailNow() } +// NotSameTf is the same as [NotSameT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func NotSameTf[P any](t T, expected *P, actual *P, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.NotSameT(t, expected, actual, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// NotSortedTf is the same as [NotSortedT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func NotSortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.NotSortedT(t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // NotSubsetf is the same as [NotSubset], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -1257,6 +1397,118 @@ func Samef(t T, expected any, actual any, msg string, args ...any) { t.FailNow() } +// SameTf is the same as [SameT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SameT(t, expected, actual, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// SliceContainsTf is the same as [SliceContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceContainsT(t, s, element, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// SliceNotContainsTf is the same as [SliceNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceNotContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceNotContainsT(t, s, element, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// SliceNotSubsetTf is the same as [SliceNotSubsetT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceNotSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceNotSubsetT(t, list, subset, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// SliceSubsetTf is the same as [SliceSubsetT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SliceSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SliceSubsetT(t, list, subset, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// SortedTf is the same as [SortedT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SortedT(t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// StringContainsTf is the same as [StringContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func StringContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.StringContainsT(t, str, substring, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// StringNotContainsTf is the same as [StringNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func StringNotContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.StringNotContainsT(t, str, substring, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // Subsetf is the same as [Subset], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. diff --git a/require/require_format_test.go b/require/require_format_test.go index d9874bcc5..2cc1451fd 100644 --- a/require/require_format_test.go +++ b/require/require_format_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -77,6 +77,26 @@ func TestDirExistsf(t *testing.T) { }) } +func TestDirNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + DirNotExistsf(t, filepath.Join(testDataPath(), "non_existing_dir"), "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + DirNotExistsf(mock, filepath.Join(testDataPath(), "existing_dir"), "test message") + // require functions don't return a value + if !mock.failed { + t.Error("DirNotExists should call FailNow()") + } + }) +} + func TestElementsMatchf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -197,6 +217,26 @@ func TestEqualExportedValuesf(t *testing.T) { }) } +func TestEqualTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + EqualTf(t, 123, 123, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + EqualTf(mock, 123, 456, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("EqualT should call FailNow()") + } + }) +} + func TestEqualValuesf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -485,6 +525,26 @@ func TestFileNotEmptyf(t *testing.T) { }) } +func TestFileNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + FileNotExistsf(t, filepath.Join(testDataPath(), "non_existing_file"), "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + FileNotExistsf(mock, filepath.Join(testDataPath(), "existing_file"), "test message") + // require functions don't return a value + if !mock.failed { + t.Error("FileNotExists should call FailNow()") + } + }) +} + func TestGreaterf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -865,6 +925,26 @@ func TestIsDecreasingf(t *testing.T) { }) } +func TestIsDecreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsDecreasingTf(t, []int{3, 2, 1}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsDecreasingTf(mock, []int{1, 2, 3}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("IsDecreasingT should call FailNow()") + } + }) +} + func TestIsIncreasingf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -885,6 +965,26 @@ func TestIsIncreasingf(t *testing.T) { }) } +func TestIsIncreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsIncreasingTf(t, []int{1, 2, 3}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsIncreasingTf(mock, []int{1, 1, 2}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("IsIncreasingT should call FailNow()") + } + }) +} + func TestIsNonDecreasingf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -897,7 +997,7 @@ func TestIsNonDecreasingf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - IsNonDecreasingf(mock, []int{2, 1, 1}, "test message") + IsNonDecreasingf(mock, []int{2, 1, 0}, "test message") // require functions don't return a value if !mock.failed { t.Error("IsNonDecreasing should call FailNow()") @@ -905,6 +1005,26 @@ func TestIsNonDecreasingf(t *testing.T) { }) } +func TestIsNonDecreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsNonDecreasingTf(t, []int{1, 1, 2}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsNonDecreasingTf(mock, []int{2, 1, 0}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("IsNonDecreasingT should call FailNow()") + } + }) +} + func TestIsNonIncreasingf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -925,6 +1045,26 @@ func TestIsNonIncreasingf(t *testing.T) { }) } +func TestIsNonIncreasingTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsNonIncreasingTf(t, []int{2, 1, 1}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsNonIncreasingTf(mock, []int{1, 2, 3}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("IsNonIncreasingT should call FailNow()") + } + }) +} + func TestIsNotTypef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1145,11 +1285,11 @@ func TestLessTf(t *testing.T) { }) } -func TestNegativef(t *testing.T) { +func TestMapContainsTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - Negativef(t, -1, "test message") + MapContainsTf(t, map[string]string{"A": "B"}, "A", "test message") // require functions don't return a value }) @@ -1157,19 +1297,19 @@ func TestNegativef(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - Negativef(mock, 1, "test message") + MapContainsTf(mock, map[string]string{"A": "B"}, "C", "test message") // require functions don't return a value if !mock.failed { - t.Error("Negative should call FailNow()") + t.Error("MapContainsT should call FailNow()") } }) } -func TestNegativeTf(t *testing.T) { +func TestMapNotContainsTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NegativeTf(t, -1, "test message") + MapNotContainsTf(t, map[string]string{"A": "B"}, "C", "test message") // require functions don't return a value }) @@ -1177,19 +1317,19 @@ func TestNegativeTf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NegativeTf(mock, 1, "test message") + MapNotContainsTf(mock, map[string]string{"A": "B"}, "A", "test message") // require functions don't return a value if !mock.failed { - t.Error("NegativeT should call FailNow()") + t.Error("MapNotContainsT should call FailNow()") } }) } -func TestNeverf(t *testing.T) { +func TestNegativef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - Neverf(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond, "test message") + Negativef(t, -1, "test message") // require functions don't return a value }) @@ -1197,19 +1337,19 @@ func TestNeverf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - Neverf(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond, "test message") + Negativef(mock, 1, "test message") // require functions don't return a value if !mock.failed { - t.Error("Never should call FailNow()") + t.Error("Negative should call FailNow()") } }) } -func TestNilf(t *testing.T) { +func TestNegativeTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - Nilf(t, nil, "test message") + NegativeTf(t, -1, "test message") // require functions don't return a value }) @@ -1217,19 +1357,19 @@ func TestNilf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - Nilf(mock, "not nil", "test message") + NegativeTf(mock, 1, "test message") // require functions don't return a value if !mock.failed { - t.Error("Nil should call FailNow()") + t.Error("NegativeT should call FailNow()") } }) } -func TestNoDirExistsf(t *testing.T) { +func TestNeverf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NoDirExistsf(t, filepath.Join(testDataPath(), "non_existing_dir"), "test message") + Neverf(t, func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond, "test message") // require functions don't return a value }) @@ -1237,19 +1377,19 @@ func TestNoDirExistsf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NoDirExistsf(mock, filepath.Join(testDataPath(), "existing_dir"), "test message") + Neverf(mock, func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond, "test message") // require functions don't return a value if !mock.failed { - t.Error("NoDirExists should call FailNow()") + t.Error("Never should call FailNow()") } }) } -func TestNoErrorf(t *testing.T) { +func TestNilf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NoErrorf(t, nil, "test message") + Nilf(t, nil, "test message") // require functions don't return a value }) @@ -1257,19 +1397,19 @@ func TestNoErrorf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NoErrorf(mock, ErrTest, "test message") + Nilf(mock, "not nil", "test message") // require functions don't return a value if !mock.failed { - t.Error("NoError should call FailNow()") + t.Error("Nil should call FailNow()") } }) } -func TestNoFileExistsf(t *testing.T) { +func TestNoErrorf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() - NoFileExistsf(t, filepath.Join(testDataPath(), "non_existing_file"), "test message") + NoErrorf(t, nil, "test message") // require functions don't return a value }) @@ -1277,10 +1417,10 @@ func TestNoFileExistsf(t *testing.T) { t.Parallel() mock := new(mockFailNowT) - NoFileExistsf(mock, filepath.Join(testDataPath(), "existing_file"), "test message") + NoErrorf(mock, ErrTest, "test message") // require functions don't return a value if !mock.failed { - t.Error("NoFileExists should call FailNow()") + t.Error("NoError should call FailNow()") } }) } @@ -1385,6 +1525,26 @@ func TestNotEqualf(t *testing.T) { }) } +func TestNotEqualTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + NotEqualTf(t, 123, 456, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + NotEqualTf(mock, 123, 123, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("NotEqualT should call FailNow()") + } + }) +} + func TestNotEqualValuesf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1585,6 +1745,46 @@ func TestNotSamef(t *testing.T) { }) } +func TestNotSameTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + NotSameTf(t, &staticVar, ptr("static string"), "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + NotSameTf(mock, &staticVar, staticVarPtr, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("NotSameT should call FailNow()") + } + }) +} + +func TestNotSortedTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + NotSortedTf(t, []int{3, 1, 3}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + NotSortedTf(mock, []int{1, 4, 8}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("NotSortedT should call FailNow()") + } + }) +} + func TestNotSubsetf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1785,6 +1985,166 @@ func TestSamef(t *testing.T) { }) } +func TestSameTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SameTf(t, &staticVar, staticVarPtr, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SameTf(mock, &staticVar, ptr("static string"), "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SameT should call FailNow()") + } + }) +} + +func TestSliceContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceContainsTf(t, []string{"A", "B"}, "A", "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceContainsTf(mock, []string{"A", "B"}, "C", "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SliceContainsT should call FailNow()") + } + }) +} + +func TestSliceNotContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceNotContainsTf(t, []string{"A", "B"}, "C", "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceNotContainsTf(mock, []string{"A", "B"}, "A", "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SliceNotContainsT should call FailNow()") + } + }) +} + +func TestSliceNotSubsetTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceNotSubsetTf(t, []int{1, 2, 3}, []int{4, 5}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceNotSubsetTf(mock, []int{1, 2, 3}, []int{1, 2}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SliceNotSubsetT should call FailNow()") + } + }) +} + +func TestSliceSubsetTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SliceSubsetTf(t, []int{1, 2, 3}, []int{1, 2}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SliceSubsetTf(mock, []int{1, 2, 3}, []int{4, 5}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SliceSubsetT should call FailNow()") + } + }) +} + +func TestSortedTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SortedTf(t, []int{1, 1, 3}, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SortedTf(mock, []int{1, 4, 2}, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SortedT should call FailNow()") + } + }) +} + +func TestStringContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + StringContainsTf(t, "AB", "A", "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + StringContainsTf(mock, "AB", "C", "test message") + // require functions don't return a value + if !mock.failed { + t.Error("StringContainsT should call FailNow()") + } + }) +} + +func TestStringNotContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + StringNotContainsTf(t, "AB", "C", "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + StringNotContainsTf(mock, "AB", "A", "test message") + // require functions don't return a value + if !mock.failed { + t.Error("StringNotContainsT should call FailNow()") + } + }) +} + func TestSubsetf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/require/require_forward.go b/require/require_forward.go index 63b71a553..68e598370 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -115,6 +115,34 @@ func (a *Assertions) DirExistsf(path string, msg string, args ...any) { a.t.FailNow() } +// DirNotExists is the same as [DirNotExists], as a method rather than a package-level function. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func (a *Assertions) DirNotExists(path string, msgAndArgs ...any) { + if h, ok := a.t.(H); ok { + h.Helper() + } + if assertions.DirNotExists(a.t, path, msgAndArgs...) { + return + } + + a.t.FailNow() +} + +// DirNotExistsf is the same as [Assertions.DirNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func (a *Assertions) DirNotExistsf(path string, msg string, args ...any) { + if h, ok := a.t.(H); ok { + h.Helper() + } + if assertions.DirNotExists(a.t, path, forwardArgs(msg, args)) { + return + } + + a.t.FailNow() +} + // ElementsMatch is the same as [ElementsMatch], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -639,6 +667,34 @@ func (a *Assertions) FileNotEmptyf(path string, msg string, args ...any) { a.t.FailNow() } +// FileNotExists is the same as [FileNotExists], as a method rather than a package-level function. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func (a *Assertions) FileNotExists(path string, msgAndArgs ...any) { + if h, ok := a.t.(H); ok { + h.Helper() + } + if assertions.FileNotExists(a.t, path, msgAndArgs...) { + return + } + + a.t.FailNow() +} + +// FileNotExistsf is the same as [Assertions.FileNotExists], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func (a *Assertions) FileNotExistsf(path string, msg string, args ...any) { + if h, ok := a.t.(H); ok { + h.Helper() + } + if assertions.FileNotExists(a.t, path, forwardArgs(msg, args)) { + return + } + + a.t.FailNow() +} + // Greater is the same as [Greater], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -1451,34 +1507,6 @@ func (a *Assertions) Nilf(object any, msg string, args ...any) { a.t.FailNow() } -// NoDirExists is the same as [NoDirExists], as a method rather than a package-level function. -// -// Upon failure, the test [T] is marked as failed and stops execution. -func (a *Assertions) NoDirExists(path string, msgAndArgs ...any) { - if h, ok := a.t.(H); ok { - h.Helper() - } - if assertions.NoDirExists(a.t, path, msgAndArgs...) { - return - } - - a.t.FailNow() -} - -// NoDirExistsf is the same as [Assertions.NoDirExists], but it accepts a format msg string to format arguments like [fmt.Printf]. -// -// Upon failure, the test [T] is marked as failed and stops execution. -func (a *Assertions) NoDirExistsf(path string, msg string, args ...any) { - if h, ok := a.t.(H); ok { - h.Helper() - } - if assertions.NoDirExists(a.t, path, forwardArgs(msg, args)) { - return - } - - a.t.FailNow() -} - // NoError is the same as [NoError], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -1507,34 +1535,6 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...any) { a.t.FailNow() } -// NoFileExists is the same as [NoFileExists], as a method rather than a package-level function. -// -// Upon failure, the test [T] is marked as failed and stops execution. -func (a *Assertions) NoFileExists(path string, msgAndArgs ...any) { - if h, ok := a.t.(H); ok { - h.Helper() - } - if assertions.NoFileExists(a.t, path, msgAndArgs...) { - return - } - - a.t.FailNow() -} - -// NoFileExistsf is the same as [Assertions.NoFileExists], but it accepts a format msg string to format arguments like [fmt.Printf]. -// -// Upon failure, the test [T] is marked as failed and stops execution. -func (a *Assertions) NoFileExistsf(path string, msg string, args ...any) { - if h, ok := a.t.(H); ok { - h.Helper() - } - if assertions.NoFileExists(a.t, path, forwardArgs(msg, args)) { - return - } - - a.t.FailNow() -} - // NotContains is the same as [NotContains], as a method rather than a package-level function. // // Upon failure, the test [T] is marked as failed and stops execution. diff --git a/require/require_forward_test.go b/require/require_forward_test.go index 8727e84a1..a6df461ca 100644 --- a/require/require_forward_test.go +++ b/require/require_forward_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -164,6 +164,55 @@ func TestAssertionsDirExistsf(t *testing.T) { }) } +func TestAssertionsDirNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + a.DirNotExists(filepath.Join(testDataPath(), "non_existing_dir")) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + a := New(mock) + a.DirNotExists(filepath.Join(testDataPath(), "existing_dir")) + // require functions don't return a value + if !mock.failed { + t.Error("DirNotExists should call FailNow()") + } + }) +} + +func TestAssertionsDirNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + a.DirNotExistsf(filepath.Join(testDataPath(), "non_existing_dir"), "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + a := New(mock) + a.DirNotExistsf(filepath.Join(testDataPath(), "existing_dir"), "test message") + // require functions don't return a value + if !mock.failed { + t.Error("Assertions.DirNotExists should mark test as failed") + } + if !mock.failed { + t.Error("Assertions.DirNotExistsf should call FailNow()") + } + }) +} + func TestAssertionsElementsMatch(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1063,6 +1112,55 @@ func TestAssertionsFileNotEmptyf(t *testing.T) { }) } +func TestAssertionsFileNotExists(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + a.FileNotExists(filepath.Join(testDataPath(), "non_existing_file")) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + a := New(mock) + a.FileNotExists(filepath.Join(testDataPath(), "existing_file")) + // require functions don't return a value + if !mock.failed { + t.Error("FileNotExists should call FailNow()") + } + }) +} + +func TestAssertionsFileNotExistsf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + + a := New(t) + a.FileNotExistsf(filepath.Join(testDataPath(), "non_existing_file"), "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + a := New(mock) + a.FileNotExistsf(filepath.Join(testDataPath(), "existing_file"), "test message") + // require functions don't return a value + if !mock.failed { + t.Error("Assertions.FileNotExists should mark test as failed") + } + if !mock.failed { + t.Error("Assertions.FileNotExistsf should call FailNow()") + } + }) +} + func TestAssertionsGreater(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1862,7 +1960,7 @@ func TestAssertionsIsNonDecreasing(t *testing.T) { mock := new(mockFailNowT) a := New(mock) - a.IsNonDecreasing([]int{2, 1, 1}) + a.IsNonDecreasing([]int{2, 1, 0}) // require functions don't return a value if !mock.failed { t.Error("IsNonDecreasing should call FailNow()") @@ -1885,7 +1983,7 @@ func TestAssertionsIsNonDecreasingf(t *testing.T) { mock := new(mockFailNowT) a := New(mock) - a.IsNonDecreasingf([]int{2, 1, 1}, "test message") + a.IsNonDecreasingf([]int{2, 1, 0}, "test message") // require functions don't return a value if !mock.failed { t.Error("Assertions.IsNonDecreasing should mark test as failed") @@ -2484,55 +2582,6 @@ func TestAssertionsNilf(t *testing.T) { }) } -func TestAssertionsNoDirExists(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - a.NoDirExists(filepath.Join(testDataPath(), "non_existing_dir")) - // require functions don't return a value - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockFailNowT) - a := New(mock) - a.NoDirExists(filepath.Join(testDataPath(), "existing_dir")) - // require functions don't return a value - if !mock.failed { - t.Error("NoDirExists should call FailNow()") - } - }) -} - -func TestAssertionsNoDirExistsf(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - a.NoDirExistsf(filepath.Join(testDataPath(), "non_existing_dir"), "test message") - // require functions don't return a value - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockFailNowT) - a := New(mock) - a.NoDirExistsf(filepath.Join(testDataPath(), "existing_dir"), "test message") - // require functions don't return a value - if !mock.failed { - t.Error("Assertions.NoDirExists should mark test as failed") - } - if !mock.failed { - t.Error("Assertions.NoDirExistsf should call FailNow()") - } - }) -} - func TestAssertionsNoError(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2582,55 +2631,6 @@ func TestAssertionsNoErrorf(t *testing.T) { }) } -func TestAssertionsNoFileExists(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - a.NoFileExists(filepath.Join(testDataPath(), "non_existing_file")) - // require functions don't return a value - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockFailNowT) - a := New(mock) - a.NoFileExists(filepath.Join(testDataPath(), "existing_file")) - // require functions don't return a value - if !mock.failed { - t.Error("NoFileExists should call FailNow()") - } - }) -} - -func TestAssertionsNoFileExistsf(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - - a := New(t) - a.NoFileExistsf(filepath.Join(testDataPath(), "non_existing_file"), "test message") - // require functions don't return a value - }) - - t.Run("failure", func(t *testing.T) { - t.Parallel() - - mock := new(mockFailNowT) - a := New(mock) - a.NoFileExistsf(filepath.Join(testDataPath(), "existing_file"), "test message") - // require functions don't return a value - if !mock.failed { - t.Error("Assertions.NoFileExists should mark test as failed") - } - if !mock.failed { - t.Error("Assertions.NoFileExistsf should call FailNow()") - } - }) -} - func TestAssertionsNotContains(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/require/require_helpers.go b/require/require_helpers.go index 3c81e56c2..f3a3cd2d3 100644 --- a/require/require_helpers.go +++ b/require/require_helpers.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require diff --git a/require/require_helpers_test.go b/require/require_helpers_test.go index 432ad7258..9fdc8a48e 100644 --- a/require/require_helpers_test.go +++ b/require/require_helpers_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require diff --git a/require/require_types.go b/require/require_types.go index 073f5bdf0..8cf49888e 100644 --- a/require/require_types.go +++ b/require/require_types.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-18 (version e12affe) using codegen version v2.1.9-0.20260118112101-e12affef2419+dirty [sha: e12affef24198e72ee13eb6d25018d2c3232629f] +// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] package require @@ -57,7 +57,7 @@ type ( // Ordered is a standard ordered type (i.e. types that support "<": [cmp.Ordered]) plus []byte and [time.Time]. // - // This is used by [GreaterT], [GreaterOrEqualT], [LessT], and [LessOrEqualT]. + // This is used by [GreaterT], [GreaterOrEqualT], [LessT], [LessOrEqualT], [IsIncreasingT], [IsDecreasingT]. // // NOTE: since [time.Time] is a struct, custom types which redeclare [time.Time] are not supported. Ordered = assertions.Ordered From b1304b0e13a058e7b310074ab5a009c8c2f62cd1 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 20 Jan 2026 00:26:26 +0100 Subject: [PATCH 05/10] feat: added generic IsOfType[T any] Support for IsOfType, IsNotOfType assertions. Originally posted as stretchr/testify#1805 Signed-off-by: Frederic BIDON --- assert/assert_assertions.go | 108 ++++++++++------ assert/assert_assertions_test.go | 52 +++++++- assert/assert_examples_test.go | 20 ++- assert/assert_format.go | 90 ++++++++------ assert/assert_format_test.go | 50 +++++++- assert/assert_forward.go | 2 +- assert/assert_forward_test.go | 2 +- assert/assert_helpers.go | 2 +- assert/assert_helpers_test.go | 2 +- assert/assert_types.go | 2 +- .../internal/generator/funcmaps/funcmaps.go | 1 + .../templates/assertion_assertions.gotmpl | 2 +- .../assertion_assertions_test.gotmpl | 12 +- .../templates/assertion_examples_test.gotmpl | 6 +- .../templates/assertion_format.gotmpl | 2 +- .../templates/assertion_format_test.gotmpl | 10 +- .../templates/requirement_assertions.gotmpl | 2 +- .../templates/requirement_format.gotmpl | 2 +- codegen/internal/model/model.go | 26 +++- docs/doc-site/api/_index.md | 6 +- docs/doc-site/api/boolean.md | 4 +- docs/doc-site/api/collection.md | 4 +- docs/doc-site/api/common.md | 4 +- docs/doc-site/api/comparison.md | 4 +- docs/doc-site/api/condition.md | 4 +- docs/doc-site/api/equality.md | 4 +- docs/doc-site/api/error.md | 4 +- docs/doc-site/api/file.md | 4 +- docs/doc-site/api/http.md | 4 +- docs/doc-site/api/json.md | 4 +- docs/doc-site/api/number.md | 4 +- docs/doc-site/api/ordering.md | 4 +- docs/doc-site/api/panic.md | 4 +- docs/doc-site/api/string.md | 4 +- docs/doc-site/api/testing.md | 4 +- docs/doc-site/api/time.md | 4 +- docs/doc-site/api/type.md | 109 ++++++++++++++-- docs/doc-site/api/yaml.md | 4 +- internal/assertions/type.go | 48 ++++++++ internal/assertions/type_test.go | 14 +++ require/require_assertions.go | 116 ++++++++++++------ require/require_assertions_test.go | 44 ++++++- require/require_examples_test.go | 20 ++- require/require_format.go | 98 +++++++++------ require/require_format_test.go | 42 ++++++- require/require_forward.go | 2 +- require/require_forward_test.go | 2 +- require/require_helpers.go | 2 +- require/require_helpers_test.go | 2 +- require/require_types.go | 2 +- 50 files changed, 750 insertions(+), 218 deletions(-) diff --git a/assert/assert_assertions.go b/assert/assert_assertions.go index 536b50104..86f7a3500 100644 --- a/assert/assert_assertions.go +++ b/assert/assert_assertions.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert @@ -135,7 +135,7 @@ func ElementsMatchT[E comparable](t T, listA []E, listB []E, msgAndArgs ...any) if h, ok := t.(H); ok { h.Helper() } - return assertions.ElementsMatchT(t, listA, listB, msgAndArgs...) + return assertions.ElementsMatchT[E](t, listA, listB, msgAndArgs...) } // Empty asserts that the given value is "empty". @@ -263,7 +263,7 @@ func EqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.EqualT(t, expected, actual, msgAndArgs...) + return assertions.EqualT[V](t, expected, actual, msgAndArgs...) } // EqualValues asserts that two objects are equal or convertible to the larger @@ -543,7 +543,7 @@ func FalseT[B Boolean](t T, value B, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.FalseT(t, value, msgAndArgs...) + return assertions.FalseT[B](t, value, msgAndArgs...) } // FileEmpty checks whether a file exists in the given path and is empty. @@ -704,7 +704,7 @@ func GreaterOrEqualT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndA if h, ok := t.(H); ok { h.Helper() } - return assertions.GreaterOrEqualT(t, e1, e2, msgAndArgs...) + return assertions.GreaterOrEqualT[Orderable](t, e1, e2, msgAndArgs...) } // GreaterT asserts that for two elements of the same type, @@ -736,7 +736,7 @@ func GreaterT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs ... if h, ok := t.(H); ok { h.Helper() } - return assertions.GreaterT(t, e1, e2, msgAndArgs...) + return assertions.GreaterT[Orderable](t, e1, e2, msgAndArgs...) } // HTTPBodyContains asserts that a specified handler returns a body that contains a string. @@ -986,7 +986,7 @@ func InDeltaT[Number Measurable](t T, expected Number, actual Number, delta Numb if h, ok := t.(H); ok { h.Helper() } - return assertions.InDeltaT(t, expected, actual, delta, msgAndArgs...) + return assertions.InDeltaT[Number](t, expected, actual, delta, msgAndArgs...) } // InEpsilon asserts that expected and actual have a relative error less than epsilon. @@ -1079,7 +1079,7 @@ func InEpsilonT[Number Measurable](t T, expected Number, actual Number, epsilon if h, ok := t.(H); ok { h.Helper() } - return assertions.InEpsilonT(t, expected, actual, epsilon, msgAndArgs...) + return assertions.InEpsilonT[Number](t, expected, actual, epsilon, msgAndArgs...) } // IsDecreasing asserts that the collection is strictly decreasing. @@ -1121,7 +1121,7 @@ func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m if h, ok := t.(H); ok { h.Helper() } - return assertions.IsDecreasingT(t, collection, msgAndArgs...) + return assertions.IsDecreasingT[OrderedSlice, E](t, collection, msgAndArgs...) } // IsIncreasing asserts that the collection is strictly increasing. @@ -1163,7 +1163,7 @@ func IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m if h, ok := t.(H); ok { h.Helper() } - return assertions.IsIncreasingT(t, collection, msgAndArgs...) + return assertions.IsIncreasingT[OrderedSlice, E](t, collection, msgAndArgs...) } // IsNonDecreasing asserts that the collection is not strictly decreasing. @@ -1205,7 +1205,7 @@ func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice if h, ok := t.(H); ok { h.Helper() } - return assertions.IsNonDecreasingT(t, collection, msgAndArgs...) + return assertions.IsNonDecreasingT[OrderedSlice, E](t, collection, msgAndArgs...) } // IsNonIncreasing asserts that the collection is not increasing. @@ -1247,7 +1247,26 @@ func IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice if h, ok := t.(H); ok { h.Helper() } - return assertions.IsNonIncreasingT(t, collection, msgAndArgs...) + return assertions.IsNonIncreasingT[OrderedSlice, E](t, collection, msgAndArgs...) +} + +// IsNotOfTypeT asserts that an object is of a given type. +// +// # Usage +// +// assertions.IsOfType[MyType](t,myVar) +// +// # Examples +// +// success: 123.123 +// failure: myType(123.123) +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsNotOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsNotOfTypeT[EType](t, object, msgAndArgs...) } // IsNotType asserts that the specified objects are not of the same type. @@ -1269,6 +1288,25 @@ func IsNotType(t T, theType any, object any, msgAndArgs ...any) bool { return assertions.IsNotType(t, theType, object, msgAndArgs...) } +// IsOfTypeT asserts that an object is of a given type. +// +// # Usage +// +// assertions.IsOfTypeT[MyType](t,myVar) +// +// # Examples +// +// success: myType(123.123) +// failure: 123.123 +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsOfTypeT[EType](t, object, msgAndArgs...) +} + // IsType asserts that the specified objects are of the same type. // // # Usage @@ -1350,7 +1388,7 @@ func JSONEqT[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msgAndArgs ...any if h, ok := t.(H); ok { h.Helper() } - return assertions.JSONEqT(t, expected, actual, msgAndArgs...) + return assertions.JSONEqT[EDoc, ADoc](t, expected, actual, msgAndArgs...) } // Kind asserts that the [reflect.Kind] of a given object matches the expected [reflect.Kind]. @@ -1477,7 +1515,7 @@ func LessOrEqualT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs if h, ok := t.(H); ok { h.Helper() } - return assertions.LessOrEqualT(t, e1, e2, msgAndArgs...) + return assertions.LessOrEqualT[Orderable](t, e1, e2, msgAndArgs...) } // LessT asserts that for two elements of the same type, the first element is strictly less than the second. @@ -1508,7 +1546,7 @@ func LessT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs ...any if h, ok := t.(H); ok { h.Helper() } - return assertions.LessT(t, e1, e2, msgAndArgs...) + return assertions.LessT[Orderable](t, e1, e2, msgAndArgs...) } // MapContainsT asserts that the specified map contains a key. @@ -1527,7 +1565,7 @@ func MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndAr if h, ok := t.(H); ok { h.Helper() } - return assertions.MapContainsT(t, m, key, msgAndArgs...) + return assertions.MapContainsT[Map, K, V](t, m, key, msgAndArgs...) } // MapNotContainsT asserts that the specified map does not contain a key. @@ -1546,7 +1584,7 @@ func MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAn if h, ok := t.(H); ok { h.Helper() } - return assertions.MapNotContainsT(t, m, key, msgAndArgs...) + return assertions.MapNotContainsT[Map, K, V](t, m, key, msgAndArgs...) } // Negative asserts that the specified element is strictly negative. @@ -1586,7 +1624,7 @@ func NegativeT[SignedNumber SignedNumeric](t T, e SignedNumber, msgAndArgs ...an if h, ok := t.(H); ok { h.Helper() } - return assertions.NegativeT(t, e, msgAndArgs...) + return assertions.NegativeT[SignedNumber](t, e, msgAndArgs...) } // Never asserts that the given condition is never satisfied within waitFor time, @@ -1729,7 +1767,7 @@ func NotElementsMatchT[E comparable](t T, listA []E, listB []E, msgAndArgs ...an if h, ok := t.(H); ok { h.Helper() } - return assertions.NotElementsMatchT(t, listA, listB, msgAndArgs...) + return assertions.NotElementsMatchT[E](t, listA, listB, msgAndArgs...) } // NotEmpty asserts that the specified object is NOT [Empty]. @@ -1793,7 +1831,7 @@ func NotEqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) bool if h, ok := t.(H); ok { h.Helper() } - return assertions.NotEqualT(t, expected, actual, msgAndArgs...) + return assertions.NotEqualT[V](t, expected, actual, msgAndArgs...) } // NotEqualValues asserts that two objects are not equal even when converted to the same type. @@ -1976,7 +2014,7 @@ func NotRegexpT[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msgAndArgs ...a if h, ok := t.(H); ok { h.Helper() } - return assertions.NotRegexpT(t, rx, actual, msgAndArgs...) + return assertions.NotRegexpT[Rex, ADoc](t, rx, actual, msgAndArgs...) } // NotSame asserts that two pointers do not reference the same object. @@ -2019,7 +2057,7 @@ func NotSameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.NotSameT(t, expected, actual, msgAndArgs...) + return assertions.NotSameT[P](t, expected, actual, msgAndArgs...) } // NotSortedT asserts that the slice of [Ordered] is NOT sorted (i.e. non-strictly increasing). @@ -2042,7 +2080,7 @@ func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgA if h, ok := t.(H); ok { h.Helper() } - return assertions.NotSortedT(t, collection, msgAndArgs...) + return assertions.NotSortedT[OrderedSlice, E](t, collection, msgAndArgs...) } // NotSubset asserts that the list (array, slice, or map) does NOT contain all @@ -2186,7 +2224,7 @@ func PositiveT[SignedNumber SignedNumeric](t T, e SignedNumber, msgAndArgs ...an if h, ok := t.(H); ok { h.Helper() } - return assertions.PositiveT(t, e, msgAndArgs...) + return assertions.PositiveT[SignedNumber](t, e, msgAndArgs...) } // Regexp asserts that a specified regular expression matches a string. @@ -2229,7 +2267,7 @@ func RegexpT[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msgAndArgs ...any) if h, ok := t.(H); ok { h.Helper() } - return assertions.RegexpT(t, rx, actual, msgAndArgs...) + return assertions.RegexpT[Rex, ADoc](t, rx, actual, msgAndArgs...) } // Same asserts that two pointers reference the same object. @@ -2270,7 +2308,7 @@ func SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.SameT(t, expected, actual, msgAndArgs...) + return assertions.SameT[P](t, expected, actual, msgAndArgs...) } // SliceContainsT asserts that the specified slice contains a comparable element. @@ -2289,7 +2327,7 @@ func SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArg if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceContainsT(t, s, element, msgAndArgs...) + return assertions.SliceContainsT[Slice, E](t, s, element, msgAndArgs...) } // SliceNotContainsT asserts that the specified slice does not contain a comparable element. @@ -2308,7 +2346,7 @@ func SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAnd if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceNotContainsT(t, s, element, msgAndArgs...) + return assertions.SliceNotContainsT[Slice, E](t, s, element, msgAndArgs...) } // SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset. @@ -2327,7 +2365,7 @@ func SliceNotSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, ms if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceNotSubsetT(t, list, subset, msgAndArgs...) + return assertions.SliceNotSubsetT[Slice, E](t, list, subset, msgAndArgs...) } // SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset. @@ -2346,7 +2384,7 @@ func SliceSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAn if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceSubsetT(t, list, subset, msgAndArgs...) + return assertions.SliceSubsetT[Slice, E](t, list, subset, msgAndArgs...) } // SortedT asserts that the slice of [Ordered] is sorted (i.e. non-strictly increasing). @@ -2369,7 +2407,7 @@ func SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndA if h, ok := t.(H); ok { h.Helper() } - return assertions.SortedT(t, collection, msgAndArgs...) + return assertions.SortedT[OrderedSlice, E](t, collection, msgAndArgs...) } // StringContainsT asserts that a string contains the specified substring. @@ -2390,7 +2428,7 @@ func StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs if h, ok := t.(H); ok { h.Helper() } - return assertions.StringContainsT(t, str, substring, msgAndArgs...) + return assertions.StringContainsT[ADoc, EDoc](t, str, substring, msgAndArgs...) } // StringNotContainsT asserts that a string does not contain the specified substring. @@ -2411,7 +2449,7 @@ func StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndAr if h, ok := t.(H); ok { h.Helper() } - return assertions.StringNotContainsT(t, str, substring, msgAndArgs...) + return assertions.StringNotContainsT[ADoc, EDoc](t, str, substring, msgAndArgs...) } // Subset asserts that the list (array, slice, or map) contains all elements @@ -2478,7 +2516,7 @@ func TrueT[B Boolean](t T, value B, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.TrueT(t, value, msgAndArgs...) + return assertions.TrueT[B](t, value, msgAndArgs...) } // WithinDuration asserts that the two times are within duration delta of each other. @@ -2593,7 +2631,7 @@ func YAMLEqT[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msgAndArgs ...any if h, ok := t.(H); ok { h.Helper() } - return assertions.YAMLEqT(t, expected, actual, msgAndArgs...) + return assertions.YAMLEqT[EDoc, ADoc](t, expected, actual, msgAndArgs...) } // Zero asserts that i is the zero value for its type. diff --git a/assert/assert_assertions_test.go b/assert/assert_assertions_test.go index a64110c3d..813a580a5 100644 --- a/assert/assert_assertions_test.go +++ b/assert/assert_assertions_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert @@ -1273,6 +1273,30 @@ func TestIsNonIncreasingT(t *testing.T) { }) } +func TestIsNotOfTypeT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsNotOfTypeT[myType](t, 123.123) + if !result { + t.Error("IsNotOfTypeT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsNotOfTypeT[myType](mock, myType(123.123)) + if result { + t.Error("IsNotOfTypeT should return false on failure") + } + if !mock.failed { + t.Error("IsNotOfTypeT should mark test as failed") + } + }) +} + func TestIsNotType(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1297,6 +1321,30 @@ func TestIsNotType(t *testing.T) { }) } +func TestIsOfTypeT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsOfTypeT[myType](t, myType(123.123)) + if !result { + t.Error("IsOfTypeT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsOfTypeT[myType](mock, 123.123) + if result { + t.Error("IsOfTypeT should return false on failure") + } + if !mock.failed { + t.Error("IsOfTypeT should mark test as failed") + } + }) +} + func TestIsType(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2818,3 +2866,5 @@ type dummyError struct { func (d *dummyError) Error() string { return "dummy error" } + +type myType float64 diff --git a/assert/assert_examples_test.go b/assert/assert_examples_test.go index db1ff083f..426706cd4 100644 --- a/assert/assert_examples_test.go +++ b/assert/assert_examples_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert_test @@ -441,6 +441,14 @@ func ExampleIsNonIncreasingT() { // Output: success: true } +func ExampleIsNotOfTypeT() { + t := new(testing.T) + success := assert.IsNotOfTypeT[myType](t, 123.123) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleIsNotType() { t := new(testing.T) success := assert.IsNotType(t, int32(123), int64(456)) @@ -449,6 +457,14 @@ func ExampleIsNotType() { // Output: success: true } +func ExampleIsOfTypeT() { + t := new(testing.T) + success := assert.IsOfTypeT[myType](t, myType(123.123)) + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleIsType() { t := new(testing.T) success := assert.IsType(t, 123, 456) @@ -991,3 +1007,5 @@ type dummyError struct { func (d *dummyError) Error() string { return "dummy error" } + +type myType float64 diff --git a/assert/assert_format.go b/assert/assert_format.go index 8735e53c3..ce2edc716 100644 --- a/assert/assert_format.go +++ b/assert/assert_format.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert @@ -72,7 +72,7 @@ func ElementsMatchTf[E comparable](t T, listA []E, listB []E, msg string, args . if h, ok := t.(H); ok { h.Helper() } - return assertions.ElementsMatchT(t, listA, listB, forwardArgs(msg, args)) + return assertions.ElementsMatchT[E](t, listA, listB, forwardArgs(msg, args)) } // Emptyf is the same as [Empty], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -122,7 +122,7 @@ func EqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) b if h, ok := t.(H); ok { h.Helper() } - return assertions.EqualT(t, expected, actual, forwardArgs(msg, args)) + return assertions.EqualT[V](t, expected, actual, forwardArgs(msg, args)) } // EqualValuesf is the same as [EqualValues], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -242,7 +242,7 @@ func FalseTf[B Boolean](t T, value B, msg string, args ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.FalseT(t, value, forwardArgs(msg, args)) + return assertions.FalseT[B](t, value, forwardArgs(msg, args)) } // FileEmptyf is the same as [FileEmpty], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -312,7 +312,7 @@ func GreaterOrEqualTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg st if h, ok := t.(H); ok { h.Helper() } - return assertions.GreaterOrEqualT(t, e1, e2, forwardArgs(msg, args)) + return assertions.GreaterOrEqualT[Orderable](t, e1, e2, forwardArgs(msg, args)) } // GreaterTf is the same as [GreaterT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -322,7 +322,7 @@ func GreaterTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg string, a if h, ok := t.(H); ok { h.Helper() } - return assertions.GreaterT(t, e1, e2, forwardArgs(msg, args)) + return assertions.GreaterT[Orderable](t, e1, e2, forwardArgs(msg, args)) } // HTTPBodyContainsf is the same as [HTTPBodyContains], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -432,7 +432,7 @@ func InDeltaTf[Number Measurable](t T, expected Number, actual Number, delta Num if h, ok := t.(H); ok { h.Helper() } - return assertions.InDeltaT(t, expected, actual, delta, forwardArgs(msg, args)) + return assertions.InDeltaT[Number](t, expected, actual, delta, forwardArgs(msg, args)) } // InEpsilonf is the same as [InEpsilon], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -462,7 +462,7 @@ func InEpsilonTf[Number Measurable](t T, expected Number, actual Number, epsilon if h, ok := t.(H); ok { h.Helper() } - return assertions.InEpsilonT(t, expected, actual, epsilon, forwardArgs(msg, args)) + return assertions.InEpsilonT[Number](t, expected, actual, epsilon, forwardArgs(msg, args)) } // IsDecreasingf is the same as [IsDecreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -482,7 +482,7 @@ func IsDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, if h, ok := t.(H); ok { h.Helper() } - return assertions.IsDecreasingT(t, collection, forwardArgs(msg, args)) + return assertions.IsDecreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) } // IsIncreasingf is the same as [IsIncreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -502,7 +502,7 @@ func IsIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, if h, ok := t.(H); ok { h.Helper() } - return assertions.IsIncreasingT(t, collection, forwardArgs(msg, args)) + return assertions.IsIncreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) } // IsNonDecreasingf is the same as [IsNonDecreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -522,7 +522,7 @@ func IsNonDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlic if h, ok := t.(H); ok { h.Helper() } - return assertions.IsNonDecreasingT(t, collection, forwardArgs(msg, args)) + return assertions.IsNonDecreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) } // IsNonIncreasingf is the same as [IsNonIncreasing], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -542,7 +542,17 @@ func IsNonIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlic if h, ok := t.(H); ok { h.Helper() } - return assertions.IsNonIncreasingT(t, collection, forwardArgs(msg, args)) + return assertions.IsNonIncreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) +} + +// IsNotOfTypeTf is the same as [IsNotOfTypeT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsNotOfTypeTf[EType any](t T, object any, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsNotOfTypeT[EType](t, object, forwardArgs(msg, args)) } // IsNotTypef is the same as [IsNotType], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -555,6 +565,16 @@ func IsNotTypef(t T, theType any, object any, msg string, args ...any) bool { return assertions.IsNotType(t, theType, object, forwardArgs(msg, args)) } +// IsOfTypeTf is the same as [IsOfTypeT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func IsOfTypeTf[EType any](t T, object any, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.IsOfTypeT[EType](t, object, forwardArgs(msg, args)) +} + // IsTypef is the same as [IsType], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. @@ -592,7 +612,7 @@ func JSONEqTf[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msg string, args if h, ok := t.(H); ok { h.Helper() } - return assertions.JSONEqT(t, expected, actual, forwardArgs(msg, args)) + return assertions.JSONEqT[EDoc, ADoc](t, expected, actual, forwardArgs(msg, args)) } // Kindf is the same as [Kind], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -642,7 +662,7 @@ func LessOrEqualTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg strin if h, ok := t.(H); ok { h.Helper() } - return assertions.LessOrEqualT(t, e1, e2, forwardArgs(msg, args)) + return assertions.LessOrEqualT[Orderable](t, e1, e2, forwardArgs(msg, args)) } // LessTf is the same as [LessT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -652,7 +672,7 @@ func LessTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg string, args if h, ok := t.(H); ok { h.Helper() } - return assertions.LessT(t, e1, e2, forwardArgs(msg, args)) + return assertions.LessT[Orderable](t, e1, e2, forwardArgs(msg, args)) } // MapContainsTf is the same as [MapContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -662,7 +682,7 @@ func MapContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg str if h, ok := t.(H); ok { h.Helper() } - return assertions.MapContainsT(t, m, key, forwardArgs(msg, args)) + return assertions.MapContainsT[Map, K, V](t, m, key, forwardArgs(msg, args)) } // MapNotContainsTf is the same as [MapNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -672,7 +692,7 @@ func MapNotContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg if h, ok := t.(H); ok { h.Helper() } - return assertions.MapNotContainsT(t, m, key, forwardArgs(msg, args)) + return assertions.MapNotContainsT[Map, K, V](t, m, key, forwardArgs(msg, args)) } // Negativef is the same as [Negative], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -692,7 +712,7 @@ func NegativeTf[SignedNumber SignedNumeric](t T, e SignedNumber, msg string, arg if h, ok := t.(H); ok { h.Helper() } - return assertions.NegativeT(t, e, forwardArgs(msg, args)) + return assertions.NegativeT[SignedNumber](t, e, forwardArgs(msg, args)) } // Neverf is the same as [Never], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -752,7 +772,7 @@ func NotElementsMatchTf[E comparable](t T, listA []E, listB []E, msg string, arg if h, ok := t.(H); ok { h.Helper() } - return assertions.NotElementsMatchT(t, listA, listB, forwardArgs(msg, args)) + return assertions.NotElementsMatchT[E](t, listA, listB, forwardArgs(msg, args)) } // NotEmptyf is the same as [NotEmpty], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -782,7 +802,7 @@ func NotEqualTf[V comparable](t T, expected V, actual V, msg string, args ...any if h, ok := t.(H); ok { h.Helper() } - return assertions.NotEqualT(t, expected, actual, forwardArgs(msg, args)) + return assertions.NotEqualT[V](t, expected, actual, forwardArgs(msg, args)) } // NotEqualValuesf is the same as [NotEqualValues], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -872,7 +892,7 @@ func NotRegexpTf[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msg string, ar if h, ok := t.(H); ok { h.Helper() } - return assertions.NotRegexpT(t, rx, actual, forwardArgs(msg, args)) + return assertions.NotRegexpT[Rex, ADoc](t, rx, actual, forwardArgs(msg, args)) } // NotSamef is the same as [NotSame], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -892,7 +912,7 @@ func NotSameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool if h, ok := t.(H); ok { h.Helper() } - return assertions.NotSameT(t, expected, actual, forwardArgs(msg, args)) + return assertions.NotSameT[P](t, expected, actual, forwardArgs(msg, args)) } // NotSortedTf is the same as [NotSortedT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -902,7 +922,7 @@ func NotSortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg if h, ok := t.(H); ok { h.Helper() } - return assertions.NotSortedT(t, collection, forwardArgs(msg, args)) + return assertions.NotSortedT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) } // NotSubsetf is the same as [NotSubset], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -972,7 +992,7 @@ func PositiveTf[SignedNumber SignedNumeric](t T, e SignedNumber, msg string, arg if h, ok := t.(H); ok { h.Helper() } - return assertions.PositiveT(t, e, forwardArgs(msg, args)) + return assertions.PositiveT[SignedNumber](t, e, forwardArgs(msg, args)) } // Regexpf is the same as [Regexp], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -992,7 +1012,7 @@ func RegexpTf[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msg string, args if h, ok := t.(H); ok { h.Helper() } - return assertions.RegexpT(t, rx, actual, forwardArgs(msg, args)) + return assertions.RegexpT[Rex, ADoc](t, rx, actual, forwardArgs(msg, args)) } // Samef is the same as [Same], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1012,7 +1032,7 @@ func SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.SameT(t, expected, actual, forwardArgs(msg, args)) + return assertions.SameT[P](t, expected, actual, forwardArgs(msg, args)) } // SliceContainsTf is the same as [SliceContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1022,7 +1042,7 @@ func SliceContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg stri if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceContainsT(t, s, element, forwardArgs(msg, args)) + return assertions.SliceContainsT[Slice, E](t, s, element, forwardArgs(msg, args)) } // SliceNotContainsTf is the same as [SliceNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1032,7 +1052,7 @@ func SliceNotContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg s if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceNotContainsT(t, s, element, forwardArgs(msg, args)) + return assertions.SliceNotContainsT[Slice, E](t, s, element, forwardArgs(msg, args)) } // SliceNotSubsetTf is the same as [SliceNotSubsetT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1042,7 +1062,7 @@ func SliceNotSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, m if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceNotSubsetT(t, list, subset, forwardArgs(msg, args)) + return assertions.SliceNotSubsetT[Slice, E](t, list, subset, forwardArgs(msg, args)) } // SliceSubsetTf is the same as [SliceSubsetT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1052,7 +1072,7 @@ func SliceSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg if h, ok := t.(H); ok { h.Helper() } - return assertions.SliceSubsetT(t, list, subset, forwardArgs(msg, args)) + return assertions.SliceSubsetT[Slice, E](t, list, subset, forwardArgs(msg, args)) } // SortedTf is the same as [SortedT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1062,7 +1082,7 @@ func SortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg st if h, ok := t.(H); ok { h.Helper() } - return assertions.SortedT(t, collection, forwardArgs(msg, args)) + return assertions.SortedT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) } // StringContainsTf is the same as [StringContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1072,7 +1092,7 @@ func StringContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string if h, ok := t.(H); ok { h.Helper() } - return assertions.StringContainsT(t, str, substring, forwardArgs(msg, args)) + return assertions.StringContainsT[ADoc, EDoc](t, str, substring, forwardArgs(msg, args)) } // StringNotContainsTf is the same as [StringNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1082,7 +1102,7 @@ func StringNotContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg str if h, ok := t.(H); ok { h.Helper() } - return assertions.StringNotContainsT(t, str, substring, forwardArgs(msg, args)) + return assertions.StringNotContainsT[ADoc, EDoc](t, str, substring, forwardArgs(msg, args)) } // Subsetf is the same as [Subset], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1112,7 +1132,7 @@ func TrueTf[B Boolean](t T, value B, msg string, args ...any) bool { if h, ok := t.(H); ok { h.Helper() } - return assertions.TrueT(t, value, forwardArgs(msg, args)) + return assertions.TrueT[B](t, value, forwardArgs(msg, args)) } // WithinDurationf is the same as [WithinDuration], but it accepts a format msg string to format arguments like [fmt.Printf]. @@ -1162,7 +1182,7 @@ func YAMLEqTf[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msg string, args if h, ok := t.(H); ok { h.Helper() } - return assertions.YAMLEqT(t, expected, actual, forwardArgs(msg, args)) + return assertions.YAMLEqT[EDoc, ADoc](t, expected, actual, forwardArgs(msg, args)) } // Zerof is the same as [Zero], but it accepts a format msg string to format arguments like [fmt.Printf]. diff --git a/assert/assert_format_test.go b/assert/assert_format_test.go index 8799db103..0d99f1cdc 100644 --- a/assert/assert_format_test.go +++ b/assert/assert_format_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert @@ -1273,6 +1273,30 @@ func TestIsNonIncreasingTf(t *testing.T) { }) } +func TestIsNotOfTypeTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsNotOfTypeTf[myType](t, 123.123, "test message") + if !result { + t.Error("IsNotOfTypeTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsNotOfTypeTf[myType](mock, myType(123.123), "test message") + if result { + t.Error("IsNotOfTypeTf should return false on failure") + } + if !mock.failed { + t.Error("IsNotOfTypeT should mark test as failed") + } + }) +} + func TestIsNotTypef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1297,6 +1321,30 @@ func TestIsNotTypef(t *testing.T) { }) } +func TestIsOfTypeTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := IsOfTypeTf[myType](t, myType(123.123), "test message") + if !result { + t.Error("IsOfTypeTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := IsOfTypeTf[myType](mock, 123.123, "test message") + if result { + t.Error("IsOfTypeTf should return false on failure") + } + if !mock.failed { + t.Error("IsOfTypeT should mark test as failed") + } + }) +} + func TestIsTypef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/assert/assert_forward.go b/assert/assert_forward.go index c0ac599ce..9fbd33154 100644 --- a/assert/assert_forward.go +++ b/assert/assert_forward.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert diff --git a/assert/assert_forward_test.go b/assert/assert_forward_test.go index b1c31a274..91b782fe1 100644 --- a/assert/assert_forward_test.go +++ b/assert/assert_forward_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert diff --git a/assert/assert_helpers.go b/assert/assert_helpers.go index b045c686b..2b90f12ed 100644 --- a/assert/assert_helpers.go +++ b/assert/assert_helpers.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert diff --git a/assert/assert_helpers_test.go b/assert/assert_helpers_test.go index f4530b215..349c27fa8 100644 --- a/assert/assert_helpers_test.go +++ b/assert/assert_helpers_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert diff --git a/assert/assert_types.go b/assert/assert_types.go index 0962f8dfb..20af5a6a8 100644 --- a/assert/assert_types.go +++ b/assert/assert_types.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package assert diff --git a/codegen/internal/generator/funcmaps/funcmaps.go b/codegen/internal/generator/funcmaps/funcmaps.go index 0a168ae54..4d76d30b6 100644 --- a/codegen/internal/generator/funcmaps/funcmaps.go +++ b/codegen/internal/generator/funcmaps/funcmaps.go @@ -48,6 +48,7 @@ func FuncMap() template.FuncMap { "docStringPackage": docStringPackage, "forward": forward, "godocbadge": godocbadge, + "hasPrefix": strings.HasPrefix, "hasSuffix": strings.HasSuffix, "imports": printImports, "mdformat": FormatMarkdown, // From markdown.go diff --git a/codegen/internal/generator/templates/assertion_assertions.gotmpl b/codegen/internal/generator/templates/assertion_assertions.gotmpl index b3c9d2bf3..22ca06fd7 100644 --- a/codegen/internal/generator/templates/assertion_assertions.gotmpl +++ b/codegen/internal/generator/templates/assertion_assertions.gotmpl @@ -23,7 +23,7 @@ func {{ .GenericName }}({{ params .AllParams }}) {{ returns .Returns }} { if h, ok := t.(H); ok { h.Helper() } - return {{ .TargetPackage }}.{{ .Name }}({{ forward .AllParams }}) + return {{ .TargetPackage }}.{{ .GenericCallName }}({{ forward .AllParams }}) } {{- end }} {{- end }} diff --git a/codegen/internal/generator/templates/assertion_assertions_test.gotmpl b/codegen/internal/generator/templates/assertion_assertions_test.gotmpl index 390ba742c..bb946f231 100644 --- a/codegen/internal/generator/templates/assertion_assertions_test.gotmpl +++ b/codegen/internal/generator/templates/assertion_assertions_test.gotmpl @@ -32,10 +32,10 @@ func Test{{ .Name }}(t *testing.T) { t.Parallel() {{- if $isRequire }} - {{ $fn.Name }}(t, {{ .TestedValue }}) + {{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ .TestedValue }}) // require functions don't return a value {{- else }} - result := {{ $fn.Name }}(t, {{ .TestedValue }}) + result := {{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ .TestedValue }}) if !result { t.Error("{{ $fn.Name }} should return true on success") } @@ -48,10 +48,10 @@ func Test{{ .Name }}(t *testing.T) { mock := new({{ $fn.UseMock }}) {{- if $isRequire }} - {{ $fn.Name }}(mock, {{ .TestedValue }}) + {{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(mock, {{ .TestedValue }}) // require functions don't return a value {{- else }} - result := {{ $fn.Name }}(mock, {{ .TestedValue }}) + result := {{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(mock, {{ .TestedValue }}) if result { t.Error("{{ $fn.Name }} should return false on failure") } @@ -66,7 +66,7 @@ func Test{{ .Name }}(t *testing.T) { t.Parallel() Panics(t, func() { - {{ $fn.Name }}(t, {{ .TestedValue }}) + {{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ .TestedValue }}) }, "{{ .AssertionMessage }}") }) {{- end }} @@ -149,3 +149,5 @@ type dummyError struct { func (d *dummyError) Error() string { return "dummy error" } + +type myType float64 diff --git a/codegen/internal/generator/templates/assertion_examples_test.gotmpl b/codegen/internal/generator/templates/assertion_examples_test.gotmpl index 2760085d2..66380aeb2 100644 --- a/codegen/internal/generator/templates/assertion_examples_test.gotmpl +++ b/codegen/internal/generator/templates/assertion_examples_test.gotmpl @@ -35,14 +35,14 @@ func Example{{ .Name }}() { {{- if (not $first) }}{{ println "" }}{{- end }}{{ $first = false }} t := new(testing.T) {{- if $isRequire }} - {{ $pkg }}.{{ $fn.Name }}(t, {{ relocate .TestedValues $pkg }}) + {{ $pkg }}.{{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ relocate .TestedValues $pkg }}) fmt.Println("passed") {{- if $runnable }} // Output: passed {{- end }} {{- else }} - success := {{ $pkg }}.{{ $fn.Name }}(t, {{ relocate .TestedValues $pkg }}) + success := {{ $pkg }}.{{ $fn.Name }}{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ relocate .TestedValues $pkg }}) fmt.Printf("success: %t\n", success) {{- if $runnable }} @@ -107,3 +107,5 @@ type dummyError struct { func (d *dummyError) Error() string { return "dummy error" } + +type myType float64 diff --git a/codegen/internal/generator/templates/assertion_format.gotmpl b/codegen/internal/generator/templates/assertion_format.gotmpl index 45d120975..791fc75ac 100644 --- a/codegen/internal/generator/templates/assertion_format.gotmpl +++ b/codegen/internal/generator/templates/assertion_format.gotmpl @@ -21,7 +21,7 @@ import ( {{ docStringPackage $.Package }} func {{ .GenericName "f" }}(t T, {{ params .Params }}, msg string, args ...any) {{ returns .Returns }} { if h, ok := t.(H); ok { h.Helper() } - return {{ .TargetPackage }}.{{ .Name }}(t, {{ forward .Params }}, forwardArgs(msg, args)) + return {{ .TargetPackage }}.{{ .GenericCallName }}(t, {{ forward .Params }}, forwardArgs(msg, args)) } {{- end }} {{- end }} diff --git a/codegen/internal/generator/templates/assertion_format_test.gotmpl b/codegen/internal/generator/templates/assertion_format_test.gotmpl index 78b82e47d..49ca09325 100644 --- a/codegen/internal/generator/templates/assertion_format_test.gotmpl +++ b/codegen/internal/generator/templates/assertion_format_test.gotmpl @@ -33,10 +33,10 @@ func Test{{ .Name }}f(t *testing.T) { t.Parallel() {{- if $isRequire }} - {{ $fn.Name }}f(t, {{ .TestedValue }}, "test message") + {{ $fn.Name }}f{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ .TestedValue }}, "test message") // require functions don't return a value {{- else }} - result := {{ $fn.Name }}f(t, {{ .TestedValue }}, "test message") + result := {{ $fn.Name }}f{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ .TestedValue }}, "test message") if !result { t.Error("{{ $fn.Name }}f should return true on success") } @@ -49,10 +49,10 @@ func Test{{ .Name }}f(t *testing.T) { mock := new({{ $fn.UseMock }}) {{- if $isRequire }} - {{ $fn.Name }}f(mock, {{ .TestedValue }}, "test message") + {{ $fn.Name }}f{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(mock, {{ .TestedValue }}, "test message") // require functions don't return a value {{- else }} - result := {{ $fn.Name }}f(mock, {{ .TestedValue }}, "test message") + result := {{ $fn.Name }}f{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(mock, {{ .TestedValue }}, "test message") if result { t.Error("{{ $fn.Name }}f should return false on failure") } @@ -67,7 +67,7 @@ func Test{{ .Name }}f(t *testing.T) { t.Parallel() Panicsf(t, func() { - {{ $fn.Name }}f(t, {{ .TestedValue }}, "test message") + {{ $fn.Name }}f{{ if hasSuffix $fn.Name "OfTypeT" }}[myType]{{ end }}(t, {{ .TestedValue }}, "test message") }, "{{ .AssertionMessage }}") }) {{- end }} diff --git a/codegen/internal/generator/templates/requirement_assertions.gotmpl b/codegen/internal/generator/templates/requirement_assertions.gotmpl index 4b3ec961c..8fcc24dc8 100644 --- a/codegen/internal/generator/templates/requirement_assertions.gotmpl +++ b/codegen/internal/generator/templates/requirement_assertions.gotmpl @@ -24,7 +24,7 @@ func {{ .GenericName }}({{ params .AllParams }}) { {{- if or (eq .Name "Fail") (eq .Name "FailNow") }}{{/* special semantics for these two, which can only fail */}} _ = {{ .TargetPackage }}.{{ .Name }}({{ forward .AllParams }}) {{- else }} - if {{ .TargetPackage }}.{{ .Name }}({{ forward .AllParams }}) { + if {{ .TargetPackage }}.{{ .GenericCallName }}({{ forward .AllParams }}) { return } {{- end }} diff --git a/codegen/internal/generator/templates/requirement_format.gotmpl b/codegen/internal/generator/templates/requirement_format.gotmpl index 8e146809f..d2a311724 100644 --- a/codegen/internal/generator/templates/requirement_format.gotmpl +++ b/codegen/internal/generator/templates/requirement_format.gotmpl @@ -24,7 +24,7 @@ func {{ .GenericName "f" }}(t T, {{ params .Params }}, msg string, args ...any) {{- if or (eq .Name "Fail") (eq .Name "FailNow") }}{{/* special semantics for these two, which can only fail */}} _ = {{ .TargetPackage }}.{{ .Name }}(t, {{ forward .Params }}, forwardArgs(msg, args)) {{- else }} - if {{ .TargetPackage }}.{{ .Name }}(t, {{ forward .Params }}, forwardArgs(msg, args)) { + if {{ .TargetPackage }}.{{ .GenericCallName }}(t, {{ forward .Params }}, forwardArgs(msg, args)) { return } {{- end }} diff --git a/codegen/internal/model/model.go b/codegen/internal/model/model.go index f436b2f88..e06761630 100644 --- a/codegen/internal/model/model.go +++ b/codegen/internal/model/model.go @@ -103,7 +103,7 @@ type Function struct { // accounting for any type parameter for generic functions. func (f Function) GenericName(suffixes ...string) string { suffix := strings.Join(suffixes, "") - if !f.IsGeneric { // means len(f.TypeParams) > 0 + if !f.IsGeneric { // means len(f.TypeParams) == 0 return f.Name + suffix } @@ -132,6 +132,30 @@ func (f Function) GenericName(suffixes ...string) string { return w.String() } +// GenericCallName renders the function name with explicit type parameters. +// This is used when forwarding type parameters, as all type parameters may not be always infered from the arguments. +func (f Function) GenericCallName(suffixes ...string) string { + suffix := strings.Join(suffixes, "") + if !f.IsGeneric { // means len(f.TypeParams) == 0 + return f.Name + suffix + } + + var w strings.Builder + w.WriteString(f.Name) + w.WriteString(suffix) + w.WriteByte('[') + c := f.TypeParams[0] + w.WriteString(c.Name) + + for _, p := range f.TypeParams[1:] { + w.WriteString(", ") + w.WriteString(p.Name) + } + w.WriteByte(']') + + return w.String() +} + func (f Function) HasTest() bool { return len(f.Tests) > 0 } diff --git a/docs/doc-site/api/_index.md b/docs/doc-site/api/_index.md index 68dbaa2e3..85065ae1c 100644 --- a/docs/doc-site/api/_index.md +++ b/docs/doc-site/api/_index.md @@ -6,7 +6,7 @@ description: | Find the assertion function you need for your data. weight: 1 -modified: 2026-01-19 +modified: 2026-01-20 --- **Go testing assertions for the rest of us** @@ -47,7 +47,7 @@ Each domain contains assertions regrouped by their use case (e.g. http, json, er - [String](./string.md) - Asserting Strings (4) - [Testing](./testing.md) - Mimicks Methods From The Testing Standard Library (2) - [Time](./time.md) - Asserting Times And Durations (2) -- [Type](./type.md) - Asserting Types Rather Than Values (8) +- [Type](./type.md) - Asserting Types Rather Than Values (10) - [Yaml](./yaml.md) - Asserting Yaml Documents (3) - [Common](./common.md) - Other Uncategorized Helpers (4) @@ -67,5 +67,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/boolean.md b/docs/doc-site/api/boolean.md index a0ac92d26..051a15263 100644 --- a/docs/doc-site/api/boolean.md +++ b/docs/doc-site/api/boolean.md @@ -1,7 +1,7 @@ --- title: "Boolean" description: "Asserting Boolean Values" -modified: 2026-01-19 +modified: 2026-01-20 weight: 1 domains: - "boolean" @@ -235,5 +235,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/collection.md b/docs/doc-site/api/collection.md index 6f8d20088..ca704fe6e 100644 --- a/docs/doc-site/api/collection.md +++ b/docs/doc-site/api/collection.md @@ -1,7 +1,7 @@ --- title: "Collection" description: "Asserting Slices And Maps" -modified: 2026-01-19 +modified: 2026-01-20 weight: 2 domains: - "collection" @@ -897,5 +897,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/common.md b/docs/doc-site/api/common.md index b4825ffd3..bc954872b 100644 --- a/docs/doc-site/api/common.md +++ b/docs/doc-site/api/common.md @@ -1,7 +1,7 @@ --- title: "Common" description: "Other Uncategorized Helpers" -modified: 2026-01-19 +modified: 2026-01-20 weight: 18 domains: - "common" @@ -160,5 +160,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/comparison.md b/docs/doc-site/api/comparison.md index c479629ed..bd514f257 100644 --- a/docs/doc-site/api/comparison.md +++ b/docs/doc-site/api/comparison.md @@ -1,7 +1,7 @@ --- title: "Comparison" description: "Comparing Ordered Values" -modified: 2026-01-19 +modified: 2026-01-20 weight: 3 domains: - "comparison" @@ -689,5 +689,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/condition.md b/docs/doc-site/api/condition.md index 8c5952d06..7f807d153 100644 --- a/docs/doc-site/api/condition.md +++ b/docs/doc-site/api/condition.md @@ -1,7 +1,7 @@ --- title: "Condition" description: "Expressing Assertions Using Conditions" -modified: 2026-01-19 +modified: 2026-01-20 weight: 4 domains: - "condition" @@ -294,5 +294,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/equality.md b/docs/doc-site/api/equality.md index 7aea7b71b..67322c42e 100644 --- a/docs/doc-site/api/equality.md +++ b/docs/doc-site/api/equality.md @@ -1,7 +1,7 @@ --- title: "Equality" description: "Asserting Two Things Are Equal" -modified: 2026-01-19 +modified: 2026-01-20 weight: 5 domains: - "equality" @@ -867,5 +867,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/error.md b/docs/doc-site/api/error.md index 0fec5605d..6128af70c 100644 --- a/docs/doc-site/api/error.md +++ b/docs/doc-site/api/error.md @@ -1,7 +1,7 @@ --- title: "Error" description: "Asserting Errors" -modified: 2026-01-19 +modified: 2026-01-20 weight: 6 domains: - "error" @@ -453,5 +453,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/file.md b/docs/doc-site/api/file.md index 588f6f368..b21306dcd 100644 --- a/docs/doc-site/api/file.md +++ b/docs/doc-site/api/file.md @@ -1,7 +1,7 @@ --- title: "File" description: "Asserting OS Files" -modified: 2026-01-19 +modified: 2026-01-20 weight: 7 domains: - "file" @@ -344,5 +344,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/http.md b/docs/doc-site/api/http.md index 213705eb6..5a129f5ae 100644 --- a/docs/doc-site/api/http.md +++ b/docs/doc-site/api/http.md @@ -1,7 +1,7 @@ --- title: "Http" description: "Asserting HTTP Response And Body" -modified: 2026-01-19 +modified: 2026-01-20 weight: 8 domains: - "http" @@ -382,5 +382,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/json.md b/docs/doc-site/api/json.md index 5f73c697d..c82792398 100644 --- a/docs/doc-site/api/json.md +++ b/docs/doc-site/api/json.md @@ -1,7 +1,7 @@ --- title: "Json" description: "Asserting JSON Documents" -modified: 2026-01-19 +modified: 2026-01-20 weight: 9 domains: - "json" @@ -197,5 +197,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/number.md b/docs/doc-site/api/number.md index 80ece2f2f..82f93c5de 100644 --- a/docs/doc-site/api/number.md +++ b/docs/doc-site/api/number.md @@ -1,7 +1,7 @@ --- title: "Number" description: "Asserting Numbers" -modified: 2026-01-19 +modified: 2026-01-20 weight: 10 domains: - "number" @@ -443,5 +443,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/ordering.md b/docs/doc-site/api/ordering.md index 2b6139a8a..b35ba837e 100644 --- a/docs/doc-site/api/ordering.md +++ b/docs/doc-site/api/ordering.md @@ -1,7 +1,7 @@ --- title: "Ordering" description: "Asserting How Collections Are Ordered" -modified: 2026-01-19 +modified: 2026-01-20 weight: 11 domains: - "ordering" @@ -539,5 +539,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/panic.md b/docs/doc-site/api/panic.md index a08e06852..0821e979d 100644 --- a/docs/doc-site/api/panic.md +++ b/docs/doc-site/api/panic.md @@ -1,7 +1,7 @@ --- title: "Panic" description: "Asserting A Panic Behavior" -modified: 2026-01-19 +modified: 2026-01-20 weight: 12 domains: - "panic" @@ -241,5 +241,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/string.md b/docs/doc-site/api/string.md index 695e85c5b..0432c2a7e 100644 --- a/docs/doc-site/api/string.md +++ b/docs/doc-site/api/string.md @@ -1,7 +1,7 @@ --- title: "String" description: "Asserting Strings" -modified: 2026-01-19 +modified: 2026-01-20 weight: 13 domains: - "string" @@ -241,5 +241,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/testing.md b/docs/doc-site/api/testing.md index 79475dc39..7c7f5b0be 100644 --- a/docs/doc-site/api/testing.md +++ b/docs/doc-site/api/testing.md @@ -1,7 +1,7 @@ --- title: "Testing" description: "Mimicks Methods From The Testing Standard Library" -modified: 2026-01-19 +modified: 2026-01-20 weight: 14 domains: - "testing" @@ -136,5 +136,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/time.md b/docs/doc-site/api/time.md index 1f38010b5..ebdad632d 100644 --- a/docs/doc-site/api/time.md +++ b/docs/doc-site/api/time.md @@ -1,7 +1,7 @@ --- title: "Time" description: "Asserting Times And Durations" -modified: 2026-01-19 +modified: 2026-01-20 weight: 15 domains: - "time" @@ -138,5 +138,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/type.md b/docs/doc-site/api/type.md index 32525ae5d..6bfcf0805 100644 --- a/docs/doc-site/api/type.md +++ b/docs/doc-site/api/type.md @@ -1,15 +1,19 @@ --- title: "Type" description: "Asserting Types Rather Than Values" -modified: 2026-01-19 +modified: 2026-01-20 weight: 16 domains: - "type" keywords: - "Implements" - "Implementsf" + - "IsNotOfTypeT" + - "IsNotOfTypeTf" - "IsNotType" - "IsNotTypef" + - "IsOfTypeT" + - "IsOfTypeTf" - "IsType" - "IsTypef" - "Kind" @@ -33,11 +37,14 @@ Asserting Types Rather Than Values _All links point to _ -This domain exposes 8 functionalities. +This domain exposes 10 functionalities. +Generic assertions are marked with a {{% icon icon="star" color=orange %}} ```tree - [Implements](#implements) | angles-right +- [IsNotOfTypeT[EType any]](#isnotoftypetetype-any) | star | orange - [IsNotType](#isnottype) | angles-right +- [IsOfTypeT[EType any]](#isoftypetetype-any) | star | orange - [IsType](#istype) | angles-right - [Kind](#kind) | angles-right - [NotImplements](#notimplements) | angles-right @@ -93,6 +100,49 @@ Implements asserts that an object is implemented by the specified interface. {{% /tab %}} {{< /tabs >}} +### IsNotOfTypeT[EType any] {{% icon icon="star" color=orange %}}{#isnotoftypetetype-any} + +IsNotOfTypeT asserts that an object is of a given type. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.IsOfType[MyType](t,myVar) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: 123.123 + failure: myType(123.123) +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.IsNotOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsNotOfTypeT) | package-level function | +| [`assert.IsNotOfTypeTf[EType any](t T, object any, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsNotOfTypeTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.IsNotOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsNotOfTypeT) | package-level function | +| [`require.IsNotOfTypeTf[EType any](t T, object any, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsNotOfTypeTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.IsNotOfTypeT(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsNotOfTypeT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNotOfTypeT](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L141) +{{% /tab %}} +{{< /tabs >}} + ### IsNotType{#isnottype} IsNotType asserts that the specified objects are not of the same type. @@ -136,7 +186,50 @@ IsNotType asserts that the specified objects are not of the same type. |--|--| | [`assertions.IsNotType(t T, theType any, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsNotType) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNotType](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L96) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNotType](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L120) +{{% /tab %}} +{{< /tabs >}} + +### IsOfTypeT[EType any] {{% icon icon="star" color=orange %}}{#isoftypetetype-any} + +IsOfTypeT asserts that an object is of a given type. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.IsOfTypeT[MyType](t,myVar) +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: myType(123.123) + failure: 123.123 +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.IsOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsOfTypeT) | package-level function | +| [`assert.IsOfTypeTf[EType any](t T, object any, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#IsOfTypeTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.IsOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsOfTypeT) | package-level function | +| [`require.IsOfTypeTf[EType any](t T, object any, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#IsOfTypeTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.IsOfTypeT(t T, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsOfTypeT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsOfTypeT](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L96) {{% /tab %}} {{< /tabs >}} @@ -233,7 +326,7 @@ are comparable to [reflect.Invalid](https://pkg.go.dev/reflect#Invalid). See als |--|--| | [`assertions.Kind(t T, expectedKind reflect.Kind, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Kind) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Kind](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L162) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Kind](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L210) {{% /tab %}} {{< /tabs >}} @@ -330,7 +423,7 @@ are comparable to [reflect.Invalid](https://pkg.go.dev/reflect#Invalid). See als |--|--| | [`assertions.NotKind(t T, expectedKind reflect.Kind, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotKind) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotKind](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L195) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotKind](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L243) {{% /tab %}} {{< /tabs >}} @@ -377,7 +470,7 @@ NotZero asserts that i is not the zero value for its type. |--|--| | [`assertions.NotZero(t T, i any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotZero) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotZero](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L138) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotZero](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L186) {{% /tab %}} {{< /tabs >}} @@ -424,7 +517,7 @@ Zero asserts that i is the zero value for its type. |--|--| | [`assertions.Zero(t T, i any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Zero) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Zero](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L117) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Zero](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L165) {{% /tab %}} {{< /tabs >}} @@ -444,5 +537,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/docs/doc-site/api/yaml.md b/docs/doc-site/api/yaml.md index 3cac1a41c..f857fcb19 100644 --- a/docs/doc-site/api/yaml.md +++ b/docs/doc-site/api/yaml.md @@ -1,7 +1,7 @@ --- title: "Yaml" description: "Asserting Yaml Documents" -modified: 2026-01-19 +modified: 2026-01-20 weight: 17 domains: - "yaml" @@ -202,5 +202,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] --> diff --git a/internal/assertions/type.go b/internal/assertions/type.go index 392a35548..d5c84b75f 100644 --- a/internal/assertions/type.go +++ b/internal/assertions/type.go @@ -83,6 +83,30 @@ func IsType(t T, expectedType, object any, msgAndArgs ...any) bool { return Fail(t, fmt.Sprintf("Object expected to be of type %T, but was %T", expectedType, object), msgAndArgs...) } +// IsOfTypeT asserts that an object is of a given type. +// +// # Usage +// +// assertions.IsOfTypeT[MyType](t,myVar) +// +// # Examples +// +// success: myType(123.123) +// failure: 123.123 +func IsOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool { + // Domain: type + if h, ok := t.(H); ok { + h.Helper() + } + + _, ok := object.(EType) + if ok { + return true + } + + return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %T", reflect.TypeFor[EType](), object), msgAndArgs...) +} + // IsNotType asserts that the specified objects are not of the same type. // // # Usage @@ -104,6 +128,30 @@ func IsNotType(t T, theType, object any, msgAndArgs ...any) bool { return Fail(t, fmt.Sprintf("Object type expected to be different than %T", theType), msgAndArgs...) } +// IsNotOfTypeT asserts that an object is of a given type. +// +// # Usage +// +// assertions.IsOfType[MyType](t,myVar) +// +// # Examples +// +// success: 123.123 +// failure: myType(123.123) +func IsNotOfTypeT[EType any](t T, object any, msgAndArgs ...any) bool { + // Domain: type + if h, ok := t.(H); ok { + h.Helper() + } + + _, ok := object.(EType) + if !ok { + return true + } + + return Fail(t, fmt.Sprintf("Object type expected to be different than %T", reflect.TypeFor[EType]()), msgAndArgs...) +} + // Zero asserts that i is the zero value for its type. // // # Usage diff --git a/internal/assertions/type_test.go b/internal/assertions/type_test.go index 08f09be4e..1f247f1e8 100644 --- a/internal/assertions/type_test.go +++ b/internal/assertions/type_test.go @@ -65,6 +65,20 @@ func TestTypeNotIsType(t *testing.T) { } } +func TestTypeIsOfTypeT(t *testing.T) { + t.Parallel() + + mock := new(mockT) + type myType float64 + var myVar myType = 1.2 + f := 1.2 + + True(t, IsOfTypeT[myType](mock, myVar), "expected myVar to be of type %T", myVar) + False(t, IsNotOfTypeT[myType](mock, myVar), "expected myVar to be of type %T", myVar) + False(t, IsOfTypeT[myType](mock, f), "expected f (%T) not to be of type %T", f, myVar) + True(t, IsNotOfTypeT[myType](mock, f), "expected f (%T) not to be of type %T", f, myVar) +} + func TestTypeZeroWithSliceTooLongToPrint(t *testing.T) { t.Parallel() mock := new(mockT) diff --git a/require/require_assertions.go b/require/require_assertions.go index 6a796fe39..af283e775 100644 --- a/require/require_assertions.go +++ b/require/require_assertions.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require @@ -155,7 +155,7 @@ func ElementsMatchT[E comparable](t T, listA []E, listB []E, msgAndArgs ...any) if h, ok := t.(H); ok { h.Helper() } - if assertions.ElementsMatchT(t, listA, listB, msgAndArgs...) { + if assertions.ElementsMatchT[E](t, listA, listB, msgAndArgs...) { return } @@ -303,7 +303,7 @@ func EqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.EqualT(t, expected, actual, msgAndArgs...) { + if assertions.EqualT[V](t, expected, actual, msgAndArgs...) { return } @@ -627,7 +627,7 @@ func FalseT[B Boolean](t T, value B, msgAndArgs ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.FalseT(t, value, msgAndArgs...) { + if assertions.FalseT[B](t, value, msgAndArgs...) { return } @@ -816,7 +816,7 @@ func GreaterOrEqualT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndA if h, ok := t.(H); ok { h.Helper() } - if assertions.GreaterOrEqualT(t, e1, e2, msgAndArgs...) { + if assertions.GreaterOrEqualT[Orderable](t, e1, e2, msgAndArgs...) { return } @@ -852,7 +852,7 @@ func GreaterT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs ... if h, ok := t.(H); ok { h.Helper() } - if assertions.GreaterT(t, e1, e2, msgAndArgs...) { + if assertions.GreaterT[Orderable](t, e1, e2, msgAndArgs...) { return } @@ -1146,7 +1146,7 @@ func InDeltaT[Number Measurable](t T, expected Number, actual Number, delta Numb if h, ok := t.(H); ok { h.Helper() } - if assertions.InDeltaT(t, expected, actual, delta, msgAndArgs...) { + if assertions.InDeltaT[Number](t, expected, actual, delta, msgAndArgs...) { return } @@ -1251,7 +1251,7 @@ func InEpsilonT[Number Measurable](t T, expected Number, actual Number, epsilon if h, ok := t.(H); ok { h.Helper() } - if assertions.InEpsilonT(t, expected, actual, epsilon, msgAndArgs...) { + if assertions.InEpsilonT[Number](t, expected, actual, epsilon, msgAndArgs...) { return } @@ -1301,7 +1301,7 @@ func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m if h, ok := t.(H); ok { h.Helper() } - if assertions.IsDecreasingT(t, collection, msgAndArgs...) { + if assertions.IsDecreasingT[OrderedSlice, E](t, collection, msgAndArgs...) { return } @@ -1351,7 +1351,7 @@ func IsIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m if h, ok := t.(H); ok { h.Helper() } - if assertions.IsIncreasingT(t, collection, msgAndArgs...) { + if assertions.IsIncreasingT[OrderedSlice, E](t, collection, msgAndArgs...) { return } @@ -1401,7 +1401,7 @@ func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice if h, ok := t.(H); ok { h.Helper() } - if assertions.IsNonDecreasingT(t, collection, msgAndArgs...) { + if assertions.IsNonDecreasingT[OrderedSlice, E](t, collection, msgAndArgs...) { return } @@ -1451,7 +1451,30 @@ func IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice if h, ok := t.(H); ok { h.Helper() } - if assertions.IsNonIncreasingT(t, collection, msgAndArgs...) { + if assertions.IsNonIncreasingT[OrderedSlice, E](t, collection, msgAndArgs...) { + return + } + + t.FailNow() +} + +// IsNotOfTypeT asserts that an object is of a given type. +// +// # Usage +// +// assertions.IsOfType[MyType](t,myVar) +// +// # Examples +// +// success: 123.123 +// failure: myType(123.123) +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsNotOfTypeT[EType any](t T, object any, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsNotOfTypeT[EType](t, object, msgAndArgs...) { return } @@ -1481,6 +1504,29 @@ func IsNotType(t T, theType any, object any, msgAndArgs ...any) { t.FailNow() } +// IsOfTypeT asserts that an object is of a given type. +// +// # Usage +// +// assertions.IsOfTypeT[MyType](t,myVar) +// +// # Examples +// +// success: myType(123.123) +// failure: 123.123 +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsOfTypeT[EType any](t T, object any, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsOfTypeT[EType](t, object, msgAndArgs...) { + return + } + + t.FailNow() +} + // IsType asserts that the specified objects are of the same type. // // # Usage @@ -1574,7 +1620,7 @@ func JSONEqT[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msgAndArgs ...any if h, ok := t.(H); ok { h.Helper() } - if assertions.JSONEqT(t, expected, actual, msgAndArgs...) { + if assertions.JSONEqT[EDoc, ADoc](t, expected, actual, msgAndArgs...) { return } @@ -1721,7 +1767,7 @@ func LessOrEqualT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs if h, ok := t.(H); ok { h.Helper() } - if assertions.LessOrEqualT(t, e1, e2, msgAndArgs...) { + if assertions.LessOrEqualT[Orderable](t, e1, e2, msgAndArgs...) { return } @@ -1756,7 +1802,7 @@ func LessT[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msgAndArgs ...any if h, ok := t.(H); ok { h.Helper() } - if assertions.LessT(t, e1, e2, msgAndArgs...) { + if assertions.LessT[Orderable](t, e1, e2, msgAndArgs...) { return } @@ -1779,7 +1825,7 @@ func MapContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAndAr if h, ok := t.(H); ok { h.Helper() } - if assertions.MapContainsT(t, m, key, msgAndArgs...) { + if assertions.MapContainsT[Map, K, V](t, m, key, msgAndArgs...) { return } @@ -1802,7 +1848,7 @@ func MapNotContainsT[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msgAn if h, ok := t.(H); ok { h.Helper() } - if assertions.MapNotContainsT(t, m, key, msgAndArgs...) { + if assertions.MapNotContainsT[Map, K, V](t, m, key, msgAndArgs...) { return } @@ -1850,7 +1896,7 @@ func NegativeT[SignedNumber SignedNumeric](t T, e SignedNumber, msgAndArgs ...an if h, ok := t.(H); ok { h.Helper() } - if assertions.NegativeT(t, e, msgAndArgs...) { + if assertions.NegativeT[SignedNumber](t, e, msgAndArgs...) { return } @@ -2017,7 +2063,7 @@ func NotElementsMatchT[E comparable](t T, listA []E, listB []E, msgAndArgs ...an if h, ok := t.(H); ok { h.Helper() } - if assertions.NotElementsMatchT(t, listA, listB, msgAndArgs...) { + if assertions.NotElementsMatchT[E](t, listA, listB, msgAndArgs...) { return } @@ -2093,7 +2139,7 @@ func NotEqualT[V comparable](t T, expected V, actual V, msgAndArgs ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NotEqualT(t, expected, actual, msgAndArgs...) { + if assertions.NotEqualT[V](t, expected, actual, msgAndArgs...) { return } @@ -2312,7 +2358,7 @@ func NotRegexpT[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msgAndArgs ...a if h, ok := t.(H); ok { h.Helper() } - if assertions.NotRegexpT(t, rx, actual, msgAndArgs...) { + if assertions.NotRegexpT[Rex, ADoc](t, rx, actual, msgAndArgs...) { return } @@ -2363,7 +2409,7 @@ func NotSameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NotSameT(t, expected, actual, msgAndArgs...) { + if assertions.NotSameT[P](t, expected, actual, msgAndArgs...) { return } @@ -2390,7 +2436,7 @@ func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgA if h, ok := t.(H); ok { h.Helper() } - if assertions.NotSortedT(t, collection, msgAndArgs...) { + if assertions.NotSortedT[OrderedSlice, E](t, collection, msgAndArgs...) { return } @@ -2562,7 +2608,7 @@ func PositiveT[SignedNumber SignedNumeric](t T, e SignedNumber, msgAndArgs ...an if h, ok := t.(H); ok { h.Helper() } - if assertions.PositiveT(t, e, msgAndArgs...) { + if assertions.PositiveT[SignedNumber](t, e, msgAndArgs...) { return } @@ -2613,7 +2659,7 @@ func RegexpT[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msgAndArgs ...any) if h, ok := t.(H); ok { h.Helper() } - if assertions.RegexpT(t, rx, actual, msgAndArgs...) { + if assertions.RegexpT[Rex, ADoc](t, rx, actual, msgAndArgs...) { return } @@ -2662,7 +2708,7 @@ func SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.SameT(t, expected, actual, msgAndArgs...) { + if assertions.SameT[P](t, expected, actual, msgAndArgs...) { return } @@ -2685,7 +2731,7 @@ func SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArg if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceContainsT(t, s, element, msgAndArgs...) { + if assertions.SliceContainsT[Slice, E](t, s, element, msgAndArgs...) { return } @@ -2708,7 +2754,7 @@ func SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAnd if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceNotContainsT(t, s, element, msgAndArgs...) { + if assertions.SliceNotContainsT[Slice, E](t, s, element, msgAndArgs...) { return } @@ -2731,7 +2777,7 @@ func SliceNotSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, ms if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceNotSubsetT(t, list, subset, msgAndArgs...) { + if assertions.SliceNotSubsetT[Slice, E](t, list, subset, msgAndArgs...) { return } @@ -2754,7 +2800,7 @@ func SliceSubsetT[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msgAn if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceSubsetT(t, list, subset, msgAndArgs...) { + if assertions.SliceSubsetT[Slice, E](t, list, subset, msgAndArgs...) { return } @@ -2781,7 +2827,7 @@ func SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndA if h, ok := t.(H); ok { h.Helper() } - if assertions.SortedT(t, collection, msgAndArgs...) { + if assertions.SortedT[OrderedSlice, E](t, collection, msgAndArgs...) { return } @@ -2806,7 +2852,7 @@ func StringContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndArgs if h, ok := t.(H); ok { h.Helper() } - if assertions.StringContainsT(t, str, substring, msgAndArgs...) { + if assertions.StringContainsT[ADoc, EDoc](t, str, substring, msgAndArgs...) { return } @@ -2831,7 +2877,7 @@ func StringNotContainsT[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msgAndAr if h, ok := t.(H); ok { h.Helper() } - if assertions.StringNotContainsT(t, str, substring, msgAndArgs...) { + if assertions.StringNotContainsT[ADoc, EDoc](t, str, substring, msgAndArgs...) { return } @@ -2910,7 +2956,7 @@ func TrueT[B Boolean](t T, value B, msgAndArgs ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.TrueT(t, value, msgAndArgs...) { + if assertions.TrueT[B](t, value, msgAndArgs...) { return } @@ -3045,7 +3091,7 @@ func YAMLEqT[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msgAndArgs ...any if h, ok := t.(H); ok { h.Helper() } - if assertions.YAMLEqT(t, expected, actual, msgAndArgs...) { + if assertions.YAMLEqT[EDoc, ADoc](t, expected, actual, msgAndArgs...) { return } diff --git a/require/require_assertions_test.go b/require/require_assertions_test.go index a487b0bb2..bc9c0bace 100644 --- a/require/require_assertions_test.go +++ b/require/require_assertions_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require @@ -1065,6 +1065,26 @@ func TestIsNonIncreasingT(t *testing.T) { }) } +func TestIsNotOfTypeT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsNotOfTypeT[myType](t, 123.123) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsNotOfTypeT[myType](mock, myType(123.123)) + // require functions don't return a value + if !mock.failed { + t.Error("IsNotOfTypeT should call FailNow()") + } + }) +} + func TestIsNotType(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1085,6 +1105,26 @@ func TestIsNotType(t *testing.T) { }) } +func TestIsOfTypeT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsOfTypeT[myType](t, myType(123.123)) + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsOfTypeT[myType](mock, 123.123) + // require functions don't return a value + if !mock.failed { + t.Error("IsOfTypeT should call FailNow()") + } + }) +} + func TestIsType(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -2370,3 +2410,5 @@ type dummyError struct { func (d *dummyError) Error() string { return "dummy error" } + +type myType float64 diff --git a/require/require_examples_test.go b/require/require_examples_test.go index e1c56cfa6..cd5cf067a 100644 --- a/require/require_examples_test.go +++ b/require/require_examples_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require_test @@ -442,6 +442,14 @@ func ExampleIsNonIncreasingT() { // Output: passed } +func ExampleIsNotOfTypeT() { + t := new(testing.T) + require.IsNotOfTypeT[myType](t, 123.123) + fmt.Println("passed") + + // Output: passed +} + func ExampleIsNotType() { t := new(testing.T) require.IsNotType(t, int32(123), int64(456)) @@ -450,6 +458,14 @@ func ExampleIsNotType() { // Output: passed } +func ExampleIsOfTypeT() { + t := new(testing.T) + require.IsOfTypeT[myType](t, myType(123.123)) + fmt.Println("passed") + + // Output: passed +} + func ExampleIsType() { t := new(testing.T) require.IsType(t, 123, 456) @@ -992,3 +1008,5 @@ type dummyError struct { func (d *dummyError) Error() string { return "dummy error" } + +type myType float64 diff --git a/require/require_format.go b/require/require_format.go index 19af5302c..bf72fd10b 100644 --- a/require/require_format.go +++ b/require/require_format.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require @@ -92,7 +92,7 @@ func ElementsMatchTf[E comparable](t T, listA []E, listB []E, msg string, args . if h, ok := t.(H); ok { h.Helper() } - if assertions.ElementsMatchT(t, listA, listB, forwardArgs(msg, args)) { + if assertions.ElementsMatchT[E](t, listA, listB, forwardArgs(msg, args)) { return } @@ -162,7 +162,7 @@ func EqualTf[V comparable](t T, expected V, actual V, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.EqualT(t, expected, actual, forwardArgs(msg, args)) { + if assertions.EqualT[V](t, expected, actual, forwardArgs(msg, args)) { return } @@ -326,7 +326,7 @@ func FalseTf[B Boolean](t T, value B, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.FalseT(t, value, forwardArgs(msg, args)) { + if assertions.FalseT[B](t, value, forwardArgs(msg, args)) { return } @@ -424,7 +424,7 @@ func GreaterOrEqualTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg st if h, ok := t.(H); ok { h.Helper() } - if assertions.GreaterOrEqualT(t, e1, e2, forwardArgs(msg, args)) { + if assertions.GreaterOrEqualT[Orderable](t, e1, e2, forwardArgs(msg, args)) { return } @@ -438,7 +438,7 @@ func GreaterTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg string, a if h, ok := t.(H); ok { h.Helper() } - if assertions.GreaterT(t, e1, e2, forwardArgs(msg, args)) { + if assertions.GreaterT[Orderable](t, e1, e2, forwardArgs(msg, args)) { return } @@ -592,7 +592,7 @@ func InDeltaTf[Number Measurable](t T, expected Number, actual Number, delta Num if h, ok := t.(H); ok { h.Helper() } - if assertions.InDeltaT(t, expected, actual, delta, forwardArgs(msg, args)) { + if assertions.InDeltaT[Number](t, expected, actual, delta, forwardArgs(msg, args)) { return } @@ -634,7 +634,7 @@ func InEpsilonTf[Number Measurable](t T, expected Number, actual Number, epsilon if h, ok := t.(H); ok { h.Helper() } - if assertions.InEpsilonT(t, expected, actual, epsilon, forwardArgs(msg, args)) { + if assertions.InEpsilonT[Number](t, expected, actual, epsilon, forwardArgs(msg, args)) { return } @@ -662,7 +662,7 @@ func IsDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, if h, ok := t.(H); ok { h.Helper() } - if assertions.IsDecreasingT(t, collection, forwardArgs(msg, args)) { + if assertions.IsDecreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) { return } @@ -690,7 +690,7 @@ func IsIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, if h, ok := t.(H); ok { h.Helper() } - if assertions.IsIncreasingT(t, collection, forwardArgs(msg, args)) { + if assertions.IsIncreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) { return } @@ -718,7 +718,7 @@ func IsNonDecreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlic if h, ok := t.(H); ok { h.Helper() } - if assertions.IsNonDecreasingT(t, collection, forwardArgs(msg, args)) { + if assertions.IsNonDecreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) { return } @@ -746,7 +746,21 @@ func IsNonIncreasingTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlic if h, ok := t.(H); ok { h.Helper() } - if assertions.IsNonIncreasingT(t, collection, forwardArgs(msg, args)) { + if assertions.IsNonIncreasingT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// IsNotOfTypeTf is the same as [IsNotOfTypeT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsNotOfTypeTf[EType any](t T, object any, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsNotOfTypeT[EType](t, object, forwardArgs(msg, args)) { return } @@ -767,6 +781,20 @@ func IsNotTypef(t T, theType any, object any, msg string, args ...any) { t.FailNow() } +// IsOfTypeTf is the same as [IsOfTypeT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func IsOfTypeTf[EType any](t T, object any, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.IsOfTypeT[EType](t, object, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // IsTypef is the same as [IsType], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. @@ -816,7 +844,7 @@ func JSONEqTf[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msg string, args if h, ok := t.(H); ok { h.Helper() } - if assertions.JSONEqT(t, expected, actual, forwardArgs(msg, args)) { + if assertions.JSONEqT[EDoc, ADoc](t, expected, actual, forwardArgs(msg, args)) { return } @@ -886,7 +914,7 @@ func LessOrEqualTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg strin if h, ok := t.(H); ok { h.Helper() } - if assertions.LessOrEqualT(t, e1, e2, forwardArgs(msg, args)) { + if assertions.LessOrEqualT[Orderable](t, e1, e2, forwardArgs(msg, args)) { return } @@ -900,7 +928,7 @@ func LessTf[Orderable Ordered](t T, e1 Orderable, e2 Orderable, msg string, args if h, ok := t.(H); ok { h.Helper() } - if assertions.LessT(t, e1, e2, forwardArgs(msg, args)) { + if assertions.LessT[Orderable](t, e1, e2, forwardArgs(msg, args)) { return } @@ -914,7 +942,7 @@ func MapContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg str if h, ok := t.(H); ok { h.Helper() } - if assertions.MapContainsT(t, m, key, forwardArgs(msg, args)) { + if assertions.MapContainsT[Map, K, V](t, m, key, forwardArgs(msg, args)) { return } @@ -928,7 +956,7 @@ func MapNotContainsTf[Map ~map[K]V, K comparable, V any](t T, m Map, key K, msg if h, ok := t.(H); ok { h.Helper() } - if assertions.MapNotContainsT(t, m, key, forwardArgs(msg, args)) { + if assertions.MapNotContainsT[Map, K, V](t, m, key, forwardArgs(msg, args)) { return } @@ -956,7 +984,7 @@ func NegativeTf[SignedNumber SignedNumeric](t T, e SignedNumber, msg string, arg if h, ok := t.(H); ok { h.Helper() } - if assertions.NegativeT(t, e, forwardArgs(msg, args)) { + if assertions.NegativeT[SignedNumber](t, e, forwardArgs(msg, args)) { return } @@ -1040,7 +1068,7 @@ func NotElementsMatchTf[E comparable](t T, listA []E, listB []E, msg string, arg if h, ok := t.(H); ok { h.Helper() } - if assertions.NotElementsMatchT(t, listA, listB, forwardArgs(msg, args)) { + if assertions.NotElementsMatchT[E](t, listA, listB, forwardArgs(msg, args)) { return } @@ -1082,7 +1110,7 @@ func NotEqualTf[V comparable](t T, expected V, actual V, msg string, args ...any if h, ok := t.(H); ok { h.Helper() } - if assertions.NotEqualT(t, expected, actual, forwardArgs(msg, args)) { + if assertions.NotEqualT[V](t, expected, actual, forwardArgs(msg, args)) { return } @@ -1208,7 +1236,7 @@ func NotRegexpTf[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msg string, ar if h, ok := t.(H); ok { h.Helper() } - if assertions.NotRegexpT(t, rx, actual, forwardArgs(msg, args)) { + if assertions.NotRegexpT[Rex, ADoc](t, rx, actual, forwardArgs(msg, args)) { return } @@ -1236,7 +1264,7 @@ func NotSameTf[P any](t T, expected *P, actual *P, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.NotSameT(t, expected, actual, forwardArgs(msg, args)) { + if assertions.NotSameT[P](t, expected, actual, forwardArgs(msg, args)) { return } @@ -1250,7 +1278,7 @@ func NotSortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg if h, ok := t.(H); ok { h.Helper() } - if assertions.NotSortedT(t, collection, forwardArgs(msg, args)) { + if assertions.NotSortedT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) { return } @@ -1348,7 +1376,7 @@ func PositiveTf[SignedNumber SignedNumeric](t T, e SignedNumber, msg string, arg if h, ok := t.(H); ok { h.Helper() } - if assertions.PositiveT(t, e, forwardArgs(msg, args)) { + if assertions.PositiveT[SignedNumber](t, e, forwardArgs(msg, args)) { return } @@ -1376,7 +1404,7 @@ func RegexpTf[Rex RegExp, ADoc Text](t T, rx Rex, actual ADoc, msg string, args if h, ok := t.(H); ok { h.Helper() } - if assertions.RegexpT(t, rx, actual, forwardArgs(msg, args)) { + if assertions.RegexpT[Rex, ADoc](t, rx, actual, forwardArgs(msg, args)) { return } @@ -1404,7 +1432,7 @@ func SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.SameT(t, expected, actual, forwardArgs(msg, args)) { + if assertions.SameT[P](t, expected, actual, forwardArgs(msg, args)) { return } @@ -1418,7 +1446,7 @@ func SliceContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg stri if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceContainsT(t, s, element, forwardArgs(msg, args)) { + if assertions.SliceContainsT[Slice, E](t, s, element, forwardArgs(msg, args)) { return } @@ -1432,7 +1460,7 @@ func SliceNotContainsTf[Slice ~[]E, E comparable](t T, s Slice, element E, msg s if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceNotContainsT(t, s, element, forwardArgs(msg, args)) { + if assertions.SliceNotContainsT[Slice, E](t, s, element, forwardArgs(msg, args)) { return } @@ -1446,7 +1474,7 @@ func SliceNotSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, m if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceNotSubsetT(t, list, subset, forwardArgs(msg, args)) { + if assertions.SliceNotSubsetT[Slice, E](t, list, subset, forwardArgs(msg, args)) { return } @@ -1460,7 +1488,7 @@ func SliceSubsetTf[Slice ~[]E, E comparable](t T, list Slice, subset Slice, msg if h, ok := t.(H); ok { h.Helper() } - if assertions.SliceSubsetT(t, list, subset, forwardArgs(msg, args)) { + if assertions.SliceSubsetT[Slice, E](t, list, subset, forwardArgs(msg, args)) { return } @@ -1474,7 +1502,7 @@ func SortedTf[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msg st if h, ok := t.(H); ok { h.Helper() } - if assertions.SortedT(t, collection, forwardArgs(msg, args)) { + if assertions.SortedT[OrderedSlice, E](t, collection, forwardArgs(msg, args)) { return } @@ -1488,7 +1516,7 @@ func StringContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg string if h, ok := t.(H); ok { h.Helper() } - if assertions.StringContainsT(t, str, substring, forwardArgs(msg, args)) { + if assertions.StringContainsT[ADoc, EDoc](t, str, substring, forwardArgs(msg, args)) { return } @@ -1502,7 +1530,7 @@ func StringNotContainsTf[ADoc, EDoc Text](t T, str ADoc, substring EDoc, msg str if h, ok := t.(H); ok { h.Helper() } - if assertions.StringNotContainsT(t, str, substring, forwardArgs(msg, args)) { + if assertions.StringNotContainsT[ADoc, EDoc](t, str, substring, forwardArgs(msg, args)) { return } @@ -1544,7 +1572,7 @@ func TrueTf[B Boolean](t T, value B, msg string, args ...any) { if h, ok := t.(H); ok { h.Helper() } - if assertions.TrueT(t, value, forwardArgs(msg, args)) { + if assertions.TrueT[B](t, value, forwardArgs(msg, args)) { return } @@ -1614,7 +1642,7 @@ func YAMLEqTf[EDoc, ADoc Text](t T, expected EDoc, actual ADoc, msg string, args if h, ok := t.(H); ok { h.Helper() } - if assertions.YAMLEqT(t, expected, actual, forwardArgs(msg, args)) { + if assertions.YAMLEqT[EDoc, ADoc](t, expected, actual, forwardArgs(msg, args)) { return } diff --git a/require/require_format_test.go b/require/require_format_test.go index 2cc1451fd..9bb997be9 100644 --- a/require/require_format_test.go +++ b/require/require_format_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require @@ -1065,6 +1065,26 @@ func TestIsNonIncreasingTf(t *testing.T) { }) } +func TestIsNotOfTypeTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsNotOfTypeTf[myType](t, 123.123, "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsNotOfTypeTf[myType](mock, myType(123.123), "test message") + // require functions don't return a value + if !mock.failed { + t.Error("IsNotOfTypeT should call FailNow()") + } + }) +} + func TestIsNotTypef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -1085,6 +1105,26 @@ func TestIsNotTypef(t *testing.T) { }) } +func TestIsOfTypeTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + IsOfTypeTf[myType](t, myType(123.123), "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + IsOfTypeTf[myType](mock, 123.123, "test message") + // require functions don't return a value + if !mock.failed { + t.Error("IsOfTypeT should call FailNow()") + } + }) +} + func TestIsTypef(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/require/require_forward.go b/require/require_forward.go index 68e598370..3c97331d4 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require diff --git a/require/require_forward_test.go b/require/require_forward_test.go index a6df461ca..15a160321 100644 --- a/require/require_forward_test.go +++ b/require/require_forward_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require diff --git a/require/require_helpers.go b/require/require_helpers.go index f3a3cd2d3..adda9f4f8 100644 --- a/require/require_helpers.go +++ b/require/require_helpers.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require diff --git a/require/require_helpers_test.go b/require/require_helpers_test.go index 9fdc8a48e..114d31788 100644 --- a/require/require_helpers_test.go +++ b/require/require_helpers_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require diff --git a/require/require_types.go b/require/require_types.go index 8cf49888e..324c793f8 100644 --- a/require/require_types.go +++ b/require/require_types.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-19 (version fbbb078) using codegen version v2.1.9-0.20260119215714-fbbb0787fd81+dirty [sha: fbbb0787fd8131d63f280f85b14e47f7c0dc8ee0] +// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] package require From f525d48605e0d20a7410d7d32e5ded55d5ac3917 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 20 Jan 2026 01:27:15 +0100 Subject: [PATCH 06/10] feat: added generic support for SeqContains/SeqNotContains Originally from upstream: stretchr/testify#1685 NOTE: did not add SeqElementsMatch as the origin PR suggested. Contains is good enough. Signed-off-by: Frederic BIDON --- assert/assert_assertions.go | 41 ++++- assert/assert_assertions_test.go | 51 +++++- assert/assert_examples_test.go | 19 ++- assert/assert_format.go | 23 ++- assert/assert_format_test.go | 51 +++++- assert/assert_forward.go | 2 +- assert/assert_forward_test.go | 2 +- assert/assert_helpers.go | 2 +- assert/assert_helpers_test.go | 2 +- assert/assert_types.go | 2 +- .../internal/generator/funcmaps/funcmaps.go | 2 +- docs/doc-site/api/_index.md | 4 +- docs/doc-site/api/boolean.md | 2 +- docs/doc-site/api/collection.md | 154 ++++++++++++++---- docs/doc-site/api/common.md | 2 +- docs/doc-site/api/comparison.md | 2 +- docs/doc-site/api/condition.md | 2 +- docs/doc-site/api/equality.md | 2 +- docs/doc-site/api/error.md | 2 +- docs/doc-site/api/file.md | 2 +- docs/doc-site/api/http.md | 2 +- docs/doc-site/api/json.md | 2 +- docs/doc-site/api/number.md | 2 +- docs/doc-site/api/ordering.md | 26 +-- docs/doc-site/api/panic.md | 2 +- docs/doc-site/api/string.md | 2 +- docs/doc-site/api/testing.md | 2 +- docs/doc-site/api/time.md | 2 +- docs/doc-site/api/type.md | 2 +- docs/doc-site/api/yaml.md | 2 +- hack/doc-site/hugo/hugo.yaml | 6 +- internal/assertions/collection.go | 49 ++++++ internal/assertions/collection_test.go | 92 +++++++++++ require/require_assertions.go | 49 +++++- require/require_assertions_test.go | 43 ++++- require/require_examples_test.go | 19 ++- require/require_format.go | 31 +++- require/require_format_test.go | 43 ++++- require/require_forward.go | 2 +- require/require_forward_test.go | 2 +- require/require_helpers.go | 2 +- require/require_helpers_test.go | 2 +- require/require_types.go | 2 +- 43 files changed, 669 insertions(+), 86 deletions(-) diff --git a/assert/assert_assertions.go b/assert/assert_assertions.go index 86f7a3500..e8ae550d8 100644 --- a/assert/assert_assertions.go +++ b/assert/assert_assertions.go @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert import ( + "iter" "net/http" "net/url" "reflect" @@ -2311,6 +2312,44 @@ func SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) bool { return assertions.SameT[P](t, expected, actual, msgAndArgs...) } +// SeqContainsT asserts that the specified iterator contains a comparable element. +// +// # Usage +// +// assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +// +// # Examples +// +// success: slices.Values([]string{"A","B"}), "A" +// failure: slices.Values([]string{"A","B"}), "C" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SeqContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SeqContainsT[E](t, iter, element, msgAndArgs...) +} + +// SeqNotContainsT asserts that the specified iterator does not contain a comparable element. +// +// # Usage +// +// assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +// +// # Examples +// +// success: slices.Values([]string{"A","B"}), "C" +// failure: slices.Values([]string{"A","B"}), "A" +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SeqNotContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SeqNotContainsT[E](t, iter, element, msgAndArgs...) +} + // SliceContainsT asserts that the specified slice contains a comparable element. // // # Usage diff --git a/assert/assert_assertions_test.go b/assert/assert_assertions_test.go index 813a580a5..837bae922 100644 --- a/assert/assert_assertions_test.go +++ b/assert/assert_assertions_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert @@ -13,6 +13,7 @@ import ( "net/url" "path/filepath" "reflect" + "slices" "testing" "time" ) @@ -2449,6 +2450,54 @@ func TestSameT(t *testing.T) { }) } +func TestSeqContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SeqContainsT(t, slices.Values([]string{"A", "B"}), "A") + if !result { + t.Error("SeqContainsT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SeqContainsT(mock, slices.Values([]string{"A", "B"}), "C") + if result { + t.Error("SeqContainsT should return false on failure") + } + if !mock.failed { + t.Error("SeqContainsT should mark test as failed") + } + }) +} + +func TestSeqNotContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SeqNotContainsT(t, slices.Values([]string{"A", "B"}), "C") + if !result { + t.Error("SeqNotContainsT should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SeqNotContainsT(mock, slices.Values([]string{"A", "B"}), "A") + if result { + t.Error("SeqNotContainsT should return false on failure") + } + if !mock.failed { + t.Error("SeqNotContainsT should mark test as failed") + } + }) +} + func TestSliceContainsT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/assert/assert_examples_test.go b/assert/assert_examples_test.go index 426706cd4..01b2b842c 100644 --- a/assert/assert_examples_test.go +++ b/assert/assert_examples_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert_test @@ -13,6 +13,7 @@ import ( "net/url" "path/filepath" "reflect" + "slices" "testing" "time" @@ -842,6 +843,22 @@ func ExampleSameT() { // Output: success: true } +func ExampleSeqContainsT() { + t := new(testing.T) + success := assert.SeqContainsT(t, slices.Values([]string{"A", "B"}), "A") + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + +func ExampleSeqNotContainsT() { + t := new(testing.T) + success := assert.SeqNotContainsT(t, slices.Values([]string{"A", "B"}), "C") + fmt.Printf("success: %t\n", success) + + // Output: success: true +} + func ExampleSliceContainsT() { t := new(testing.T) success := assert.SliceContainsT(t, []string{"A", "B"}, "A") diff --git a/assert/assert_format.go b/assert/assert_format.go index ce2edc716..f5295aead 100644 --- a/assert/assert_format.go +++ b/assert/assert_format.go @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert import ( + "iter" "net/http" "net/url" "reflect" @@ -1035,6 +1036,26 @@ func SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) bool { return assertions.SameT[P](t, expected, actual, forwardArgs(msg, args)) } +// SeqContainsTf is the same as [SeqContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SeqContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SeqContainsT[E](t, iter, element, forwardArgs(msg, args)) +} + +// SeqNotContainsTf is the same as [SeqNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and continues execution. +func SeqNotContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) bool { + if h, ok := t.(H); ok { + h.Helper() + } + return assertions.SeqNotContainsT[E](t, iter, element, forwardArgs(msg, args)) +} + // SliceContainsTf is the same as [SliceContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and continues execution. diff --git a/assert/assert_format_test.go b/assert/assert_format_test.go index 0d99f1cdc..781a9ffab 100644 --- a/assert/assert_format_test.go +++ b/assert/assert_format_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert @@ -13,6 +13,7 @@ import ( "net/url" "path/filepath" "reflect" + "slices" "testing" "time" ) @@ -2449,6 +2450,54 @@ func TestSameTf(t *testing.T) { }) } +func TestSeqContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SeqContainsTf(t, slices.Values([]string{"A", "B"}), "A", "test message") + if !result { + t.Error("SeqContainsTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SeqContainsTf(mock, slices.Values([]string{"A", "B"}), "C", "test message") + if result { + t.Error("SeqContainsTf should return false on failure") + } + if !mock.failed { + t.Error("SeqContainsT should mark test as failed") + } + }) +} + +func TestSeqNotContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + result := SeqNotContainsTf(t, slices.Values([]string{"A", "B"}), "C", "test message") + if !result { + t.Error("SeqNotContainsTf should return true on success") + } + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := SeqNotContainsTf(mock, slices.Values([]string{"A", "B"}), "A", "test message") + if result { + t.Error("SeqNotContainsTf should return false on failure") + } + if !mock.failed { + t.Error("SeqNotContainsT should mark test as failed") + } + }) +} + func TestSliceContainsTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/assert/assert_forward.go b/assert/assert_forward.go index 9fbd33154..47acb10b5 100644 --- a/assert/assert_forward.go +++ b/assert/assert_forward.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert diff --git a/assert/assert_forward_test.go b/assert/assert_forward_test.go index 91b782fe1..ef4a0de14 100644 --- a/assert/assert_forward_test.go +++ b/assert/assert_forward_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert diff --git a/assert/assert_helpers.go b/assert/assert_helpers.go index 2b90f12ed..5c1f0b295 100644 --- a/assert/assert_helpers.go +++ b/assert/assert_helpers.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert diff --git a/assert/assert_helpers_test.go b/assert/assert_helpers_test.go index 349c27fa8..a7c3c33e0 100644 --- a/assert/assert_helpers_test.go +++ b/assert/assert_helpers_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert diff --git a/assert/assert_types.go b/assert/assert_types.go index 20af5a6a8..bcf6fcccf 100644 --- a/assert/assert_types.go +++ b/assert/assert_types.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package assert diff --git a/codegen/internal/generator/funcmaps/funcmaps.go b/codegen/internal/generator/funcmaps/funcmaps.go index 4d76d30b6..cba923ef0 100644 --- a/codegen/internal/generator/funcmaps/funcmaps.go +++ b/codegen/internal/generator/funcmaps/funcmaps.go @@ -355,7 +355,7 @@ func slugize(in string) string { switch r { case '.', '_', ' ', '\t', ':': return '-' - case '[', ']', ',': + case '[', ']', ',', '~': return -1 default: return r diff --git a/docs/doc-site/api/_index.md b/docs/doc-site/api/_index.md index 85065ae1c..435d15298 100644 --- a/docs/doc-site/api/_index.md +++ b/docs/doc-site/api/_index.md @@ -33,7 +33,7 @@ Each domain contains assertions regrouped by their use case (e.g. http, json, er --- - [Boolean](./boolean.md) - Asserting Boolean Values (4) -- [Collection](./collection.md) - Asserting Slices And Maps (17) +- [Collection](./collection.md) - Asserting Slices And Maps (19) - [Comparison](./comparison.md) - Comparing Ordered Values (12) - [Condition](./condition.md) - Expressing Assertions Using Conditions (4) - [Equality](./equality.md) - Asserting Two Things Are Equal (16) @@ -67,5 +67,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/boolean.md b/docs/doc-site/api/boolean.md index 051a15263..64fb03a32 100644 --- a/docs/doc-site/api/boolean.md +++ b/docs/doc-site/api/boolean.md @@ -235,5 +235,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/collection.md b/docs/doc-site/api/collection.md index ca704fe6e..1f0a706d7 100644 --- a/docs/doc-site/api/collection.md +++ b/docs/doc-site/api/collection.md @@ -26,6 +26,10 @@ keywords: - "NotElementsMatchTf" - "NotSubset" - "NotSubsetf" + - "SeqContainsT" + - "SeqContainsTf" + - "SeqNotContainsT" + - "SeqNotContainsTf" - "SliceContainsT" - "SliceContainsTf" - "SliceNotContainsT" @@ -51,7 +55,7 @@ Asserting Slices And Maps _All links point to _ -This domain exposes 17 functionalities. +This domain exposes 19 functionalities. Generic assertions are marked with a {{% icon icon="star" color=orange %}} ```tree @@ -59,16 +63,18 @@ Generic assertions are marked with a {{% icon icon="star" color=orange %}} - [ElementsMatch](#elementsmatch) | angles-right - [ElementsMatchT[E comparable]](#elementsmatchte-comparable) | star | orange - [Len](#len) | angles-right -- [MapContainsT[Map ~map[K]V, K comparable, V any]](#mapcontainstmap-~mapkv-k-comparable-v-any) | star | orange -- [MapNotContainsT[Map ~map[K]V, K comparable, V any]](#mapnotcontainstmap-~mapkv-k-comparable-v-any) | star | orange +- [MapContainsT[Map ~map[K]V, K comparable, V any]](#mapcontainstmap-mapkv-k-comparable-v-any) | star | orange +- [MapNotContainsT[Map ~map[K]V, K comparable, V any]](#mapnotcontainstmap-mapkv-k-comparable-v-any) | star | orange - [NotContains](#notcontains) | angles-right - [NotElementsMatch](#notelementsmatch) | angles-right - [NotElementsMatchT[E comparable]](#notelementsmatchte-comparable) | star | orange - [NotSubset](#notsubset) | angles-right -- [SliceContainsT[Slice ~[]E, E comparable]](#slicecontainstslice-~e-e-comparable) | star | orange -- [SliceNotContainsT[Slice ~[]E, E comparable]](#slicenotcontainstslice-~e-e-comparable) | star | orange -- [SliceNotSubsetT[Slice ~[]E, E comparable]](#slicenotsubsettslice-~e-e-comparable) | star | orange -- [SliceSubsetT[Slice ~[]E, E comparable]](#slicesubsettslice-~e-e-comparable) | star | orange +- [SeqContainsT[E comparable]](#seqcontainste-comparable) | star | orange +- [SeqNotContainsT[E comparable]](#seqnotcontainste-comparable) | star | orange +- [SliceContainsT[Slice ~[]E, E comparable]](#slicecontainstslice-e-e-comparable) | star | orange +- [SliceNotContainsT[Slice ~[]E, E comparable]](#slicenotcontainstslice-e-e-comparable) | star | orange +- [SliceNotSubsetT[Slice ~[]E, E comparable]](#slicenotsubsettslice-e-e-comparable) | star | orange +- [SliceSubsetT[Slice ~[]E, E comparable]](#slicesubsettslice-e-e-comparable) | star | orange - [StringContainsT[ADoc, EDoc Text]](#stringcontainstadoc-edoc-text) | star | orange - [StringNotContainsT[ADoc, EDoc Text]](#stringnotcontainstadoc-edoc-text) | star | orange - [Subset](#subset) | angles-right @@ -120,7 +126,7 @@ specified substring or element. |--|--| | [`assertions.Contains(t T, s any, contains any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Contains) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Contains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L64) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Contains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L65) {{% /tab %}} {{< /tabs >}} @@ -169,7 +175,7 @@ the number of appearances of each of them in both lists should match. |--|--| | [`assertions.ElementsMatch(t T, listA any, listB any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#ElementsMatch) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L473) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L522) {{% /tab %}} {{< /tabs >}} @@ -214,7 +220,7 @@ the number of appearances of each of them in both lists should match. |--|--| | [`assertions.ElementsMatchT(t T, listA []E, listB []E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#ElementsMatchT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L546) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#ElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L595) {{% /tab %}} {{< /tabs >}} @@ -269,14 +275,14 @@ See also [reflect.Len](https://pkg.go.dev/reflect#Len). |--|--| | [`assertions.Len(t T, object any, length int, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Len) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Len](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L32) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Len](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L33) > **Note** > (proposals) this does not currently support iterators, or collection objects that have a Len() method. {{% /tab %}} {{< /tabs >}} -### MapContainsT[Map ~map[K]V, K comparable, V any] {{% icon icon="star" color=orange %}}{#mapcontainstmap-~mapkv-k-comparable-v-any} +### MapContainsT[Map ~map[K]V, K comparable, V any] {{% icon icon="star" color=orange %}}{#mapcontainstmap-mapkv-k-comparable-v-any} MapContainsT asserts that the specified map contains a key. @@ -315,11 +321,11 @@ MapContainsT asserts that the specified map contains a key. |--|--| | [`assertions.MapContainsT(t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#MapContainsT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#MapContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L139) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#MapContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L164) {{% /tab %}} {{< /tabs >}} -### MapNotContainsT[Map ~map[K]V, K comparable, V any] {{% icon icon="star" color=orange %}}{#mapnotcontainstmap-~mapkv-k-comparable-v-any} +### MapNotContainsT[Map ~map[K]V, K comparable, V any] {{% icon icon="star" color=orange %}}{#mapnotcontainstmap-mapkv-k-comparable-v-any} MapNotContainsT asserts that the specified map does not contain a key. @@ -358,7 +364,7 @@ MapNotContainsT asserts that the specified map does not contain a key. |--|--| | [`assertions.MapNotContainsT(t T, m Map, key K, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#MapNotContainsT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#MapNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L241) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#MapNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L290) {{% /tab %}} {{< /tabs >}} @@ -408,7 +414,7 @@ specified substring or element. |--|--| | [`assertions.NotContains(t T, s any, contains any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotContains) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotContains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L166) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotContains](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L191) {{% /tab %}} {{< /tabs >}} @@ -460,7 +466,7 @@ This is an inverse of ElementsMatch. |--|--| | [`assertions.NotElementsMatch(t T, listA any, listB any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatch) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L510) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatch](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L559) {{% /tab %}} {{< /tabs >}} @@ -508,7 +514,7 @@ This is an inverse of ElementsMatch. |--|--| | [`assertions.NotElementsMatchT(t T, listA []E, listB []E, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatchT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L582) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotElementsMatchT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L631) {{% /tab %}} {{< /tabs >}} @@ -561,11 +567,97 @@ only the map key is evaluated. |--|--| | [`assertions.NotSubset(t T, list any, subset any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotSubset) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSubset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L375) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotSubset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L424) {{% /tab %}} {{< /tabs >}} -### SliceContainsT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicecontainstslice-~e-e-comparable} +### SeqContainsT[E comparable] {{% icon icon="star" color=orange %}}{#seqcontainste-comparable} + +SeqContainsT asserts that the specified iterator contains a comparable element. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: slices.Values([]string{"A","B"}), "A" + failure: slices.Values([]string{"A","B"}), "C" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SeqContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SeqContainsT) | package-level function | +| [`assert.SeqContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SeqContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SeqContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SeqContainsT) | package-level function | +| [`require.SeqContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SeqContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SeqContainsT(t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SeqContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SeqContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L140) +{{% /tab %}} +{{< /tabs >}} + +### SeqNotContainsT[E comparable] {{% icon icon="star" color=orange %}}{#seqnotcontainste-comparable} + +SeqNotContainsT asserts that the specified iterator does not contain a comparable element. + +{{% expand title="Examples" %}} +{{< tabs >}} +{{% tab title="Usage" %}} +```go + assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +``` +{{< /tab >}} +{{% tab title="Examples" %}} +```go + success: slices.Values([]string{"A","B"}), "C" + failure: slices.Values([]string{"A","B"}), "A" +``` +{{< /tab >}} +{{< /tabs >}} +{{% /expand %}} + +{{< tabs >}} +{{% tab title="assert" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`assert.SeqNotContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SeqNotContainsT) | package-level function | +| [`assert.SeqNotContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#SeqNotContainsTf) | formatted variant | +{{% /tab %}} +{{% tab title="require" style="secondary" %}} +| Signature | Usage | +|--|--| +| [`require.SeqNotContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SeqNotContainsT) | package-level function | +| [`require.SeqNotContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/require#SeqNotContainsTf) | formatted variant | +{{% /tab %}} + +{{% tab title="internal" style="accent" icon="wrench" %}} +| Signature | Usage | +|--|--| +| [`assertions.SeqNotContainsT(t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SeqNotContainsT) | internal implementation | + +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SeqNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L266) +{{% /tab %}} +{{< /tabs >}} + +### SliceContainsT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicecontainstslice-e-e-comparable} SliceContainsT asserts that the specified slice contains a comparable element. @@ -604,11 +696,11 @@ SliceContainsT asserts that the specified slice contains a comparable element. |--|--| | [`assertions.SliceContainsT(t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceContainsT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L116) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L117) {{% /tab %}} {{< /tabs >}} -### SliceNotContainsT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicenotcontainstslice-~e-e-comparable} +### SliceNotContainsT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicenotcontainstslice-e-e-comparable} SliceNotContainsT asserts that the specified slice does not contain a comparable element. @@ -647,11 +739,11 @@ SliceNotContainsT asserts that the specified slice does not contain a comparable |--|--| | [`assertions.SliceNotContainsT(t T, s Slice, element E, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceNotContainsT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L218) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L243) {{% /tab %}} {{< /tabs >}} -### SliceNotSubsetT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicenotsubsettslice-~e-e-comparable} +### SliceNotSubsetT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicenotsubsettslice-e-e-comparable} SliceNotSubsetT asserts that a slice of comparable elements does not contain all the elements given in the subset. @@ -690,11 +782,11 @@ SliceNotSubsetT asserts that a slice of comparable elements does not contain all |--|--| | [`assertions.SliceNotSubsetT(t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceNotSubsetT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceNotSubsetT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L446) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceNotSubsetT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L495) {{% /tab %}} {{< /tabs >}} -### SliceSubsetT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicesubsettslice-~e-e-comparable} +### SliceSubsetT[Slice ~[]E, E comparable] {{% icon icon="star" color=orange %}}{#slicesubsettslice-e-e-comparable} SliceSubsetT asserts that a slice of comparable elements contains all the elements given in the subset. @@ -733,7 +825,7 @@ SliceSubsetT asserts that a slice of comparable elements contains all the elemen |--|--| | [`assertions.SliceSubsetT(t T, list Slice, subset Slice, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#SliceSubsetT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceSubsetT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L344) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#SliceSubsetT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L393) {{% /tab %}} {{< /tabs >}} @@ -778,7 +870,7 @@ Strings may be go strings or []byte. |--|--| | [`assertions.StringContainsT(t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#StringContainsT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#StringContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L93) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#StringContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L94) {{% /tab %}} {{< /tabs >}} @@ -823,7 +915,7 @@ Strings may be go strings or []byte. |--|--| | [`assertions.StringNotContainsT(t T, str ADoc, substring EDoc, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#StringNotContainsT) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#StringNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L195) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#StringNotContainsT](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L220) {{% /tab %}} {{< /tabs >}} @@ -877,7 +969,7 @@ only the map key is evaluated. |--|--| | [`assertions.Subset(t T, list any, subset any, msgAndArgs ...any) (ok bool)`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Subset) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Subset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L272) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Subset](https://github.com/go-openapi/testify/blob/master/internal/assertions/collection.go#L321) {{% /tab %}} {{< /tabs >}} @@ -897,5 +989,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/common.md b/docs/doc-site/api/common.md index bc954872b..3d6054584 100644 --- a/docs/doc-site/api/common.md +++ b/docs/doc-site/api/common.md @@ -160,5 +160,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/comparison.md b/docs/doc-site/api/comparison.md index bd514f257..190d45889 100644 --- a/docs/doc-site/api/comparison.md +++ b/docs/doc-site/api/comparison.md @@ -689,5 +689,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/condition.md b/docs/doc-site/api/condition.md index 7f807d153..3a2576b6f 100644 --- a/docs/doc-site/api/condition.md +++ b/docs/doc-site/api/condition.md @@ -294,5 +294,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/equality.md b/docs/doc-site/api/equality.md index 67322c42e..6983d8f66 100644 --- a/docs/doc-site/api/equality.md +++ b/docs/doc-site/api/equality.md @@ -867,5 +867,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/error.md b/docs/doc-site/api/error.md index 6128af70c..9602b9ded 100644 --- a/docs/doc-site/api/error.md +++ b/docs/doc-site/api/error.md @@ -453,5 +453,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/file.md b/docs/doc-site/api/file.md index b21306dcd..45ae3387a 100644 --- a/docs/doc-site/api/file.md +++ b/docs/doc-site/api/file.md @@ -344,5 +344,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/http.md b/docs/doc-site/api/http.md index 5a129f5ae..c67a91298 100644 --- a/docs/doc-site/api/http.md +++ b/docs/doc-site/api/http.md @@ -382,5 +382,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/json.md b/docs/doc-site/api/json.md index c82792398..a1febdd42 100644 --- a/docs/doc-site/api/json.md +++ b/docs/doc-site/api/json.md @@ -197,5 +197,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/number.md b/docs/doc-site/api/number.md index 82f93c5de..1a080e0a0 100644 --- a/docs/doc-site/api/number.md +++ b/docs/doc-site/api/number.md @@ -443,5 +443,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/ordering.md b/docs/doc-site/api/ordering.md index b35ba837e..2acdc61d4 100644 --- a/docs/doc-site/api/ordering.md +++ b/docs/doc-site/api/ordering.md @@ -42,15 +42,15 @@ Generic assertions are marked with a {{% icon icon="star" color=orange %}} ```tree - [IsDecreasing](#isdecreasing) | angles-right -- [IsDecreasingT[OrderedSlice ~[]E, E Ordered]](#isdecreasingtorderedslice-~e-e-ordered) | star | orange +- [IsDecreasingT[OrderedSlice ~[]E, E Ordered]](#isdecreasingtorderedslice-e-e-ordered) | star | orange - [IsIncreasing](#isincreasing) | angles-right -- [IsIncreasingT[OrderedSlice ~[]E, E Ordered]](#isincreasingtorderedslice-~e-e-ordered) | star | orange +- [IsIncreasingT[OrderedSlice ~[]E, E Ordered]](#isincreasingtorderedslice-e-e-ordered) | star | orange - [IsNonDecreasing](#isnondecreasing) | angles-right -- [IsNonDecreasingT[OrderedSlice ~[]E, E Ordered]](#isnondecreasingtorderedslice-~e-e-ordered) | star | orange +- [IsNonDecreasingT[OrderedSlice ~[]E, E Ordered]](#isnondecreasingtorderedslice-e-e-ordered) | star | orange - [IsNonIncreasing](#isnonincreasing) | angles-right -- [IsNonIncreasingT[OrderedSlice ~[]E, E Ordered]](#isnonincreasingtorderedslice-~e-e-ordered) | star | orange -- [NotSortedT[OrderedSlice ~[]E, E Ordered]](#notsortedtorderedslice-~e-e-ordered) | star | orange -- [SortedT[OrderedSlice ~[]E, E Ordered]](#sortedtorderedslice-~e-e-ordered) | star | orange +- [IsNonIncreasingT[OrderedSlice ~[]E, E Ordered]](#isnonincreasingtorderedslice-e-e-ordered) | star | orange +- [NotSortedT[OrderedSlice ~[]E, E Ordered]](#notsortedtorderedslice-e-e-ordered) | star | orange +- [SortedT[OrderedSlice ~[]E, E Ordered]](#sortedtorderedslice-e-e-ordered) | star | orange ``` ### IsDecreasing{#isdecreasing} @@ -102,7 +102,7 @@ IsDecreasing asserts that the collection is strictly decreasing. {{% /tab %}} {{< /tabs >}} -### IsDecreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isdecreasingtorderedslice-~e-e-ordered} +### IsDecreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isdecreasingtorderedslice-e-e-ordered} IsDecreasingT asserts that a slice of [Ordered] is strictly decreasing. @@ -196,7 +196,7 @@ IsIncreasing asserts that the collection is strictly increasing. {{% /tab %}} {{< /tabs >}} -### IsIncreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isincreasingtorderedslice-~e-e-ordered} +### IsIncreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isincreasingtorderedslice-e-e-ordered} IsIncreasingT asserts that a slice of [Ordered] is strictly increasing. @@ -290,7 +290,7 @@ IsNonDecreasing asserts that the collection is not strictly decreasing. {{% /tab %}} {{< /tabs >}} -### IsNonDecreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isnondecreasingtorderedslice-~e-e-ordered} +### IsNonDecreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isnondecreasingtorderedslice-e-e-ordered} IsNonDecreasingT asserts that a slice of [Ordered] is not decreasing. @@ -384,7 +384,7 @@ IsNonIncreasing asserts that the collection is not increasing. {{% /tab %}} {{< /tabs >}} -### IsNonIncreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isnonincreasingtorderedslice-~e-e-ordered} +### IsNonIncreasingT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#isnonincreasingtorderedslice-e-e-ordered} IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. @@ -429,7 +429,7 @@ IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. {{% /tab %}} {{< /tabs >}} -### NotSortedT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#notsortedtorderedslice-~e-e-ordered} +### NotSortedT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#notsortedtorderedslice-e-e-ordered} NotSortedT asserts that the slice of [Ordered] is NOT sorted (i.e. non-strictly increasing). @@ -476,7 +476,7 @@ Unlike [IsDecreasingT], it accepts slices that are neither increasing nor decrea {{% /tab %}} {{< /tabs >}} -### SortedT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#sortedtorderedslice-~e-e-ordered} +### SortedT[OrderedSlice ~[]E, E Ordered] {{% icon icon="star" color=orange %}}{#sortedtorderedslice-e-e-ordered} SortedT asserts that the slice of [Ordered] is sorted (i.e. non-strictly increasing). @@ -539,5 +539,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/panic.md b/docs/doc-site/api/panic.md index 0821e979d..de68b1483 100644 --- a/docs/doc-site/api/panic.md +++ b/docs/doc-site/api/panic.md @@ -241,5 +241,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/string.md b/docs/doc-site/api/string.md index 0432c2a7e..7fc68a217 100644 --- a/docs/doc-site/api/string.md +++ b/docs/doc-site/api/string.md @@ -241,5 +241,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/testing.md b/docs/doc-site/api/testing.md index 7c7f5b0be..3a0e121c4 100644 --- a/docs/doc-site/api/testing.md +++ b/docs/doc-site/api/testing.md @@ -136,5 +136,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/time.md b/docs/doc-site/api/time.md index ebdad632d..29a19a1b3 100644 --- a/docs/doc-site/api/time.md +++ b/docs/doc-site/api/time.md @@ -138,5 +138,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/type.md b/docs/doc-site/api/type.md index 6bfcf0805..4159d7ca7 100644 --- a/docs/doc-site/api/type.md +++ b/docs/doc-site/api/type.md @@ -537,5 +537,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/docs/doc-site/api/yaml.md b/docs/doc-site/api/yaml.md index f857fcb19..4b18c9058 100644 --- a/docs/doc-site/api/yaml.md +++ b/docs/doc-site/api/yaml.md @@ -202,5 +202,5 @@ SPDX-License-Identifier: Apache-2.0 Document generated by github.com/go-openapi/testify/codegen/v2 DO NOT EDIT. -Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] --> diff --git a/hack/doc-site/hugo/hugo.yaml b/hack/doc-site/hugo/hugo.yaml index c90e9b24b..47922f67b 100644 --- a/hack/doc-site/hugo/hugo.yaml +++ b/hack/doc-site/hugo/hugo.yaml @@ -14,9 +14,9 @@ contentDir: content # Output formats outputs: home: - - HTML - - RSS - - SEARCH + - html + - rss + - print # Markup configuration markup: diff --git a/internal/assertions/collection.go b/internal/assertions/collection.go index 81e03684d..ddceac45d 100644 --- a/internal/assertions/collection.go +++ b/internal/assertions/collection.go @@ -6,6 +6,7 @@ package assertions import ( "bytes" "fmt" + "iter" "reflect" "slices" "strings" @@ -126,6 +127,30 @@ func SliceContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAndArg return true } +// SeqContainsT asserts that the specified iterator contains a comparable element. +// +// # Usage +// +// assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +// +// # Examples +// +// success: slices.Values([]string{"A","B"}), "A" +// failure: slices.Values([]string{"A","B"}), "C" +func SeqContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + s := slices.Collect(iter) + if !slices.Contains(s, element) { + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", s), element), msgAndArgs...) + } + + return true +} + // MapContainsT asserts that the specified map contains a key. // // # Usage @@ -228,6 +253,30 @@ func SliceNotContainsT[Slice ~[]E, E comparable](t T, s Slice, element E, msgAnd return true } +// SeqNotContainsT asserts that the specified iterator does not contain a comparable element. +// +// # Usage +// +// assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +// +// # Examples +// +// success: slices.Values([]string{"A","B"}), "C" +// failure: slices.Values([]string{"A","B"}), "A" +func SeqNotContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) bool { + // Domain: collection + if h, ok := t.(H); ok { + h.Helper() + } + + s := slices.Collect(iter) + if slices.Contains(s, element) { + return Fail(t, fmt.Sprintf("%s does not contain %#v", truncatingFormat("%#v", s), element), msgAndArgs...) + } + + return true +} + // MapNotContainsT asserts that the specified map does not contain a key. // // # Usage diff --git a/internal/assertions/collection_test.go b/internal/assertions/collection_test.go index c4a6871f5..d056c3c3c 100644 --- a/internal/assertions/collection_test.go +++ b/internal/assertions/collection_test.go @@ -919,6 +919,76 @@ func TestSliceNotContainsT(t *testing.T) { } } +func TestSeqContainsT(t *testing.T) { + t.Parallel() + + for tc := range sliceContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Type dispatch + switch container := tc.container.(type) { + case []int: + element, ok := tc.element.(int) + if !ok { + t.Fatalf("invalid test case: requires int element but got %T", tc.element) + } + testSeqContainsT(SeqContainsT[int], container, element, tc.shouldPass)(t) + case []string: + element, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string element but got %T", tc.element) + } + testSeqContainsT(SeqContainsT[string], container, element, tc.shouldPass)(t) + case []float64: + element, ok := tc.element.(float64) + if !ok { + t.Fatalf("invalid test case: requires float64 element but got %T", tc.element) + } + testSeqContainsT(SeqContainsT[float64], container, element, tc.shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + +func TestSeqNotContainsT(t *testing.T) { + t.Parallel() + + for tc := range sliceContainsTCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // Invert shouldPass for NotContains + shouldPass := !tc.shouldPass + + // Type dispatch + switch container := tc.container.(type) { + case []int: + element, ok := tc.element.(int) + if !ok { + t.Fatalf("invalid test case: requires int element but got %T", tc.element) + } + testSeqContainsT(SeqNotContainsT[int], container, element, shouldPass)(t) + case []string: + element, ok := tc.element.(string) + if !ok { + t.Fatalf("invalid test case: requires string element but got %T", tc.element) + } + testSeqContainsT(SeqNotContainsT[string], container, element, shouldPass)(t) + case []float64: + element, ok := tc.element.(float64) + if !ok { + t.Fatalf("invalid test case: requires float64 element but got %T", tc.element) + } + testSeqContainsT(SeqNotContainsT[float64], container, element, shouldPass)(t) + default: + t.Fatalf("unexpected type: %T", container) + } + }) + } +} + func TestMapContainsT(t *testing.T) { t.Parallel() @@ -1034,6 +1104,28 @@ func testSliceContainsT[Slice ~[]E, E comparable]( } } +//nolint:thelper // linter false positive: these are not helpers +func testSeqContainsT[Slice ~[]E, E comparable]( + fn func(T, iter.Seq[E], E, ...any) bool, + slice Slice, + element E, + shouldPass bool, +) func(*testing.T) { + return func(t *testing.T) { + mock := new(mockT) + result := fn(mock, slices.Values(slice), element) + + if shouldPass { + True(t, result) + False(t, mock.Failed()) + return + } + + False(t, result) + True(t, mock.Failed()) + } +} + //nolint:thelper // linter false positive: these are not helpers func testMapContainsT[Map ~map[K]V, K comparable, V any]( fn func(T, Map, K, ...any) bool, diff --git a/require/require_assertions.go b/require/require_assertions.go index af283e775..9f8df864f 100644 --- a/require/require_assertions.go +++ b/require/require_assertions.go @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require import ( + "iter" "net/http" "net/url" "reflect" @@ -2715,6 +2716,52 @@ func SameT[P any](t T, expected *P, actual *P, msgAndArgs ...any) { t.FailNow() } +// SeqContainsT asserts that the specified iterator contains a comparable element. +// +// # Usage +// +// assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +// +// # Examples +// +// success: slices.Values([]string{"A","B"}), "A" +// failure: slices.Values([]string{"A","B"}), "C" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SeqContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SeqContainsT[E](t, iter, element, msgAndArgs...) { + return + } + + t.FailNow() +} + +// SeqNotContainsT asserts that the specified iterator does not contain a comparable element. +// +// # Usage +// +// assertions.SeqContainsT(t, slices.Values([]{"Hello","World"}), "World") +// +// # Examples +// +// success: slices.Values([]string{"A","B"}), "C" +// failure: slices.Values([]string{"A","B"}), "A" +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SeqNotContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SeqNotContainsT[E](t, iter, element, msgAndArgs...) { + return + } + + t.FailNow() +} + // SliceContainsT asserts that the specified slice contains a comparable element. // // # Usage diff --git a/require/require_assertions_test.go b/require/require_assertions_test.go index bc9c0bace..6fdcc9445 100644 --- a/require/require_assertions_test.go +++ b/require/require_assertions_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require @@ -13,6 +13,7 @@ import ( "net/url" "path/filepath" "reflect" + "slices" "testing" "time" ) @@ -2045,6 +2046,46 @@ func TestSameT(t *testing.T) { }) } +func TestSeqContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SeqContainsT(t, slices.Values([]string{"A", "B"}), "A") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SeqContainsT(mock, slices.Values([]string{"A", "B"}), "C") + // require functions don't return a value + if !mock.failed { + t.Error("SeqContainsT should call FailNow()") + } + }) +} + +func TestSeqNotContainsT(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SeqNotContainsT(t, slices.Values([]string{"A", "B"}), "C") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SeqNotContainsT(mock, slices.Values([]string{"A", "B"}), "A") + // require functions don't return a value + if !mock.failed { + t.Error("SeqNotContainsT should call FailNow()") + } + }) +} + func TestSliceContainsT(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/require/require_examples_test.go b/require/require_examples_test.go index cd5cf067a..749f60689 100644 --- a/require/require_examples_test.go +++ b/require/require_examples_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require_test @@ -13,6 +13,7 @@ import ( "net/url" "path/filepath" "reflect" + "slices" "testing" "time" @@ -843,6 +844,22 @@ func ExampleSameT() { // Output: passed } +func ExampleSeqContainsT() { + t := new(testing.T) + require.SeqContainsT(t, slices.Values([]string{"A", "B"}), "A") + fmt.Println("passed") + + // Output: passed +} + +func ExampleSeqNotContainsT() { + t := new(testing.T) + require.SeqNotContainsT(t, slices.Values([]string{"A", "B"}), "C") + fmt.Println("passed") + + // Output: passed +} + func ExampleSliceContainsT() { t := new(testing.T) require.SliceContainsT(t, []string{"A", "B"}, "A") diff --git a/require/require_format.go b/require/require_format.go index bf72fd10b..7c547d826 100644 --- a/require/require_format.go +++ b/require/require_format.go @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require import ( + "iter" "net/http" "net/url" "reflect" @@ -1439,6 +1440,34 @@ func SameTf[P any](t T, expected *P, actual *P, msg string, args ...any) { t.FailNow() } +// SeqContainsTf is the same as [SeqContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SeqContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SeqContainsT[E](t, iter, element, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + +// SeqNotContainsTf is the same as [SeqNotContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. +// +// Upon failure, the test [T] is marked as failed and stops execution. +func SeqNotContainsTf[E comparable](t T, iter iter.Seq[E], element E, msg string, args ...any) { + if h, ok := t.(H); ok { + h.Helper() + } + if assertions.SeqNotContainsT[E](t, iter, element, forwardArgs(msg, args)) { + return + } + + t.FailNow() +} + // SliceContainsTf is the same as [SliceContainsT], but it accepts a format msg string to format arguments like [fmt.Printf]. // // Upon failure, the test [T] is marked as failed and stops execution. diff --git a/require/require_format_test.go b/require/require_format_test.go index 9bb997be9..483bceff2 100644 --- a/require/require_format_test.go +++ b/require/require_format_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require @@ -13,6 +13,7 @@ import ( "net/url" "path/filepath" "reflect" + "slices" "testing" "time" ) @@ -2045,6 +2046,46 @@ func TestSameTf(t *testing.T) { }) } +func TestSeqContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SeqContainsTf(t, slices.Values([]string{"A", "B"}), "A", "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SeqContainsTf(mock, slices.Values([]string{"A", "B"}), "C", "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SeqContainsT should call FailNow()") + } + }) +} + +func TestSeqNotContainsTf(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + SeqNotContainsTf(t, slices.Values([]string{"A", "B"}), "C", "test message") + // require functions don't return a value + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + mock := new(mockFailNowT) + SeqNotContainsTf(mock, slices.Values([]string{"A", "B"}), "A", "test message") + // require functions don't return a value + if !mock.failed { + t.Error("SeqNotContainsT should call FailNow()") + } + }) +} + func TestSliceContainsTf(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { diff --git a/require/require_forward.go b/require/require_forward.go index 3c97331d4..486b392d1 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require diff --git a/require/require_forward_test.go b/require/require_forward_test.go index 15a160321..73e8dc239 100644 --- a/require/require_forward_test.go +++ b/require/require_forward_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require diff --git a/require/require_helpers.go b/require/require_helpers.go index adda9f4f8..5b376445f 100644 --- a/require/require_helpers.go +++ b/require/require_helpers.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require diff --git a/require/require_helpers_test.go b/require/require_helpers_test.go index 114d31788..9412da3b8 100644 --- a/require/require_helpers_test.go +++ b/require/require_helpers_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require diff --git a/require/require_types.go b/require/require_types.go index 324c793f8..4688674e8 100644 --- a/require/require_types.go +++ b/require/require_types.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Code generated with github.com/go-openapi/testify/codegen/v2; DO NOT EDIT. -// Generated on 2026-01-20 (version ff38347) using codegen version v2.1.9-0.20260119220113-ff3834752ffb+dirty [sha: ff3834752ffbc6e4e938c8a0293cc8363f861398] +// Generated on 2026-01-20 (version 74d5686) using codegen version v2.1.9-0.20260119232631-74d5686313f0+dirty [sha: 74d5686313f0820ae0e2758b95d598f646cd7ad5] package require From c11a28ae1242c50217a4832d3827882ef3af8ed0 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 20 Jan 2026 15:08:32 +0100 Subject: [PATCH 07/10] fix: aligned the behavior of IsNonDecreasing, IsNonIncreasing to sounder expectations Now the generic version and the reflect-based version are functionally equivalent and aligned with the documentation - no quirks no more. test: refactored tests Tests for functions of the order domain fully refactored to eliminate code duplicate and remain rather maintainable. Signed-off-by: Frederic BIDON --- internal/assertions/equal_test.go | 189 +++++- internal/assertions/number_test.go | 166 ++--- internal/assertions/order.go | 81 ++- internal/assertions/order_test.go | 987 ++++++++++++----------------- internal/assertions/string_test.go | 302 +++------ 5 files changed, 818 insertions(+), 907 deletions(-) diff --git a/internal/assertions/equal_test.go b/internal/assertions/equal_test.go index d21ce76ed..a1b0b8261 100644 --- a/internal/assertions/equal_test.go +++ b/internal/assertions/equal_test.go @@ -17,6 +17,7 @@ import ( const shortpkg = "assertions" +// TODO: apply table-driven pattern. func TestEqualNotNil(t *testing.T) { t.Parallel() mock := new(testing.T) @@ -34,8 +35,10 @@ func TestEqualNotNil(t *testing.T) { } } +// TODO: apply table-driven pattern, factorize with Nil tests. func TestEqualNil(t *testing.T) { t.Parallel() + mock := new(testing.T) if !Nil(mock, nil) { @@ -51,46 +54,51 @@ func TestEqualNil(t *testing.T) { } } -func TestEqualSameWithSliceTooLongToPrint(t *testing.T) { +func TestEqualErrorMessages(t *testing.T) { t.Parallel() - mock := new(mockT) - longSlice := make([]int, 1_000_000) - Same(mock, &[]int{}, &longSlice) - Contains(t, mock.errorString(), `&[]int{0, 0, 0,`) -} + t.Run("same, with slice too long to print", func(t *testing.T) { + t.Parallel() + mock := new(mockT) -func TestEqualNotSameWithSliceTooLongToPrint(t *testing.T) { - t.Parallel() - mock := new(mockT) + longSlice := make([]int, 1_000_000) + Same(mock, &[]int{}, &longSlice) + Contains(t, mock.errorString(), `&[]int{0, 0, 0,`) + }) - longSlice := make([]int, 1_000_000) - NotSame(mock, &longSlice, &longSlice) - Contains(t, mock.errorString(), `&[]int{0, 0, 0,`) -} + t.Run("not same, with slice too long to print", func(t *testing.T) { + t.Parallel() -func TestEqualNotEqualWithSliceTooLongToPrint(t *testing.T) { - t.Parallel() - mock := new(mockT) + mock := new(mockT) - longSlice := make([]int, 1_000_000) - NotEqual(mock, longSlice, longSlice) - Contains(t, mock.errorString(), ` + longSlice := make([]int, 1_000_000) + NotSame(mock, &longSlice, &longSlice) + Contains(t, mock.errorString(), `&[]int{0, 0, 0,`) + }) + + t.Run("not equal, with slice too long to print", func(t *testing.T) { + t.Parallel() + mock := new(mockT) + + longSlice := make([]int, 1_000_000) + NotEqual(mock, longSlice, longSlice) + Contains(t, mock.errorString(), ` Error Trace: Error: Should not be: []int{0, 0, 0,`) - Contains(t, mock.errorString(), `<... truncated>`) -} + Contains(t, mock.errorString(), `<... truncated>`) + }) -func TestEqualNotEqualValuesWithSliceTooLongToPrint(t *testing.T) { - t.Parallel() - mock := new(mockT) + t.Run("not equal values, with slice too long to print", func(t *testing.T) { + t.Parallel() + mock := new(mockT) - longSlice := make([]int, 1_000_000) - NotEqualValues(mock, longSlice, longSlice) - Contains(t, mock.errorString(), ` + longSlice := make([]int, 1_000_000) + NotEqualValues(mock, longSlice, longSlice) + Contains(t, mock.errorString(), ` Error Trace: Error: Should not be: []int{0, 0, 0,`) - Contains(t, mock.errorString(), `<... truncated>`) + Contains(t, mock.errorString(), `<... truncated>`) + }) } func TestEqual(t *testing.T) { @@ -377,7 +385,7 @@ func TestEqualT(t *testing.T) { } } -func TestNotEqualT(t *testing.T) { +func TestEqualNotEqualT(t *testing.T) { t.Parallel() for tc := range equalTCases() { @@ -385,6 +393,129 @@ func TestNotEqualT(t *testing.T) { } } +func TestEqualStringErrorMessage(t *testing.T) { + // checking error messsages on Equal with a regexp. The object of the test is Equal, not Regexp + t.Parallel() + + t.Run("error message should match Regexp", func(t *testing.T) { + for tc := range stringEqualFormattingCases() { + t.Run(tc.name, func(t *testing.T) { + mock := &bufferT{} + + isEqual := Equal(mock, tc.equalWant, tc.equalGot, tc.msgAndArgs...) + if isEqual { + t.Errorf("expected %q to be different than %q", tc.equalGot, tc.equalWant) + + return + } + rex := regexp.MustCompile(tc.want) + match := rex.MatchString(mock.buf.String()) + if !match { + t.Errorf("expected message to match %q, but got:\n%s", tc.want, mock.buf.String()) + } + }) + } + }) +} + +type equalStringCase struct { + name string + equalWant string + equalGot string + msgAndArgs []any + want string +} + +func stringEqualFormattingCases() iter.Seq[equalStringCase] { + return slices.Values([]equalStringCase{ + { + name: "multiline diff message", + equalWant: "hi, \nmy name is", + equalGot: "what,\nmy name is", + want: "\t[a-z]+.go:\\d+: \n" + // NOTE: the exact file name reported should be asserted in integration tests + "\t+Error Trace:\t\n+" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"hi, \\\\nmy name is\"\n" + + "\\s+actual\\s+: " + "\"what,\\\\nmy name is\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n\\s+\\++ " + + "Actual\n" + + "\\s+@@ -1,2 \\+1,2 @@\n" + + "\\s+-hi, \n\\s+\\+what,\n" + + "\\s+my name is", + }, + { + name: "single line diff message", + equalWant: "want", + equalGot: "got", + want: "\t[a-z]+.go:\\d+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ " + + "Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n", + }, + { + name: "diff message with args", + equalWant: "want", + equalGot: "got", + msgAndArgs: []any{"hello, %v!", "world"}, + want: "\t[a-z]+.go:[0-9]+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n" + + "\\s+\\++ Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n" + + "\\s+Messages:\\s+hello, world!\n", + }, + { + name: "diff message with integer arg", + equalWant: "want", + equalGot: "got", + msgAndArgs: []any{123}, + want: "\t[a-z]+.go:[0-9]+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n" + + "\\s+\\++ Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n" + + "\\s+Messages:\\s+123\n", + }, + { + name: "diff message with struct arg", + equalWant: "want", + equalGot: "got", + msgAndArgs: []any{struct{ a string }{"hello"}}, + want: "\t[a-z]+.go:[0-9]+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n" + + "\\s+\\++ Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n" + + "\\s+Messages:\\s+{a:hello}\n", + }, + }) +} + type panicCase struct { name string value1 any diff --git a/internal/assertions/number_test.go b/internal/assertions/number_test.go index ebf5e4144..6a471417d 100644 --- a/internal/assertions/number_test.go +++ b/internal/assertions/number_test.go @@ -7,78 +7,29 @@ import ( "iter" "math" "slices" + "strings" "testing" ) -func TestNumberInDelta(t *testing.T) { - t.Parallel() - - t.Run("InDelta specific (type conversion)", func(t *testing.T) { - t.Parallel() - mock := new(testing.T) - False(t, InDelta(mock, "", nil, 1), "Expected non numerals to fail") - }) - - // run all test cases with both InDelta and InDeltaT - for tc := range deltaCases() { - t.Run(tc.name, tc.test) - } -} - -func TestNumberInDeltaSlice(t *testing.T) { - t.Parallel() - - for tc := range deltaSliceCases() { - t.Run(tc.name, tc.test) - } -} - -func TestNumberInDeltaMapValues(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - for tc := range numberInDeltaMapCases() { - tc.f(t, InDeltaMapValues(mock, tc.expect, tc.actual, tc.delta), tc.title+"\n"+diff(tc.expect, tc.actual)) - } -} - -func TestNumberInEpsilon(t *testing.T) { - t.Parallel() - - // run all test cases with both InEpsilon and InEpsilonT - for tc := range epsilonCases() { - t.Run(tc.name, tc.test) - } -} - -func TestNumberInEpsilonSlice(t *testing.T) { - t.Parallel() - - for tc := range epsilonSliceCases() { - t.Run(tc.name, tc.test) - } -} - -func TestInDeltaTErrorMessage(t *testing.T) { +func TestNumberInDeltaTErrorMessage(t *testing.T) { t.Parallel() mock := new(mockT) // Test that error message shows correct difference - InDeltaT(mock, 10, 1, 5) - - if !mock.Failed() { + result := InDeltaT(mock, 10, 1, 5) + if result || !mock.Failed() { t.Error("Expected test to fail but it passed") } // Verify the error message contains the actual difference (9) errorMsg := mock.errorString() - if !Contains(t, errorMsg, "difference was 9") { + if !strings.Contains(errorMsg, "difference was 9") { t.Errorf("Error message should contain 'difference was 9', got: %s", errorMsg) } } -func TestInEpsilonTErrorMessage(t *testing.T) { +func TestNumberInEpsilonTErrorMessage(t *testing.T) { t.Parallel() t.Run("relative error message", func(t *testing.T) { @@ -87,17 +38,17 @@ func TestInEpsilonTErrorMessage(t *testing.T) { mock := new(mockT) // Test relative error: 100 vs 110 has 10% error, exceeds 5% epsilon - InEpsilonT(mock, 100.0, 110.0, 0.05) - - if !mock.Failed() { + result := InEpsilonT(mock, 100.0, 110.0, 0.05) + if result || !mock.Failed() { t.Error("Expected test to fail but it passed") } // Verify the error message contains relative error errorMsg := mock.errorString() - if !Contains(t, errorMsg, "Relative error is too high") { + if !strings.Contains(errorMsg, "Relative error is too high") { t.Errorf("Error message should contain 'Relative error is too high', got: %s", errorMsg) } + // Should show actual relative error of 0.1 (10%) if !Contains(t, errorMsg, "0.1") { t.Errorf("Error message should contain '0.1' (10%% relative error), got: %s", errorMsg) @@ -110,24 +61,89 @@ func TestInEpsilonTErrorMessage(t *testing.T) { mock := new(mockT) // Test absolute error: expected=0, actual=0.5, epsilon=0.1 - InEpsilonT(mock, 0.0, 0.5, 0.1) - - if !mock.Failed() { + result := InEpsilonT(mock, 0.0, 0.5, 0.1) + if result || !mock.Failed() { t.Error("Expected test to fail but it passed") } // Verify the error message mentions absolute error errorMsg := mock.errorString() - if !Contains(t, errorMsg, "Expected value is zero, using absolute error comparison") { + if !strings.Contains(errorMsg, "Expected value is zero, using absolute error comparison") { t.Errorf("Error message should mention absolute error comparison, got: %s", errorMsg) } // Should show actual absolute difference of 0.5 - if !Contains(t, errorMsg, "0.5") { + if !strings.Contains(errorMsg, "0.5") { t.Errorf("Error message should contain '0.5' (absolute difference), got: %s", errorMsg) } }) } +func TestNumberInDeltaEdgeCases(t *testing.T) { + t.Parallel() + + t.Run("InDelta specific (type conversion)", func(t *testing.T) { + t.Parallel() + + mock := new(testing.T) + result := InDelta(mock, "", nil, 1) + if result { + t.Errorf("Expected non numerals to fail") + } + }) +} + +func TestNumberInDelta(t *testing.T) { + t.Parallel() + + // run all test cases with both InDelta and InDeltaT + // + // NOTE: testing pattern, focused on the expected result (true/false) and _NOT_ the content of the returned message. + // - deltaCases: loop over generic test cases AND type combinations (reason: not all types are compatible, e.g. uint64 may overflow float64) + // - testAllDelta: dispatch over the assertion variants (reflection-based, generic, X vs NotX semantics) + // Single assertion test functions: + // - testDelta + // - testDeltaT + for tc := range deltaCases() { + t.Run(tc.name, tc.test) + } +} + +func TestNumberInDeltaSlice(t *testing.T) { + t.Parallel() + + // only have a reflection-based assertion here + for tc := range deltaSliceCases() { + t.Run(tc.name, tc.test) + } +} + +func TestNumberInDeltaMapValues(t *testing.T) { + t.Parallel() + mock := new(testing.T) + + // only have a reflection-based assertion here + for tc := range numberInDeltaMapCases() { + tc.f(t, InDeltaMapValues(mock, tc.expect, tc.actual, tc.delta), tc.name+"\n"+diff(tc.expect, tc.actual)) + } +} + +func TestNumberInEpsilon(t *testing.T) { + t.Parallel() + + // run all test cases with both InEpsilon and InEpsilonT + for tc := range epsilonCases() { + t.Run(tc.name, tc.test) + } +} + +func TestNumberInEpsilonSlice(t *testing.T) { + t.Parallel() + + for tc := range epsilonSliceCases() { + t.Run(tc.name, tc.test) + } +} + // Helper functions and test data for InDelta/InDeltaT func deltaCases() iter.Seq[genericTestCase] { @@ -327,7 +343,7 @@ func testDeltaT[Number Measurable](expected, actual, delta Number, shouldPass bo // Helper functions and test data for InDeltaMapValues type numberInDeltaMapCase struct { - title string + name string expect any actual any f func(T, bool, ...any) bool @@ -340,7 +356,7 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { return slices.Values([]numberInDeltaMapCase{ { - title: "Within delta", + name: "Within delta", expect: map[string]float64{ "foo": 1.0, "bar": 2.0, @@ -355,7 +371,7 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { f: True, }, { - title: "Within delta", + name: "Within delta", expect: map[int]float64{ 1: 1.0, 2: 2.0, @@ -368,7 +384,7 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { f: True, }, { - title: "Different number of keys", + name: "Different number of keys", expect: map[int]float64{ 1: 1.0, 2: 2.0, @@ -380,7 +396,7 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { f: False, }, { - title: "Within delta with zero value", + name: "Within delta with zero value", expect: map[string]float64{ "zero": 0, }, @@ -391,7 +407,7 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { f: True, }, { - title: "With missing key with zero value", + name: "With missing key with zero value", expect: map[string]float64{ "zero": 0, "foo": 0, @@ -403,25 +419,25 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { f: False, }, { - title: "With nil maps", + name: "With nil maps", expect: map[string]float64(nil), actual: map[string]float64(nil), f: True, }, { - title: "With nil values (not a map)", + name: "With nil values (not a map)", expect: map[string]float64(nil), actual: []float64(nil), f: False, }, { - title: "With nil values (not a map)", + name: "With nil values (not a map)", expect: []float64(nil), actual: map[string]float64(nil), f: False, }, { - title: "With expected nil keys", + name: "With expected nil keys", expect: map[*string]float64{ &keyA: 1.00, (*string)(nil): 2.00, @@ -433,7 +449,7 @@ func numberInDeltaMapCases() iter.Seq[numberInDeltaMapCase] { f: True, }, { - title: "With expected invalid value", + name: "With expected invalid value", expect: map[string]any{ keyA: &iface, }, diff --git a/internal/assertions/order.go b/internal/assertions/order.go index e18560feb..c1dd14bee 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -21,12 +21,21 @@ import ( // // success: []int{1, 2, 3} // failure: []int{1, 1, 2} -func IsIncreasing(t T, object any, msgAndArgs ...any) bool { +func IsIncreasing(t T, collection any, msgAndArgs ...any) bool { // Domain: ordering if h, ok := t.(H); ok { h.Helper() } - return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) + + values, ok, err := isStrictlyOrdered(collection, false) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if !ok { + return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", values...), msgAndArgs...) + } + + return true } // IsIncreasingT asserts that a slice of [Ordered] is strictly increasing. @@ -74,6 +83,7 @@ func SortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgAndA if h, ok := t.(H); ok { h.Helper() } + isSorted := slices.IsSortedFunc(collection, compareOrdered) if !isSorted { return Fail(t, "should be sorted", msgAndArgs...) @@ -101,6 +111,7 @@ func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgA if h, ok := t.(H); ok { h.Helper() } + isSorted := slices.IsSortedFunc(collection, compareOrdered) if isSorted { return Fail(t, "should not be sorted", msgAndArgs...) @@ -121,12 +132,21 @@ func NotSortedT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, msgA // // success: []int{2, 1, 1} // failure: []int{1, 2, 3} -func IsNonIncreasing(t T, object any, msgAndArgs ...any) bool { +func IsNonIncreasing(t T, collection any, msgAndArgs ...any) bool { // Domain: ordering if h, ok := t.(H); ok { h.Helper() } - return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) + + _, ok, err := isStrictlyOrdered(collection, false) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if !ok { + return true + } + + return Fail(t, "should not be increasing", msgAndArgs...) } // IsNonIncreasingT asserts that a slice of [Ordered] is NOT strictly increasing. @@ -167,12 +187,22 @@ func IsNonIncreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice // // success: []int{3, 2, 1} // failure: []int{1, 2, 3} -func IsDecreasing(t T, object any, msgAndArgs ...any) bool { +func IsDecreasing(t T, collection any, msgAndArgs ...any) bool { // Domain: ordering if h, ok := t.(H); ok { h.Helper() } - return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) + + values, ok, err := isStrictlyOrdered(collection, true) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if !ok { + values = append(values, msgAndArgs...) + return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", values...), msgAndArgs...) + } + + return true } // IsDecreasingT asserts that a slice of [Ordered] is strictly decreasing. @@ -213,12 +243,21 @@ func IsDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice, m // // success: []int{1, 1, 2} // failure: []int{2, 1, 0} -func IsNonDecreasing(t T, object any, msgAndArgs ...any) bool { +func IsNonDecreasing(t T, collection any, msgAndArgs ...any) bool { // Domain: ordering if h, ok := t.(H); ok { h.Helper() } - return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) + + _, ok, err := isStrictlyOrdered(collection, true) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if !ok { + return true + } + + return Fail(t, "should not be decreasing", msgAndArgs...) } // IsNonDecreasingT asserts that a slice of [Ordered] is not decreasing. @@ -247,21 +286,28 @@ func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice return true } -// isOrdered checks that collection contains orderable elements. -func isOrdered(t T, object any, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...any) bool { +// isStrictlyOrdered checks that collection contains orderable elements, which are strictly ordered. +// +// It returns an error if the object can't be ordered. +// When not strictly ordered, it returns the first 2 offending values found. +func isStrictlyOrdered(object any, reverseOrder bool) ([]any, bool, error) { objKind := reflect.TypeOf(object).Kind() if objKind != reflect.Slice && objKind != reflect.Array { - return Fail(t, fmt.Sprintf("object %T is not an ordered collection", object), msgAndArgs...) + return nil, false, fmt.Errorf("object %T is not an ordered collection", object) } objValue := reflect.ValueOf(object) objLen := objValue.Len() if objLen <= 1 { - return true + return nil, true, nil } value := objValue.Index(0) + if !value.CanInterface() { + // this should not be possible with current relect, since values are retrieved from an array or slice, not a struct + panic(fmt.Errorf("internal error: can't resolve Interface() for value %v", value)) + } valueInterface := value.Interface() firstValueKind := value.Kind() @@ -270,20 +316,23 @@ func isOrdered(t T, object any, allowedComparesResults []compareResult, failMess prevValueInterface := valueInterface value = objValue.Index(i) + if !value.CanInterface() { + panic(fmt.Errorf("internal error: can't resolve Interface() for value %v", value)) + } valueInterface = value.Interface() compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind) if !isComparable { - return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...) + return nil, false, fmt.Errorf(`cannot compare type "%T" and "%T"`, value, prevValue) } - if !containsValue(allowedComparesResults, compareResult) { - return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...) + if (!reverseOrder && compareResult != -1) || (reverseOrder && compareResult != 1) { + return []any{prevValueInterface, valueInterface}, false, nil } } - return true + return nil, true, nil } func compareStrictOrdered[E Ordered](a, b E) int { diff --git a/internal/assertions/order_test.go b/internal/assertions/order_test.go index 7822708fb..65c49ec34 100644 --- a/internal/assertions/order_test.go +++ b/internal/assertions/order_test.go @@ -8,657 +8,482 @@ import ( "fmt" "iter" "slices" + "strings" "testing" + "time" ) -type orderedFixture struct { - collection any - msg string -} +func TestOrderErrorMessages(t *testing.T) { + // Test reflection-based assertions + + t.Run("with error messages", func(t *testing.T) { + t.Parallel() + + const ( + format = "format %s %x" + arg1 = "this" + arg2 = 0xc001 + expectedOutput = "format this c001\n" + ) + + msgAndArgs := []any{format, arg1, arg2} + collection := []int{1, 2, 1} // is neither increasing nor decreasing + increasingCollection := []int{1, 2, 3} + decreasingCollection := []int{3, 2, 1} + + funcs := []struct { + name string + fn func(T, any, ...any) bool + collection []int + }{ + {"IsIncreasing", IsIncreasing, collection}, + {"IsNonIncreasing", IsNonIncreasing, increasingCollection}, + {"IsDecreasing", IsDecreasing, collection}, + {"IsNonDecreasing", IsNonDecreasing, decreasingCollection}, + } -func TestOrderIsIncreasing(t *testing.T) { - t.Parallel() - mock := new(testing.T) + for _, fn := range funcs { + t.Run(fn.name, func(t *testing.T) { + t.Parallel() - if !IsIncreasing(mock, []int{1, 2}) { - t.Error("IsIncreasing should return true") - } + mock := &outputT{buf: bytes.NewBuffer(nil)} + result := fn.fn(mock, fn.collection, msgAndArgs...) + if result { + t.Errorf("expected ordering assertion %q to fail on %v", fn.name, fn.collection) - if !IsIncreasing(mock, []int{1, 2, 3, 4, 5}) { - t.Error("IsIncreasing should return true") - } + return + } - if IsIncreasing(mock, []int{1, 1}) { - t.Error("IsIncreasing should return false") - } - - if IsIncreasing(mock, []int{2, 1}) { - t.Error("IsIncreasing should return false") - } + if !strings.Contains(mock.buf.String(), expectedOutput) { + t.Errorf("expected error message to contain: %s but got %q", expectedOutput, mock.buf.String()) + } + }) + } + }) - // Check error report - for currCase := range decreasingFixtures() { - t.Run(fmt.Sprintf("%#v", currCase.collection), func(t *testing.T) { - t.Parallel() + t.Run("with detailed error messages", func(t *testing.T) { + // Test specific error messages for reflection-based assertions + t.Parallel() + + testCases := []struct { + name string + fn func(T, any, ...any) bool + collection any + expected string + }{ + // IsIncreasing errors + {"IsIncreasing/string", IsIncreasing, []string{"b", "a"}, `"b" is not less than "a"`}, + {"IsIncreasing/int", IsIncreasing, []int{2, 1}, `"2" is not less than "1"`}, + {"IsIncreasing/int8", IsIncreasing, []int8{2, 1}, `"2" is not less than "1"`}, + {"IsIncreasing/float32", IsIncreasing, []float32{2.34, 1.23}, `"2.34" is not less than "1.23"`}, + {"IsIncreasing/invalid-type", IsIncreasing, struct{}{}, `object struct {} is not an ordered collection`}, + + // IsNonIncreasing errors + {"IsNonIncreasing/string", IsNonIncreasing, []string{"a", "b"}, `should not be increasing`}, + {"IsNonIncreasing/int", IsNonIncreasing, []int{1, 2}, `should not be increasing`}, + {"IsNonIncreasing/float64", IsNonIncreasing, []float64{1.23, 2.34}, `should not be increasing`}, + + // IsDecreasing errors + {"IsDecreasing/string", IsDecreasing, []string{"a", "b"}, `"a" is not greater than "b"`}, + {"IsDecreasing/int", IsDecreasing, []int{1, 2}, `"1" is not greater than "2"`}, + {"IsDecreasing/uint64", IsDecreasing, []uint64{1, 2}, `"1" is not greater than "2"`}, + + // IsNonDecreasing errors + {"IsNonDecreasing/string", IsNonDecreasing, []string{"b", "a"}, `should not be decreasing`}, + {"IsNonDecreasing/int", IsNonDecreasing, []int{2, 1}, `should not be decreasing`}, + {"IsNonDecreasing/float32", IsNonDecreasing, []float32{2.34, 1.23}, `should not be decreasing`}, + } - out := &outputT{buf: bytes.NewBuffer(nil)} - False(t, IsIncreasing(out, currCase.collection)) - Contains(t, out.buf.String(), currCase.msg) - }) - } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + out := &outputT{buf: bytes.NewBuffer(nil)} + result := tc.fn(out, tc.collection) + if result { + t.Errorf("expected ordering assertion %q to fail on %v", tc.name, tc.collection) + + return + } + if !strings.Contains(out.buf.String(), tc.expected) { + t.Errorf("expected error message to contain: %s but got %q", tc.expected, out.buf.String()) + } + }) + } + }) } -func TestOrderIsNonIncreasing(t *testing.T) { +// Test functions for reflection-based and generic assertions + +// Unified test for all order assertions, with different input types +// +// NOTE: Unified testing pattern for ordering assertions. +// Test cases are defined with their intrinsic ordering property (kind). +// Expected pass/fail is determined from the kind + assertion semantics. +// +// Unlike the pattern used in string_test.go, we can't easily use a conversion for slices. +// Therefore, we have to resort to a type switch with a fixed known list of tested slice types. +// +// The matrix of expected assertion semantics is defined by [expectedStatusForAssertion]. +func TestOrder(t *testing.T) { t.Parallel() - mock := new(testing.T) - if !IsNonIncreasing(mock, []int{2, 1}) { - t.Error("IsNonIncreasing should return true") - } - - if !IsNonIncreasing(mock, []int{5, 4, 4, 3, 2, 1}) { - t.Error("IsNonIncreasing should return true") - } - - if !IsNonIncreasing(mock, []int{1, 1}) { - t.Error("IsNonIncreasing should return true") - } - - if IsNonIncreasing(mock, []int{1, 2}) { - t.Error("IsNonIncreasing should return false") - } - - // Check error report - for currCase := range increasingFixtures() { - t.Run(fmt.Sprintf("%#v", currCase.collection), func(t *testing.T) { - out := &outputT{buf: bytes.NewBuffer(nil)} - False(t, IsNonIncreasing(out, currCase.collection)) - Contains(t, out.buf.String(), currCase.msg) - }) + for tc := range unifiedOrderCases() { + t.Run(tc.name, testAllOrdersWithTypes(tc)) } } -func TestOrderIsDecreasing(t *testing.T) { - t.Parallel() - mock := new(testing.T) +func testAllOrdersWithTypes(tc orderTestCase) func(*testing.T) { + return func(t *testing.T) { + t.Run("with IsIncreasing", func(t *testing.T) { + t.Parallel() - if !IsDecreasing(mock, []int{2, 1}) { - t.Error("IsDecreasing should return true") - } + shouldPass := expectedStatusForAssertion(increasingKind, tc.kind) + t.Run("with reflection", testOrderReflectBased(IsIncreasing, tc.collection, shouldPass)) + if !tc.reflectionOnly { + t.Run("with generic", testOrderGeneric(increasingKind, tc.collection, shouldPass)) + } + }) - if !IsDecreasing(mock, []int{5, 4, 3, 2, 1}) { - t.Error("IsDecreasing should return true") - } + t.Run("with IsNonIncreasing", func(t *testing.T) { + t.Parallel() - if IsDecreasing(mock, []int{1, 1}) { - t.Error("IsDecreasing should return false") - } + shouldPass := expectedStatusForAssertion(notIncreasingKind, tc.kind) + t.Run("with reflection", testOrderReflectBased(IsNonIncreasing, tc.collection, shouldPass)) + if !tc.reflectionOnly { + t.Run("with generic", testOrderGeneric(notIncreasingKind, tc.collection, shouldPass)) + } + }) - if IsDecreasing(mock, []int{1, 2}) { - t.Error("IsDecreasing should return false") - } + t.Run("with IsDecreasing", func(t *testing.T) { + t.Parallel() - // Check error report - for currCase := range increasingFixtures2() { - t.Run(fmt.Sprintf("%#v", currCase.collection), func(t *testing.T) { - out := &outputT{buf: bytes.NewBuffer(nil)} - False(t, IsDecreasing(out, currCase.collection)) - Contains(t, out.buf.String(), currCase.msg) + shouldPass := expectedStatusForAssertion(decreasingKind, tc.kind) + t.Run("with reflection", testOrderReflectBased(IsDecreasing, tc.collection, shouldPass)) + if !tc.reflectionOnly { + t.Run("with generic", testOrderGeneric(decreasingKind, tc.collection, shouldPass)) + } }) - } -} -func TestOrderIsNonDecreasing(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - if !IsNonDecreasing(mock, []int{1, 2}) { - t.Error("IsNonDecreasing should return true") - } + t.Run("with IsNonDecreasing", func(t *testing.T) { + t.Parallel() - if !IsNonDecreasing(mock, []int{1, 1, 2, 3, 4, 5}) { - t.Error("IsNonDecreasing should return true") - } + shouldPass := expectedStatusForAssertion(notDecreasingKind, tc.kind) + t.Run("with reflection", testOrderReflectBased(IsNonDecreasing, tc.collection, shouldPass)) + if !tc.reflectionOnly { + t.Run("with generic", testOrderGeneric(notDecreasingKind, tc.collection, shouldPass)) + } + }) - if !IsNonDecreasing(mock, []int{1, 1}) { - t.Error("IsNonDecreasing should return false") - } + if tc.reflectionOnly { + return + } - if IsNonDecreasing(mock, []int{2, 1}) { - t.Error("IsNonDecreasing should return false") - } + t.Run("with SortedT", func(t *testing.T) { + t.Parallel() - // Check error report - for currCase := range decreasingFixtures2() { - t.Run(fmt.Sprintf("%#v", currCase.collection), func(t *testing.T) { - out := &outputT{buf: bytes.NewBuffer(nil)} - False(t, IsNonDecreasing(out, currCase.collection)) - Contains(t, out.buf.String(), currCase.msg) + shouldPass := expectedStatusForAssertion(sortedKind, tc.kind) + t.Run("with generic only", testOrderGeneric(sortedKind, tc.collection, shouldPass)) }) - } -} -func TestOrderMsgAndArgsForwarding(t *testing.T) { - t.Parallel() + t.Run("with NotSortedT", func(t *testing.T) { + t.Parallel() - msgAndArgs := []any{"format %s %x", "this", 0xc001} - expectedOutput := "format this c001\n" - collection := []int{1, 2, 1} - funcs := []func(t T){ - func(t T) { IsIncreasing(t, collection, msgAndArgs...) }, - func(t T) { IsNonIncreasing(t, collection, msgAndArgs...) }, - func(t T) { IsDecreasing(t, collection, msgAndArgs...) }, - func(t T) { IsNonDecreasing(t, collection, msgAndArgs...) }, - } - for _, f := range funcs { - out := &outputT{buf: bytes.NewBuffer(nil)} - f(out) - Contains(t, out.buf.String(), expectedOutput) + shouldPass := expectedStatusForAssertion(notSortedKind, tc.kind) + t.Run("with generic only", testOrderGeneric(notSortedKind, tc.collection, shouldPass)) + }) } } -func decreasingFixtures() iter.Seq[orderedFixture] { - return slices.Values( - []orderedFixture{ - {collection: []string{"b", "a"}, msg: `"b" is not less than "a"`}, - {collection: []int{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []int{2, 1, 3, 4, 5, 6, 7}, msg: `"2" is not less than "1"`}, - {collection: []int{-1, 0, 2, 1}, msg: `"2" is not less than "1"`}, - {collection: []int8{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []int16{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []int32{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []int64{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []uint8{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []uint16{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []uint32{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []uint64{2, 1}, msg: `"2" is not less than "1"`}, - {collection: []float32{2.34, 1.23}, msg: `"2.34" is not less than "1.23"`}, - {collection: []float64{2.34, 1.23}, msg: `"2.34" is not less than "1.23"`}, - {collection: struct{}{}, msg: `object struct {} is not an ordered collection`}, - }, - ) -} - -func increasingFixtures() iter.Seq[orderedFixture] { - return slices.Values( - []orderedFixture{ - {collection: []string{"a", "b"}, msg: `"a" is not greater than or equal to "b"`}, - {collection: []int{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []int{1, 2, 7, 6, 5, 4, 3}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []int{5, 4, 3, 1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []int8{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []int16{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []int32{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []int64{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []uint8{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []uint16{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []uint32{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []uint64{1, 2}, msg: `"1" is not greater than or equal to "2"`}, - {collection: []float32{1.23, 2.34}, msg: `"1.23" is not greater than or equal to "2.34"`}, - {collection: []float64{1.23, 2.34}, msg: `"1.23" is not greater than or equal to "2.34"`}, - {collection: struct{}{}, msg: `object struct {} is not an ordered collection`}, - }, - ) -} - -func increasingFixtures2() iter.Seq[orderedFixture] { - return slices.Values( - []orderedFixture{ - {collection: []string{"a", "b"}, msg: `"a" is not greater than "b"`}, - {collection: []int{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []int{1, 2, 7, 6, 5, 4, 3}, msg: `"1" is not greater than "2"`}, - {collection: []int{5, 4, 3, 1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []int8{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []int16{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []int32{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []int64{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []uint8{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []uint16{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []uint32{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []uint64{1, 2}, msg: `"1" is not greater than "2"`}, - {collection: []float32{1.23, 2.34}, msg: `"1.23" is not greater than "2.34"`}, - {collection: []float64{1.23, 2.34}, msg: `"1.23" is not greater than "2.34"`}, - {collection: struct{}{}, msg: `object struct {} is not an ordered collection`}, - }, - ) -} +// collectionKind represents the ordering property of a collection. +type collectionKind int + +const ( + allEqual collectionKind = iota // all values equal (sorted but not strictly) + strictlyAsc // strictly ascending (each < next) + strictlyDesc // strictly descending (each > next) + nonStrictlyAsc // non-strictly ascending (each <= next, some equal) + nonStrictlyDesc // non-strictly descending (each >= next, some equal) + unsorted // no ordering + passAll // empty or single element collection + errorCase // should fail with error (not panic) +) -func decreasingFixtures2() iter.Seq[orderedFixture] { - return slices.Values( - []orderedFixture{ - {collection: []string{"b", "a"}, msg: `"b" is not less than or equal to "a"`}, - {collection: []int{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []int{2, 1, 3, 4, 5, 6, 7}, msg: `"2" is not less than or equal to "1"`}, - {collection: []int{-1, 0, 2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []int8{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []int16{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []int32{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []int64{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []uint8{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []uint16{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []uint32{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []uint64{2, 1}, msg: `"2" is not less than or equal to "1"`}, - {collection: []float32{2.34, 1.23}, msg: `"2.34" is not less than or equal to "1.23"`}, - {collection: []float64{2.34, 1.23}, msg: `"2.34" is not less than or equal to "1.23"`}, - {collection: struct{}{}, msg: `object struct {} is not an ordered collection`}, - }, - ) -} +type orderAssertionKind int -// Tests for generic ordering functions +const ( + increasingKind orderAssertionKind = iota + notIncreasingKind + decreasingKind + notDecreasingKind + sortedKind + notSortedKind +) +// orderTestCase represents a test case that can be used for all ordering assertions. type orderTestCase struct { - name string - collection any - shouldPass bool + name string + collection any + kind collectionKind + reflectionOnly bool } -func orderIncreasingCases() iter.Seq[orderTestCase] { - return slices.Values([]orderTestCase{ - // Success cases - strictly increasing - {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: true}, - {name: "int8/increasing", collection: []int8{1, 2, 3}, shouldPass: true}, - {name: "int16/increasing", collection: []int16{1, 2, 3}, shouldPass: true}, - {name: "int32/increasing", collection: []int32{1, 2, 3}, shouldPass: true}, - {name: "int64/increasing", collection: []int64{1, 2, 3}, shouldPass: true}, - {name: "uint/increasing", collection: []uint{1, 2, 3}, shouldPass: true}, - {name: "uint8/increasing", collection: []uint8{1, 2, 3}, shouldPass: true}, - {name: "uint16/increasing", collection: []uint16{1, 2, 3}, shouldPass: true}, - {name: "uint32/increasing", collection: []uint32{1, 2, 3}, shouldPass: true}, - {name: "uint64/increasing", collection: []uint64{1, 2, 3}, shouldPass: true}, - {name: "float32/increasing", collection: []float32{1.1, 2.2, 3.3}, shouldPass: true}, - {name: "float64/increasing", collection: []float64{1.1, 2.2, 3.3}, shouldPass: true}, - {name: "string/increasing", collection: []string{"a", "b", "c"}, shouldPass: true}, - - // Failure cases - not strictly increasing (equal or decreasing) - {name: "int/equal", collection: []int{1, 1, 2}, shouldPass: false}, - {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: false}, - {name: "float64/equal", collection: []float64{1.1, 1.1, 2.2}, shouldPass: false}, - {name: "string/equal", collection: []string{"a", "a", "b"}, shouldPass: false}, - }) -} +// Unified test cases for all ordering assertions. +type ( + myFloat float64 + myCollection []myFloat +) -func orderDecreasingCases() iter.Seq[orderTestCase] { - return slices.Values([]orderTestCase{ - // Success cases - strictly decreasing - {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: true}, - {name: "int8/decreasing", collection: []int8{3, 2, 1}, shouldPass: true}, - {name: "int16/decreasing", collection: []int16{3, 2, 1}, shouldPass: true}, - {name: "int32/decreasing", collection: []int32{3, 2, 1}, shouldPass: true}, - {name: "int64/decreasing", collection: []int64{3, 2, 1}, shouldPass: true}, - {name: "uint/decreasing", collection: []uint{3, 2, 1}, shouldPass: true}, - {name: "uint8/decreasing", collection: []uint8{3, 2, 1}, shouldPass: true}, - {name: "uint16/decreasing", collection: []uint16{3, 2, 1}, shouldPass: true}, - {name: "uint32/decreasing", collection: []uint32{3, 2, 1}, shouldPass: true}, - {name: "uint64/decreasing", collection: []uint64{3, 2, 1}, shouldPass: true}, - {name: "float32/decreasing", collection: []float32{3.3, 2.2, 1.1}, shouldPass: true}, - {name: "float64/decreasing", collection: []float64{3.3, 2.2, 1.1}, shouldPass: true}, - {name: "string/decreasing", collection: []string{"c", "b", "a"}, shouldPass: true}, - - // Failure cases - not strictly decreasing (equal or increasing) - {name: "int/equal", collection: []int{2, 1, 1}, shouldPass: false}, - {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: false}, - {name: "float64/equal", collection: []float64{2.2, 1.1, 1.1}, shouldPass: false}, - {name: "string/equal", collection: []string{"b", "a", "a"}, shouldPass: false}, - }) -} +func unifiedOrderCases() iter.Seq[orderTestCase] { + t0 := time.Now() + t1 := t0.Add(time.Second) + t2 := t1.Add(time.Second) -func orderNonIncreasingCases() iter.Seq[orderTestCase] { - return slices.Values([]orderTestCase{ - // Success cases - decreasing or equal (not increasing) - {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: true}, - {name: "int/with-equal", collection: []int{3, 2, 2, 1}, shouldPass: true}, - {name: "int/all-equal", collection: []int{2, 2, 2}, shouldPass: true}, - {name: "int8/decreasing", collection: []int8{3, 2, 1}, shouldPass: true}, - {name: "int16/decreasing", collection: []int16{3, 2, 1}, shouldPass: true}, - {name: "int32/decreasing", collection: []int32{3, 2, 1}, shouldPass: true}, - {name: "int64/decreasing", collection: []int64{3, 2, 1}, shouldPass: true}, - {name: "uint/decreasing", collection: []uint{3, 2, 1}, shouldPass: true}, - {name: "uint8/decreasing", collection: []uint8{3, 2, 1}, shouldPass: true}, - {name: "uint16/decreasing", collection: []uint16{3, 2, 1}, shouldPass: true}, - {name: "uint32/decreasing", collection: []uint32{3, 2, 1}, shouldPass: true}, - {name: "uint64/decreasing", collection: []uint64{3, 2, 1}, shouldPass: true}, - {name: "float32/decreasing", collection: []float32{3.3, 2.2, 1.1}, shouldPass: true}, - {name: "float64/decreasing", collection: []float64{3.3, 2.2, 1.1}, shouldPass: true}, - {name: "float64/with-equal", collection: []float64{3.3, 2.2, 2.2}, shouldPass: true}, - {name: "string/decreasing", collection: []string{"c", "b", "a"}, shouldPass: true}, - {name: "string/with-equal", collection: []string{"c", "b", "b"}, shouldPass: true}, - - // Failure cases - increasing - {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: false}, - {name: "float64/increasing", collection: []float64{1.1, 2.2, 3.3}, shouldPass: false}, - {name: "string/increasing", collection: []string{"a", "b", "c"}, shouldPass: false}, - }) -} + // Test types for reflection-only edge cases. + type nonComparableStruct struct { + Value int + Data []int // slices make structs non-comparable + } + + type structWithUnexportedField struct { + unexported int + } -func orderNonDecreasingCases() iter.Seq[orderTestCase] { return slices.Values([]orderTestCase{ - // Success cases - increasing or equal (not decreasing) - {name: "int/increasing", collection: []int{1, 2, 3}, shouldPass: true}, - {name: "int/with-equal", collection: []int{1, 2, 2, 3}, shouldPass: true}, - {name: "int/all-equal", collection: []int{2, 2, 2}, shouldPass: true}, - {name: "int8/increasing", collection: []int8{1, 2, 3}, shouldPass: true}, - {name: "int16/increasing", collection: []int16{1, 2, 3}, shouldPass: true}, - {name: "int32/increasing", collection: []int32{1, 2, 3}, shouldPass: true}, - {name: "int64/increasing", collection: []int64{1, 2, 3}, shouldPass: true}, - {name: "uint/increasing", collection: []uint{1, 2, 3}, shouldPass: true}, - {name: "uint8/increasing", collection: []uint8{1, 2, 3}, shouldPass: true}, - {name: "uint16/increasing", collection: []uint16{1, 2, 3}, shouldPass: true}, - {name: "uint32/increasing", collection: []uint32{1, 2, 3}, shouldPass: true}, - {name: "uint64/increasing", collection: []uint64{1, 2, 3}, shouldPass: true}, - {name: "float32/increasing", collection: []float32{1.1, 2.2, 3.3}, shouldPass: true}, - {name: "float64/increasing", collection: []float64{1.1, 2.2, 3.3}, shouldPass: true}, - {name: "float64/with-equal", collection: []float64{1.1, 2.2, 2.2}, shouldPass: true}, - {name: "string/increasing", collection: []string{"a", "b", "c"}, shouldPass: true}, - {name: "string/with-equal", collection: []string{"a", "b", "b"}, shouldPass: true}, - - // Failure cases - decreasing - {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: false}, - {name: "float64/decreasing", collection: []float64{3.3, 2.2, 1.1}, shouldPass: false}, - {name: "string/decreasing", collection: []string{"c", "b", "a"}, shouldPass: false}, + // Edge cases: nil, empty, single element collections + {"empty/int", []int{}, passAll, false}, + {"nil/int", []int(nil), passAll, false}, + {"single/int", []int{1}, passAll, false}, + + // All equal - both non-strict pass, both strict fail, sorted + {"all-equal/int", []int{2, 2, 2}, allEqual, false}, + {"all-equal/float64", []float64{1.5, 1.5, 1.5}, allEqual, false}, + {"all-equal/~float64", []myFloat{1.5, 1.5, 1.5}, allEqual, false}, + {"all-equal/~[]~float64", myCollection{1.5, 1.5, 1.5}, allEqual, false}, + {"all-equal/string", []string{"a", "a", "a"}, allEqual, false}, + {"all-equal/time.Time", []time.Time{t0, t0, t0}, allEqual, false}, + {"all-equal/[]byte", [][]byte{[]byte("a"), []byte("a"), []byte("a")}, allEqual, false}, + + // Strictly ascending - IsIncreasing passes, IsNonDecreasing passes, sorted + {"strictly-asc/int-short", []int{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/int-long", []int{1, 2, 3, 4, 5}, strictlyAsc, false}, + {"strictly-asc/int8", []int8{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/int16", []int16{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/int32", []int32{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/int64", []int64{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/uint", []uint{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/uint8", []uint8{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/uint16", []uint16{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/uint32", []uint32{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/uint64", []uint64{1, 2, 3}, strictlyAsc, false}, + {"strictly-asc/float32", []float32{1.1, 2.2, 3.3}, strictlyAsc, false}, + {"strictly-asc/float64", []float64{1.1, 2.2, 3.3}, strictlyAsc, false}, + {"strictly-asc/~float64", []myFloat{1.1, 2.2, 3.3}, strictlyAsc, false}, + {"strictly-asc/~[]~float64", myCollection{1.1, 2.2, 3.3}, strictlyAsc, false}, + {"strictly-asc/string", []string{"a", "b", "c"}, strictlyAsc, false}, + {"strictly-asc/time.Time", []time.Time{t0, t1, t2}, strictlyAsc, false}, + {"strictly-asc/[]byte", [][]byte{[]byte("a"), []byte("b"), []byte("c")}, strictlyAsc, false}, + + // Strictly descending - IsDecreasing passes, IsNonIncreasing passes, not sorted + {"strictly-desc/int-short", []int{3, 2, 1}, strictlyDesc, false}, + {"strictly-desc/int-long", []int{5, 4, 3, 2, 1}, strictlyDesc, false}, + {"strictly-desc/float64", []float64{3.3, 2.2, 1.1}, strictlyDesc, false}, + {"strictly-desc/~float64", []myFloat{3.3, 2.2, 1.1}, strictlyDesc, false}, + {"strictly-desc/~[]~float64", myCollection{3.3, 2.2, 1.1}, strictlyDesc, false}, + {"strictly-desc/string", []string{"c", "b", "a"}, strictlyDesc, false}, + {"strictly-desc/time.Time", []time.Time{t2, t1, t0}, strictlyDesc, false}, + {"strictly-desc/[]byte", [][]byte{[]byte("c"), []byte("b"), []byte("a")}, strictlyDesc, false}, + + // Non-strictly ascending - sorted, but not strictly (has equal adjacent) + {"non-strictly-asc/int-with-equal", []int{1, 1, 2, 3}, nonStrictlyAsc, false}, + {"non-strictly-asc/int-with-equal-middle", []int{1, 2, 2, 3}, nonStrictlyAsc, false}, + {"non-strictly-asc/float64", []float64{1.1, 2.2, 2.2, 3.3}, nonStrictlyAsc, false}, + {"non-strictly-asc/~float64", []myFloat{1.1, 1.1, 2.2, 3.3}, nonStrictlyAsc, false}, + {"non-strictly-asc/~[]~float64", myCollection{1.1, 1.1, 2.2, 3.3}, nonStrictlyAsc, false}, + {"non-strictly-asc/string", []string{"a", "a", "b", "c"}, nonStrictlyAsc, false}, + {"non-strictly-asc/time.Time", []time.Time{t0, t0, t1, t2}, nonStrictlyAsc, false}, + {"non-strictly-asc/[]byte", [][]byte{[]byte("a"), []byte("a"), []byte("b"), []byte("c")}, nonStrictlyAsc, false}, + + // Non-strictly descending - not sorted, but consistently >= (has equal adjacent) + {"non-strictly-desc/int-with-equal", []int{3, 2, 2, 1}, nonStrictlyDesc, false}, + {"non-strictly-desc/int-with-equal-start", []int{3, 3, 2, 1}, nonStrictlyDesc, false}, + {"non-strictly-desc/float64", []float64{3.3, 2.2, 2.2, 1.1}, nonStrictlyDesc, false}, + {"non-strictly-desc/~float64", []myFloat{3.3, 2.2, 2.2, 1.1}, nonStrictlyDesc, false}, + {"non-strictly-desc/~[]~float64", myCollection{3.3, 2.2, 2.2, 1.1}, nonStrictlyDesc, false}, + {"non-strictly-desc/string", []string{"c", "b", "b", "a"}, nonStrictlyDesc, false}, + {"non-strictly-desc/time.Time", []time.Time{t2, t1, t1, t0}, nonStrictlyDesc, false}, + {"non-strictly-desc/[]byte", [][]byte{[]byte("c"), []byte("b"), []byte("b"), []byte("a")}, nonStrictlyDesc, false}, + + // Unsorted - no ordering pattern + {"unsorted/int-mixed", []int{1, 4, 2}, unsorted, false}, + {"unsorted/int-up-down-up", []int{1, 3, 2, 4}, unsorted, false}, + {"unsorted/float64", []float64{1.1, 3.3, 2.2}, unsorted, false}, + {"unsorted/~float64", []myFloat{1.1, 3.3, 2.2}, unsorted, false}, + {"unsorted/~[]~float64", myCollection{1.1, 3.3, 2.2}, unsorted, false}, + {"unsorted/string", []string{"b", "a", "c"}, unsorted, false}, + {"unsorted/time.Time", []time.Time{t1, t0, t2}, unsorted, false}, + {"unsorted/[]byte", [][]byte{[]byte("b"), []byte("a"), []byte("c")}, unsorted, false}, + + // Reflection-only edge cases + + // Case 1: Object is not a slice or array (should error) + {"error/not-a-collection", nonComparableStruct{Value: 1, Data: []int{1}}, errorCase, true}, + + // Case 2: Slice of non-comparable elements (should error) + {"error/non-comparable-elements", []nonComparableStruct{ + {Value: 1, Data: []int{1}}, + {Value: 2, Data: []int{2}}, + }, errorCase, true}, + + // Case 3: Slice with unexported fields (triggers panic bug in isStrictlyOrdered) + {"panic/unexported-fields", []structWithUnexportedField{ + {unexported: 1}, + {unexported: 2}, + }, errorCase, true}, }) } -func TestOrderIsIncreasingT(t *testing.T) { - t.Parallel() - - for tc := range orderIncreasingCases() { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - // Dispatch based on type - switch coll := tc.collection.(type) { - case []int: - testOrderingT(IsIncreasingT[[]int, int], coll, tc.shouldPass)(t) - case []int8: - testOrderingT(IsIncreasingT[[]int8, int8], coll, tc.shouldPass)(t) - case []int16: - testOrderingT(IsIncreasingT[[]int16, int16], coll, tc.shouldPass)(t) - case []int32: - testOrderingT(IsIncreasingT[[]int32, int32], coll, tc.shouldPass)(t) - case []int64: - testOrderingT(IsIncreasingT[[]int64, int64], coll, tc.shouldPass)(t) - case []uint: - testOrderingT(IsIncreasingT[[]uint, uint], coll, tc.shouldPass)(t) - case []uint8: - testOrderingT(IsIncreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) - case []uint16: - testOrderingT(IsIncreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) - case []uint32: - testOrderingT(IsIncreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) - case []uint64: - testOrderingT(IsIncreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) - case []float32: - testOrderingT(IsIncreasingT[[]float32, float32], coll, tc.shouldPass)(t) - case []float64: - testOrderingT(IsIncreasingT[[]float64, float64], coll, tc.shouldPass)(t) - case []string: - testOrderingT(IsIncreasingT[[]string, string], coll, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", coll) - } - }) +// Determine the expected pass/fail status for each assertion based on ordering kind. +func expectedStatusForAssertion(assertionKind orderAssertionKind, kind collectionKind) bool { + // Error cases always fail (return false) + if kind == errorCase { + return false } -} - -func TestOrderIsDecreasingT(t *testing.T) { - t.Parallel() - - for tc := range orderDecreasingCases() { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - // Dispatch based on type - switch coll := tc.collection.(type) { - case []int: - testOrderingT(IsDecreasingT[[]int, int], coll, tc.shouldPass)(t) - case []int8: - testOrderingT(IsDecreasingT[[]int8, int8], coll, tc.shouldPass)(t) - case []int16: - testOrderingT(IsDecreasingT[[]int16, int16], coll, tc.shouldPass)(t) - case []int32: - testOrderingT(IsDecreasingT[[]int32, int32], coll, tc.shouldPass)(t) - case []int64: - testOrderingT(IsDecreasingT[[]int64, int64], coll, tc.shouldPass)(t) - case []uint: - testOrderingT(IsDecreasingT[[]uint, uint], coll, tc.shouldPass)(t) - case []uint8: - testOrderingT(IsDecreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) - case []uint16: - testOrderingT(IsDecreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) - case []uint32: - testOrderingT(IsDecreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) - case []uint64: - testOrderingT(IsDecreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) - case []float32: - testOrderingT(IsDecreasingT[[]float32, float32], coll, tc.shouldPass)(t) - case []float64: - testOrderingT(IsDecreasingT[[]float64, float64], coll, tc.shouldPass)(t) - case []string: - testOrderingT(IsDecreasingT[[]string, string], coll, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", coll) - } - }) + switch assertionKind { + case increasingKind: + // IsIncreasing: strictly ascending only + return kind == strictlyAsc || kind == passAll + case notIncreasingKind: + return kind != strictlyAsc && kind != passAll + case decreasingKind: + // IsDecreasing: strictly descending only + return kind == strictlyDesc || kind == passAll + case notDecreasingKind: + return kind != strictlyDesc && kind != passAll + case sortedKind: + // SortedT: passes for sorted (non-strictly ascending, allows equal) + return kind == allEqual || kind == strictlyAsc || kind == nonStrictlyAsc || kind == passAll + case notSortedKind: + // NotSortedT: inverse of SortedT + return kind != allEqual && kind != strictlyAsc && kind != nonStrictlyAsc && kind != passAll + default: + panic(fmt.Errorf("test case configuration error: invalid orderAssertionKind: %d", assertionKind)) } } -func TestOrderIsNonIncreasingT(t *testing.T) { - t.Parallel() - - for tc := range orderNonIncreasingCases() { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() +func testOrderReflectBased(orderAssertion func(T, any, ...any) bool, collection any, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() - // Dispatch based on type - switch coll := tc.collection.(type) { - case []int: - testOrderingT(IsNonIncreasingT[[]int, int], coll, tc.shouldPass)(t) - case []int8: - testOrderingT(IsNonIncreasingT[[]int8, int8], coll, tc.shouldPass)(t) - case []int16: - testOrderingT(IsNonIncreasingT[[]int16, int16], coll, tc.shouldPass)(t) - case []int32: - testOrderingT(IsNonIncreasingT[[]int32, int32], coll, tc.shouldPass)(t) - case []int64: - testOrderingT(IsNonIncreasingT[[]int64, int64], coll, tc.shouldPass)(t) - case []uint: - testOrderingT(IsNonIncreasingT[[]uint, uint], coll, tc.shouldPass)(t) - case []uint8: - testOrderingT(IsNonIncreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) - case []uint16: - testOrderingT(IsNonIncreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) - case []uint32: - testOrderingT(IsNonIncreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) - case []uint64: - testOrderingT(IsNonIncreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) - case []float32: - testOrderingT(IsNonIncreasingT[[]float32, float32], coll, tc.shouldPass)(t) - case []float64: - testOrderingT(IsNonIncreasingT[[]float64, float64], coll, tc.shouldPass)(t) - case []string: - testOrderingT(IsNonIncreasingT[[]string, string], coll, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", coll) - } - }) - } -} + mock := new(mockT) + result := orderAssertion(mock, collection) -func TestOrderIsNonDecreasingT(t *testing.T) { - t.Parallel() + if shouldPass { + t.Run("should pass", func(t *testing.T) { + if !result || mock.Failed() { + t.Errorf("expected to pass") + } + }) - for tc := range orderNonDecreasingCases() { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() + return + } - // Dispatch based on type - switch coll := tc.collection.(type) { - case []int: - testOrderingT(IsNonDecreasingT[[]int, int], coll, tc.shouldPass)(t) - case []int8: - testOrderingT(IsNonDecreasingT[[]int8, int8], coll, tc.shouldPass)(t) - case []int16: - testOrderingT(IsNonDecreasingT[[]int16, int16], coll, tc.shouldPass)(t) - case []int32: - testOrderingT(IsNonDecreasingT[[]int32, int32], coll, tc.shouldPass)(t) - case []int64: - testOrderingT(IsNonDecreasingT[[]int64, int64], coll, tc.shouldPass)(t) - case []uint: - testOrderingT(IsNonDecreasingT[[]uint, uint], coll, tc.shouldPass)(t) - case []uint8: - testOrderingT(IsNonDecreasingT[[]uint8, uint8], coll, tc.shouldPass)(t) - case []uint16: - testOrderingT(IsNonDecreasingT[[]uint16, uint16], coll, tc.shouldPass)(t) - case []uint32: - testOrderingT(IsNonDecreasingT[[]uint32, uint32], coll, tc.shouldPass)(t) - case []uint64: - testOrderingT(IsNonDecreasingT[[]uint64, uint64], coll, tc.shouldPass)(t) - case []float32: - testOrderingT(IsNonDecreasingT[[]float32, float32], coll, tc.shouldPass)(t) - case []float64: - testOrderingT(IsNonDecreasingT[[]float64, float64], coll, tc.shouldPass)(t) - case []string: - testOrderingT(IsNonDecreasingT[[]string, string], coll, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", coll) + t.Run("should fail", func(t *testing.T) { + if result || !mock.Failed() { + t.Errorf("expected to fail") } }) } } -func sortedTCases() iter.Seq[orderTestCase] { - return slices.Values([]orderTestCase{ - // Success cases - sorted (non-strictly increasing, allows equal values) - {name: "int/sorted-increasing", collection: []int{1, 2, 3}, shouldPass: true}, - {name: "int/sorted-with-equal", collection: []int{1, 1, 2, 3}, shouldPass: true}, - {name: "int/sorted-all-equal", collection: []int{2, 2, 2}, shouldPass: true}, - {name: "int8/sorted", collection: []int8{1, 2, 3}, shouldPass: true}, - {name: "int16/sorted", collection: []int16{1, 2, 3}, shouldPass: true}, - {name: "int32/sorted", collection: []int32{1, 2, 3}, shouldPass: true}, - {name: "int64/sorted", collection: []int64{1, 2, 3}, shouldPass: true}, - {name: "uint/sorted", collection: []uint{1, 2, 3}, shouldPass: true}, - {name: "uint8/sorted", collection: []uint8{1, 2, 3}, shouldPass: true}, - {name: "uint16/sorted", collection: []uint16{1, 2, 3}, shouldPass: true}, - {name: "uint32/sorted", collection: []uint32{1, 2, 3}, shouldPass: true}, - {name: "uint64/sorted", collection: []uint64{1, 2, 3}, shouldPass: true}, - {name: "float32/sorted", collection: []float32{1.1, 2.2, 3.3}, shouldPass: true}, - {name: "float64/sorted", collection: []float64{1.1, 2.2, 3.3}, shouldPass: true}, - {name: "string/sorted", collection: []string{"a", "b", "c"}, shouldPass: true}, - {name: "string/sorted-with-equal", collection: []string{"a", "a", "b"}, shouldPass: true}, - - // Failure cases - not sorted - {name: "int/unsorted", collection: []int{1, 4, 2}, shouldPass: false}, - {name: "int/decreasing", collection: []int{3, 2, 1}, shouldPass: false}, - {name: "float64/unsorted", collection: []float64{1.1, 3.3, 2.2}, shouldPass: false}, - {name: "string/unsorted", collection: []string{"b", "a", "c"}, shouldPass: false}, - }) -} +func testOrderGeneric(assertionKind orderAssertionKind, collection any, shouldPass bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() -func TestSortedT(t *testing.T) { - t.Parallel() + mock := new(mockT) + result := testOrderAssertionResult(mock, assertionKind, collection) - for tc := range sortedTCases() { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() + if shouldPass { + t.Run("should pass", func(t *testing.T) { + if !result || mock.Failed() { + t.Errorf("expected to pass") + } + }) - // Dispatch based on type - switch coll := tc.collection.(type) { - case []int: - testOrderingT(SortedT[[]int, int], coll, tc.shouldPass)(t) - case []int8: - testOrderingT(SortedT[[]int8, int8], coll, tc.shouldPass)(t) - case []int16: - testOrderingT(SortedT[[]int16, int16], coll, tc.shouldPass)(t) - case []int32: - testOrderingT(SortedT[[]int32, int32], coll, tc.shouldPass)(t) - case []int64: - testOrderingT(SortedT[[]int64, int64], coll, tc.shouldPass)(t) - case []uint: - testOrderingT(SortedT[[]uint, uint], coll, tc.shouldPass)(t) - case []uint8: - testOrderingT(SortedT[[]uint8, uint8], coll, tc.shouldPass)(t) - case []uint16: - testOrderingT(SortedT[[]uint16, uint16], coll, tc.shouldPass)(t) - case []uint32: - testOrderingT(SortedT[[]uint32, uint32], coll, tc.shouldPass)(t) - case []uint64: - testOrderingT(SortedT[[]uint64, uint64], coll, tc.shouldPass)(t) - case []float32: - testOrderingT(SortedT[[]float32, float32], coll, tc.shouldPass)(t) - case []float64: - testOrderingT(SortedT[[]float64, float64], coll, tc.shouldPass)(t) - case []string: - testOrderingT(SortedT[[]string, string], coll, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", coll) + return + } + + t.Run("should fail", func(t *testing.T) { + if result || !mock.Failed() { + t.Errorf("expected to fail") } }) } } -func TestNotSortedT(t *testing.T) { - t.Parallel() - - for tc := range sortedTCases() { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - // Invert shouldPass for NotSorted - shouldPass := !tc.shouldPass - - // Dispatch based on type - switch coll := tc.collection.(type) { - case []int: - testOrderingT(NotSortedT[[]int, int], coll, shouldPass)(t) - case []int8: - testOrderingT(NotSortedT[[]int8, int8], coll, shouldPass)(t) - case []int16: - testOrderingT(NotSortedT[[]int16, int16], coll, shouldPass)(t) - case []int32: - testOrderingT(NotSortedT[[]int32, int32], coll, shouldPass)(t) - case []int64: - testOrderingT(NotSortedT[[]int64, int64], coll, shouldPass)(t) - case []uint: - testOrderingT(NotSortedT[[]uint, uint], coll, shouldPass)(t) - case []uint8: - testOrderingT(NotSortedT[[]uint8, uint8], coll, shouldPass)(t) - case []uint16: - testOrderingT(NotSortedT[[]uint16, uint16], coll, shouldPass)(t) - case []uint32: - testOrderingT(NotSortedT[[]uint32, uint32], coll, shouldPass)(t) - case []uint64: - testOrderingT(NotSortedT[[]uint64, uint64], coll, shouldPass)(t) - case []float32: - testOrderingT(NotSortedT[[]float32, float32], coll, shouldPass)(t) - case []float64: - testOrderingT(NotSortedT[[]float64, float64], coll, shouldPass)(t) - case []string: - testOrderingT(NotSortedT[[]string, string], coll, shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", coll) - } - }) +func testOrderAssertionResult(mock T, assertionKind orderAssertionKind, collection any) bool { + // Type switch to call the appropriate generic function. + // + // This switch doesn't cover ALL variants of ~[]Ordered but is deemed sufficient for the purpose + // of testing ordering. + switch coll := collection.(type) { + case []int: + return testGenericAssertion(mock, assertionKind, coll) + case []int8: + return testGenericAssertion(mock, assertionKind, coll) + case []int16: + return testGenericAssertion(mock, assertionKind, coll) + case []int32: + return testGenericAssertion(mock, assertionKind, coll) + case []int64: + return testGenericAssertion(mock, assertionKind, coll) + case []uint: + return testGenericAssertion(mock, assertionKind, coll) + case []uint8: + return testGenericAssertion(mock, assertionKind, coll) + case []uint16: + return testGenericAssertion(mock, assertionKind, coll) + case []uint32: + return testGenericAssertion(mock, assertionKind, coll) + case []uint64: + return testGenericAssertion(mock, assertionKind, coll) + case []uintptr: + return testGenericAssertion(mock, assertionKind, coll) + case []float32: + return testGenericAssertion(mock, assertionKind, coll) + case []float64: + return testGenericAssertion(mock, assertionKind, coll) + case []myFloat: + return testGenericAssertion(mock, assertionKind, coll) + case myCollection: + return testGenericAssertion(mock, assertionKind, coll) + case [][]byte: + return testGenericAssertion(mock, assertionKind, coll) + case []string: + return testGenericAssertion(mock, assertionKind, coll) + case []time.Time: + return testGenericAssertion(mock, assertionKind, coll) + default: + panic(fmt.Errorf("internal test error: unsupported collection type in test suite: %T", coll)) } } -//nolint:thelper // linter false positive: these are not helpers -func testOrderingT[OrderedSlice ~[]E, E Ordered]( - fn func(T, OrderedSlice, ...any) bool, - collection OrderedSlice, - shouldPass bool, -) func(*testing.T) { - return func(t *testing.T) { - mock := new(mockT) - result := fn(mock, collection) - - if shouldPass { - True(t, result) - False(t, mock.Failed()) - return - } - - False(t, result) - True(t, mock.Failed()) +func testGenericAssertion[Collection ~[]E, E Ordered](mock T, assertionKind orderAssertionKind, collection Collection) bool { + switch assertionKind { + case increasingKind: + return IsIncreasingT(mock, collection) + case notIncreasingKind: + return IsNonIncreasingT(mock, collection) + case decreasingKind: + return IsDecreasingT(mock, collection) + case notDecreasingKind: + return IsNonDecreasingT(mock, collection) + case sortedKind: + return SortedT(mock, collection) + case notSortedKind: + return NotSortedT(mock, collection) + default: + panic(fmt.Errorf("test case configuration error: invalid orderAssertionKind: %d", assertionKind)) } } diff --git a/internal/assertions/string_test.go b/internal/assertions/string_test.go index a5fd202de..183b4a881 100644 --- a/internal/assertions/string_test.go +++ b/internal/assertions/string_test.go @@ -10,94 +10,117 @@ import ( "testing" ) -func TestStringEqualAndRegexp(t *testing.T) { - t.Parallel() - - i := 0 - for currCase := range stringEqualCases() { - mock := &bufferT{} +func TestStringRegexpEdgeCases(t *testing.T) { + // check edge cases for reflection-based Regexp, such as unsupported types or nil input or when the input + // is converted using fmt.Sprint. - Equal(mock, currCase.equalWant, currCase.equalGot, currCase.msgAndArgs...) - Regexp(t, regexp.MustCompile(currCase.want), mock.buf.String(), "Case %d", i) - i++ - } -} + t.Run("with unsupported regexp type", func(t *testing.T) { + t.Parallel() -func TestStringEqualAndRegexpFormatting(t *testing.T) { - t.Parallel() + const ( + str = "whatever" + msg = "expected this invalid call to fail (regexp=%v)" + ) - i := 0 - for currCase := range stringEqualFormattingCases() { - mock := &bufferT{} + mock := new(mockT) - Equal(mock, currCase.equalWant, currCase.equalGot, currCase.msgAndArgs...) - Regexp(t, regexp.MustCompile(currCase.want), mock.buf.String(), "Case %d", i) - i++ - } -} - -func TestStringRegexp(t *testing.T) { - t.Parallel() - - // run test cases with all combinations of supported types - for tc := range stringRegexpCases() { - t.Run(tc.name, tc.test) - } + t.Run("should fail (invalid regexp type)", func(t *testing.T) { + invalidRex := struct{ a string }{a: "invalid"} - t.Run("with edge cases", func(t *testing.T) { - t.Run("with unsupported regexp type", func(t *testing.T) { - t.Parallel() + if Regexp(mock, invalidRex, str) { + t.Errorf(msg, invalidRex) + } + if NotRegexp(mock, invalidRex, str) { + t.Errorf(msg, invalidRex) + } + }) - const ( - str = "whatever" - msg = "expected this invalid call to fail (regexp=%v)" - ) + t.Run("should fail (nil regexp)", func(t *testing.T) { + invalidRex := []byte(nil) - mock := new(mockT) + if Regexp(mock, invalidRex, str) { + t.Errorf(msg, invalidRex) + } + if NotRegexp(mock, invalidRex, str) { + t.Errorf(msg, invalidRex) + } + }) + }) - t.Run("should fail (invalid regexp type)", func(t *testing.T) { - invalidRex := struct{ a string }{a: "invalid"} + t.Run("with fmt.Sprint conversion (edge case)", func(t *testing.T) { + t.Parallel() - if Regexp(mock, invalidRex, str) { - t.Errorf(msg, invalidRex) - } - if NotRegexp(mock, invalidRex, str) { - t.Errorf(msg, invalidRex) - } - }) + const ( + numeric = 1234 + msg = "expected %q to match %q" + rex = "^[0-9]+$" + ) - t.Run("should fail (nil regexp)", func(t *testing.T) { - invalidRex := []byte(nil) + mock := new(mockT) - if Regexp(mock, invalidRex, str) { - t.Errorf(msg, invalidRex) - } - if NotRegexp(mock, invalidRex, str) { - t.Errorf(msg, invalidRex) - } - }) + t.Run("should match string representation of a number", func(t *testing.T) { + if !Regexp(mock, rex, numeric) { + t.Errorf(msg, numeric, rex) + } + if NotRegexp(mock, rex, numeric) { + t.Errorf(msg, numeric, rex) + } }) + }) +} - t.Run("with fmt.Sprint conversion (edge case)", func(t *testing.T) { - t.Parallel() - - const ( - numeric = 1234 - msg = "expected %q to match %q" - rex = "^[0-9]+$" - ) +func TestStringRegexp(t *testing.T) { + t.Parallel() - mock := new(mockT) + // run test cases with all combinations of supported types + // + // NOTE: testing pattern, focused on the expected result (true/false) and _NOT_ the content of the returned message. + // - stringRegexpCases: loop over generic test cases + // - testAllRegexpWithTypes: dispatch over type combinations of values + // - testAllRegexp: dispatch over the assertion variants (reflection-based, generic, X vs NotX semantics) + // Single assertion test functions: + // - testRegexp + // - testRegexpT + // - testNotRegexp + // - testNotRegexpT + for tc := range stringRegexpCases() { + t.Run(tc.name, tc.test) + } +} - t.Run("should match string representation of a number", func(t *testing.T) { - if !Regexp(mock, rex, numeric) { - t.Errorf(msg, numeric, rex) - } - if NotRegexp(mock, rex, numeric) { - t.Errorf(msg, numeric, rex) - } - }) - }) +// Values to populate the test harness: +// +// - valid and invalid patterns +// - matching and not matching expressions. +func stringRegexpCases() iter.Seq[genericTestCase] { + return slices.Values([]genericTestCase{ + // successful matches + {"^start (match)", testAllRegexpWithTypes( + "^start", "start of the line", true, true, + )}, + {"end$ (match)", testAllRegexpWithTypes( + "end$", "in the end", true, true, + )}, + {"end$ (match)", testAllRegexpWithTypes( + "end$", "in the end", true, true, + )}, + {"phone number (match)", testAllRegexpWithTypes( + "[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12.34", true, true, + )}, + // failed matches + {"start (no match)", testAllRegexpWithTypes( + "^asdfastart", "Not the start of the line", false, true, + )}, + {"end$ (no match)", testAllRegexpWithTypes( + "end$", "in the end.", false, true, + )}, + {"phone number (no match)", testAllRegexpWithTypes( + "[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12a.34", false, true, + )}, + // invalid pattern + {"invalid regexp", testAllRegexpWithTypes( + "\\C", "whatever", false, false, + )}, }) } @@ -261,136 +284,3 @@ func testRex(rex string) *regexp.Regexp { rx, _ := compileRegex(rex) return rx } - -type stringEqualCase struct { - equalWant string - equalGot string - msgAndArgs []any - want string -} - -func stringEqualCases() iter.Seq[stringEqualCase] { - return slices.Values([]stringEqualCase{ - { - equalWant: "hi, \nmy name is", - equalGot: "what,\nmy name is", - want: "\t[a-z]+.go:\\d+: \n" + // NOTE: the exact file name reported should be asserted in integration tests - "\t+Error Trace:\t\n+" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"hi, \\\\nmy name is\"\n" + - "\\s+actual\\s+: " + "\"what,\\\\nmy name is\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n\\s+\\++ " + - "Actual\n" + - "\\s+@@ -1,2 \\+1,2 @@\n" + - "\\s+-hi, \n\\s+\\+what,\n" + - "\\s+my name is", - }, - }) -} - -func stringEqualFormattingCases() iter.Seq[stringEqualCase] { - return slices.Values([]stringEqualCase{ - { - equalWant: "want", - equalGot: "got", - want: "\t[a-z]+.go:\\d+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ " + - "Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n", - }, - { - equalWant: "want", - equalGot: "got", - msgAndArgs: []any{"hello, %v!", "world"}, - want: "\t[a-z]+.go:[0-9]+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n" + - "\\s+\\++ Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n" + - "\\s+Messages:\\s+hello, world!\n", - }, - { - equalWant: "want", - equalGot: "got", - msgAndArgs: []any{123}, - want: "\t[a-z]+.go:[0-9]+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n" + - "\\s+\\++ Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n" + - "\\s+Messages:\\s+123\n", - }, - { - equalWant: "want", - equalGot: "got", - msgAndArgs: []any{struct{ a string }{"hello"}}, - want: "\t[a-z]+.go:[0-9]+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n" + - "\\s+\\++ Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n" + - "\\s+Messages:\\s+{a:hello}\n", - }, - }) -} - -// Values to populate the test harness: -// -// - valid and invalid patterns -// - matching and not matching expressions. -func stringRegexpCases() iter.Seq[genericTestCase] { - return slices.Values([]genericTestCase{ - // successful matches - {"^start (match)", testAllRegexpWithTypes( - "^start", "start of the line", true, true, - )}, - {"end$ (match)", testAllRegexpWithTypes( - "end$", "in the end", true, true, - )}, - {"end$ (match)", testAllRegexpWithTypes( - "end$", "in the end", true, true, - )}, - {"phone number (match)", testAllRegexpWithTypes( - "[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12.34", true, true, - )}, - // failed matches - {"start (no match)", testAllRegexpWithTypes( - "^asdfastart", "Not the start of the line", false, true, - )}, - {"end$ (no match)", testAllRegexpWithTypes( - "end$", "in the end.", false, true, - )}, - {"phone number (no match)", testAllRegexpWithTypes( - "[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12a.34", false, true, - )}, - // invalid pattern - {"invalid regexp", testAllRegexpWithTypes( - "\\C", "whatever", false, false, - )}, - }) -} From 68cdfe939cac19221c9a7f2a21ea7f355ccbd9e1 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 20 Jan 2026 15:24:45 +0100 Subject: [PATCH 08/10] test(order): refactored message assertions Signed-off-by: Frederic BIDON --- internal/assertions/mock_test.go | 4 + internal/assertions/order_test.go | 155 ++++++++++++------------------ 2 files changed, 65 insertions(+), 94 deletions(-) diff --git a/internal/assertions/mock_test.go b/internal/assertions/mock_test.go index 625989c7e..c59b1fe3c 100644 --- a/internal/assertions/mock_test.go +++ b/internal/assertions/mock_test.go @@ -101,6 +101,10 @@ type outputT struct { helpers map[string]struct{} } +func newOutputMock() *outputT { + return &outputT{buf: bytes.NewBuffer(nil)} +} + // Implements T. func (t *outputT) Errorf(format string, args ...any) { s := fmt.Sprintf(format, args...) diff --git a/internal/assertions/order_test.go b/internal/assertions/order_test.go index 65c49ec34..55dd87e72 100644 --- a/internal/assertions/order_test.go +++ b/internal/assertions/order_test.go @@ -4,7 +4,6 @@ package assertions import ( - "bytes" "fmt" "iter" "slices" @@ -14,103 +13,25 @@ import ( ) func TestOrderErrorMessages(t *testing.T) { - // Test reflection-based assertions - - t.Run("with error messages", func(t *testing.T) { - t.Parallel() - - const ( - format = "format %s %x" - arg1 = "this" - arg2 = 0xc001 - expectedOutput = "format this c001\n" - ) - - msgAndArgs := []any{format, arg1, arg2} - collection := []int{1, 2, 1} // is neither increasing nor decreasing - increasingCollection := []int{1, 2, 3} - decreasingCollection := []int{3, 2, 1} - - funcs := []struct { - name string - fn func(T, any, ...any) bool - collection []int - }{ - {"IsIncreasing", IsIncreasing, collection}, - {"IsNonIncreasing", IsNonIncreasing, increasingCollection}, - {"IsDecreasing", IsDecreasing, collection}, - {"IsNonDecreasing", IsNonDecreasing, decreasingCollection}, - } - - for _, fn := range funcs { - t.Run(fn.name, func(t *testing.T) { - t.Parallel() - - mock := &outputT{buf: bytes.NewBuffer(nil)} - result := fn.fn(mock, fn.collection, msgAndArgs...) - if result { - t.Errorf("expected ordering assertion %q to fail on %v", fn.name, fn.collection) - - return - } - - if !strings.Contains(mock.buf.String(), expectedOutput) { - t.Errorf("expected error message to contain: %s but got %q", expectedOutput, mock.buf.String()) - } - }) - } - }) - - t.Run("with detailed error messages", func(t *testing.T) { - // Test specific error messages for reflection-based assertions - t.Parallel() + t.Parallel() - testCases := []struct { - name string - fn func(T, any, ...any) bool - collection any - expected string - }{ - // IsIncreasing errors - {"IsIncreasing/string", IsIncreasing, []string{"b", "a"}, `"b" is not less than "a"`}, - {"IsIncreasing/int", IsIncreasing, []int{2, 1}, `"2" is not less than "1"`}, - {"IsIncreasing/int8", IsIncreasing, []int8{2, 1}, `"2" is not less than "1"`}, - {"IsIncreasing/float32", IsIncreasing, []float32{2.34, 1.23}, `"2.34" is not less than "1.23"`}, - {"IsIncreasing/invalid-type", IsIncreasing, struct{}{}, `object struct {} is not an ordered collection`}, - - // IsNonIncreasing errors - {"IsNonIncreasing/string", IsNonIncreasing, []string{"a", "b"}, `should not be increasing`}, - {"IsNonIncreasing/int", IsNonIncreasing, []int{1, 2}, `should not be increasing`}, - {"IsNonIncreasing/float64", IsNonIncreasing, []float64{1.23, 2.34}, `should not be increasing`}, - - // IsDecreasing errors - {"IsDecreasing/string", IsDecreasing, []string{"a", "b"}, `"a" is not greater than "b"`}, - {"IsDecreasing/int", IsDecreasing, []int{1, 2}, `"1" is not greater than "2"`}, - {"IsDecreasing/uint64", IsDecreasing, []uint64{1, 2}, `"1" is not greater than "2"`}, - - // IsNonDecreasing errors - {"IsNonDecreasing/string", IsNonDecreasing, []string{"b", "a"}, `should not be decreasing`}, - {"IsNonDecreasing/int", IsNonDecreasing, []int{2, 1}, `should not be decreasing`}, - {"IsNonDecreasing/float32", IsNonDecreasing, []float32{2.34, 1.23}, `should not be decreasing`}, - } + for tc := range orderErrorMessageCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() + mock := newOutputMock() + result := tc.fn(mock, tc.collection, tc.msgAndArgs...) + if result { + t.Errorf("expected ordering assertion %q to fail on %v", tc.name, tc.collection) - out := &outputT{buf: bytes.NewBuffer(nil)} - result := tc.fn(out, tc.collection) - if result { - t.Errorf("expected ordering assertion %q to fail on %v", tc.name, tc.collection) + return + } - return - } - if !strings.Contains(out.buf.String(), tc.expected) { - t.Errorf("expected error message to contain: %s but got %q", tc.expected, out.buf.String()) - } - }) - } - }) + if !strings.Contains(mock.buf.String(), tc.expectedInMsg) { + t.Errorf("expected error message to contain: %s but got %q", tc.expectedInMsg, mock.buf.String()) + } + }) + } } // Test functions for reflection-based and generic assertions @@ -487,3 +408,49 @@ func testGenericAssertion[Collection ~[]E, E Ordered](mock T, assertionKind orde panic(fmt.Errorf("test case configuration error: invalid orderAssertionKind: %d", assertionKind)) } } + +type errorMessageTestCase struct { + name string + fn func(T, any, ...any) bool + collection any + msgAndArgs []any + expectedInMsg string +} + +func orderErrorMessageCases() iter.Seq[errorMessageTestCase] { + const ( + format = "format %s %x" + arg1 = "this" + arg2 = 0xc001 + expectedOutput = "format this c001\n" + ) + + msgAndArgs := []any{format, arg1, arg2} + + return slices.Values([]errorMessageTestCase{ + // Test msgAndArgs formatting + {"IsIncreasing/with-msgAndArgs", IsIncreasing, []int{1, 2, 1}, msgAndArgs, expectedOutput}, + {"IsNonIncreasing/with-msgAndArgs", IsNonIncreasing, []int{1, 2, 3}, msgAndArgs, expectedOutput}, + {"IsDecreasing/with-msgAndArgs", IsDecreasing, []int{1, 2, 1}, msgAndArgs, expectedOutput}, + {"IsNonDecreasing/with-msgAndArgs", IsNonDecreasing, []int{3, 2, 1}, msgAndArgs, expectedOutput}, + + // Test specific error messages + {"IsIncreasing/string", IsIncreasing, []string{"b", "a"}, nil, `"b" is not less than "a"`}, + {"IsIncreasing/int", IsIncreasing, []int{2, 1}, nil, `"2" is not less than "1"`}, + {"IsIncreasing/int8", IsIncreasing, []int8{2, 1}, nil, `"2" is not less than "1"`}, + {"IsIncreasing/float32", IsIncreasing, []float32{2.34, 1.23}, nil, `"2.34" is not less than "1.23"`}, + {"IsIncreasing/invalid-type", IsIncreasing, struct{}{}, nil, `object struct {} is not an ordered collection`}, + + {"IsNonIncreasing/string", IsNonIncreasing, []string{"a", "b"}, nil, `should not be increasing`}, + {"IsNonIncreasing/int", IsNonIncreasing, []int{1, 2}, nil, `should not be increasing`}, + {"IsNonIncreasing/float64", IsNonIncreasing, []float64{1.23, 2.34}, nil, `should not be increasing`}, + + {"IsDecreasing/string", IsDecreasing, []string{"a", "b"}, nil, `"a" is not greater than "b"`}, + {"IsDecreasing/int", IsDecreasing, []int{1, 2}, nil, `"1" is not greater than "2"`}, + {"IsDecreasing/uint64", IsDecreasing, []uint64{1, 2}, nil, `"1" is not greater than "2"`}, + + {"IsNonDecreasing/string", IsNonDecreasing, []string{"b", "a"}, nil, `should not be decreasing`}, + {"IsNonDecreasing/int", IsNonDecreasing, []int{2, 1}, nil, `should not be decreasing`}, + {"IsNonDecreasing/float32", IsNonDecreasing, []float32{2.34, 1.23}, nil, `should not be decreasing`}, + }) +} From debbdcb5a0499e9f3bd2e8bac68bd92c93c485df Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 20 Jan 2026 23:11:23 +0100 Subject: [PATCH 09/10] fix: aligned edge case behavior of EqualValues with Equal all equality assertions should fail the same when input is a function. fix: type safety added CanInterface guard to EqualExportedValues (even if it seems currently impossible to make it panic with current reflect) test: refactored equal tests Signed-off-by: Frederic BIDON --- docs/doc-site/project/maintainers/ROADMAP.md | 6 +- internal/assertions/equal.go | 330 +-- internal/assertions/equal_impl_test.go | 62 + internal/assertions/equal_pointer.go | 126 ++ internal/assertions/equal_pointer_test.go | 180 ++ internal/assertions/equal_test.go | 2053 +++++++----------- internal/assertions/equal_unary.go | 154 ++ internal/assertions/equal_unary_test.go | 281 +++ internal/assertions/helpers_impl_test.go | 9 + internal/assertions/mock_test.go | 24 + internal/assertions/object_test.go | 189 +- internal/assertions/order.go | 2 +- internal/assertions/order_test.go | 34 +- 13 files changed, 1693 insertions(+), 1757 deletions(-) create mode 100644 internal/assertions/equal_pointer.go create mode 100644 internal/assertions/equal_pointer_test.go create mode 100644 internal/assertions/equal_unary.go create mode 100644 internal/assertions/equal_unary_test.go diff --git a/docs/doc-site/project/maintainers/ROADMAP.md b/docs/doc-site/project/maintainers/ROADMAP.md index aa9db261d..da24f14ca 100644 --- a/docs/doc-site/project/maintainers/ROADMAP.md +++ b/docs/doc-site/project/maintainers/ROADMAP.md @@ -22,10 +22,10 @@ timeline : removed deprecated : optional dependencies (colorized) : upstream PRs: Kind/NotKind - : generics - v2.2 (Fev 2026) : generics (cont.) + v2.2 (Fev 2026) : : generics + : SortedT, NotSortedT : JSON assertions. JSONMarshalsAs... - : IsSorted, IsSortedFunc + : complete test refactoring : more benchmarks. Perf improvements v2.3 (Mar 2026) : other extensions (TBD) : more documentation and examples diff --git a/internal/assertions/equal.go b/internal/assertions/equal.go index 8f61e7a1c..17a40fc44 100644 --- a/internal/assertions/equal.go +++ b/internal/assertions/equal.go @@ -82,6 +82,8 @@ func EqualT[V comparable](t T, expected, actual V, msgAndArgs ...any) bool { // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). // +// Function equality cannot be determined and will always fail. +// // # Examples // // success: 123, 456 @@ -124,156 +126,77 @@ func NotEqualT[V comparable](t T, expected, actual V, msgAndArgs ...any) bool { return true } -// Same asserts that two pointers reference the same object. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // -// Both arguments must be pointer variables. Pointer variable sameness is -// determined based on the equality of both type and value. +// Function equality cannot be determined and will always fail. // // # Usage // -// assertions.Same(t, ptr1, ptr2) +// assertions.EqualValues(t, uint32(123), int32(123)) // // # Examples // -// success: &staticVar, staticVarPtr -// failure: &staticVar, ptr("static string") -func Same(t T, expected, actual any, msgAndArgs ...any) bool { +// success: uint32(123), int32(123) +// failure: uint32(123), int32(456) +func EqualValues(t T, expected, actual any, msgAndArgs ...any) bool { // Domain: equality if h, ok := t.(H); ok { h.Helper() } - same, ok := samePointers(expected, actual) - if !ok { - return Fail(t, "Both arguments must be pointers", msgAndArgs...) - } - - if !same { - // both are pointers but not the same type & pointing to the same address - return Fail(t, fmt.Sprintf("Not same: \n"+ - "expected: %[2]s (%[1]T)(%[1]p)\n"+ - "actual : %[4]s (%[3]T)(%[3]p)", expected, truncatingFormat("%#v", expected), actual, truncatingFormat("%#v", actual)), msgAndArgs...) - } - - return true -} - -// SameT asserts that two pointers of the same type reference the same object. -// -// # Usage -// -// assertions.SameT(t, ptr1, ptr2) -// -// # Examples -// -// success: &staticVar, staticVarPtr -// failure: &staticVar, ptr("static string") -func SameT[P any](t T, expected, actual *P, msgAndArgs ...any) bool { - // Domain: equality - if h, ok := t.(H); ok { - h.Helper() + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) } - if expected != actual { - return Fail(t, fmt.Sprintf("Not same: \n"+ - "expected: %[2]s (%[1]T)(%[1]p)\n"+ - "actual : %[4]s (%[3]T)(%[3]p)", expected, truncatingFormat("%#v", expected), actual, truncatingFormat("%#v", actual)), msgAndArgs...) + if !ObjectsAreEqualValues(expected, actual) { + diff := diff(expected, actual) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true } -// NotSame asserts that two pointers do not reference the same object. +// NotEqualValues asserts that two objects are not equal even when converted to the same type. // -// Both arguments must be pointer variables. Pointer variable sameness is -// determined based on the equality of both type and value. +// Function equality cannot be determined and will always fail. // // # Usage // -// assertions.NotSame(t, ptr1, ptr2) +// assertions.NotEqualValues(t, obj1, obj2) // // # Examples // -// success: &staticVar, ptr("static string") -// failure: &staticVar, staticVarPtr -func NotSame(t T, expected, actual any, msgAndArgs ...any) bool { +// success: uint32(123), int32(456) +// failure: uint32(123), int32(123) +func NotEqualValues(t T, expected, actual any, msgAndArgs ...any) bool { // Domain: equality if h, ok := t.(H); ok { h.Helper() } - same, ok := samePointers(expected, actual) - if !ok { - // fails when the arguments are not pointers - return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) - } - - if same { - return Fail(t, fmt.Sprintf( - "Expected and actual point to the same object: %p %s", - expected, truncatingFormat("%#v", expected)), msgAndArgs...) - } - return true -} - -// NotSameT asserts that two pointers do not reference the same object. -// -// See [SameT] -// -// # Usage -// -// assertions.NotSameT(t, ptr1, ptr2) -// -// # Examples -// -// success: &staticVar, ptr("static string") -// failure: &staticVar, staticVarPtr -func NotSameT[P any](t T, expected, actual *P, msgAndArgs ...any) bool { - // Domain: equality - if h, ok := t.(H); ok { - h.Helper() + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) } - if expected == actual { - return Fail(t, fmt.Sprintf( - "Expected and actual point to the same object: %p %s", - expected, truncatingFormat("%#v", expected)), msgAndArgs...) + if ObjectsAreEqualValues(expected, actual) { + return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) } return true } -// EqualValues asserts that two objects are equal or convertible to the larger -// type and equal. -// -// # Usage -// -// assertions.EqualValues(t, uint32(123), int32(123)) +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. // -// # Examples +// This is useful for comparing structs that have private fields that could potentially differ. // -// success: uint32(123), int32(123) -// failure: uint32(123), int32(456) -func EqualValues(t T, expected, actual any, msgAndArgs ...any) bool { - // Domain: equality - if h, ok := t.(H); ok { - h.Helper() - } - - if !ObjectsAreEqualValues(expected, actual) { - diff := diff(expected, actual) - expected, actual = formatUnequalValues(expected, actual) - return Fail(t, fmt.Sprintf("Not equal: \n"+ - "expected: %s\n"+ - "actual : %s%s", expected, actual, diff), msgAndArgs...) - } - - return true -} - -// EqualExportedValues asserts that the types of two objects are equal and their public -// fields are also equal. This is useful for comparing structs that have private fields -// that could potentially differ. +// Function equality cannot be determined and will always fail. // // # Usage // @@ -294,6 +217,11 @@ func EqualExportedValues(t T, expected, actual any, msgAndArgs ...any) bool { h.Helper() } + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) + } + aType := reflect.TypeOf(expected) bType := reflect.TypeOf(actual) @@ -341,129 +269,6 @@ func Exactly(t T, expected, actual any, msgAndArgs ...any) bool { return Equal(t, expected, actual, msgAndArgs...) } -// NotNil asserts that the specified object is not nil. -// -// # Usage -// -// assertions.NotNil(t, err) -// -// # Examples -// -// success: "not nil" -// failure: nil -func NotNil(t T, object any, msgAndArgs ...any) bool { - // Domain: equality - if !isNil(object) { - return true - } - if h, ok := t.(H); ok { - h.Helper() - } - return Fail(t, "Expected value not to be nil.", msgAndArgs...) -} - -// Nil asserts that the specified object is nil. -// -// # Usage -// -// assertions.Nil(t, err) -// -// # Examples -// -// success: nil -// failure: "not nil" -func Nil(t T, object any, msgAndArgs ...any) bool { - // Domain: equality - if isNil(object) { - return true - } - if h, ok := t.(H); ok { - h.Helper() - } - return Fail(t, "Expected nil, but got: "+truncatingFormat("%#v", object), msgAndArgs...) -} - -// Empty asserts that the given value is "empty". -// -// Zero values are "empty". -// -// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). -// -// Slices, maps and channels with zero length are "empty". -// -// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". -// -// # Usage -// -// assertions.Empty(t, obj) -// -// # Examples -// -// success: "" -// failure: "not empty" -// -// [Zero values]: https://go.dev/ref/spec#The_zero_value -func Empty(t T, object any, msgAndArgs ...any) bool { - // Domain: equality - pass := isEmpty(object) - if !pass { - if h, ok := t.(H); ok { - h.Helper() - } - Fail(t, "Should be empty, but was "+truncatingFormat("%v", object), msgAndArgs...) - } - - return pass -} - -// NotEmpty asserts that the specified object is NOT [Empty]. -// -// # Usage -// -// if assert.NotEmpty(t, obj) { -// assertions.Equal(t, "two", obj[1]) -// } -// -// # Examples -// -// success: "not empty" -// failure: "" -func NotEmpty(t T, object any, msgAndArgs ...any) bool { - // Domain: equality - pass := !isEmpty(object) - if !pass { - if h, ok := t.(H); ok { - h.Helper() - } - Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) - } - - return pass -} - -// NotEqualValues asserts that two objects are not equal even when converted to the same type. -// -// # Usage -// -// assertions.NotEqualValues(t, obj1, obj2) -// -// # Examples -// -// success: uint32(123), int32(456) -// failure: uint32(123), int32(123) -func NotEqualValues(t T, expected, actual any, msgAndArgs ...any) bool { - // Domain: equality - if h, ok := t.(H); ok { - h.Helper() - } - - if ObjectsAreEqualValues(expected, actual) { - return Fail(t, fmt.Sprintf("Should not be: %s\n", truncatingFormat("%#v", actual)), msgAndArgs...) - } - - return true -} - func failWithDiff(t T, expected, actual any, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() @@ -487,27 +292,8 @@ func failWithDiff(t T, expected, actual any, msgAndArgs ...any) bool { ) } -// isNil checks if a specified object is nil or not, without Failing. -func isNil(object any) bool { - if object == nil { - return true - } - - value := reflect.ValueOf(object) - switch value.Kind() { - case - reflect.Chan, reflect.Func, - reflect.Interface, reflect.Map, - reflect.Ptr, reflect.Slice, reflect.UnsafePointer: - - return value.IsNil() - default: - return false - } -} - // validateEqualArgs checks whether provided arguments can be safely used in the -// Equal/NotEqual functions. +// Equal/NotEqual/EqualValues/NotEqualValues functions. func validateEqualArgs(expected, actual any) error { if expected == nil && actual == nil { return nil @@ -558,35 +344,6 @@ func formatUnequalValues(expected, actual any) (e string, a string) { } } -// isEmpty gets whether the specified object is considered empty or not. -func isEmpty(object any) bool { - // get nil case out of the way - if object == nil { - return true - } - - return isEmptyValue(reflect.ValueOf(object)) -} - -// isEmptyValue gets whether the specified reflect.Value is considered empty or not. -func isEmptyValue(objValue reflect.Value) bool { - if objValue.IsZero() { - return true - } - // Special cases of non-zero values that we consider empty - switch objValue.Kind() { - // collection types are empty when they have no element - // Note: array types are empty when they match their zero-initialized state. - case reflect.Chan, reflect.Map, reflect.Slice: - return objValue.Len() == 0 - // non-nil pointers are empty if the value they point to is empty - case reflect.Ptr: - return isEmptyValue(objValue.Elem()) - default: - return false - } -} - // copyExportedFields iterates downward through nested data structures and creates a copy // that only contains the exported struct fields. func copyExportedFields(expected any) any { @@ -630,8 +387,9 @@ func copyExportedFields(expected any) any { } for i := range expectedValue.Len() { index := expectedValue.Index(i) - if isNil(index) { - continue + if !index.CanInterface() { + // this should not be possible with current reflect, since values are retrieved from an array or slice, not a struct + panic(fmt.Errorf("internal error: can't resolve Interface() for value %v", index)) } unexportedRemoved := copyExportedFields(index.Interface()) result.Index(i).Set(reflect.ValueOf(unexportedRemoved)) @@ -642,6 +400,10 @@ func copyExportedFields(expected any) any { result := reflect.MakeMap(expectedType) for _, k := range expectedValue.MapKeys() { index := expectedValue.MapIndex(k) + if !index.CanInterface() { + // this should not be possible with current reflect, since values are retrieved from a map, not a struct + panic(fmt.Errorf("internal error: can't resolve Interface() for value %v", index)) + } unexportedRemoved := copyExportedFields(index.Interface()) result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved)) } diff --git a/internal/assertions/equal_impl_test.go b/internal/assertions/equal_impl_test.go index 263155a36..0a40b5c80 100644 --- a/internal/assertions/equal_impl_test.go +++ b/internal/assertions/equal_impl_test.go @@ -12,6 +12,8 @@ import ( "time" ) +const shortpkg = "assertions" + func TestEqualUnexportedImplementationDetails(t *testing.T) { t.Parallel() @@ -228,3 +230,63 @@ func formatUnequalCases() iter.Seq[formatUnequalCase] { {uint64(123), uint64(124), `123`, `124`, "uint64 should print clean"}, }) } + +type samePointersCase struct { + name string + args args + same BoolAssertionFunc + ok BoolAssertionFunc +} + +type args struct { + first any + second any +} + +func equalSamePointersCases() iter.Seq[samePointersCase] { + p := ptr(2) + return slices.Values([]samePointersCase{ + { + name: "1 != 2", + args: args{first: 1, second: 2}, + same: False, + ok: False, + }, + { + name: "1 != 1 (not same ptr)", + args: args{first: 1, second: 1}, + same: False, + ok: False, + }, + { + name: "ptr(1) == ptr(1)", + args: args{first: p, second: p}, + same: True, + ok: True, + }, + { + name: "int(1) != float32(1)", + args: args{first: int(1), second: float32(1)}, + same: False, + ok: False, + }, + { + name: "array != slice", + args: args{first: [2]int{1, 2}, second: []int{1, 2}}, + same: False, + ok: False, + }, + { + name: "non-pointer vs pointer (1 != ptr(2))", + args: args{first: 1, second: p}, + same: False, + ok: False, + }, + { + name: "pointer vs non-pointer (ptr(2) != 1)", + args: args{first: p, second: 1}, + same: False, + ok: False, + }, + }) +} diff --git a/internal/assertions/equal_pointer.go b/internal/assertions/equal_pointer.go new file mode 100644 index 000000000..c492c4200 --- /dev/null +++ b/internal/assertions/equal_pointer.go @@ -0,0 +1,126 @@ +package assertions + +import "fmt" + +// Same asserts that two pointers reference the same object. +// +// Both arguments must be pointer variables. +// +// Pointer variable sameness is determined based on the equality of both type and value. +// +// Unlike [Equal] pointers, [Same] pointers point to the same memory address. +// +// # Usage +// +// assertions.Same(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, staticVarPtr +// failure: &staticVar, ptr("static string") +func Same(t T, expected, actual any, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + + same, ok := samePointers(expected, actual) + if !ok { + return Fail(t, "Both arguments must be pointers", msgAndArgs...) + } + + if !same { + // both are pointers but not the same type & pointing to the same address + return Fail(t, fmt.Sprintf("Not same: \n"+ + "expected: %[2]s (%[1]T)(%[1]p)\n"+ + "actual : %[4]s (%[3]T)(%[3]p)", expected, truncatingFormat("%#v", expected), actual, truncatingFormat("%#v", actual)), msgAndArgs...) + } + + return true +} + +// SameT asserts that two pointers of the same type reference the same object. +// +// See [Same]. +// +// # Usage +// +// assertions.SameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, staticVarPtr +// failure: &staticVar, ptr("static string") +func SameT[P any](t T, expected, actual *P, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + + if expected != actual { + return Fail(t, fmt.Sprintf("Not same: \n"+ + "expected: %[2]s (%[1]T)(%[1]p)\n"+ + "actual : %[4]s (%[3]T)(%[3]p)", expected, truncatingFormat("%#v", expected), actual, truncatingFormat("%#v", actual)), msgAndArgs...) + } + + return true +} + +// NotSame asserts that two pointers do not reference the same object. +// +// See [Same]. +// +// # Usage +// +// assertions.NotSame(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, ptr("static string") +// failure: &staticVar, staticVarPtr +func NotSame(t T, expected, actual any, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + + same, ok := samePointers(expected, actual) + if !ok { + // fails when the arguments are not pointers + return Fail(t, "Both arguments must be pointers", msgAndArgs...) + } + + if same { + return Fail(t, fmt.Sprintf( + "Expected and actual point to the same object: %p %s", + expected, truncatingFormat("%#v", expected)), msgAndArgs...) + } + return true +} + +// NotSameT asserts that two pointers do not reference the same object. +// +// See [SameT]. +// +// # Usage +// +// assertions.NotSameT(t, ptr1, ptr2) +// +// # Examples +// +// success: &staticVar, ptr("static string") +// failure: &staticVar, staticVarPtr +func NotSameT[P any](t T, expected, actual *P, msgAndArgs ...any) bool { + // Domain: equality + if h, ok := t.(H); ok { + h.Helper() + } + + if expected == actual { + return Fail(t, fmt.Sprintf( + "Expected and actual point to the same object: %p %s", + expected, truncatingFormat("%#v", expected)), msgAndArgs...) + } + + return true +} diff --git a/internal/assertions/equal_pointer_test.go b/internal/assertions/equal_pointer_test.go new file mode 100644 index 000000000..3d3cc6a82 --- /dev/null +++ b/internal/assertions/equal_pointer_test.go @@ -0,0 +1,180 @@ +package assertions + +import ( + "fmt" + "iter" + "slices" + "testing" +) + +// Pointer identity tests (Same, SameT, NotSame, NotSameT). +func TestEqualPointers(t *testing.T) { + t.Parallel() + + for tc := range unifiedPointerPairCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + expected, actual := tc.makeValues() + + if !tc.reflectionOnly { + t.Run("with Same", testPointerAssertion(tc, sameKind, Same, expected, actual)) + t.Run("with NotSame", testPointerAssertion(tc, notSameKind, NotSame, expected, actual)) + + // Generic variants - type dispatch + t.Run("with SameT", testPointerAssertionT(tc, sameTKind, expected, actual)) + t.Run("with NotSameT", testPointerAssertionT(tc, notSameTKind, expected, actual)) + + return + } + + // Reflection-only cases (non-pointer args, different types, one nil) + t.Run("with Same (reflection)", testPointerAssertion(tc, sameKind, Same, expected, actual)) + t.Run("with NotSame (reflection)", testPointerAssertion(tc, notSameKind, NotSame, expected, actual)) + }) + } +} + +type pointerPairTestCase struct { + name string + makeValues func() (expected, actual any) + relationship pairRelationship + reflectionOnly bool +} + +func unifiedPointerPairCases() iter.Seq[pointerPairTestCase] { + const hello = "hello" + + return slices.Values([]pointerPairTestCase{ + // Both nil + {"both-nil/ptr", func() (any, any) { return (*int)(nil), (*int)(nil) }, bothNil, false}, + {"both-nil/different-types", func() (any, any) { return (*int)(nil), (*string)(nil) }, differentTypes, true}, + + // One nil + {"one-nil/first", func() (any, any) { v := 42; return (*int)(nil), &v }, oneNil, true}, + {"one-nil/second", func() (any, any) { v := 42; return &v, (*int)(nil) }, oneNil, true}, + + // Same identity - both point to same address + {"same-identity/int", func() (any, any) { v := 42; return &v, &v }, sameIdentity, false}, + {"same-identity/string", func() (any, any) { s := hello; return &s, &s }, sameIdentity, false}, + {"same-identity/float64", func() (any, any) { f := 3.14; return &f, &f }, sameIdentity, false}, + + // Different identity - point to different adresses + {"different-identity/equal-values", func() (any, any) { v1, v2 := 42, 42; return &v1, &v2 }, differentIdentity, false}, + {"different-identity/different-values", func() (any, any) { v1, v2 := 42, 43; return &v1, &v2 }, differentIdentity, false}, + + // Different types (reflection-only) + {"different-types/int-string", func() (any, any) { + i, s := 1, hello + return &i, &s + }, differentTypes, true}, + + // Edge cases (always false) + {"not-pointer/right", func() (any, any) { v1 := 12; return &v1, 1 }, notPointer, true}, + {"not-pointer/left", func() (any, any) { v1 := 12; return 1, &v1 }, notPointer, true}, + {"not-pointer/both", func() (any, any) { return 1, 1 }, notPointer, true}, + }) +} + +func testPointerAssertion( + tc pointerPairTestCase, + kind pointerAssertionKind, + pointerAssertion func(T, any, any, ...any) bool, + expected, actual any, +) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := pointerAssertion(mock, expected, actual) + shouldPass := expectedStatusForPointerAssertion(kind, tc.relationship) + shouldPassOrFail(t, mock, result, shouldPass) + } +} + +type pointerAssertionKind int + +const ( + sameKind pointerAssertionKind = iota + sameTKind + notSameKind + notSameTKind +) + +type pairRelationship int + +const ( + bothNil pairRelationship = iota + oneNil + sameIdentity + differentIdentity + differentTypes + notPointer +) + +func expectedStatusForPointerAssertion(kind pointerAssertionKind, relationship pairRelationship) bool { + positive := kind == sameKind || kind == sameTKind + + switch relationship { + case notPointer: + return false + case sameIdentity, bothNil: + // Two nil pointers of the same type are considered "same" in Go + return positive + case oneNil, differentIdentity, differentTypes: + return !positive + default: + panic(fmt.Errorf("test case configuration error: invalid pairRelationship: %d", relationship)) + } +} + +func testPointerAssertionT(tc pointerPairTestCase, kind pointerAssertionKind, expected, actual any) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + stop := func(expected string, actual any) { + t.Fatalf("test case error: expected=%s, actual=%T", expected, actual) + } + + // Type switch with safety check + // + // Add more supported types to the switch with new test cases + var result bool + switch exp := expected.(type) { + case *int: + act, ok := actual.(*int) + if !ok { + stop("*int", actual) + } + result = testPointerGenericAssertion(mock, kind, exp, act) + case *string: + act, ok := actual.(*string) + if !ok { + stop("*string", actual) + } + result = testPointerGenericAssertion(mock, kind, exp, act) + case *float64: + act, ok := actual.(*float64) + if !ok { + stop("*float64", actual) + } + result = testPointerGenericAssertion(mock, kind, exp, act) + default: + t.Fatalf("unsupported type: %T", expected) + } + + shouldPass := expectedStatusForPointerAssertion(kind, tc.relationship) + shouldPassOrFail(t, mock, result, shouldPass) + } +} + +func testPointerGenericAssertion[P any](mock T, kind pointerAssertionKind, expected, actual *P) bool { + switch kind { + case sameTKind: + return SameT(mock, expected, actual) + case notSameTKind: + return NotSameT(mock, expected, actual) + default: + panic(fmt.Errorf("test case configuration error: invalid pointerAssertionKind: %d", kind)) + } +} diff --git a/internal/assertions/equal_test.go b/internal/assertions/equal_test.go index a1b0b8261..abba4b8dc 100644 --- a/internal/assertions/equal_test.go +++ b/internal/assertions/equal_test.go @@ -4,400 +4,20 @@ package assertions import ( - "errors" "fmt" "iter" - "os" - "reflect" "regexp" "slices" + "strings" "testing" - "time" ) -const shortpkg = "assertions" - -// TODO: apply table-driven pattern. -func TestEqualNotNil(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - if !NotNil(mock, new(AssertionTesterConformingObject)) { - t.Error("NotNil should return true: object is not nil") - } - - if NotNil(mock, nil) { - t.Error("NotNil should return false: object is nil") - } - - if NotNil(mock, (*struct{})(nil)) { - t.Error("NotNil should return false: object is (*struct{})(nil)") - } -} - -// TODO: apply table-driven pattern, factorize with Nil tests. -func TestEqualNil(t *testing.T) { - t.Parallel() - - mock := new(testing.T) - - if !Nil(mock, nil) { - t.Error("Nil should return true: object is nil") - } - - if !Nil(mock, (*struct{})(nil)) { - t.Error("Nil should return true: object is (*struct{})(nil)") - } - - if Nil(mock, new(AssertionTesterConformingObject)) { - t.Error("Nil should return false: object is not nil") - } -} - func TestEqualErrorMessages(t *testing.T) { t.Parallel() - t.Run("same, with slice too long to print", func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - longSlice := make([]int, 1_000_000) - Same(mock, &[]int{}, &longSlice) - Contains(t, mock.errorString(), `&[]int{0, 0, 0,`) - }) - - t.Run("not same, with slice too long to print", func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - - longSlice := make([]int, 1_000_000) - NotSame(mock, &longSlice, &longSlice) - Contains(t, mock.errorString(), `&[]int{0, 0, 0,`) - }) - - t.Run("not equal, with slice too long to print", func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - longSlice := make([]int, 1_000_000) - NotEqual(mock, longSlice, longSlice) - Contains(t, mock.errorString(), ` - Error Trace: - Error: Should not be: []int{0, 0, 0,`) - Contains(t, mock.errorString(), `<... truncated>`) - }) - - t.Run("not equal values, with slice too long to print", func(t *testing.T) { - t.Parallel() - mock := new(mockT) - - longSlice := make([]int, 1_000_000) - NotEqualValues(mock, longSlice, longSlice) - Contains(t, mock.errorString(), ` - Error Trace: - Error: Should not be: []int{0, 0, 0,`) - Contains(t, mock.errorString(), `<... truncated>`) - }) -} - -func TestEqual(t *testing.T) { - t.Parallel() - - for c := range equalCases() { - t.Run(fmt.Sprintf("Equal(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - res := Equal(mock, c.expected, c.actual) - if res != c.result { - t.Errorf("Equal(%#v, %#v) should return %#v: %s", c.expected, c.actual, c.result, c.remark) - } - }) - } -} - -func TestEqualSame(t *testing.T) { - t.Parallel() - mock := new(mockT) - - if Same(mock, ptr(1), ptr(1)) { - t.Error("Same should return false") - } - - if Same(mock, 1, 1) { - t.Error("Same should return false") - } - - p := ptr(2) - if Same(mock, p, *p) { - t.Error("Same should return false") - } - - if !Same(mock, p, p) { - t.Error("Same should return true") - } - - t.Run("same object, different type", func(t *testing.T) { - type s struct { - i int - } - type sPtr *s - ps := &s{1} - dps := sPtr(ps) - if Same(mock, dps, ps) { - t.Error("Same should return false") - } - expPat := fmt.Sprintf(`expected: &%[1]s.s\{i:1\} \(%[1]s.sPtr\)\((0x[a-f0-9]+)\)\s*\n`, shortpkg) + - fmt.Sprintf(`\s+actual : &%[1]s.s\{i:1\} \(\*%[1]s.s\)\((0x[a-f0-9]+)\)`, shortpkg) - Regexp(t, regexp.MustCompile(expPat), mock.errorString()) - }) -} - -func TestEqualNotSame(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - if !NotSame(mock, ptr(1), ptr(1)) { - t.Error("NotSame should return true; different pointers") - } - - if !NotSame(mock, 1, 1) { - t.Error("NotSame should return true; constant inputs") - } - - p := ptr(2) - if !NotSame(mock, p, *p) { - t.Error("NotSame should return true; mixed-type inputs") - } - - if NotSame(mock, p, p) { - t.Error("NotSame should return false") - } -} - -func TestEqualNotEqual(t *testing.T) { - t.Parallel() - - for c := range equalNotEqualCases() { - t.Run(fmt.Sprintf("NotEqual(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - res := NotEqual(mock, c.expected, c.actual) - - if res != c.result { - t.Errorf("NotEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result) - } - }) - } -} - -func TestEqualValuesAndNotEqualValues(t *testing.T) { - t.Parallel() - - for c := range equalValuesCases() { - mock := new(testing.T) - - // Test NotEqualValues - t.Run(fmt.Sprintf("NotEqualValues(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { - res := NotEqualValues(mock, c.expected, c.actual) - - if res != c.notEqualResult { - t.Errorf("NotEqualValues(%#v, %#v) should return %#v", c.expected, c.actual, c.notEqualResult) - } - }) - - // Test EqualValues (inverse of NotEqualValues) - t.Run(fmt.Sprintf("EqualValues(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { - expectedEqualResult := !c.notEqualResult // EqualValues should return opposite of NotEqualValues - res := EqualValues(mock, c.expected, c.actual) - - if res != expectedEqualResult { - t.Errorf("EqualValues(%#v, %#v) should return %#v", c.expected, c.actual, expectedEqualResult) - } - }) - } -} - -func TestEqualEmpty(t *testing.T) { - t.Parallel() - - // Proposal for enhancement: redundant test context declaration - chWithValue := make(chan struct{}, 1) - chWithValue <- struct{}{} - var tiP *time.Time - var tiNP time.Time - var s *string - var f *os.File - sP := &s - x := 1 - xP := &x - - type TString string - type TStruct struct { - x int - } - - t.Run("should be empty", func(t *testing.T) { - mock := new(testing.T) - - True(t, Empty(mock, ""), "Empty string is empty") - True(t, Empty(mock, nil), "Nil is empty") - True(t, Empty(mock, []string{}), "Empty string array is empty") - True(t, Empty(mock, 0), "Zero int value is empty") - True(t, Empty(mock, false), "False value is empty") - True(t, Empty(mock, make(chan struct{})), "Channel without values is empty") - True(t, Empty(mock, s), "Nil string pointer is empty") - True(t, Empty(mock, f), "Nil os.File pointer is empty") - True(t, Empty(mock, tiP), "Nil time.Time pointer is empty") - True(t, Empty(mock, tiNP), "time.Time is empty") - True(t, Empty(mock, TStruct{}), "struct with zero values is empty") - True(t, Empty(mock, TString("")), "empty aliased string is empty") - True(t, Empty(mock, sP), "ptr to nil value is empty") - True(t, Empty(mock, [1]int{}), "array is state") - }) - - t.Run("should not be empty", func(t *testing.T) { - mock := new(testing.T) - - False(t, Empty(mock, "something"), "Non Empty string is not empty") - False(t, Empty(mock, errors.New("something")), "Non nil object is not empty") - False(t, Empty(mock, []string{"something"}), "Non empty string array is not empty") - False(t, Empty(mock, 1), "Non-zero int value is not empty") - False(t, Empty(mock, true), "True value is not empty") - False(t, Empty(mock, chWithValue), "Channel with values is not empty") - False(t, Empty(mock, TStruct{x: 1}), "struct with initialized values is empty") - False(t, Empty(mock, TString("abc")), "non-empty aliased string is empty") - False(t, Empty(mock, xP), "ptr to non-nil value is not empty") - False(t, Empty(mock, [1]int{42}), "array is not state") - }) - - // error messages validation - for tt := range equalEmptyCases() { - t.Run(tt.name, func(t *testing.T) { - mock := new(captureT) - - res := Empty(mock, tt.value) - mock.checkResultAndErrMsg(t, res, tt.expectedResult, tt.expectedErrMsg) - }) - } -} - -func TestEqualNotEmpty(t *testing.T) { - t.Parallel() - - t.Run("should not be empty", func(t *testing.T) { - mock := new(testing.T) - - False(t, NotEmpty(mock, ""), "Empty string is empty") - False(t, NotEmpty(mock, nil), "Nil is empty") - False(t, NotEmpty(mock, []string{}), "Empty string array is empty") - False(t, NotEmpty(mock, 0), "Zero int value is empty") - False(t, NotEmpty(mock, false), "False value is empty") - False(t, NotEmpty(mock, make(chan struct{})), "Channel without values is empty") - False(t, NotEmpty(mock, [1]int{}), "array is state") - }) - - t.Run("should be empty", func(t *testing.T) { - mock := new(testing.T) - - chWithValue := make(chan struct{}, 1) - chWithValue <- struct{}{} - - False(t, NotEmpty(mock, ""), "Empty string is empty") - True(t, NotEmpty(mock, "something"), "Non Empty string is not empty") - True(t, NotEmpty(mock, errors.New("something")), "Non nil object is not empty") - True(t, NotEmpty(mock, []string{"something"}), "Non empty string array is not empty") - True(t, NotEmpty(mock, 1), "Non-zero int value is not empty") - True(t, NotEmpty(mock, true), "True value is not empty") - True(t, NotEmpty(mock, chWithValue), "Channel with values is not empty") - True(t, NotEmpty(mock, [1]int{42}), "array is not state") - }) - - // error messages validation - for tt := range equalNotEmptyCases() { - t.Run(tt.name, func(t *testing.T) { - mock := new(captureT) - - res := NotEmpty(mock, tt.value) - mock.checkResultAndErrMsg(t, tt.expectedResult, res, tt.expectedErrMsg) - }) - } -} - -func TestEqualExactly(t *testing.T) { - t.Parallel() - - for c := range equalExactlyCases() { - t.Run(fmt.Sprintf("Exactly(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { - t.Parallel() - mock := new(testing.T) - - res := Exactly(mock, c.expected, c.actual) - if res != c.result { - t.Errorf("Exactly(%#v, %#v) should return %#v", c.expected, c.actual, c.result) - } - }) - } -} - -func TestEqualBytes(t *testing.T) { - t.Parallel() - - i := 0 - for c := range equalBytesCases() { - Equal(t, reflect.DeepEqual(c.a, c.b), ObjectsAreEqual(c.a, c.b), "case %d failed", i) - i++ - } -} - -func TestEqualValuePanics(t *testing.T) { - t.Parallel() - - for tt := range panicCases() { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - mock := new(mockT) - NotPanics(t, func() { - Equal(mock, tt.value1, tt.value2) - }, "should not panic") - - if !tt.expectEqual { - True(t, mock.Failed(), "should have failed") - Contains(t, mock.errorString(), "Not equal:", "error message should mention inequality") - - return - } - - False(t, mock.Failed(), "should have been successful") - Empty(t, mock.errorString()) - }) - } -} - -func TestEqualT(t *testing.T) { - t.Parallel() - - for tc := range equalTCases() { - t.Run(tc.name, testAllEqualT(tc)) - } -} - -func TestEqualNotEqualT(t *testing.T) { - t.Parallel() - - for tc := range equalTCases() { - t.Run(tc.name, testAllNotEqualT(tc)) - } -} - -func TestEqualStringErrorMessage(t *testing.T) { - // checking error messsages on Equal with a regexp. The object of the test is Equal, not Regexp - t.Parallel() - - t.Run("error message should match Regexp", func(t *testing.T) { + t.Run("should render when slice too long to print", testTooLongToPrint()) + t.Run("error message should match expression", func(t *testing.T) { + // checking error messsages on Equal with a regexp. The object of the test is Equal, not Regexp for tc := range stringEqualFormattingCases() { t.Run(tc.name, func(t *testing.T) { mock := &bufferT{} @@ -408,6 +28,7 @@ func TestEqualStringErrorMessage(t *testing.T) { return } + rex := regexp.MustCompile(tc.want) match := rex.MatchString(mock.buf.String()) if !match { @@ -418,1002 +39,890 @@ func TestEqualStringErrorMessage(t *testing.T) { }) } -type equalStringCase struct { - name string - equalWant string - equalGot string - msgAndArgs []any - want string -} - -func stringEqualFormattingCases() iter.Seq[equalStringCase] { - return slices.Values([]equalStringCase{ - { - name: "multiline diff message", - equalWant: "hi, \nmy name is", - equalGot: "what,\nmy name is", - want: "\t[a-z]+.go:\\d+: \n" + // NOTE: the exact file name reported should be asserted in integration tests - "\t+Error Trace:\t\n+" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"hi, \\\\nmy name is\"\n" + - "\\s+actual\\s+: " + "\"what,\\\\nmy name is\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n\\s+\\++ " + - "Actual\n" + - "\\s+@@ -1,2 \\+1,2 @@\n" + - "\\s+-hi, \n\\s+\\+what,\n" + - "\\s+my name is", - }, - { - name: "single line diff message", - equalWant: "want", - equalGot: "got", - want: "\t[a-z]+.go:\\d+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ " + - "Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n", - }, - { - name: "diff message with args", - equalWant: "want", - equalGot: "got", - msgAndArgs: []any{"hello, %v!", "world"}, - want: "\t[a-z]+.go:[0-9]+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n" + - "\\s+\\++ Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n" + - "\\s+Messages:\\s+hello, world!\n", - }, - { - name: "diff message with integer arg", - equalWant: "want", - equalGot: "got", - msgAndArgs: []any{123}, - want: "\t[a-z]+.go:[0-9]+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n" + - "\\s+\\++ Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n" + - "\\s+Messages:\\s+123\n", - }, - { - name: "diff message with struct arg", - equalWant: "want", - equalGot: "got", - msgAndArgs: []any{struct{ a string }{"hello"}}, - want: "\t[a-z]+.go:[0-9]+: \n" + - "\t+Error Trace:\t\n" + - "\t+Error:\\s+Not equal:\\s+\n" + - "\\s+expected: \"want\"\n" + - "\\s+actual\\s+: \"got\"\n" + - "\\s+Diff:\n" + - "\\s+-+ Expected\n" + - "\\s+\\++ Actual\n" + - "\\s+@@ -1 \\+1 @@\n" + - "\\s+-want\n" + - "\\s+\\+got\n" + - "\\s+Messages:\\s+{a:hello}\n", - }, - }) -} - -type panicCase struct { - name string - value1 any - value2 any - expectEqual bool -} - -func panicCases() iter.Seq[panicCase] { - type structWithUnexportedMapWithArrayKey struct { - m any - } - type s struct { - f map[[1]byte]int - } - - return slices.Values([]panicCase{ - { - // from issue https://github.com/stretchr/testify/pull/1816 - name: "panic behavior on struct with array key and unexported field (some keys vs none)", - value1: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {1}: nil, - {2}: nil, - }, - }, - value2: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{}, - }, - expectEqual: false, - }, - { - name: "panic behavior on struct with array key and unexported field (same keys)", - value1: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {1}: nil, - {2}: nil, - }, - }, - value2: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {2}: nil, - {1}: nil, - }, - }, - expectEqual: true, - }, - { - name: "panic behavior on struct with array key and unexported field (non-nil values)", - value1: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {1}: {}, - {2}: nil, - }, - }, - value2: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {1}: {}, - {2}: nil, - }, - }, - expectEqual: true, - }, - { - name: "panic behavior on struct with array key and unexported field (different, non-nil values)", - value1: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {1}: {}, - {2}: nil, - }, - }, - value2: structWithUnexportedMapWithArrayKey{ - map[[1]byte]*struct{}{ - {1}: nil, - {2}: {}, - }, - }, - expectEqual: false, - }, - { - name: "panic behavior on map with array key", - value1: s{ - f: map[[1]byte]int{ - {0x1}: 0, - {0x2}: 0, - }, - }, - value2: s{}, - expectEqual: false, - }, - }) -} - -type equalCase struct { - expected any - actual any - result bool - remark string -} - -func equalCases() iter.Seq[equalCase] { - type myType string - var m map[string]any - return slices.Values([]equalCase{ - {"Hello World", "Hello World", true, ""}, - {123, 123, true, ""}, - {123.5, 123.5, true, ""}, - {[]byte("Hello World"), []byte("Hello World"), true, ""}, - {nil, nil, true, ""}, - {int32(123), int32(123), true, ""}, - {uint64(123), uint64(123), true, ""}, - {myType("1"), myType("1"), true, ""}, - {&struct{}{}, &struct{}{}, true, "pointer equality is based on equality of underlying value"}, - - // Not expected to be equal - {m["bar"], "something", false, ""}, - {myType("1"), myType("2"), false, ""}, - - // A case that might be confusing, especially with numeric literals - {10, uint(10), false, ""}, - {int(1), uint(1), false, ""}, - }) -} - -type samePointersCase struct { - name string - args args - same BoolAssertionFunc - ok BoolAssertionFunc -} - -type args struct { - first any - second any -} - -func ptr(i int) *int { - return &i -} - -func equalSamePointersCases() iter.Seq[samePointersCase] { - p := ptr(2) - return slices.Values([]samePointersCase{ - { - name: "1 != 2", - args: args{first: 1, second: 2}, - same: False, - ok: False, - }, - { - name: "1 != 1 (not same ptr)", - args: args{first: 1, second: 1}, - same: False, - ok: False, - }, - { - name: "ptr(1) == ptr(1)", - args: args{first: p, second: p}, - same: True, - ok: True, - }, - { - name: "int(1) != float32(1)", - args: args{first: int(1), second: float32(1)}, - same: False, - ok: False, - }, - { - name: "array != slice", - args: args{first: [2]int{1, 2}, second: []int{1, 2}}, - same: False, - ok: False, - }, - { - name: "non-pointer vs pointer (1 != ptr(2))", - args: args{first: 1, second: p}, - same: False, - ok: False, - }, - { - name: "pointer vs non-pointer (ptr(2) != 1)", - args: args{first: p, second: 1}, - same: False, - ok: False, - }, - }) -} - -type equalNotEqualCase struct { - expected any - actual any - result bool -} - -func equalNotEqualCases() iter.Seq[equalNotEqualCase] { - return slices.Values([]equalNotEqualCase{ - // cases that are expected not to match - {"Hello World", "Hello World!", true}, - {123, 1234, true}, - {123.5, 123.55, true}, - {[]byte("Hello World"), []byte("Hello World!"), true}, - {nil, new(AssertionTesterConformingObject), true}, - - // cases that are expected to match - {nil, nil, false}, - {"Hello World", "Hello World", false}, - {123, 123, false}, - {123.5, 123.5, false}, - {[]byte("Hello World"), []byte("Hello World"), false}, - {new(AssertionTesterConformingObject), new(AssertionTesterConformingObject), false}, - {&struct{}{}, &struct{}{}, false}, - {func() int { return 23 }, func() int { return 24 }, false}, - // A case that might be confusing, especially with numeric literals - {int(10), uint(10), true}, - }) -} - -type equalValuesCase struct { - expected any - actual any - notEqualResult bool // result for NotEqualValues -} - -func equalValuesCases() iter.Seq[equalValuesCase] { - return slices.Values([]equalValuesCase{ - // cases that are expected not to match - {"Hello World", "Hello World!", true}, - {123, 1234, true}, - {123.5, 123.55, true}, - {[]byte("Hello World"), []byte("Hello World!"), true}, - {nil, new(AssertionTesterConformingObject), true}, +// Test NotEqualValues. +func TestEqualValuesAndNotEqualValues(t *testing.T) { + t.Parallel() - // cases that are expected to match - {nil, nil, false}, - {"Hello World", "Hello World", false}, - {123, 123, false}, - {123.5, 123.5, false}, - {[]byte("Hello World"), []byte("Hello World"), false}, - {new(AssertionTesterConformingObject), new(AssertionTesterConformingObject), false}, - {&struct{}{}, &struct{}{}, false}, - - // Different behavior from NotEqual() - {func() int { return 23 }, func() int { return 24 }, true}, - {int(10), int(11), true}, - {int(10), uint(10), false}, - - {struct{}{}, struct{}{}, false}, - }) -} + for tc := range equalValuesCases() { + mock := new(testing.T) -type equalEmptyCase struct { - name string - value any - expectedResult bool - expectedErrMsg string -} + t.Run(tc.name, func(t *testing.T) { + res := NotEqualValues(mock, tc.expected, tc.actual) + if res != tc.notEqualValue { + t.Errorf("NotEqualValues(%#v, %#v) should return %t", tc.expected, tc.actual, tc.notEqualValue) + } + }) -func equalEmptyCases() iter.Seq[equalEmptyCase] { - chWithValue := make(chan struct{}, 1) - chWithValue <- struct{}{} - // var tiP *time.Time - // var tiNP time.Time - // var s *string - // var f *os.File - // sP := &s - x := 1 - xP := &x - - type TString string - type TStruct struct { - x int + // Test EqualValues (inverse of NotEqualValues) + t.Run(tc.name, func(t *testing.T) { + res := EqualValues(mock, tc.expected, tc.actual) + if res != tc.equalValue { + t.Errorf("EqualValues(%#v, %#v) should return %t", tc.expected, tc.actual, tc.equalValue) + } + }) } - - return slices.Values([]equalEmptyCase{ - { - name: "Non Empty string is not empty", - value: "something", - expectedResult: false, - expectedErrMsg: "Should be empty, but was something\n", - }, - { - name: "Non nil object is not empty", - value: errors.New("something"), - expectedResult: false, - expectedErrMsg: "Should be empty, but was something\n", - }, - { - name: "Non empty string array is not empty", - value: []string{"something"}, - expectedResult: false, - expectedErrMsg: "Should be empty, but was [something]\n", - }, - { - name: "Non-zero int value is not empty", - value: 1, - expectedResult: false, - expectedErrMsg: "Should be empty, but was 1\n", - }, - { - name: "True value is not empty", - value: true, - expectedResult: false, - expectedErrMsg: "Should be empty, but was true\n", - }, - { - name: "Channel with values is not empty", - value: chWithValue, - expectedResult: false, - expectedErrMsg: fmt.Sprintf("Should be empty, but was %v\n", chWithValue), - }, - { - name: "struct with initialized values is empty", - value: TStruct{x: 1}, - expectedResult: false, - expectedErrMsg: "Should be empty, but was {1}\n", - }, - { - name: "non-empty aliased string is empty", - value: TString("abc"), - expectedResult: false, - expectedErrMsg: "Should be empty, but was abc\n", - }, - { - name: "ptr to non-nil value is not empty", - value: xP, - expectedResult: false, - expectedErrMsg: fmt.Sprintf("Should be empty, but was %p\n", xP), - }, - { - name: "array is not state", - value: [1]int{42}, - expectedResult: false, - expectedErrMsg: "Should be empty, but was [42]\n", - }, - - // Here are some edge cases - { - name: "string with only spaces is not empty", - value: " ", - expectedResult: false, - expectedErrMsg: "Should be empty, but was \n", // Proposal for enhancement: FIX THIS strange error message - }, - { - name: "string with a line feed is not empty", - value: "\n", - expectedResult: false, - // Proposal for enhancement: This is the exact same error message as for an empty string - expectedErrMsg: "Should be empty, but was \n", // Proposal for enhancement: FIX THIS strange error message - }, - { - name: "string with only tabulation and lines feed is not empty", - value: "\n\t\n", - expectedResult: false, - // Proposal for enhancement: The line feeds and tab are not helping to spot what is expected - expectedErrMsg: "" + // this syntax is used to show how errors are reported. - "Should be empty, but was \n" + - "\t\n", - }, - { - name: "string with trailing lines feed is not empty", - value: "foo\n\n", - expectedResult: false, - // Proposal for enhancement: it's not clear if one or two lines feed are expected - expectedErrMsg: "Should be empty, but was foo\n\n", - }, - { - name: "string with leading and trailing tabulation and lines feed is not empty", - value: "\n\nfoo\t\n\t\n", - expectedResult: false, - // Proposal for enhancement: The line feeds and tab are not helping to figure what is expected - expectedErrMsg: "" + - "Should be empty, but was \n" + - "\n" + - "foo\t\n" + - "\t\n", - }, - { - name: "non-printable character is not empty", - value: "\u00a0", // NO-BREAK SPACE UNICODE CHARACTER - expectedResult: false, - // Proposal for enhancement: here you cannot figure out what is expected - expectedErrMsg: "Should be empty, but was \u00a0\n", - }, - // Here we are testing there is no error message on success - { - name: "Empty string is empty", - value: "", - expectedResult: true, - expectedErrMsg: "", - }, - }) -} - -type equalNotEmptyCase struct { - name string - value any - expectedResult bool - expectedErrMsg string -} - -func equalNotEmptyCases() iter.Seq[equalNotEmptyCase] { - return slices.Values([]equalNotEmptyCase{ - { - name: "Empty string is empty", - value: "", - expectedResult: false, - expectedErrMsg: `Should NOT be empty, but was ` + "\n", // Proposal for enhancement: FIX THIS strange error message - }, - { - name: "Nil is empty", - value: nil, - expectedResult: false, - expectedErrMsg: "Should NOT be empty, but was \n", - }, - { - name: "Empty string array is empty", - value: []string{}, - expectedResult: false, - expectedErrMsg: "Should NOT be empty, but was []\n", - }, - { - name: "Zero int value is empty", - value: 0, - expectedResult: false, - expectedErrMsg: "Should NOT be empty, but was 0\n", - }, - { - name: "False value is empty", - value: false, - expectedResult: false, - expectedErrMsg: "Should NOT be empty, but was false\n", - }, - { - name: "array is state", - value: [1]int{}, - expectedResult: false, - expectedErrMsg: "Should NOT be empty, but was [0]\n", - }, - - // Here we are testing there is no error message on success - { - name: "Non Empty string is not empty", - value: "something", - expectedResult: true, - expectedErrMsg: "", - }, - }) -} - -type diffTestingStruct struct { - A string - B int -} - -func (d *diffTestingStruct) String() string { - return d.A -} - -type equalExactlyCase struct { - expected any - actual any - result bool } -func equalExactlyCases() iter.Seq[equalExactlyCase] { - a := float32(1) - b := float64(1) - c := float32(1) - d := float32(2) - - return slices.Values([]equalExactlyCase{ - {a, b, false}, - {a, d, false}, - {a, c, true}, - {nil, a, false}, - {a, nil, false}, - }) -} - -type equalBytesCase struct { - a, b []byte -} - -func equalBytesCases() iter.Seq[equalBytesCase] { - return slices.Values([]equalBytesCase{ - {make([]byte, 2), make([]byte, 2)}, - {make([]byte, 2), make([]byte, 2, 3)}, - {nil, make([]byte, 0)}, - }) -} - -// Generic Equal function tests +// Test EqualExportedValues. +func TestEqualExportedValues(t *testing.T) { + t.Parallel() -type equalTTestCase struct { - name string - expected any - actual any - shouldPass bool -} + for tc := range objectEqualExportedValuesCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() -func equalTCases() iter.Seq[equalTTestCase] { - return slices.Values([]equalTTestCase{ - // Success cases - equal values - {name: "int/equal", expected: 42, actual: 42, shouldPass: true}, - {name: "int8/equal", expected: int8(10), actual: int8(10), shouldPass: true}, - {name: "int16/equal", expected: int16(100), actual: int16(100), shouldPass: true}, - {name: "int32/equal", expected: int32(1000), actual: int32(1000), shouldPass: true}, - {name: "int64/equal", expected: int64(10000), actual: int64(10000), shouldPass: true}, - {name: "uint/equal", expected: uint(42), actual: uint(42), shouldPass: true}, - {name: "uint8/equal", expected: uint8(10), actual: uint8(10), shouldPass: true}, - {name: "uint16/equal", expected: uint16(100), actual: uint16(100), shouldPass: true}, - {name: "uint32/equal", expected: uint32(1000), actual: uint32(1000), shouldPass: true}, - {name: "uint64/equal", expected: uint64(10000), actual: uint64(10000), shouldPass: true}, - {name: "string/equal", expected: "hello", actual: "hello", shouldPass: true}, - {name: "float32/equal", expected: float32(3.14), actual: float32(3.14), shouldPass: true}, - {name: "float64/equal", expected: 3.14, actual: 3.14, shouldPass: true}, - {name: "bool/true", expected: true, actual: true, shouldPass: true}, - {name: "bool/false", expected: false, actual: false, shouldPass: true}, - - // Failure cases - not equal - {name: "int/not-equal", expected: 42, actual: 43, shouldPass: false}, - {name: "string/not-equal", expected: "hello", actual: "world", shouldPass: false}, - {name: "bool/not-equal", expected: true, actual: false, shouldPass: false}, - {name: "float64/not-equal", expected: 3.14, actual: 2.71, shouldPass: false}, - }) -} + mockT := new(mockT) -func testAllEqualT(tc equalTTestCase) func(*testing.T) { - return func(t *testing.T) { - t.Parallel() - - // Type dispatch - switch expected := tc.expected.(type) { - case int: - actual, ok := tc.actual.(int) - if !ok { - t.Fatalf("invalid test case: requires int but got %T", tc.actual) + actual := EqualExportedValues(mockT, tc.expected, tc.actual) + if actual != tc.expectedEqual { + t.Errorf("Expected EqualExportedValues to be %t, but was %t", tc.expectedEqual, actual) } - testEqualT(EqualT[int], expected, actual, tc.shouldPass)(t) - case int8: - actual, ok := tc.actual.(int8) - if !ok { - t.Fatalf("invalid test case: requires int8 but got %T", tc.actual) - } - testEqualT(EqualT[int8], expected, actual, tc.shouldPass)(t) - case int16: - actual, ok := tc.actual.(int16) - if !ok { - t.Fatalf("invalid test case: requires int16 but got %T", tc.actual) - } - testEqualT(EqualT[int16], expected, actual, tc.shouldPass)(t) - case int32: - actual, ok := tc.actual.(int32) - if !ok { - t.Fatalf("invalid test case: requires int32 but got %T", tc.actual) - } - testEqualT(EqualT[int32], expected, actual, tc.shouldPass)(t) - case int64: - actual, ok := tc.actual.(int64) - if !ok { - t.Fatalf("invalid test case: requires int64 but got %T", tc.actual) - } - testEqualT(EqualT[int64], expected, actual, tc.shouldPass)(t) - case uint: - actual, ok := tc.actual.(uint) - if !ok { - t.Fatalf("invalid test case: requires uint but got %T", tc.actual) - } - testEqualT(EqualT[uint], expected, actual, tc.shouldPass)(t) - case uint8: - actual, ok := tc.actual.(uint8) - if !ok { - t.Fatalf("invalid test case: requires uint8 but got %T", tc.actual) - } - testEqualT(EqualT[uint8], expected, actual, tc.shouldPass)(t) - case uint16: - actual, ok := tc.actual.(uint16) - if !ok { - t.Fatalf("invalid test case: requires uint16 but got %T", tc.actual) - } - testEqualT(EqualT[uint16], expected, actual, tc.shouldPass)(t) - case uint32: - actual, ok := tc.actual.(uint32) - if !ok { - t.Fatalf("invalid test case: requires uint32 but got %T", tc.actual) - } - testEqualT(EqualT[uint32], expected, actual, tc.shouldPass)(t) - case uint64: - actual, ok := tc.actual.(uint64) - if !ok { - t.Fatalf("invalid test case: requires uint64 but got %T", tc.actual) - } - testEqualT(EqualT[uint64], expected, actual, tc.shouldPass)(t) - case string: - actual, ok := tc.actual.(string) - if !ok { - t.Fatalf("invalid test case: requires string but got %T", tc.actual) - } - testEqualT(EqualT[string], expected, actual, tc.shouldPass)(t) - case float32: - actual, ok := tc.actual.(float32) - if !ok { - t.Fatalf("invalid test case: requires float32 but got %T", tc.actual) + + if tc.expectedFailMsg == "" { + // skip error message check + return } - testEqualT(EqualT[float32], expected, actual, tc.shouldPass)(t) - case float64: - actual, ok := tc.actual.(float64) - if !ok { - t.Fatalf("invalid test case: requires float64 but got %T", tc.actual) + + actualFail := mockT.errorString() + if !strings.Contains(actualFail, tc.expectedFailMsg) { + t.Errorf("Contains failure should include %q but was %q", tc.expectedFailMsg, actualFail) } - testEqualT(EqualT[float64], expected, actual, tc.shouldPass)(t) - case bool: - actual, ok := tc.actual.(bool) - if !ok { - t.Fatalf("invalid test case: requires bool but got %T", tc.actual) + }) + } +} + +// Deep equality tests (Equal, EqualT, NotEqual, NotEqualT, Exactly). +func TestEqualDeepEqual(t *testing.T) { + t.Parallel() + + for tc := range unifiedEqualityCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + expected, actual := tc.makeValues() + + if !tc.reflectionOnly { + t.Run("with Equal", testEqualityAssertion(tc, equalKind, Equal, expected, actual)) + t.Run("with NotEqual", testEqualityAssertion(tc, notEqualKind, NotEqual, expected, actual)) + t.Run("with Exactly", testEqualityAssertion(tc, exactlyKind, Exactly, expected, actual)) + + // Generic variants - type dispatch + t.Run("with EqualT", testEqualityAssertionT(tc, equalTKind, expected, actual)) + t.Run("with NotEqualT", testEqualityAssertionT(tc, notEqualTKind, expected, actual)) + } else { + // Reflection-only cases + t.Run("with Equal (reflection)", testEqualityAssertion(tc, equalKind, Equal, expected, actual)) + t.Run("with NotEqual (reflection)", testEqualityAssertion(tc, notEqualKind, NotEqual, expected, actual)) + t.Run("with Exactly (reflection)", testEqualityAssertion(tc, exactlyKind, Exactly, expected, actual)) } - testEqualT(EqualT[bool], expected, actual, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", expected) - } + }) + } +} + +type equalityRelationship int + +const ( + eqBothNil equalityRelationship = iota + eqOneNil + eqSameIdentity + eqEqualValueComparable + eqEqualValueNonComparable + eqDifferentValueSameType + eqDifferentType + eqFunction +) + +func testEqualityAssertion(tc equalityTestCase, kind equalityAssertionKind, equalityAssertion func(T, any, any, ...any) bool, expected, actual any) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := equalityAssertion(mock, expected, actual) + shouldPass := expectedStatusForEqualityAssertion(kind, tc.relationship) + shouldPassOrFail(t, mock, result, shouldPass) } } -func testAllNotEqualT(tc equalTTestCase) func(*testing.T) { +//nolint:gocognit,gocyclo,cyclop // no other way here than a big type switch +func testEqualityAssertionT(tc equalityTestCase, kind equalityAssertionKind, expected, actual any) func(*testing.T) { return func(t *testing.T) { t.Parallel() - // Invert shouldPass for NotEqual - shouldPass := !tc.shouldPass + mock := new(mockT) + stop := func(expected, actual any) { + t.Fatalf("test case error: expected=%s, actual=%T", expected, actual) + } - // Type dispatch - switch expected := tc.expected.(type) { + // Type switch with safety check + // + // Add more (comparable) types when new test cases require it. + var result bool + switch exp := expected.(type) { case int: - actual, ok := tc.actual.(int) + act, ok := actual.(int) if !ok { - t.Fatalf("invalid test case: requires int but got %T", tc.actual) + stop("int", actual) } - testEqualT(NotEqualT[int], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case int8: - actual, ok := tc.actual.(int8) + act, ok := actual.(int8) if !ok { - t.Fatalf("invalid test case: requires int8 but got %T", tc.actual) + stop("int8", actual) } - testEqualT(NotEqualT[int8], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case int16: - actual, ok := tc.actual.(int16) + act, ok := actual.(int16) if !ok { - t.Fatalf("invalid test case: requires int16 but got %T", tc.actual) + stop("int16", actual) } - testEqualT(NotEqualT[int16], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case int32: - actual, ok := tc.actual.(int32) + act, ok := actual.(int32) if !ok { - t.Fatalf("invalid test case: requires int32 but got %T", tc.actual) + stop("int32", actual) } - testEqualT(NotEqualT[int32], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case int64: - actual, ok := tc.actual.(int64) + act, ok := actual.(int64) if !ok { - t.Fatalf("invalid test case: requires int64 but got %T", tc.actual) + stop("int32", actual) } - testEqualT(NotEqualT[int64], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case uint: - actual, ok := tc.actual.(uint) + act, ok := actual.(uint) if !ok { - t.Fatalf("invalid test case: requires uint but got %T", tc.actual) + stop("uint", actual) } - testEqualT(NotEqualT[uint], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case uint8: - actual, ok := tc.actual.(uint8) + act, ok := actual.(uint8) if !ok { - t.Fatalf("invalid test case: requires uint8 but got %T", tc.actual) + stop("uint8", actual) } - testEqualT(NotEqualT[uint8], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case uint16: - actual, ok := tc.actual.(uint16) + act, ok := actual.(uint16) if !ok { - t.Fatalf("invalid test case: requires uint16 but got %T", tc.actual) + stop("uint16", actual) } - testEqualT(NotEqualT[uint16], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case uint32: - actual, ok := tc.actual.(uint32) + act, ok := actual.(uint32) if !ok { - t.Fatalf("invalid test case: requires uint32 but got %T", tc.actual) + stop("uint32", actual) } - testEqualT(NotEqualT[uint32], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case uint64: - actual, ok := tc.actual.(uint64) + act, ok := actual.(uint64) if !ok { - t.Fatalf("invalid test case: requires uint64 but got %T", tc.actual) + stop("uint64", actual) } - testEqualT(NotEqualT[uint64], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case string: - actual, ok := tc.actual.(string) + act, ok := actual.(string) if !ok { - t.Fatalf("invalid test case: requires string but got %T", tc.actual) + stop("string", actual) } - testEqualT(NotEqualT[string], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) + case bool: + act, ok := actual.(bool) + if !ok { + stop("bool", actual) + } + result = testEqualityGenericAssertion(mock, kind, exp, act) case float32: - actual, ok := tc.actual.(float32) + act, ok := actual.(float32) if !ok { - t.Fatalf("invalid test case: requires float32 but got %T", tc.actual) + stop("float32", actual) } - testEqualT(NotEqualT[float32], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) case float64: - actual, ok := tc.actual.(float64) + act, ok := actual.(float64) if !ok { - t.Fatalf("invalid test case: requires float64 but got %T", tc.actual) + stop("float64", actual) } - testEqualT(NotEqualT[float64], expected, actual, shouldPass)(t) - case bool: - actual, ok := tc.actual.(bool) + result = testEqualityGenericAssertion(mock, kind, exp, act) + case *int: + act, ok := actual.(*int) + if !ok { + stop("*int", actual) + } + result = testEqualityGenericAssertion(mock, kind, exp, act) + case *string: + act, ok := actual.(*string) + if !ok { + stop("*string", actual) + } + result = testEqualityGenericAssertion(mock, kind, exp, act) + case myType: + act, ok := actual.(myType) if !ok { - t.Fatalf("invalid test case: requires bool but got %T", tc.actual) + stop("myType", actual) } - testEqualT(NotEqualT[bool], expected, actual, shouldPass)(t) + result = testEqualityGenericAssertion(mock, kind, exp, act) + case struct{}: + act, ok := actual.(struct{}) + if !ok { + stop("struct{}", actual) + } + result = testEqualityGenericAssertion(mock, kind, exp, act) + case *struct{}: + act, ok := actual.(*struct{}) + if !ok { + stop("*struct{}", actual) + } + result = testEqualityGenericAssertion(mock, kind, exp, act) default: - t.Fatalf("unexpected type: %T", expected) + t.Fatalf("unsupported type: %T", expected) } + + shouldPass := expectedStatusForEqualityAssertion(kind, tc.relationship) + shouldPassOrFail(t, mock, result, shouldPass) } } -//nolint:thelper // linter false positive: these are not helpers -func testEqualT[V comparable]( - fn func(T, V, V, ...any) bool, - expected, actual V, - shouldPass bool, -) func(*testing.T) { - return func(t *testing.T) { - mock := new(mockT) - result := fn(mock, expected, actual) - - if shouldPass { - True(t, result) - False(t, mock.Failed()) - return - } +type equalityTestCase struct { + name string + makeValues func() (expected, actual any) + relationship equalityRelationship + reflectionOnly bool +} - False(t, result) - True(t, mock.Failed()) +type ( + structWithUnexportedMapWithArrayKey struct { + m any } -} + s struct { + f map[[1]byte]int + } + + myType string + myMap map[string]any +) -// Generic Same function tests +func unifiedEqualityCases() iter.Seq[equalityTestCase] { + const hello = "hello" + s1 := struct{}{} + p1 := &s1 + p2 := &s1 + f1 := func() bool { return true } + + return slices.Values([]equalityTestCase{ + // Both nil + {"both-nil/ptr", func() (any, any) { return (*int)(nil), (*int)(nil) }, eqBothNil, false}, + {"both-nil/interface", func() (any, any) { return (any)(nil), (any)(nil) }, eqBothNil, true}, + + // One nil (reflection only - type mismatch) + {"one-nil/first", func() (any, any) { v := 42; return nil, &v }, eqOneNil, true}, + {"one-nil/second", func() (any, any) { v := 42; return &v, nil }, eqOneNil, true}, + {"one-nil/bytes", func() (any, any) { return nil, make([]byte, 0) }, eqOneNil, true}, + {"one-nil/struct", func() (any, any) { return nil, new(AssertionTesterConformingObject) }, eqOneNil, true}, + + // Same identity (pointers to same object) + {"same-identity/int-ptr", func() (any, any) { v := 42; return &v, &v }, eqSameIdentity, false}, + {"same-identity/string-ptr", func() (any, any) { s := hello; return &s, &s }, eqSameIdentity, false}, + { + "same-identity/pointer-to-struct", func() (any, any) { + return p1, p2 + }, + eqSameIdentity, false, + }, -type sameTTestCase struct { - name string - makeValues func() (expected, actual any) // Function to create fresh pointers - shouldPass bool -} + // Equal value, comparable (core types - start with 5) + {"equal-comparable/int", func() (any, any) { return 42, 42 }, eqEqualValueComparable, false}, + {"equal-comparable/string", func() (any, any) { return hello, hello }, eqEqualValueComparable, false}, + {"equal-comparable/bool-true", func() (any, any) { return true, true }, eqEqualValueComparable, false}, + {"equal-comparable/bool-false", func() (any, any) { return false, false }, eqEqualValueComparable, false}, + {"equal-comparable/float64", func() (any, any) { return 3.14, 3.14 }, eqEqualValueComparable, false}, + {"equal-comparable/float32", func() (any, any) { return float32(3.14), float32(3.14) }, eqEqualValueComparable, false}, + {"equal-comparable/int8", func() (any, any) { return int8(10), int8(10) }, eqEqualValueComparable, false}, + {"equal-comparable/int16", func() (any, any) { return int16(100), int16(100) }, eqEqualValueComparable, false}, + {"equal-comparable/int32", func() (any, any) { return int32(1000), int32(1000) }, eqEqualValueComparable, false}, + {"equal-comparable/int64", func() (any, any) { return int64(10000), int64(10000) }, eqEqualValueComparable, false}, + {"equal-comparable/uint", func() (any, any) { return uint(42), uint(42) }, eqEqualValueComparable, false}, + {"equal-comparable/uint8", func() (any, any) { return uint8(10), uint8(10) }, eqEqualValueComparable, false}, + {"equal-comparable/uint16", func() (any, any) { return uint16(100), uint16(100) }, eqEqualValueComparable, false}, + {"equal-comparable/uint32", func() (any, any) { return uint32(1000), uint32(1000) }, eqEqualValueComparable, false}, + {"equal-comparable/uint64", func() (any, any) { return uint64(10000), uint64(10000) }, eqEqualValueComparable, false}, + {"equal-comparable/~string", func() (any, any) { return myType("1"), myType("1") }, eqEqualValueComparable, false}, + { + "equal-comparable/anonymous-struct", func() (any, any) { + return struct{}{}, struct{}{} + }, eqEqualValueComparable, false, + }, + { + "equal-comparable/pointer-to-anonymous-struct", func() (any, any) { + return &struct{}{}, &struct{}{} // this a special case in go, as the pointer to this empty type is not allocated: values are equal + }, eqEqualValueComparable, false, + }, -func sameTCases() iter.Seq[sameTTestCase] { - return slices.Values([]sameTTestCase{ - // Success cases - same pointer + // Equal value, non-comparable (reflection only) + {"equal-non-comparable/slice", func() (any, any) { return []int{1, 2, 3}, []int{1, 2, 3} }, eqEqualValueNonComparable, true}, + {"equal-non-comparable/struct-ptr", func() (any, any) { return &struct{}{}, &struct{}{} }, eqEqualValueNonComparable, true}, + {"equal-non-comparable/bytes", func() (any, any) { return []byte(hello), []byte(hello) }, eqEqualValueNonComparable, true}, + {"equal-non-comparable/map", func() (any, any) { return myMap{"bar": 1}, myMap{"bar": 1} }, eqEqualValueNonComparable, true}, { - name: "int/same-pointer", - makeValues: func() (any, any) { - x := 42 - return &x, &x - }, - shouldPass: true, + "equal-non-comparable/bytes-zero-same-len", func() (any, any) { + return make([]byte, 2), make([]byte, 2) + }, eqEqualValueNonComparable, true, }, { - name: "string/same-pointer", - makeValues: func() (any, any) { - s := "hello" - return &s, &s - }, - shouldPass: true, + "equal-non-comparable/bytes-zero-same-len-diff-cap", func() (any, any) { + return make([]byte, 2), make([]byte, 2, 3) + }, eqEqualValueNonComparable, true, + }, + { + "equal-non-comparable/bytes-zero-same-len-diff-cap", func() (any, any) { + return new(AssertionTesterConformingObject), new(AssertionTesterConformingObject) + }, eqEqualValueNonComparable, true, }, { - name: "float64/same-pointer", - makeValues: func() (any, any) { - f := 3.14 - return &f, &f + "equal-non-comparable/map-unexported-struct", func() (any, any) { + return structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{1}: nil, {2}: nil}}, + structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{2}: nil, {1}: nil}} }, - shouldPass: true, + eqEqualValueNonComparable, true, }, - - // Failure cases - different pointers (even with same value) { - name: "int/different-pointers-same-value", - makeValues: func() (any, any) { - x, y := 42, 42 - return &x, &y + "equal-non-comparable/map-unexported-struct-non-nil", func() (any, any) { + return structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{1}: {}, {2}: nil}}, + structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{1}: {}, {2}: nil}} }, - shouldPass: false, + eqEqualValueNonComparable, true, + }, + + // Different value, same type + {"diff-value/int", func() (any, any) { return 42, 43 }, eqDifferentValueSameType, false}, + {"diff-value/string", func() (any, any) { return hello, "world" }, eqDifferentValueSameType, false}, + {"diff-value/bool", func() (any, any) { return true, false }, eqDifferentValueSameType, false}, + {"diff-value/float64", func() (any, any) { return 3.14, 2.71 }, eqDifferentValueSameType, false}, + {"diff-value/float32", func() (any, any) { return float32(3.15), float32(3.14) }, eqDifferentValueSameType, false}, + {"diff-value/int8", func() (any, any) { return int8(10), int8(11) }, eqDifferentValueSameType, false}, + {"diff-value/int16", func() (any, any) { return int16(110), int16(100) }, eqDifferentValueSameType, false}, + {"diff-value/int32", func() (any, any) { return int32(1003), int32(1000) }, eqDifferentValueSameType, false}, + {"diff-value/int64", func() (any, any) { return int64(10400), int64(10000) }, eqDifferentValueSameType, false}, + {"diff-value/uint", func() (any, any) { return uint(43), uint(42) }, eqDifferentValueSameType, false}, + {"diff-value/uint8", func() (any, any) { return uint8(10), uint8(11) }, eqDifferentValueSameType, false}, + {"diff-value/uint16", func() (any, any) { return uint16(101), uint16(100) }, eqDifferentValueSameType, false}, + {"diff-value/uint32", func() (any, any) { return uint32(1040), uint32(1000) }, eqDifferentValueSameType, false}, + {"diff-value/uint64", func() (any, any) { return uint64(10000), uint64(14000) }, eqDifferentValueSameType, false}, + {"diff-value/~string", func() (any, any) { return myType("1"), myType("2") }, eqDifferentValueSameType, false}, + {"diff-value/slice", func() (any, any) { return []int{1, 2}, []int{1, 3} }, eqDifferentValueSameType, true}, + {"diff-value/map", func() (any, any) { return myMap{"bar": 1}, myMap{"bar": 2} }, eqDifferentValueSameType, true}, + { + "diff-value/map-unexported-struct", func() (any, any) { + return structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{1}: nil, {2}: nil}}, + structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{}} + }, + eqDifferentValueSameType, true, }, { - name: "string/different-pointers", - makeValues: func() (any, any) { - s1, s2 := "hello", "world" - return &s1, &s2 + "diff-value/map-unexported-struct-non-nil", func() (any, any) { + return structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{1}: {}, {2}: nil}}, + structWithUnexportedMapWithArrayKey{map[[1]byte]*struct{}{{1}: nil, {2}: {}}} }, - shouldPass: false, + eqDifferentValueSameType, true, }, { - name: "float64/different-pointers-same-value", - makeValues: func() (any, any) { - f1, f2 := 3.14, 3.14 - return &f1, &f2 + "diff-value/func", func() (any, any) { + return func() int { return 23 }, func() int { return 24 } }, - shouldPass: false, + eqFunction, true, }, + { + "same-value/func", func() (any, any) { + return f1, f1 + }, + eqFunction, true, + }, + + // Different type (reflection only - can't use with generics) + {"diff-type/int-uint", func() (any, any) { return 42, uint(42) }, eqDifferentType, true}, + {"diff-type/int-int64", func() (any, any) { return 42, int64(42) }, eqDifferentType, true}, + {"diff-type/int-float64", func() (any, any) { return 10, 10.0 }, eqDifferentType, true}, + {"diff-type/float32-float64", func() (any, any) { return float32(10), float64(10) }, eqDifferentType, true}, + {"diff-value/edge-case-map", func() (any, any) { // this case used to panic + return s{ + f: map[[1]byte]int{ + {0x1}: 0, + {0x2}: 0, + }, + }, + s{} + }, eqDifferentValueSameType, true}, }) } -func TestSameT(t *testing.T) { - t.Parallel() +type equalityAssertionKind int - for tc := range sameTCases() { - t.Run(tc.name, func(t *testing.T) { +const ( + equalKind equalityAssertionKind = iota + equalTKind + notEqualKind + notEqualTKind + exactlyKind +) + +func expectedStatusForEqualityAssertion(kind equalityAssertionKind, relationship equalityRelationship) bool { + positive := kind == equalKind || kind == equalTKind || kind == exactlyKind + + switch relationship { + case eqFunction: + // A special validation is carried out to reject function types with an error + return false + case eqBothNil, eqSameIdentity, eqEqualValueComparable, eqEqualValueNonComparable: + return positive + case eqOneNil, eqDifferentValueSameType: + return !positive + case eqDifferentType: + // Exactly requires exact type match (fails on different types) + // Equal uses reflection (can handle different types) + // EqualT requires same type (won't compile with different types) + if kind == exactlyKind { + return false + } + // Equal might handle some type coercion, but generally fails + return !positive + default: + panic(fmt.Errorf("test case configuration error: invalid equalityRelationship: %d", relationship)) + } +} + +func testEqualityGenericAssertion[V comparable](mock T, kind equalityAssertionKind, expected, actual V) bool { + switch kind { + case equalTKind: + return EqualT(mock, expected, actual) + case notEqualTKind: + return NotEqualT(mock, expected, actual) + default: + panic(fmt.Errorf("test case configuration error: invalid equalityAssertionKind for generic: %d", kind)) + } +} + +func testTooLongToPrint() func(*testing.T) { + const ( + expected = `&[]int{0, 0, 0,` + message = ` + Error Trace: + Error: Should not be: []int{0, 0, 0,` + trailer = `<... truncated>` + ) + + return func(t *testing.T) { + t.Run("with Same", func(t *testing.T) { t.Parallel() - expected, actual := tc.makeValues() + mock := new(mockT) - // Type dispatch based on pointer type - switch exp := expected.(type) { - case *int: - act, ok := actual.(*int) - if !ok { - t.Fatalf("invalid test case: requires *int but got %T", actual) - } - testSameT(SameT[int], exp, act, tc.shouldPass)(t) - case *string: - act, ok := actual.(*string) - if !ok { - t.Fatalf("invalid test case: requires *string but got %T", actual) - } - testSameT(SameT[string], exp, act, tc.shouldPass)(t) - case *float64: - act, ok := actual.(*float64) - if !ok { - t.Fatalf("invalid test case: requires *float64 but got %T", actual) - } - testSameT(SameT[float64], exp, act, tc.shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", exp) + longSlice := make([]int, 1_000_000) + result := Same(mock, &[]int{}, &longSlice) + if result { + t.Errorf("expected Same to fail") + return + } + + if !strings.Contains(mock.errorString(), expected) { + t.Errorf("expected message to contain %q but got: %q", expected, mock.errorString()) } }) - } -} -func TestNotSameT(t *testing.T) { - t.Parallel() + t.Run("with NotSame", func(t *testing.T) { + t.Parallel() - for tc := range sameTCases() { - t.Run(tc.name, func(t *testing.T) { + mock := new(mockT) + + longSlice := make([]int, 1_000_000) + result := NotSame(mock, &longSlice, &longSlice) + if result { + t.Errorf("expected NotSame to fail") + return + } + + if !strings.Contains(mock.errorString(), expected) { + t.Errorf("expected message to contain %q but got: %q", expected, mock.errorString()) + } + }) + + t.Run("with NotEqual", func(t *testing.T) { t.Parallel() - // Invert shouldPass for NotSame - shouldPass := !tc.shouldPass + mock := new(mockT) - expected, actual := tc.makeValues() + longSlice := make([]int, 1_000_000) + result := NotEqual(mock, longSlice, longSlice) + if result { + t.Errorf("expected NotEqual to fail") + return + } - // Type dispatch based on pointer type - switch exp := expected.(type) { - case *int: - act, ok := actual.(*int) - if !ok { - t.Fatalf("invalid test case: requires *int but got %T", actual) - } - testSameT(NotSameT[int], exp, act, shouldPass)(t) - case *string: - act, ok := actual.(*string) - if !ok { - t.Fatalf("invalid test case: requires *string but got %T", actual) - } - testSameT(NotSameT[string], exp, act, shouldPass)(t) - case *float64: - act, ok := actual.(*float64) - if !ok { - t.Fatalf("invalid test case: requires *float64 but got %T", actual) - } - testSameT(NotSameT[float64], exp, act, shouldPass)(t) - default: - t.Fatalf("unexpected type: %T", exp) + if !strings.Contains(mock.errorString(), message) { + t.Errorf("expected message to contain %q but got: %q", message, mock.errorString()) + } + + if !strings.Contains(mock.errorString(), trailer) { + t.Errorf("expected message to contain %q but got: %q", trailer, mock.errorString()) + } + }) + + t.Run("with NotEqualValues", func(t *testing.T) { + t.Parallel() + mock := new(mockT) + + longSlice := make([]int, 1_000_000) + result := NotEqualValues(mock, longSlice, longSlice) + if result { + t.Errorf("expected NotEqualValues to fail") + return + } + + if !strings.Contains(mock.errorString(), message) { + t.Errorf("expected message to contain %q but got: %q", message, mock.errorString()) + } + const trailer = `<... truncated>` + if !strings.Contains(mock.errorString(), trailer) { + t.Errorf("expected message to contain %q but got: %q", trailer, mock.errorString()) } }) } } -//nolint:thelper // linter false positive: these are not helpers -func testSameT[P any]( - fn func(T, *P, *P, ...any) bool, - expected, actual *P, - shouldPass bool, -) func(*testing.T) { - return func(t *testing.T) { - mock := new(mockT) - result := fn(mock, expected, actual) +type equalStringCase struct { + name string + equalWant string + equalGot string + msgAndArgs []any + want string +} - if shouldPass { - True(t, result) - False(t, mock.Failed()) - return - } +func stringEqualFormattingCases() iter.Seq[equalStringCase] { + return slices.Values([]equalStringCase{ + { + name: "multiline diff message", + equalWant: "hi, \nmy name is", + equalGot: "what,\nmy name is", + want: "\t[a-z]+.go:\\d+: \n" + + "\t+Error Trace:\t\n+" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"hi, \\\\nmy name is\"\n" + + "\\s+actual\\s+: " + "\"what,\\\\nmy name is\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n\\s+\\++ " + + "Actual\n" + + "\\s+@@ -1,2 \\+1,2 @@\n" + + "\\s+-hi, \n\\s+\\+what,\n" + + "\\s+my name is", + }, + { + name: "single line diff message", + equalWant: "want", + equalGot: "got", + want: "\t[a-z]+.go:\\d+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ " + + "Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n", + }, + { + name: "diff message with args", + equalWant: "want", + equalGot: "got", + msgAndArgs: []any{"hello, %v!", "world"}, + want: "\t[a-z]+.go:[0-9]+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n" + + "\\s+\\++ Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n" + + "\\s+Messages:\\s+hello, world!\n", + }, + { + name: "diff message with integer arg", + equalWant: "want", + equalGot: "got", + msgAndArgs: []any{123}, + want: "\t[a-z]+.go:[0-9]+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n" + + "\\s+\\++ Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n" + + "\\s+Messages:\\s+123\n", + }, + { + name: "diff message with struct arg", + equalWant: "want", + equalGot: "got", + msgAndArgs: []any{struct{ a string }{"hello"}}, + want: "\t[a-z]+.go:[0-9]+: \n" + + "\t+Error Trace:\t\n" + + "\t+Error:\\s+Not equal:\\s+\n" + + "\\s+expected: \"want\"\n" + + "\\s+actual\\s+: \"got\"\n" + + "\\s+Diff:\n" + + "\\s+-+ Expected\n" + + "\\s+\\++ Actual\n" + + "\\s+@@ -1 \\+1 @@\n" + + "\\s+-want\n" + + "\\s+\\+got\n" + + "\\s+Messages:\\s+{a:hello}\n", + }, + }) +} + +type equalValuesCase struct { + name string + expected any + actual any + equalValue bool + notEqualValue bool // notEqualValue = !equalValue, except for invalid types (e.g. functions) +} + +func equalValuesCases() iter.Seq[equalValuesCase] { + return slices.Values([]equalValuesCase{ + // cases that are expected not to match + {"not-equal/string", "Hello World", "Hello World!", false, true}, + {"not-equal/int", 123, 1234, false, true}, + {"not-equal/float64", 123.5, 123.55, false, true}, + {"not-equal/[]byte", []byte("Hello World"), []byte("Hello World!"), false, true}, + {"not-equal/nil-not-nil", nil, new(AssertionTesterConformingObject), false, true}, + {"not-equal/converted", uint(10), int(11), false, true}, + + // cases that are expected to match + {"equal/nil-nil", nil, nil, true, false}, + {"equal/string", "Hello World", "Hello World", true, false}, + {"equal/int", 123, 123, true, false}, + {"equal/float64", 123.5, 123.5, true, false}, + {"equal/[]byte", []byte("Hello World"), []byte("Hello World"), true, false}, + {"equal/pointer-to-struct", new(AssertionTesterConformingObject), new(AssertionTesterConformingObject), true, false}, + {"equal/pointer-to-anonymous-struct", &struct{}{}, &struct{}{}, true, false}, + {"equal/converted", int(10), uint(10), true, false}, + {"equal/anonymous-struct", struct{}{}, struct{}{}, true, false}, + + // always fail + {"always-fail/func", func() int { return 23 }, func() int { return 24 }, false, false}, + }) +} + +type objectEqualExportedValuesCase struct { + name string + expected any + actual any + expectedEqual bool + expectedFailMsg string +} - False(t, result) - True(t, mock.Failed()) +func objectEqualExportedValuesCases() iter.Seq[objectEqualExportedValuesCase] { + type specialKey struct { + a string } + + return slices.Values([]objectEqualExportedValuesCase{ + { + name: "edge-case/func", + expected: func() {}, + actual: func() {}, + expectedEqual: false, + }, + { + name: "edge-case/expect-nil", + expected: nil, + actual: nil, + expectedEqual: true, + }, + { + name: "edge-case/expect-nil-actual-not-nil", + expected: nil, + actual: 1, + expectedEqual: false, + }, + { + name: "edge-case/map-with-struct-key", + expected: map[specialKey]S{{a: "a"}: {}}, + actual: map[specialKey]S{{a: "a"}: {}}, + expectedEqual: true, + }, + { + name: "equal-values/map", + expected: map[string]S{ + "key": {1, Nested{2, 3}, 4, Nested{5, 6}}, + }, + actual: map[string]S{ + "key": {1, Nested{2, nil}, nil, Nested{}}, + }, + expectedEqual: true, + }, + { + name: "diff-values/map", + expected: map[string]S{ + "key": {1, Nested{2, 3}, 4, Nested{5, 6}}, + }, + actual: map[string]S{ + "x": {1, Nested{2, nil}, nil, Nested{}}, + }, + expectedEqual: false, + }, + { + name: "equal-values/nested-struct", + expected: S{1, Nested{2, 3}, 4, Nested{5, 6}}, + actual: S{1, Nested{2, nil}, nil, Nested{}}, + expectedEqual: true, + }, + { + name: "diff-values/nested-struct(1)", + expected: S{1, Nested{2, 3}, 4, Nested{5, 6}}, + actual: S{1, Nested{1, nil}, nil, Nested{}}, + expectedEqual: false, + expectedFailMsg: fmt.Sprintf(` + Diff: + --- Expected + +++ Actual + @@ -3,3 +3,3 @@ + Exported2: (%s.Nested) { + - Exported: (int) 2, + + Exported: (int) 1, + notExported: (interface {}) `, + shortpkg), + }, + { + name: "diff-values/nested-struct(2)", + expected: S3{&Nested{1, 2}, &Nested{3, 4}}, + actual: S3{&Nested{"a", 2}, &Nested{3, 4}}, + expectedEqual: false, + expectedFailMsg: fmt.Sprintf(` + Diff: + --- Expected + +++ Actual + @@ -2,3 +2,3 @@ + Exported1: (*%s.Nested)({ + - Exported: (int) 1, + + Exported: (string) (len=1) "a", + notExported: (interface {}) `, + shortpkg), + }, + { + name: "diff-values/inner-slice", + expected: S4{[]*Nested{ + {1, 2}, + {3, 4}, + }}, + actual: S4{[]*Nested{ + {1, "a"}, + {2, "b"}, + }}, + expectedEqual: false, + expectedFailMsg: fmt.Sprintf(` + Diff: + --- Expected + +++ Actual + @@ -7,3 +7,3 @@ + (*%s.Nested)({ + - Exported: (int) 3, + + Exported: (int) 2, + notExported: (interface {}) `, + shortpkg), + }, + { + name: "equal-values/inner-array-unexported-diff", + expected: S{[2]int{1, 2}, Nested{2, 3}, 4, Nested{5, 6}}, + actual: S{[2]int{1, 2}, Nested{2, nil}, nil, Nested{}}, + expectedEqual: true, + }, + { + name: "equal-values/inner-array", + expected: &S{1, Nested{2, 3}, 4, Nested{5, 6}}, + actual: &S{1, Nested{2, nil}, nil, Nested{}}, + expectedEqual: true, + }, + { + name: "diff-values/inner-slice-exported-diff", + expected: &S{1, Nested{2, 3}, 4, Nested{5, 6}}, + actual: &S{1, Nested{1, nil}, nil, Nested{}}, + expectedEqual: false, + expectedFailMsg: fmt.Sprintf(` + Diff: + --- Expected + +++ Actual + @@ -3,3 +3,3 @@ + Exported2: (%s.Nested) { + - Exported: (int) 2, + + Exported: (int) 1, + notExported: (interface {}) `, + shortpkg), + }, + { + name: "equal-values/slice", + expected: []int{1, 2}, + actual: []int{1, 2}, + expectedEqual: true, + }, + { + name: "diff-values/slice", + expected: []int{1, 2}, + actual: []int{1, 3}, + expectedEqual: false, + expectedFailMsg: ` + Diff: + --- Expected + +++ Actual + @@ -2,3 +2,3 @@ + (int) 1, + - (int) 2 + + (int) 3 + }`, + }, + { + name: "equal-values/slice-of-pointers", + expected: []*int{ptr(1), nil, ptr(2)}, + actual: []*int{ptr(1), nil, ptr(2)}, + expectedEqual: true, + }, + { + name: "equal-values/slice-of-struct", + expected: []*Nested{ + {1, 2}, + {3, 4}, + }, + actual: []*Nested{ + {1, "a"}, + {3, "b"}, + }, + expectedEqual: true, + }, + { + name: "diff-values/slice-of-struct", + expected: []*Nested{ + {1, 2}, + {3, 4}, + }, + actual: []*Nested{ + {1, "a"}, + {2, "b"}, + }, + expectedEqual: false, + expectedFailMsg: fmt.Sprintf(` + Diff: + --- Expected + +++ Actual + @@ -6,3 +6,3 @@ + (*%s.Nested)({ + - Exported: (int) 3, + + Exported: (int) 2, + notExported: (interface {}) `, + shortpkg), + }, + }) } diff --git a/internal/assertions/equal_unary.go b/internal/assertions/equal_unary.go new file mode 100644 index 000000000..5d84122b3 --- /dev/null +++ b/internal/assertions/equal_unary.go @@ -0,0 +1,154 @@ +package assertions + +import ( + "fmt" + "reflect" +) + +// Nil asserts that the specified object is nil. +// +// # Usage +// +// assertions.Nil(t, err) +// +// # Examples +// +// success: nil +// failure: "not nil" +func Nil(t T, object any, msgAndArgs ...any) bool { + // Domain: equality + if isNil(object) { + return true + } + if h, ok := t.(H); ok { + h.Helper() + } + return Fail(t, "Expected nil, but got: "+truncatingFormat("%#v", object), msgAndArgs...) +} + +// NotNil asserts that the specified object is not nil. +// +// # Usage +// +// assertions.NotNil(t, err) +// +// # Examples +// +// success: "not nil" +// failure: nil +func NotNil(t T, object any, msgAndArgs ...any) bool { + // Domain: equality + if !isNil(object) { + return true + } + if h, ok := t.(H); ok { + h.Helper() + } + return Fail(t, "Expected value not to be nil.", msgAndArgs...) +} + +// Empty asserts that the given value is "empty". +// +// Zero values are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". +// +// # Usage +// +// assertions.Empty(t, obj) +// +// # Examples +// +// success: "" +// failure: "not empty" +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value +func Empty(t T, object any, msgAndArgs ...any) bool { + // Domain: equality + pass := isEmpty(object) + if !pass { + if h, ok := t.(H); ok { + h.Helper() + } + Fail(t, "Should be empty, but was "+truncatingFormat("%v", object), msgAndArgs...) + } + + return pass +} + +// NotEmpty asserts that the specified object is NOT [Empty]. +// +// # Usage +// +// if assert.NotEmpty(t, obj) { +// assertions.Equal(t, "two", obj[1]) +// } +// +// # Examples +// +// success: "not empty" +// failure: "" +func NotEmpty(t T, object any, msgAndArgs ...any) bool { + // Domain: equality + pass := !isEmpty(object) + if !pass { + if h, ok := t.(H); ok { + h.Helper() + } + Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) + } + + return pass +} + +// isNil checks if a specified object is nil or not, without Failing. +func isNil(object any) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + switch value.Kind() { + case + reflect.Chan, reflect.Func, + reflect.Interface, reflect.Map, + reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + + return value.IsNil() + default: + return false + } +} + +// isEmpty gets whether the specified object is considered empty or not. +func isEmpty(object any) bool { + // get nil case out of the way + if object == nil { + return true + } + + return isEmptyValue(reflect.ValueOf(object)) +} + +// isEmptyValue gets whether the specified reflect.Value is considered empty or not. +func isEmptyValue(objValue reflect.Value) bool { + if objValue.IsZero() { + return true + } + // Special cases of non-zero values that we consider empty + switch objValue.Kind() { + // collection types are empty when they have no element + // Note: array types are empty when they match their zero-initialized state. + case reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // non-nil pointers are empty if the value they point to is empty + case reflect.Ptr: + return isEmptyValue(objValue.Elem()) + default: + return false + } +} diff --git a/internal/assertions/equal_unary_test.go b/internal/assertions/equal_unary_test.go new file mode 100644 index 000000000..7385dfc4b --- /dev/null +++ b/internal/assertions/equal_unary_test.go @@ -0,0 +1,281 @@ +package assertions + +import ( + "errors" + "fmt" + "iter" + "slices" + "testing" +) + +func TestEqualUnaryErrorMessages(t *testing.T) { + // error messages validation + for tc := range equalEmptyCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mock := new(captureT) + + res := Empty(mock, tc.value) + mock.checkResultAndErrMsg(t, res, tc.expectedResult, tc.expectedErrMsg) + }) + } +} + +// Unary assertion tests (Nil, NotNil, Empty, NotEmpty). +func TestEqualUnaryAssertions(t *testing.T) { + t.Parallel() + + for tc := range unifiedUnaryCases() { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + t.Run("with Nil", testUnaryAssertion(tc, nilKind, Nil)) + t.Run("with NotNil", testUnaryAssertion(tc, notNilKind, NotNil)) + t.Run("with Empty", testUnaryAssertion(tc, emptyKind, Empty)) + t.Run("with NotEmpty", testUnaryAssertion(tc, notEmptyKind, NotEmpty)) + }) + } +} + +type unaryTestCase struct { + name string + object any + category objectCategory +} + +func unifiedUnaryCases() iter.Seq[unaryTestCase] { + chWithValue := make(chan struct{}, 1) + chWithValue <- struct{}{} + x := 1 + xP := &x + z := 0 + zP := &z + + type TString string + type TStruct struct { + x int + } + + return slices.Values([]unaryTestCase{ + // Nil category + {"nil/nil-ptr", (*int)(nil), nilCategory}, + {"nil/nil-slice", []int(nil), nilCategory}, + {"nil/nil-interface", (any)(nil), nilCategory}, + {"nil/nil-struct-ptr", (*struct{})(nil), nilCategory}, + + // Empty non-nil category + {"empty/slice", []int{}, emptyNonNil}, + {"empty/string", "", emptyNonNil}, + {"empty/zero-int", 0, emptyNonNil}, + {"empty/zero-bool", false, emptyNonNil}, + {"empty/channel", make(chan struct{}), emptyNonNil}, + {"empty/zero-struct", TStruct{}, emptyNonNil}, + {"empty/aliased-string", TString(""), emptyNonNil}, + {"empty/zero-array", [1]int{}, emptyNonNil}, + {"empty/zero-ptr", zP, emptyNonNil}, + + // Non-empty comparable category + {"non-empty/int", 42, nonEmptyComparable}, + {"non-empty/string", "hello", nonEmptyComparable}, + {"non-empty/bool", true, nonEmptyComparable}, + {"non-empty/slice", []int{1}, nonEmptyComparable}, + {"non-empty/channel", chWithValue, nonEmptyComparable}, + {"non-empty/struct", TStruct{x: 1}, nonEmptyComparable}, + {"non-empty/aliased-string", TString("abc"), nonEmptyComparable}, + {"non-empty/ptr", xP, nonEmptyComparable}, + {"non-empty/array", [1]int{42}, nonEmptyComparable}, + + // Non-empty non-comparable category + {"non-empty/error", errors.New("something"), nonEmptyNonComparable}, + }) +} + +type unaryAssertionKind int + +const ( + nilKind unaryAssertionKind = iota + notNilKind + emptyKind + notEmptyKind +) + +type objectCategory int + +const ( + nilCategory objectCategory = iota + emptyNonNil + nonEmptyComparable + nonEmptyNonComparable +) + +// expectedStatusForUnaryAssertion returns the expected semantics for a given assertion (Nil, Empty, ...) +// and a given category of input. +func expectedStatusForUnaryAssertion(kind unaryAssertionKind, category objectCategory) bool { + switch kind { + case nilKind: + return category == nilCategory + case notNilKind: + return category != nilCategory + case emptyKind: + return category == nilCategory || category == emptyNonNil + case notEmptyKind: + return category == nonEmptyComparable || category == nonEmptyNonComparable + default: + panic(fmt.Errorf("test case configuration error: invalid unaryAssertionKind: %d", kind)) + } +} + +func testUnaryAssertion(tc unaryTestCase, kind unaryAssertionKind, unaryAssertion func(T, any, ...any) bool) func(*testing.T) { + return func(t *testing.T) { + t.Parallel() + + mock := new(mockT) + result := unaryAssertion(mock, tc.object) + shouldPass := expectedStatusForUnaryAssertion(kind, tc.category) + shouldPassOrFail(t, mock, result, shouldPass) + } +} + +type equalEmptyCase struct { + name string + value any + expectedResult bool + expectedErrMsg string +} + +func equalEmptyCases() iter.Seq[equalEmptyCase] { + chWithValue := make(chan struct{}, 1) + chWithValue <- struct{}{} + // var tiP *time.Time + // var tiNP time.Time + // var s *string + // var f *os.File + // sP := &s + x := 1 + xP := &x + + type TString string + type TStruct struct { + x int + } + + return slices.Values([]equalEmptyCase{ + { + name: "Non Empty string is not empty", + value: "something", + expectedResult: false, + expectedErrMsg: "Should be empty, but was something\n", + }, + { + name: "Non nil object is not empty", + value: errors.New("something"), + expectedResult: false, + expectedErrMsg: "Should be empty, but was something\n", + }, + { + name: "Non empty string array is not empty", + value: []string{"something"}, + expectedResult: false, + expectedErrMsg: "Should be empty, but was [something]\n", + }, + { + name: "Non-zero int value is not empty", + value: 1, + expectedResult: false, + expectedErrMsg: "Should be empty, but was 1\n", + }, + { + name: "True value is not empty", + value: true, + expectedResult: false, + expectedErrMsg: "Should be empty, but was true\n", + }, + { + name: "Channel with values is not empty", + value: chWithValue, + expectedResult: false, + expectedErrMsg: fmt.Sprintf("Should be empty, but was %v\n", chWithValue), + }, + { + name: "struct with initialized values is empty", + value: TStruct{x: 1}, + expectedResult: false, + expectedErrMsg: "Should be empty, but was {1}\n", + }, + { + name: "non-empty aliased string is empty", + value: TString("abc"), + expectedResult: false, + expectedErrMsg: "Should be empty, but was abc\n", + }, + { + name: "ptr to non-nil value is not empty", + value: xP, + expectedResult: false, + expectedErrMsg: fmt.Sprintf("Should be empty, but was %p\n", xP), + }, + { + name: "array is not state", + value: [1]int{42}, + expectedResult: false, + expectedErrMsg: "Should be empty, but was [42]\n", + }, + + // Here are some edge cases + { + name: "string with only spaces is not empty", + value: " ", + expectedResult: false, + expectedErrMsg: "Should be empty, but was \n", // Proposal for enhancement: FIX THIS strange error message + }, + { + name: "string with a line feed is not empty", + value: "\n", + expectedResult: false, + // Proposal for enhancement: This is the exact same error message as for an empty string + expectedErrMsg: "Should be empty, but was \n", // Proposal for enhancement: FIX THIS strange error message + }, + { + name: "string with only tabulation and lines feed is not empty", + value: "\n\t\n", + expectedResult: false, + // Proposal for enhancement: The line feeds and tab are not helping to spot what is expected + expectedErrMsg: "" + // this syntax is used to show how errors are reported. + "Should be empty, but was \n" + + "\t\n", + }, + { + name: "string with trailing lines feed is not empty", + value: "foo\n\n", + expectedResult: false, + // Proposal for enhancement: it's not clear if one or two lines feed are expected + expectedErrMsg: "Should be empty, but was foo\n\n", + }, + { + name: "string with leading and trailing tabulation and lines feed is not empty", + value: "\n\nfoo\t\n\t\n", + expectedResult: false, + // Proposal for enhancement: The line feeds and tab are not helping to figure what is expected + expectedErrMsg: "" + + "Should be empty, but was \n" + + "\n" + + "foo\t\n" + + "\t\n", + }, + { + name: "non-printable character is not empty", + value: "\u00a0", // NO-BREAK SPACE UNICODE CHARACTER + expectedResult: false, + // Proposal for enhancement: here you cannot figure out what is expected + expectedErrMsg: "Should be empty, but was \u00a0\n", + }, + // Here we are testing there is no error message on success + { + name: "Empty string is empty", + value: "", + expectedResult: true, + expectedErrMsg: "", + }, + }) +} diff --git a/internal/assertions/helpers_impl_test.go b/internal/assertions/helpers_impl_test.go index 311add0a5..9e1b0d447 100644 --- a/internal/assertions/helpers_impl_test.go +++ b/internal/assertions/helpers_impl_test.go @@ -86,6 +86,15 @@ type diffCase struct { expected string } +type diffTestingStruct struct { + A string + B int +} + +func (d *diffTestingStruct) String() string { + return d.A +} + func diffCases() iter.Seq[diffCase] { const n = 5 type Key struct { diff --git a/internal/assertions/mock_test.go b/internal/assertions/mock_test.go index c59b1fe3c..b2ac98c52 100644 --- a/internal/assertions/mock_test.go +++ b/internal/assertions/mock_test.go @@ -264,3 +264,27 @@ type testCase struct { actual any result bool } + +func shouldPassOrFail(t *testing.T, mock *mockT, result, shouldPass bool) { + t.Helper() + + if shouldPass { + t.Run("should pass", func(t *testing.T) { + if !result || mock.Failed() { + t.Errorf("expected to pass") + } + }) + + return + } + + t.Run("should fail", func(t *testing.T) { + if result || !mock.Failed() { + t.Errorf("expected to fail") + } + }) +} + +func ptr(i int) *int { + return &i +} diff --git a/internal/assertions/object_test.go b/internal/assertions/object_test.go index 0f6349fef..88217263d 100644 --- a/internal/assertions/object_test.go +++ b/internal/assertions/object_test.go @@ -8,7 +8,6 @@ import ( "iter" "math" "slices" - "strings" "testing" "time" ) @@ -27,6 +26,30 @@ func TestObjectsAreEqual(t *testing.T) { } } +/* redundant with Equal +func TestEqualBytes(t *testing.T) { + t.Parallel() + + i := 0 + for c := range equalBytesCases() { + Equal(t, reflect.DeepEqual(c.a, c.b), ObjectsAreEqual(c.a, c.b), "case %d failed", i) + i++ + } +} + +type equalBytesCase struct { + a, b []byte +} + +func equalBytesCases() iter.Seq[equalBytesCase] { + return slices.Values([]equalBytesCase{ + {make([]byte, 2), make([]byte, 2)}, + {make([]byte, 2), make([]byte, 2, 3)}, + {nil, make([]byte, 0)}, + }) +} +*/ + func TestObjectsAreEqualValues(t *testing.T) { t.Parallel() @@ -54,26 +77,6 @@ func TestObjectsCopyExportedFields(t *testing.T) { } } -func TestObjectsEqualExportedValues(t *testing.T) { - t.Parallel() - - for c := range objectEqualExportedValuesCases() { - t.Run("", func(t *testing.T) { - mockT := new(mockT) - - actual := EqualExportedValues(mockT, c.value1, c.value2) - if actual != c.expectedEqual { - t.Errorf("Expected EqualExportedValues to be %t, but was %t", c.expectedEqual, actual) - } - - actualFail := mockT.errorString() - if !strings.Contains(actualFail, c.expectedFail) { - t.Errorf("Contains failure should include %q but was %q", c.expectedFail, actualFail) - } - }) - } -} - type Nested struct { Exported any notExported any @@ -234,147 +237,3 @@ func objectCopyExportedFieldsCases() iter.Seq[objectCopyFieldsCase] { }, }) } - -type objectEqualExportedValuesCase struct { - value1 any - value2 any - expectedEqual bool - expectedFail string -} - -func objectEqualExportedValuesCases() iter.Seq[objectEqualExportedValuesCase] { - return slices.Values([]objectEqualExportedValuesCase{ - { - value1: S{1, Nested{2, 3}, 4, Nested{5, 6}}, - value2: S{1, Nested{2, nil}, nil, Nested{}}, - expectedEqual: true, - }, - { - value1: S{1, Nested{2, 3}, 4, Nested{5, 6}}, - value2: S{1, Nested{1, nil}, nil, Nested{}}, - expectedEqual: false, - expectedFail: fmt.Sprintf(` - Diff: - --- Expected - +++ Actual - @@ -3,3 +3,3 @@ - Exported2: (%s.Nested) { - - Exported: (int) 2, - + Exported: (int) 1, - notExported: (interface {}) `, - shortpkg), - }, - { - value1: S3{&Nested{1, 2}, &Nested{3, 4}}, - value2: S3{&Nested{"a", 2}, &Nested{3, 4}}, - expectedEqual: false, - expectedFail: fmt.Sprintf(` - Diff: - --- Expected - +++ Actual - @@ -2,3 +2,3 @@ - Exported1: (*%s.Nested)({ - - Exported: (int) 1, - + Exported: (string) (len=1) "a", - notExported: (interface {}) `, - shortpkg), - }, - { - value1: S4{[]*Nested{ - {1, 2}, - {3, 4}, - }}, - value2: S4{[]*Nested{ - {1, "a"}, - {2, "b"}, - }}, - expectedEqual: false, - expectedFail: fmt.Sprintf(` - Diff: - --- Expected - +++ Actual - @@ -7,3 +7,3 @@ - (*%s.Nested)({ - - Exported: (int) 3, - + Exported: (int) 2, - notExported: (interface {}) `, - shortpkg), - }, - { - value1: S{[2]int{1, 2}, Nested{2, 3}, 4, Nested{5, 6}}, - value2: S{[2]int{1, 2}, Nested{2, nil}, nil, Nested{}}, - expectedEqual: true, - }, - { - value1: &S{1, Nested{2, 3}, 4, Nested{5, 6}}, - value2: &S{1, Nested{2, nil}, nil, Nested{}}, - expectedEqual: true, - }, - { - value1: &S{1, Nested{2, 3}, 4, Nested{5, 6}}, - value2: &S{1, Nested{1, nil}, nil, Nested{}}, - expectedEqual: false, - expectedFail: fmt.Sprintf(` - Diff: - --- Expected - +++ Actual - @@ -3,3 +3,3 @@ - Exported2: (%s.Nested) { - - Exported: (int) 2, - + Exported: (int) 1, - notExported: (interface {}) `, - shortpkg), - }, - { - value1: []int{1, 2}, - value2: []int{1, 2}, - expectedEqual: true, - }, - { - value1: []int{1, 2}, - value2: []int{1, 3}, - expectedEqual: false, - expectedFail: ` - Diff: - --- Expected - +++ Actual - @@ -2,3 +2,3 @@ - (int) 1, - - (int) 2 - + (int) 3 - }`, - }, - { - value1: []*Nested{ - {1, 2}, - {3, 4}, - }, - value2: []*Nested{ - {1, "a"}, - {3, "b"}, - }, - expectedEqual: true, - }, - { - value1: []*Nested{ - {1, 2}, - {3, 4}, - }, - value2: []*Nested{ - {1, "a"}, - {2, "b"}, - }, - expectedEqual: false, - expectedFail: fmt.Sprintf(` - Diff: - --- Expected - +++ Actual - @@ -6,3 +6,3 @@ - (*%s.Nested)({ - - Exported: (int) 3, - + Exported: (int) 2, - notExported: (interface {}) `, - shortpkg), - }, - }) -} diff --git a/internal/assertions/order.go b/internal/assertions/order.go index c1dd14bee..f29613edc 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -305,7 +305,7 @@ func isStrictlyOrdered(object any, reverseOrder bool) ([]any, bool, error) { value := objValue.Index(0) if !value.CanInterface() { - // this should not be possible with current relect, since values are retrieved from an array or slice, not a struct + // this should not be possible with current reflect, since values are retrieved from an array or slice, not a struct panic(fmt.Errorf("internal error: can't resolve Interface() for value %v", value)) } valueInterface := value.Interface() diff --git a/internal/assertions/order_test.go b/internal/assertions/order_test.go index 55dd87e72..11c4b1c9d 100644 --- a/internal/assertions/order_test.go +++ b/internal/assertions/order_test.go @@ -299,22 +299,7 @@ func testOrderReflectBased(orderAssertion func(T, any, ...any) bool, collection mock := new(mockT) result := orderAssertion(mock, collection) - - if shouldPass { - t.Run("should pass", func(t *testing.T) { - if !result || mock.Failed() { - t.Errorf("expected to pass") - } - }) - - return - } - - t.Run("should fail", func(t *testing.T) { - if result || !mock.Failed() { - t.Errorf("expected to fail") - } - }) + shouldPassOrFail(t, mock, result, shouldPass) } } @@ -324,22 +309,7 @@ func testOrderGeneric(assertionKind orderAssertionKind, collection any, shouldPa mock := new(mockT) result := testOrderAssertionResult(mock, assertionKind, collection) - - if shouldPass { - t.Run("should pass", func(t *testing.T) { - if !result || mock.Failed() { - t.Errorf("expected to pass") - } - }) - - return - } - - t.Run("should fail", func(t *testing.T) { - if result || !mock.Failed() { - t.Errorf("expected to fail") - } - }) + shouldPassOrFail(t, mock, result, shouldPass) } } From 5ea42d8151bace35a17f87018423ccb5bbfaa2eb Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Wed, 21 Jan 2026 00:13:47 +0100 Subject: [PATCH 10/10] test: updated benchmarks reflection-based vs generics Signed-off-by: Frederic BIDON --- internal/assertions/benchmarks_test.go | 526 +++++++++++++++++-------- 1 file changed, 352 insertions(+), 174 deletions(-) diff --git a/internal/assertions/benchmarks_test.go b/internal/assertions/benchmarks_test.go index b2946e593..88e02b15c 100644 --- a/internal/assertions/benchmarks_test.go +++ b/internal/assertions/benchmarks_test.go @@ -3,7 +3,50 @@ package assertions -import "testing" +import ( + "slices" + "testing" +) + +// Helper functions to reduce duplication in benchmarks + +// benchmarkComparison runs a benchmark comparing reflection vs generic implementations. +func benchmarkComparison( + b *testing.B, + name string, + reflectFn func(*mockT), + genericFn func(*mockT), +) { + b.Helper() + mockT := &mockT{} + + b.Run("reflect/"+name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + reflectFn(mockT) + } + }) + + b.Run("generic/"+name, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + genericFn(mockT) + } + }) +} + +// benchmarkGenericOnly runs a benchmark for generic-only functions (no reflection equivalent). +func benchmarkGenericOnly(b *testing.B, genericFn func(*mockT)) { + b.Helper() + mockT := &mockT{} + + b.Run("generic", func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + genericFn(mockT) + } + }) +} func Benchmark_isEmpty(b *testing.B) { b.ReportAllocs() @@ -49,91 +92,39 @@ func BenchmarkBytesEqual(b *testing.B) { // BenchmarkGreater compares Greater (reflect) vs GreaterT (generic). func BenchmarkGreater(b *testing.B) { - mockT := &mockT{} - - b.Run("reflect/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Greater(mockT, 100, 50) - } - }) - - b.Run("generic/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - GreaterT(mockT, 100, 50) - } - }) - - b.Run("reflect/float64", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Greater(mockT, 100.5, 50.5) - } - }) - - b.Run("generic/float64", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - GreaterT(mockT, 100.5, 50.5) - } - }) - - b.Run("reflect/string", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Greater(mockT, "beta", "alpha") - } - }) - - b.Run("generic/string", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - GreaterT(mockT, "beta", "alpha") - } - }) + benchmarkComparison(b, "int", + func(t *mockT) { Greater(t, 100, 50) }, + func(t *mockT) { GreaterT(t, 100, 50) }, + ) + + benchmarkComparison(b, "float64", + func(t *mockT) { Greater(t, 100.5, 50.5) }, + func(t *mockT) { GreaterT(t, 100.5, 50.5) }, + ) + + benchmarkComparison(b, "string", + func(t *mockT) { Greater(t, "beta", "alpha") }, + func(t *mockT) { GreaterT(t, "beta", "alpha") }, + ) } // BenchmarkLess compares Less (reflect) vs LessT (generic). func BenchmarkLess(b *testing.B) { - mockT := &mockT{} - - b.Run("reflect/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Less(mockT, 50, 100) - } - }) - - b.Run("generic/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - LessT(mockT, 50, 100) - } - }) + benchmarkComparison(b, "int", + func(t *mockT) { Less(t, 50, 100) }, + func(t *mockT) { LessT(t, 50, 100) }, + ) } // BenchmarkElementsMatch compares ElementsMatch (reflect) vs ElementsMatchT (generic). func BenchmarkElementsMatch(b *testing.B) { - mockT := &mockT{} - // Small slices (10 elements) smallA := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} smallB := []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - b.Run("reflect/small_10", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatch(mockT, smallA, smallB) - } - }) - - b.Run("generic/small_10", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatchT(mockT, smallA, smallB) - } - }) + benchmarkComparison(b, "small_10", + func(t *mockT) { ElementsMatch(t, smallA, smallB) }, + func(t *mockT) { ElementsMatchT(t, smallA, smallB) }, + ) // Medium slices (100 elements) mediumA := make([]int, 100) @@ -142,20 +133,10 @@ func BenchmarkElementsMatch(b *testing.B) { mediumA[i] = i mediumB[99-i] = i } - - b.Run("reflect/medium_100", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatch(mockT, mediumA, mediumB) - } - }) - - b.Run("generic/medium_100", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatchT(mockT, mediumA, mediumB) - } - }) + benchmarkComparison(b, "medium_100", + func(t *mockT) { ElementsMatch(t, mediumA, mediumB) }, + func(t *mockT) { ElementsMatchT(t, mediumA, mediumB) }, + ) // Large slices (1000 elements) largeA := make([]int, 1000) @@ -164,111 +145,308 @@ func BenchmarkElementsMatch(b *testing.B) { largeA[i] = i largeB[999-i] = i } - - b.Run("reflect/large_1000", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatch(mockT, largeA, largeB) - } - }) - - b.Run("generic/large_1000", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatchT(mockT, largeA, largeB) - } - }) + benchmarkComparison(b, "large_1000", + func(t *mockT) { ElementsMatch(t, largeA, largeB) }, + func(t *mockT) { ElementsMatchT(t, largeA, largeB) }, + ) // String slices stringsA := []string{"apple", "banana", "cherry", "date", "elderberry"} stringsB := []string{"elderberry", "date", "cherry", "banana", "apple"} - - b.Run("reflect/strings_5", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatch(mockT, stringsA, stringsB) - } - }) - - b.Run("generic/strings_5", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - ElementsMatchT(mockT, stringsA, stringsB) - } - }) + benchmarkComparison(b, "strings_5", + func(t *mockT) { ElementsMatch(t, stringsA, stringsB) }, + func(t *mockT) { ElementsMatchT(t, stringsA, stringsB) }, + ) } // BenchmarkNotElementsMatch compares NotElementsMatch (reflect) vs NotElementsMatchT (generic). func BenchmarkNotElementsMatch(b *testing.B) { - mockT := &mockT{} - - // Slices that don't match sliceA := []int{1, 2, 3, 4, 5} sliceB := []int{1, 2, 3, 4, 6} - - b.Run("reflect", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - NotElementsMatch(mockT, sliceA, sliceB) - } - }) - - b.Run("generic", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - NotElementsMatchT(mockT, sliceA, sliceB) - } - }) + benchmarkComparison(b, "", + func(t *mockT) { NotElementsMatch(t, sliceA, sliceB) }, + func(t *mockT) { NotElementsMatchT(t, sliceA, sliceB) }, + ) } // BenchmarkPositive compares Positive (reflect) vs PositiveT (generic). func BenchmarkPositive(b *testing.B) { - mockT := &mockT{} + benchmarkComparison(b, "int", + func(t *mockT) { Positive(t, 42) }, + func(t *mockT) { PositiveT(t, 42) }, + ) + benchmarkComparison(b, "float64", + func(t *mockT) { Positive(t, 42.5) }, + func(t *mockT) { PositiveT(t, 42.5) }, + ) +} - b.Run("reflect/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Positive(mockT, 42) - } - }) +// BenchmarkNegative compares Negative (reflect) vs NegativeT (generic). +func BenchmarkNegative(b *testing.B) { + benchmarkComparison(b, "int", + func(t *mockT) { Negative(t, -42) }, + func(t *mockT) { NegativeT(t, -42) }, + ) +} - b.Run("generic/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - PositiveT(mockT, 42) - } - }) +// BenchmarkEqual compares Equal (reflect) vs EqualT (generic). +func BenchmarkEqual(b *testing.B) { + benchmarkComparison(b, "int", + func(t *mockT) { Equal(t, 42, 42) }, + func(t *mockT) { EqualT(t, 42, 42) }, + ) + benchmarkComparison(b, "string", + func(t *mockT) { Equal(t, "hello", "hello") }, + func(t *mockT) { EqualT(t, "hello", "hello") }, + ) + benchmarkComparison(b, "float64", + func(t *mockT) { Equal(t, 3.14, 3.14) }, + func(t *mockT) { EqualT(t, 3.14, 3.14) }, + ) +} - b.Run("reflect/float64", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Positive(mockT, 42.5) - } - }) +// BenchmarkNotEqual compares NotEqual (reflect) vs NotEqualT (generic). +func BenchmarkNotEqual(b *testing.B) { + benchmarkComparison(b, "int", + func(t *mockT) { NotEqual(t, 42, 43) }, + func(t *mockT) { NotEqualT(t, 42, 43) }, + ) +} - b.Run("generic/float64", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - PositiveT(mockT, 42.5) - } - }) +// BenchmarkSame compares Same (reflect) vs SameT (generic). +func BenchmarkSame(b *testing.B) { + v := 42 + p := &v + benchmarkComparison(b, "", + func(t *mockT) { Same(t, p, p) }, + func(t *mockT) { SameT(t, p, p) }, + ) } -// BenchmarkNegative compares Negative (reflect) vs NegativeT (generic). -func BenchmarkNegative(b *testing.B) { - mockT := &mockT{} +// BenchmarkNotSame compares NotSame (reflect) vs NotSameT (generic). +func BenchmarkNotSame(b *testing.B) { + v1, v2 := 42, 42 + p1, p2 := &v1, &v2 + benchmarkComparison(b, "", + func(t *mockT) { NotSame(t, p1, p2) }, + func(t *mockT) { NotSameT(t, p1, p2) }, + ) +} - b.Run("reflect/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - Negative(mockT, -42) - } - }) +// BenchmarkGreaterOrEqual compares GreaterOrEqual (reflect) vs GreaterOrEqualT (generic). +func BenchmarkGreaterOrEqual(b *testing.B) { + benchmarkComparison(b, "int", + func(t *mockT) { GreaterOrEqual(t, 100, 50) }, + func(t *mockT) { GreaterOrEqualT(t, 100, 50) }, + ) +} - b.Run("generic/int", func(b *testing.B) { - b.ReportAllocs() - for b.Loop() { - NegativeT(mockT, -42) - } - }) +// BenchmarkLessOrEqual compares LessOrEqual (reflect) vs LessOrEqualT (generic). +func BenchmarkLessOrEqual(b *testing.B) { + benchmarkComparison(b, "int", + func(t *mockT) { LessOrEqual(t, 50, 100) }, + func(t *mockT) { LessOrEqualT(t, 50, 100) }, + ) +} + +// BenchmarkIsIncreasing compares IsIncreasing (reflect) vs IsIncreasingT (generic). +func BenchmarkIsIncreasing(b *testing.B) { + slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + benchmarkComparison(b, "", + func(t *mockT) { IsIncreasing(t, slice) }, + func(t *mockT) { IsIncreasingT(t, slice) }, + ) +} + +// BenchmarkIsNonIncreasing compares IsNonIncreasing (reflect) vs IsNonIncreasingT (generic). +func BenchmarkIsNonIncreasing(b *testing.B) { + slice := []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + benchmarkComparison(b, "", + func(t *mockT) { IsNonIncreasing(t, slice) }, + func(t *mockT) { IsNonIncreasingT(t, slice) }, + ) +} + +// BenchmarkIsDecreasing compares IsDecreasing (reflect) vs IsDecreasingT (generic). +func BenchmarkIsDecreasing(b *testing.B) { + slice := []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + benchmarkComparison(b, "", + func(t *mockT) { IsDecreasing(t, slice) }, + func(t *mockT) { IsDecreasingT(t, slice) }, + ) +} + +// BenchmarkIsNonDecreasing compares IsNonDecreasing (reflect) vs IsNonDecreasingT (generic). +func BenchmarkIsNonDecreasing(b *testing.B) { + slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + benchmarkComparison(b, "", + func(t *mockT) { IsNonDecreasing(t, slice) }, + func(t *mockT) { IsNonDecreasingT(t, slice) }, + ) +} + +// BenchmarkContains compares Contains (reflect) vs various ContainsT generics. +func BenchmarkContains(b *testing.B) { + benchmarkComparison(b, "string", + func(t *mockT) { Contains(t, "hello world", "world") }, + func(t *mockT) { StringContainsT(t, "hello world", "world") }, + ) + + slice := []int{1, 2, 3, 4, 5} + benchmarkComparison(b, "slice", + func(t *mockT) { Contains(t, slice, 3) }, + func(t *mockT) { SliceContainsT(t, slice, 3) }, + ) + + m := map[string]int{"a": 1, "b": 2, "c": 3} + benchmarkComparison(b, "map", + func(t *mockT) { Contains(t, m, "b") }, + func(t *mockT) { MapContainsT(t, m, "b") }, + ) +} + +// BenchmarkNotContains compares NotContains (reflect) vs various NotContainsT generics. +func BenchmarkNotContains(b *testing.B) { + benchmarkComparison(b, "string", + func(t *mockT) { NotContains(t, "hello world", "xyz") }, + func(t *mockT) { StringNotContainsT(t, "hello world", "xyz") }, + ) + + slice := []int{1, 2, 3, 4, 5} + benchmarkComparison(b, "slice", + func(t *mockT) { NotContains(t, slice, 99) }, + func(t *mockT) { SliceNotContainsT(t, slice, 99) }, + ) +} + +// BenchmarkSubset compares Subset (reflect) vs SubsetT (generic). +func BenchmarkSubset(b *testing.B) { + list := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + subset := []int{2, 4, 6} + benchmarkComparison(b, "", + func(t *mockT) { Subset(t, list, subset) }, + func(t *mockT) { SliceSubsetT(t, list, subset) }, + ) +} + +// BenchmarkNotSubset compares NotSubset (reflect) vs NotSubsetT (generic). +func BenchmarkNotSubset(b *testing.B) { + list := []int{1, 2, 3, 4, 5} + subset := []int{1, 2, 99} + benchmarkComparison(b, "", + func(t *mockT) { NotSubset(t, list, subset) }, + func(t *mockT) { SliceNotSubsetT(t, list, subset) }, + ) +} + +// BenchmarkInDelta compares InDelta (reflect) vs InDeltaT (generic). +func BenchmarkInDelta(b *testing.B) { + benchmarkComparison(b, "float64", + func(t *mockT) { InDelta(t, 3.14, 3.15, 0.02) }, + func(t *mockT) { InDeltaT(t, 3.14, 3.15, 0.02) }, + ) + benchmarkComparison(b, "int", + func(t *mockT) { InDelta(t, 100, 102, 5) }, + func(t *mockT) { InDeltaT(t, 100, 102, 5) }, + ) +} + +// BenchmarkInEpsilon compares InEpsilon (reflect) vs InEpsilonT (generic). +func BenchmarkInEpsilon(b *testing.B) { + benchmarkComparison(b, "float64", + func(t *mockT) { InEpsilon(t, 100.0, 101.0, 0.02) }, + func(t *mockT) { InEpsilonT(t, 100.0, 101.0, 0.02) }, + ) +} + +// BenchmarkTrue compares True (reflect) vs TrueT (generic). +func BenchmarkTrue(b *testing.B) { + benchmarkComparison(b, "", + func(t *mockT) { True(t, true) }, + func(t *mockT) { TrueT(t, true) }, + ) +} + +// BenchmarkFalse compares False (reflect) vs FalseT (generic). +func BenchmarkFalse(b *testing.B) { + benchmarkComparison(b, "", + func(t *mockT) { False(t, false) }, + func(t *mockT) { FalseT(t, false) }, + ) +} + +// BenchmarkRegexp compares Regexp (reflect) vs RegexpT (generic). +func BenchmarkRegexp(b *testing.B) { + benchmarkComparison(b, "string", + func(t *mockT) { Regexp(t, `^\d{3}-\d{4}$`, "123-4567") }, + func(t *mockT) { RegexpT(t, `^\d{3}-\d{4}$`, "123-4567") }, + ) +} + +// BenchmarkNotRegexp compares NotRegexp (reflect) vs NotRegexpT (generic). +func BenchmarkNotRegexp(b *testing.B) { + benchmarkComparison(b, "string", + func(t *mockT) { NotRegexp(t, `^\d{3}-\d{4}$`, "hello") }, + func(t *mockT) { NotRegexpT(t, `^\d{3}-\d{4}$`, "hello") }, + ) +} + +// BenchmarkSorted benchmarks SortedT (generic-only, no reflection version). +func BenchmarkSorted(b *testing.B) { + slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + benchmarkGenericOnly(b, func(t *mockT) { SortedT(t, slice) }) +} + +// BenchmarkNotSorted benchmarks NotSortedT (generic-only, no reflection version). +func BenchmarkNotSorted(b *testing.B) { + slice := []int{1, 3, 2, 4, 5} + benchmarkGenericOnly(b, func(t *mockT) { NotSortedT(t, slice) }) +} + +// BenchmarkSeqContains compares Contains (reflect) vs SeqContainsT (generic) for iterators. +func BenchmarkSeqContains(b *testing.B) { + slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + benchmarkComparison(b, "", + func(t *mockT) { Contains(t, slices.Values(slice), 5) }, + func(t *mockT) { SeqContainsT(t, slices.Values(slice), 5) }, + ) +} + +// BenchmarkSeqNotContains compares NotContains (reflect) vs SeqNotContainsT (generic) for iterators. +func BenchmarkSeqNotContains(b *testing.B) { + slice := []int{1, 2, 3, 4, 5} + benchmarkComparison(b, "", + func(t *mockT) { NotContains(t, slices.Values(slice), 99) }, + func(t *mockT) { SeqNotContainsT(t, slices.Values(slice), 99) }, + ) +} + +// BenchmarkJSONEq compares JSONEq (reflect) vs JSONEqT (generic). +func BenchmarkJSONEq(b *testing.B) { + expected := `{"name":"John","age":30}` + actual := `{"age":30,"name":"John"}` + benchmarkComparison(b, "", + func(t *mockT) { JSONEq(t, expected, actual) }, + func(t *mockT) { JSONEqT(t, expected, actual) }, + ) +} + +// BenchmarkIsOfType compares IsType (reflect) vs IsOfTypeT (generic). +func BenchmarkIsOfType(b *testing.B) { + var expected int + actual := 42 + benchmarkComparison(b, "", + func(t *mockT) { IsType(t, expected, actual) }, + func(t *mockT) { IsOfTypeT[int](t, actual) }, + ) +} + +// BenchmarkIsNotOfType compares IsNotType (reflect) vs IsNotOfTypeT (generic). +func BenchmarkIsNotOfType(b *testing.B) { + var expected int + actual := "string" + benchmarkComparison(b, "", + func(t *mockT) { IsNotType(t, expected, actual) }, + func(t *mockT) { IsNotOfTypeT[int](t, actual) }, + ) }