From 2c5cd53cae4f55ac9d55e69d40134e46aea220b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:12:05 +0000 Subject: [PATCH 1/4] Initial plan From 72b2490eff4e8eef68eb9edba431c0f414b3779c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:21:53 +0000 Subject: [PATCH 2/4] fix: guard assertion functions against nil pointer dereference and add fuzz tests Add nil-safety checks to assertion functions that dereference reflect.TypeOf() results without verifying they are non-nil: - collection.go: isList() panicked when list was nil - compare.go: Positive()/Negative() panicked when value was nil - order.go: isStrictlyOrdered() panicked when collection was nil - type.go: Implements()/NotImplements() panicked when interfaceObject was nil Add property-based and fuzz tests in internal/testintegration/assertions/ using pgregory.net/rapid to exercise assertion functions with arbitrary values (including nil) and verify they never panic. Signed-off-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- internal/assertions/collection.go | 7 +- internal/assertions/compare.go | 12 +- internal/assertions/order.go | 6 +- internal/assertions/type.go | 12 +- .../assertions/assertions_fuzz_test.go | 56 ++++ .../assertions/assertions_test.go | 306 ++++++++++++++++++ internal/testintegration/assertions/doc.go | 23 ++ ...stNilSafetyBinary-20260619051753-8674.fail | 11 + ...SafetyCollections-20260619051754-8674.fail | 11 + ...SafetyCollections-20260619051853-8821.fail | 16 + ...lSafetyComparison-20260619051753-8674.fail | 11 + ...lSafetyComparison-20260619051853-8821.fail | 14 + ...etyExportedValues-20260619051753-8674.fail | 6 + ...TestNilSafetyType-20260619051753-8674.fail | 6 + ...estNilSafetyUnary-20260619051753-8674.fail | 6 + 15 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 internal/testintegration/assertions/assertions_fuzz_test.go create mode 100644 internal/testintegration/assertions/assertions_test.go create mode 100644 internal/testintegration/assertions/doc.go create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyBinary/TestNilSafetyBinary-20260619051753-8674.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051754-8674.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051853-8821.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051753-8674.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051853-8821.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyExportedValues/TestNilSafetyExportedValues-20260619051753-8674.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyType/TestNilSafetyType-20260619051753-8674.fail create mode 100644 internal/testintegration/assertions/testdata/rapid/TestNilSafetyUnary/TestNilSafetyUnary-20260619051753-8674.fail diff --git a/internal/assertions/collection.go b/internal/assertions/collection.go index 084b92ac2..6fc0e0168 100644 --- a/internal/assertions/collection.go +++ b/internal/assertions/collection.go @@ -876,7 +876,12 @@ func containsElement(list any, element any) (ok, found bool) { // isList checks that the provided value is array or slice. func isList(t T, list any, msgAndArgs ...any) (ok bool) { - kind := reflect.TypeOf(list).Kind() + listType := reflect.TypeOf(list) + if listType == nil { + return Fail(t, fmt.Sprintf("%q has an unsupported type , expecting array or slice", list), + msgAndArgs...) + } + kind := listType.Kind() if kind != reflect.Array && kind != reflect.Slice { return Fail(t, fmt.Sprintf("%q has an unsupported type %s, expecting array or slice", list, kind), msgAndArgs...) diff --git a/internal/assertions/compare.go b/internal/assertions/compare.go index 18327d2c4..0b354e120 100644 --- a/internal/assertions/compare.go +++ b/internal/assertions/compare.go @@ -275,7 +275,11 @@ func Positive(t T, e any, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - zero := reflect.Zero(reflect.TypeOf(e)) + eType := reflect.TypeOf(e) + if eType == nil { + return Fail(t, fmt.Sprintf("\"%v\" is not positive", e), msgAndArgs...) + } + zero := reflect.Zero(eType) failMessage := fmt.Sprintf("\"%v\" is not positive", e) return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...) } @@ -322,7 +326,11 @@ func Negative(t T, e any, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - zero := reflect.Zero(reflect.TypeOf(e)) + eType := reflect.TypeOf(e) + if eType == nil { + return Fail(t, fmt.Sprintf("\"%v\" is not negative", e), msgAndArgs...) + } + zero := reflect.Zero(eType) failMessage := fmt.Sprintf("\"%v\" is not negative", e) return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...) } diff --git a/internal/assertions/order.go b/internal/assertions/order.go index 0e86c1203..efc90b94e 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -320,7 +320,11 @@ func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice // 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() + objType := reflect.TypeOf(object) + if objType == nil { + return nil, false, fmt.Errorf("object is not an ordered collection") + } + objKind := objType.Kind() if objKind != reflect.Slice && objKind != reflect.Array { return nil, false, fmt.Errorf("object %T is not an ordered collection", object) } diff --git a/internal/assertions/type.go b/internal/assertions/type.go index e250ebdd4..c2e7567f4 100644 --- a/internal/assertions/type.go +++ b/internal/assertions/type.go @@ -24,7 +24,11 @@ func Implements(t T, interfaceObject any, object any, msgAndArgs ...any) bool { if h, ok := t.(H); ok { h.Helper() } - interfaceType := reflect.TypeOf(interfaceObject).Elem() + ifType := reflect.TypeOf(interfaceObject) + if ifType == nil { + return Fail(t, "interfaceObject must be a pointer to an interface type", msgAndArgs...) + } + interfaceType := ifType.Elem() if object == nil { return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...) @@ -51,7 +55,11 @@ func NotImplements(t T, interfaceObject any, object any, msgAndArgs ...any) bool if h, ok := t.(H); ok { h.Helper() } - interfaceType := reflect.TypeOf(interfaceObject).Elem() + ifType := reflect.TypeOf(interfaceObject) + if ifType == nil { + return Fail(t, "interfaceObject must be a pointer to an interface type", msgAndArgs...) + } + interfaceType := ifType.Elem() if object == nil { return Fail(t, fmt.Sprintf("Cannot check if nil does not implement %v", interfaceType), msgAndArgs...) diff --git a/internal/testintegration/assertions/assertions_fuzz_test.go b/internal/testintegration/assertions/assertions_fuzz_test.go new file mode 100644 index 000000000..e3c29b940 --- /dev/null +++ b/internal/testintegration/assertions/assertions_fuzz_test.go @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package assertions + +import ( + "testing" + + "pgregory.net/rapid" + + "github.com/go-openapi/testify/v2/internal/assertions" +) + +// FuzzAssertionsNilSafety is the fuzzed equivalent of TestNilSafetyUnary +// and TestNilSafetyBinary. +// +// Given a high number of values of different types generated randomly, +// the fuzz engine will alter these values and run assertion functions. +// +// # Property +// +// No assertion function should ever panic, regardless of the inputs. +func FuzzAssertionsNilSafety(f *testing.F) { + prop := func(rt *rapid.T) { + value := genAny().Draw(rt, "value") + other := genAny().Draw(rt, "other") + mock := silentT{} + + noPanic(rt, "Nil", func() { sink = assertions.Nil(mock, value) }) + noPanic(rt, "NotNil", func() { sink = assertions.NotNil(mock, value) }) + noPanic(rt, "Empty", func() { sink = assertions.Empty(mock, value) }) + noPanic(rt, "NotEmpty", func() { sink = assertions.NotEmpty(mock, value) }) + noPanic(rt, "Zero", func() { sink = assertions.Zero(mock, value) }) + noPanic(rt, "NotZero", func() { sink = assertions.NotZero(mock, value) }) + noPanic(rt, "Len", func() { sink = assertions.Len(mock, value, 0) }) + noPanic(rt, "Equal", func() { sink = assertions.Equal(mock, value, other) }) + noPanic(rt, "NotEqual", func() { sink = assertions.NotEqual(mock, value, other) }) + noPanic(rt, "EqualValues", func() { sink = assertions.EqualValues(mock, value, other) }) + noPanic(rt, "Contains", func() { sink = assertions.Contains(mock, value, other) }) + noPanic(rt, "NotContains", func() { sink = assertions.NotContains(mock, value, other) }) + noPanic(rt, "IsType", func() { sink = assertions.IsType(mock, value, other) }) + noPanic(rt, "IsNotType", func() { sink = assertions.IsNotType(mock, value, other) }) + } + + f.Fuzz(rapid.MakeFuzz(prop)) +} + +// noPanic wraps a function call and reports a test error if it panics. +func noPanic(t *rapid.T, name string, fn func()) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("%s panicked: %v", name, r) + } + }() + fn() +} diff --git a/internal/testintegration/assertions/assertions_test.go b/internal/testintegration/assertions/assertions_test.go new file mode 100644 index 000000000..5cac7bccb --- /dev/null +++ b/internal/testintegration/assertions/assertions_test.go @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package assertions + +import ( + "flag" + "fmt" + "os" + "reflect" + "strconv" + "testing" + + "pgregory.net/rapid" + + "github.com/go-openapi/testify/v2/internal/assertions" +) + +func TestMain(m *testing.M) { + os.Args = append(os.Args, "-rapid.checks", strconv.Itoa(testLoad())) + flag.Parse() + + os.Exit(m.Run()) +} + +func testLoad() int { + isCI := os.Getenv("CI") != "" + + if isCI { + return 100 + } + + return 100_000 +} + +// silentT is a T that silently absorbs assertion failures. +type silentT struct{} + +func (silentT) Errorf(string, ...any) {} +func (silentT) Helper() {} + +var sink bool + +// TestNilSafetyUnary verifies that unary assertion functions (taking a single +// value of type any) never panic, even with nil, nil-pointer, or arbitrary +// inputs. +func TestNilSafetyUnary(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(rt *rapid.T) { + value := genAny().Draw(rt, "value") + mock := silentT{} + + // These functions must never panic, regardless of the value passed. + sink = assertions.Nil(mock, value) + sink = assertions.NotNil(mock, value) + sink = assertions.Empty(mock, value) + sink = assertions.NotEmpty(mock, value) + sink = assertions.Zero(mock, value) + sink = assertions.NotZero(mock, value) + sink = assertions.Len(mock, value, 0) + sink = assertions.Len(mock, value, 1) + }) +} + +// TestNilSafetyBinary verifies that binary assertion functions (comparing two +// values of type any) never panic with arbitrary inputs. +func TestNilSafetyBinary(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(rt *rapid.T) { + a := genAny().Draw(rt, "a") + b := genAny().Draw(rt, "b") + mock := silentT{} + + sink = assertions.Equal(mock, a, b) + sink = assertions.NotEqual(mock, a, b) + sink = assertions.EqualValues(mock, a, b) + sink = assertions.NotEqualValues(mock, a, b) + sink = assertions.Exactly(mock, a, b) + sink = assertions.Same(mock, a, b) + sink = assertions.NotSame(mock, a, b) + _ = assertions.ObjectsAreEqual(a, b) + _ = assertions.ObjectsAreEqualValues(a, b) + }) +} + +// TestNilSafetyCollections verifies that collection-oriented assertion +// functions never panic with arbitrary inputs. +func TestNilSafetyCollections(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(rt *rapid.T) { + collection := genAny().Draw(rt, "collection") + element := genAny().Draw(rt, "element") + mock := silentT{} + + sink = assertions.Contains(mock, collection, element) + sink = assertions.NotContains(mock, collection, element) + sink = assertions.Subset(mock, collection, element) + sink = assertions.NotSubset(mock, collection, element) + sink = assertions.ElementsMatch(mock, collection, element) + sink = assertions.NotElementsMatch(mock, collection, element) + sink = assertions.IsIncreasing(mock, collection) + sink = assertions.IsDecreasing(mock, collection) + sink = assertions.IsNonIncreasing(mock, collection) + sink = assertions.IsNonDecreasing(mock, collection) + }) +} + +// TestNilSafetyComparison verifies that comparison/numeric assertion +// functions never panic with arbitrary inputs. +func TestNilSafetyComparison(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(rt *rapid.T) { + a := genAny().Draw(rt, "a") + b := genAny().Draw(rt, "b") + mock := silentT{} + + sink = assertions.Greater(mock, a, b) + sink = assertions.GreaterOrEqual(mock, a, b) + sink = assertions.Less(mock, a, b) + sink = assertions.LessOrEqual(mock, a, b) + sink = assertions.Positive(mock, a) + sink = assertions.Negative(mock, a) + sink = assertions.InDelta(mock, a, b, 1.0) + sink = assertions.InEpsilon(mock, a, b, 0.01) + }) +} + +// TestNilSafetyType verifies that type-checking assertion functions never +// panic with arbitrary inputs. +func TestNilSafetyType(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(rt *rapid.T) { + a := genAny().Draw(rt, "a") + b := genAny().Draw(rt, "b") + mock := silentT{} + + sink = assertions.IsType(mock, a, b) + sink = assertions.IsNotType(mock, a, b) + sink = assertions.Kind(mock, reflect.Int, a) + sink = assertions.NotKind(mock, reflect.Int, a) + sink = assertions.Kind(mock, reflect.Pointer, a) + sink = assertions.NotKind(mock, reflect.Pointer, a) + }) +} + +// TestNilSafetyExportedValues verifies that EqualExportedValues never panics. +func TestNilSafetyExportedValues(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(rt *rapid.T) { + a := genAny().Draw(rt, "a") + b := genAny().Draw(rt, "b") + mock := silentT{} + + sink = assertions.EqualExportedValues(mock, a, b) + }) +} + +// genAny generates random values of many different types, with emphasis on +// nil and nil-pointer edge cases that could trigger nil-pointer dereferences. +func genAny() *rapid.Generator[any] { + return rapid.Custom(func(t *rapid.T) any { + kind := rapid.IntRange(0, 9).Draw(t, "kind") + switch kind { + case 0: + return genNilValue(t) + case 1: + return rapid.Int().Draw(t, "int") + case 2: + return rapid.Float64().Draw(t, "float64") + case 3: + return rapid.String().Draw(t, "string") + case 4: + return rapid.Bool().Draw(t, "bool") + case 5: + return genSlice(t) + case 6: + return genMap(t) + case 7: + return genStruct(t) + case 8: + return genPointer(t) + default: + return rapid.Byte().Draw(t, "byte") + } + }) +} + +// genNilValue produces various nil representations. +func genNilValue(t *rapid.T) any { + variant := rapid.IntRange(0, 11).Draw(t, "nil-variant") + switch variant { + case 0: + return nil + case 1: + return (*int)(nil) + case 2: + return (*string)(nil) + case 3: + return (*[]int)(nil) + case 4: + return (*map[string]int)(nil) + case 5: + return ([]int)(nil) + case 6: + return (map[string]int)(nil) + case 7: + return (chan int)(nil) + case 8: + return (func())(nil) + case 9: + return (*struct{})(nil) + case 10: + return (error)(nil) + default: + return (fmt.Stringer)(nil) + } +} + +func genSlice(t *rapid.T) any { + variant := rapid.IntRange(0, 4).Draw(t, "slice-variant") + switch variant { + case 0: + return rapid.SliceOfN(rapid.Int(), 0, 5).Draw(t, "int-slice") + case 1: + return rapid.SliceOfN(rapid.String(), 0, 5).Draw(t, "string-slice") + case 2: + return rapid.SliceOfN(rapid.Float64(), 0, 5).Draw(t, "float-slice") + case 3: + return []any{nil, rapid.Int().Draw(t, "elem"), "hello", nil} + default: + return []any(nil) + } +} + +func genMap(t *rapid.T) any { + variant := rapid.IntRange(0, 3).Draw(t, "map-variant") + switch variant { + case 0: + return rapid.MapOfN(rapid.String(), rapid.Int(), 0, 5).Draw(t, "string-int-map") + case 1: + return rapid.MapOfN(rapid.String(), rapid.String(), 0, 5).Draw(t, "string-string-map") + case 2: + return map[string]any{"key": nil, "val": rapid.Int().Draw(t, "v")} + default: + return map[string]any(nil) + } +} + +type nested struct { + Inner *int + Name string +} + +type outer struct { + Nested *nested + Value any +} + +func genStruct(t *rapid.T) any { + variant := rapid.IntRange(0, 2).Draw(t, "struct-variant") + switch variant { + case 0: + return nested{ + Inner: nil, + Name: rapid.String().Draw(t, "name"), + } + case 1: + v := rapid.Int().Draw(t, "v") + return outer{ + Nested: &nested{Inner: &v, Name: "test"}, + Value: &v, + } + default: + return outer{ + Nested: nil, + Value: nil, + } + } +} + +func genPointer(t *rapid.T) any { + variant := rapid.IntRange(0, 5).Draw(t, "ptr-type") + switch variant { + case 0: + v := rapid.Int().Draw(t, "int-val") + return &v + case 1: + v := rapid.String().Draw(t, "string-val") + return &v + case 2: + v := rapid.Float64().Draw(t, "float-val") + return &v + case 3: + return (*int)(nil) + case 4: + return (*string)(nil) + default: + return (*struct{})(nil) + } +} diff --git a/internal/testintegration/assertions/doc.go b/internal/testintegration/assertions/doc.go new file mode 100644 index 000000000..466b43c18 --- /dev/null +++ b/internal/testintegration/assertions/doc.go @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +// Package assertions runs integration tests against +// [github.com/go-openapi/testify/v2/internal/assertions]. +// +// It verifies that assertion functions never panic when called with +// arbitrary (including nil) values generated by property-based testing +// and Go native fuzzing. +// +// # Motivation +// +// Assertion functions handle values of type [any] and make heavy use +// of [reflect]. Malformed or nil inputs must never cause a nil-pointer +// dereference or other panic; instead the assertion should simply +// return true or false (and optionally mark the test as failed). +// +// The implementation depends on [pgregory.net/rapid]. +// +// This internal testing functionality is therefore defined as an +// independent module and does not affect the dependencies of +// [github.com/go-openapi/testify/v2]. +package assertions diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyBinary/TestNilSafetyBinary-20260619051753-8674.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyBinary/TestNilSafetyBinary-20260619051753-8674.fail new file mode 100644 index 000000000..1450bbb54 --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyBinary/TestNilSafetyBinary-20260619051753-8674.fail @@ -0,0 +1,11 @@ +# 2026/06/19 05:17:53.922735 [TestNilSafetyBinary] [rapid] draw a: +# +v0.4.8#15055956381287494900 +0x0 +0x0 +0x0 +0x0 +0x6b74f03291620 +0x4 +0x38e38e38e38e4 +0x2 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051754-8674.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051754-8674.fail new file mode 100644 index 000000000..b1c560207 --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051754-8674.fail @@ -0,0 +1,11 @@ +# 2026/06/19 05:17:54.004087 [TestNilSafetyCollections] [rapid] draw collection: +# +v0.4.8#16895031227624166861 +0x0 +0x0 +0x0 +0x0 +0x6b74f03291620 +0x4 +0x38e38e38e38e4 +0x2 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051853-8821.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051853-8821.fail new file mode 100644 index 000000000..ad8be8659 --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyCollections/TestNilSafetyCollections-20260619051853-8821.fail @@ -0,0 +1,16 @@ +# 2026/06/19 05:18:53.755113 [TestNilSafetyCollections] [rapid] draw collection: +# 2026/06/19 05:18:53.755120 [TestNilSafetyCollections] [rapid] draw element: 1 +# +v0.4.8#14540679974311099312 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x1 +0x0 +0x0 +0x1 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051753-8674.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051753-8674.fail new file mode 100644 index 000000000..f93af2fbd --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051753-8674.fail @@ -0,0 +1,11 @@ +# 2026/06/19 05:17:53.887646 [TestNilSafetyComparison] [rapid] draw a: +# +v0.4.8#7344516036865584811 +0x0 +0x0 +0x0 +0x0 +0x38e38e38e38e4 +0x2 +0x38e38e38e38e4 +0x3 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051853-8821.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051853-8821.fail new file mode 100644 index 000000000..2dacfb2be --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyComparison/TestNilSafetyComparison-20260619051853-8821.fail @@ -0,0 +1,14 @@ +# 2026/06/19 05:18:53.797295 [TestNilSafetyComparison] [rapid] draw a: +# 2026/06/19 05:18:53.797300 [TestNilSafetyComparison] [rapid] draw b: "" +# +v0.4.8#13804209869386381879 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x38e38e38e38e4 +0x3 +0x0 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyExportedValues/TestNilSafetyExportedValues-20260619051753-8674.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyExportedValues/TestNilSafetyExportedValues-20260619051753-8674.fail new file mode 100644 index 000000000..3b88bb249 --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyExportedValues/TestNilSafetyExportedValues-20260619051753-8674.fail @@ -0,0 +1,6 @@ +# +v0.4.8#8907104496899871461 +0x38e38e38e38e4 +0x2 +0x38e38e38e38e4 +0x3 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyType/TestNilSafetyType-20260619051753-8674.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyType/TestNilSafetyType-20260619051753-8674.fail new file mode 100644 index 000000000..19985558c --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyType/TestNilSafetyType-20260619051753-8674.fail @@ -0,0 +1,6 @@ +# +v0.4.8#10654126862263660569 +0x38e38e38e38e4 +0x2 +0x38e38e38e38e4 +0x3 \ No newline at end of file diff --git a/internal/testintegration/assertions/testdata/rapid/TestNilSafetyUnary/TestNilSafetyUnary-20260619051753-8674.fail b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyUnary/TestNilSafetyUnary-20260619051753-8674.fail new file mode 100644 index 000000000..470c8c91f --- /dev/null +++ b/internal/testintegration/assertions/testdata/rapid/TestNilSafetyUnary/TestNilSafetyUnary-20260619051753-8674.fail @@ -0,0 +1,6 @@ +# +v0.4.8#14191776748235187193 +0x6b74f03291620 +0x4 +0x38e38e38e38e4 +0x2 \ No newline at end of file From 0db1233bde83e5691455d433e0ecf99fafcaca9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 06:03:05 +0000 Subject: [PATCH 3/4] fix: replace fmt.Errorf with errors.New for static string (perfsprint) Signed-off-by: GitHub Copilot --- internal/assertions/order.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/assertions/order.go b/internal/assertions/order.go index efc90b94e..5d087c297 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -4,6 +4,7 @@ package assertions import ( + "errors" "fmt" "reflect" "slices" @@ -322,7 +323,7 @@ func IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](t T, collection OrderedSlice func isStrictlyOrdered(object any, reverseOrder bool) ([]any, bool, error) { objType := reflect.TypeOf(object) if objType == nil { - return nil, false, fmt.Errorf("object is not an ordered collection") + return nil, false, errors.New("object is not an ordered collection") } objKind := objType.Kind() if objKind != reflect.Slice && objKind != reflect.Array { From e48b8bd0425ba73a1c7ba78dcc482ec6e056569b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 06:15:15 +0000 Subject: [PATCH 4/4] fix: eliminate data race in property tests by removing shared sink variable Signed-off-by: GitHub Copilot --- .../assertions/assertions_fuzz_test.go | 28 +++---- .../assertions/assertions_test.go | 82 +++++++++---------- 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/internal/testintegration/assertions/assertions_fuzz_test.go b/internal/testintegration/assertions/assertions_fuzz_test.go index e3c29b940..d08b5b9ca 100644 --- a/internal/testintegration/assertions/assertions_fuzz_test.go +++ b/internal/testintegration/assertions/assertions_fuzz_test.go @@ -26,20 +26,20 @@ func FuzzAssertionsNilSafety(f *testing.F) { other := genAny().Draw(rt, "other") mock := silentT{} - noPanic(rt, "Nil", func() { sink = assertions.Nil(mock, value) }) - noPanic(rt, "NotNil", func() { sink = assertions.NotNil(mock, value) }) - noPanic(rt, "Empty", func() { sink = assertions.Empty(mock, value) }) - noPanic(rt, "NotEmpty", func() { sink = assertions.NotEmpty(mock, value) }) - noPanic(rt, "Zero", func() { sink = assertions.Zero(mock, value) }) - noPanic(rt, "NotZero", func() { sink = assertions.NotZero(mock, value) }) - noPanic(rt, "Len", func() { sink = assertions.Len(mock, value, 0) }) - noPanic(rt, "Equal", func() { sink = assertions.Equal(mock, value, other) }) - noPanic(rt, "NotEqual", func() { sink = assertions.NotEqual(mock, value, other) }) - noPanic(rt, "EqualValues", func() { sink = assertions.EqualValues(mock, value, other) }) - noPanic(rt, "Contains", func() { sink = assertions.Contains(mock, value, other) }) - noPanic(rt, "NotContains", func() { sink = assertions.NotContains(mock, value, other) }) - noPanic(rt, "IsType", func() { sink = assertions.IsType(mock, value, other) }) - noPanic(rt, "IsNotType", func() { sink = assertions.IsNotType(mock, value, other) }) + noPanic(rt, "Nil", func() { _ = assertions.Nil(mock, value) }) + noPanic(rt, "NotNil", func() { _ = assertions.NotNil(mock, value) }) + noPanic(rt, "Empty", func() { _ = assertions.Empty(mock, value) }) + noPanic(rt, "NotEmpty", func() { _ = assertions.NotEmpty(mock, value) }) + noPanic(rt, "Zero", func() { _ = assertions.Zero(mock, value) }) + noPanic(rt, "NotZero", func() { _ = assertions.NotZero(mock, value) }) + noPanic(rt, "Len", func() { _ = assertions.Len(mock, value, 0) }) + noPanic(rt, "Equal", func() { _ = assertions.Equal(mock, value, other) }) + noPanic(rt, "NotEqual", func() { _ = assertions.NotEqual(mock, value, other) }) + noPanic(rt, "EqualValues", func() { _ = assertions.EqualValues(mock, value, other) }) + noPanic(rt, "Contains", func() { _ = assertions.Contains(mock, value, other) }) + noPanic(rt, "NotContains", func() { _ = assertions.NotContains(mock, value, other) }) + noPanic(rt, "IsType", func() { _ = assertions.IsType(mock, value, other) }) + noPanic(rt, "IsNotType", func() { _ = assertions.IsNotType(mock, value, other) }) } f.Fuzz(rapid.MakeFuzz(prop)) diff --git a/internal/testintegration/assertions/assertions_test.go b/internal/testintegration/assertions/assertions_test.go index 5cac7bccb..c34004311 100644 --- a/internal/testintegration/assertions/assertions_test.go +++ b/internal/testintegration/assertions/assertions_test.go @@ -39,8 +39,6 @@ type silentT struct{} func (silentT) Errorf(string, ...any) {} func (silentT) Helper() {} -var sink bool - // TestNilSafetyUnary verifies that unary assertion functions (taking a single // value of type any) never panic, even with nil, nil-pointer, or arbitrary // inputs. @@ -52,14 +50,14 @@ func TestNilSafetyUnary(t *testing.T) { mock := silentT{} // These functions must never panic, regardless of the value passed. - sink = assertions.Nil(mock, value) - sink = assertions.NotNil(mock, value) - sink = assertions.Empty(mock, value) - sink = assertions.NotEmpty(mock, value) - sink = assertions.Zero(mock, value) - sink = assertions.NotZero(mock, value) - sink = assertions.Len(mock, value, 0) - sink = assertions.Len(mock, value, 1) + _ = assertions.Nil(mock, value) + _ = assertions.NotNil(mock, value) + _ = assertions.Empty(mock, value) + _ = assertions.NotEmpty(mock, value) + _ = assertions.Zero(mock, value) + _ = assertions.NotZero(mock, value) + _ = assertions.Len(mock, value, 0) + _ = assertions.Len(mock, value, 1) }) } @@ -73,13 +71,13 @@ func TestNilSafetyBinary(t *testing.T) { b := genAny().Draw(rt, "b") mock := silentT{} - sink = assertions.Equal(mock, a, b) - sink = assertions.NotEqual(mock, a, b) - sink = assertions.EqualValues(mock, a, b) - sink = assertions.NotEqualValues(mock, a, b) - sink = assertions.Exactly(mock, a, b) - sink = assertions.Same(mock, a, b) - sink = assertions.NotSame(mock, a, b) + _ = assertions.Equal(mock, a, b) + _ = assertions.NotEqual(mock, a, b) + _ = assertions.EqualValues(mock, a, b) + _ = assertions.NotEqualValues(mock, a, b) + _ = assertions.Exactly(mock, a, b) + _ = assertions.Same(mock, a, b) + _ = assertions.NotSame(mock, a, b) _ = assertions.ObjectsAreEqual(a, b) _ = assertions.ObjectsAreEqualValues(a, b) }) @@ -95,16 +93,16 @@ func TestNilSafetyCollections(t *testing.T) { element := genAny().Draw(rt, "element") mock := silentT{} - sink = assertions.Contains(mock, collection, element) - sink = assertions.NotContains(mock, collection, element) - sink = assertions.Subset(mock, collection, element) - sink = assertions.NotSubset(mock, collection, element) - sink = assertions.ElementsMatch(mock, collection, element) - sink = assertions.NotElementsMatch(mock, collection, element) - sink = assertions.IsIncreasing(mock, collection) - sink = assertions.IsDecreasing(mock, collection) - sink = assertions.IsNonIncreasing(mock, collection) - sink = assertions.IsNonDecreasing(mock, collection) + _ = assertions.Contains(mock, collection, element) + _ = assertions.NotContains(mock, collection, element) + _ = assertions.Subset(mock, collection, element) + _ = assertions.NotSubset(mock, collection, element) + _ = assertions.ElementsMatch(mock, collection, element) + _ = assertions.NotElementsMatch(mock, collection, element) + _ = assertions.IsIncreasing(mock, collection) + _ = assertions.IsDecreasing(mock, collection) + _ = assertions.IsNonIncreasing(mock, collection) + _ = assertions.IsNonDecreasing(mock, collection) }) } @@ -118,14 +116,14 @@ func TestNilSafetyComparison(t *testing.T) { b := genAny().Draw(rt, "b") mock := silentT{} - sink = assertions.Greater(mock, a, b) - sink = assertions.GreaterOrEqual(mock, a, b) - sink = assertions.Less(mock, a, b) - sink = assertions.LessOrEqual(mock, a, b) - sink = assertions.Positive(mock, a) - sink = assertions.Negative(mock, a) - sink = assertions.InDelta(mock, a, b, 1.0) - sink = assertions.InEpsilon(mock, a, b, 0.01) + _ = assertions.Greater(mock, a, b) + _ = assertions.GreaterOrEqual(mock, a, b) + _ = assertions.Less(mock, a, b) + _ = assertions.LessOrEqual(mock, a, b) + _ = assertions.Positive(mock, a) + _ = assertions.Negative(mock, a) + _ = assertions.InDelta(mock, a, b, 1.0) + _ = assertions.InEpsilon(mock, a, b, 0.01) }) } @@ -139,12 +137,12 @@ func TestNilSafetyType(t *testing.T) { b := genAny().Draw(rt, "b") mock := silentT{} - sink = assertions.IsType(mock, a, b) - sink = assertions.IsNotType(mock, a, b) - sink = assertions.Kind(mock, reflect.Int, a) - sink = assertions.NotKind(mock, reflect.Int, a) - sink = assertions.Kind(mock, reflect.Pointer, a) - sink = assertions.NotKind(mock, reflect.Pointer, a) + _ = assertions.IsType(mock, a, b) + _ = assertions.IsNotType(mock, a, b) + _ = assertions.Kind(mock, reflect.Int, a) + _ = assertions.NotKind(mock, reflect.Int, a) + _ = assertions.Kind(mock, reflect.Pointer, a) + _ = assertions.NotKind(mock, reflect.Pointer, a) }) } @@ -157,7 +155,7 @@ func TestNilSafetyExportedValues(t *testing.T) { b := genAny().Draw(rt, "b") mock := silentT{} - sink = assertions.EqualExportedValues(mock, a, b) + _ = assertions.EqualExportedValues(mock, a, b) }) }