diff --git a/codegen/internal/generator/funcmaps/funcmaps.go b/codegen/internal/generator/funcmaps/funcmaps.go index 5eae1f784..2a446eae6 100644 --- a/codegen/internal/generator/funcmaps/funcmaps.go +++ b/codegen/internal/generator/funcmaps/funcmaps.go @@ -99,7 +99,7 @@ func comment(str string) string { func rTrimEmpty(lines []string) []string { var i int - for i = len(lines) - 1; i >= 0; i-- { + for i = len(lines) - 1; i >= 0; i-- { //nolint:modernize // slicesbackward: we actually need the index, not the value. if lines[i] != "" { break } diff --git a/codegen/internal/generator/generator.go b/codegen/internal/generator/generator.go index b59f75aaf..7f45a6208 100644 --- a/codegen/internal/generator/generator.go +++ b/codegen/internal/generator/generator.go @@ -122,6 +122,12 @@ func (g *Generator) Generate(opts ...GenerateOption) error { return nil } +// Documentation yields the transformed package model as a [model.Documentation] +// usable by a generation step by the [DocGenerator]. +func (g *Generator) Documentation() model.Documentation { + return g.docs +} + // orphanVariantRx matches a generated build-variant filename for the given target package, // e.g. "assert_assertions_go126.go" or "assert_forward_go126_test.go". The "_go" infix is // what distinguishes a variant file from the default (unsuffixed) ones, which are always @@ -179,7 +185,7 @@ func (g *Generator) sweepOrphanVariants() error { // isGeneratedFile reports whether the file carries our "DO NOT EDIT" generated-code marker. func isGeneratedFile(path string) (bool, error) { - data, err := os.ReadFile(path) //nolint:gosec // path is built from a controlled target directory listing + data, err := os.ReadFile(path) // path is built from a controlled target directory listing if err != nil { return false, fmt.Errorf("can't read candidate orphan %q: %w", path, err) } @@ -258,12 +264,6 @@ func variantTarget(base *model.AssertionPackage, constraint string) *model.Asser return tgt } -// Documentation yields the transformed package model as a [model.Documentation] -// usable by a generation step by the [DocGenerator]. -func (g *Generator) Documentation() model.Documentation { - return g.docs -} - func (g *Generator) initContext(opts []GenerateOption) error { // prepare options g.ctx = &genCtx{ diff --git a/codegen/internal/model/model.go b/codegen/internal/model/model.go index b42bed8cf..c202283a6 100644 --- a/codegen/internal/model/model.go +++ b/codegen/internal/model/model.go @@ -470,6 +470,8 @@ func (t CommentTag) String() string { return "comment-tag-note" case CommentTagDomainDescription: return "comment-tag-domain-description" + case CommentTagOpposite: + return "comment-tag-opposite" default: return "invalid-value" } diff --git a/codegen/internal/scanner/comments/extractor.go b/codegen/internal/scanner/comments/extractor.go index 0f06f2541..be43ed957 100644 --- a/codegen/internal/scanner/comments/extractor.go +++ b/codegen/internal/scanner/comments/extractor.go @@ -247,7 +247,7 @@ func extractCommentFromTypeSpec(typedDeclaration *ast.GenDecl, spec ast.Spec, ob case *ast.ValueSpec: for _, ident := range typedSpec.Names { if ident.Name != object.Name() { - return nil + continue } // return Doc, checking both GenDecl.Doc and ValueSpec.Doc diff --git a/docs/doc-site/api/equality.md b/docs/doc-site/api/equality.md index c8f0eeae4..2c8f35a1a 100644 --- a/docs/doc-site/api/equality.md +++ b/docs/doc-site/api/equality.md @@ -303,7 +303,7 @@ func main() { |--|--| | [`assertions.Equal(t T, expected any, actual any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#Equal) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Equal](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L30) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Equal](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L33) {{% /tab %}} {{< /tabs >}} @@ -434,7 +434,7 @@ type dummyStruct struct { |--|--| | [`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#L217) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualExportedValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L220) {{% /tab %}} {{< /tabs >}} @@ -548,7 +548,7 @@ func main() { |--|--| | [`assertions.EqualT[V comparable](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#L68) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualT](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L71) {{% /tab %}} {{< /tabs >}} @@ -662,7 +662,7 @@ func main() { |--|--| | [`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#L144) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#EqualValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L147) {{% /tab %}} {{< /tabs >}} @@ -773,7 +773,7 @@ func main() { |--|--| | [`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#L259) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Exactly](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L270) {{% /tab %}} {{< /tabs >}} @@ -1111,7 +1111,7 @@ func main() { |--|--| | [`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#L93) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqual](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L96) {{% /tab %}} {{< /tabs >}} @@ -1220,7 +1220,7 @@ func main() { |--|--| | [`assertions.NotEqualT[V comparable](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#L122) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqualT](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L125) {{% /tab %}} {{< /tabs >}} @@ -1333,7 +1333,7 @@ func main() { |--|--| | [`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#L179) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotEqualValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/equal.go#L182) {{% /tab %}} {{< /tabs >}} diff --git a/docs/doc-site/api/file.md b/docs/doc-site/api/file.md index 68d4eabdd..8294a33c3 100644 --- a/docs/doc-site/api/file.md +++ b/docs/doc-site/api/file.md @@ -645,7 +645,7 @@ func testDataPath() string { |--|--| | [`assertions.FileNotEmpty(t T, path string, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#FileNotEmpty) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#FileNotEmpty](https://github.com/go-openapi/testify/blob/master/internal/assertions/file.go#L183) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#FileNotEmpty](https://github.com/go-openapi/testify/blob/master/internal/assertions/file.go#L191) {{% /tab %}} {{< /tabs >}} diff --git a/docs/doc-site/api/number.md b/docs/doc-site/api/number.md index 7d49ed8c1..623a2f1b2 100644 --- a/docs/doc-site/api/number.md +++ b/docs/doc-site/api/number.md @@ -281,7 +281,7 @@ func main() { |--|--| | [`assertions.InDeltaMapValues(t T, expected any, actual any, delta float64, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#InDeltaMapValues) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#InDeltaMapValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/number.go#L375) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#InDeltaMapValues](https://github.com/go-openapi/testify/blob/master/internal/assertions/number.go#L378) {{% /tab %}} {{< /tabs >}} @@ -754,7 +754,7 @@ func main() { |--|--| | [`assertions.InEpsilonSlice(t T, expected any, actual any, epsilon float64, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#InEpsilonSlice) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#InEpsilonSlice](https://github.com/go-openapi/testify/blob/master/internal/assertions/number.go#L430) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#InEpsilonSlice](https://github.com/go-openapi/testify/blob/master/internal/assertions/number.go#L433) {{% /tab %}} {{< /tabs >}} diff --git a/docs/doc-site/api/ordering.md b/docs/doc-site/api/ordering.md index 801ba0223..0fedc3fd8 100644 --- a/docs/doc-site/api/ordering.md +++ b/docs/doc-site/api/ordering.md @@ -270,7 +270,7 @@ func main() { |--|--| | [`assertions.IsDecreasingT[OrderedSlice ~[]E, E Ordered](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#L237) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsDecreasingT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L236) {{% /tab %}} {{< /tabs >}} @@ -611,7 +611,7 @@ func main() { |--|--| | [`assertions.IsNonDecreasing(t T, collection 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#L270) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasing](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L269) {{% /tab %}} {{< /tabs >}} @@ -726,7 +726,7 @@ func main() { |--|--| | [`assertions.IsNonDecreasingT[OrderedSlice ~[]E, E Ordered](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#L305) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNonDecreasingT](https://github.com/go-openapi/testify/blob/master/internal/assertions/order.go#L304) {{% /tab %}} {{< /tabs >}} diff --git a/docs/doc-site/api/type.md b/docs/doc-site/api/type.md index 73243029f..0bfb2a7d2 100644 --- a/docs/doc-site/api/type.md +++ b/docs/doc-site/api/type.md @@ -296,7 +296,7 @@ type myType float64 |--|--| | [`assertions.IsNotOfTypeT[EType any](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#L152) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNotOfTypeT](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L153) {{% /tab %}} {{< /tabs >}} @@ -407,7 +407,7 @@ func main() { |--|--| | [`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#L131) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsNotType](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L132) {{% /tab %}} {{< /tabs >}} @@ -518,7 +518,7 @@ type myType float64 |--|--| | [`assertions.IsOfTypeT[EType any](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#L106) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsOfTypeT](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L107) {{% /tab %}} {{< /tabs >}} @@ -629,7 +629,7 @@ func main() { |--|--| | [`assertions.IsType(t T, expectedType any, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#IsType) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsType](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L84) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#IsType](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L85) {{% /tab %}} {{< /tabs >}} @@ -745,7 +745,7 @@ func main() { |--|--| | [`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#L222) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Kind](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L223) {{% /tab %}} {{< /tabs >}} @@ -856,7 +856,7 @@ func main() { |--|--| | [`assertions.NotImplements(t T, interfaceObject any, object any, msgAndArgs ...any) bool`](https://pkg.go.dev/github.com/go-openapi/testify/v2/internal/assertions#NotImplements) | internal implementation | -**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotImplements](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L53) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotImplements](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L54) {{% /tab %}} {{< /tabs >}} @@ -972,7 +972,7 @@ func main() { |--|--| | [`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#L256) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotKind](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L257) {{% /tab %}} {{< /tabs >}} @@ -1083,7 +1083,7 @@ func main() { |--|--| | [`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#L198) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#NotZero](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L199) {{% /tab %}} {{< /tabs >}} @@ -1194,7 +1194,7 @@ func main() { |--|--| | [`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#L176) +**Source:** [github.com/go-openapi/testify/v2/internal/assertions#Zero](https://github.com/go-openapi/testify/blob/master/internal/assertions/type.go#L177) {{% /tab %}} {{< /tabs >}} diff --git a/internal/assertions/collection.go b/internal/assertions/collection.go index 6fc0e0168..c3deda309 100644 --- a/internal/assertions/collection.go +++ b/internal/assertions/collection.go @@ -294,7 +294,7 @@ func SeqNotContainsT[E comparable](t T, iter iter.Seq[E], element E, msgAndArgs for iterated := range iter { if iterated == element { - return Fail(t, fmt.Sprintf("sequence does not contain %#v", element), msgAndArgs...) + return Fail(t, fmt.Sprintf("sequence should not contain %#v", element), msgAndArgs...) } } diff --git a/internal/assertions/equal.go b/internal/assertions/equal.go index 66747e834..a4b5e0742 100644 --- a/internal/assertions/equal.go +++ b/internal/assertions/equal.go @@ -236,22 +236,22 @@ func EqualExportedValues(t T, expected, actual any, msgAndArgs ...any) bool { return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) } - expected, err = copyExportedFields(expected) + thisExpected, err := copyExportedFields(expected) if err != nil { return Fail(t, fmt.Sprintf("An error occurred while exploring the expected value: %v", err), msgAndArgs...) } - actual, err = copyExportedFields(actual) + thisActual, err := copyExportedFields(actual) if err != nil { return Fail(t, fmt.Sprintf("An error occurred while exploring the actual value: %v", err), msgAndArgs...) } - if !ObjectsAreEqualValues(expected, actual) { - diff := diff(expected, actual) - expected, actual = formatUnequalValues(expected, actual) + if !ObjectsAreEqualValues(thisExpected, thisActual) { + diff := diff(thisExpected, thisActual) + thisExpected, thisActual = formatUnequalValues(thisExpected, thisActual) return Fail(t, fmt.Sprintf("Not equal (comparing only exported fields): \n"+ "expected: %s\n"+ - "actual : %s%s", expected, actual, diff), msgAndArgs...) + "actual : %s%s", thisExpected, thisActual, diff), msgAndArgs...) } return true diff --git a/internal/assertions/error.go b/internal/assertions/error.go index e912afb02..51933d9d7 100644 --- a/internal/assertions/error.go +++ b/internal/assertions/error.go @@ -272,7 +272,7 @@ func NotErrorAs(t T, err error, target any, msgAndArgs ...any) bool { } func unwrapAll(err error) []error { - return appendUnwrapped(nil, err, make(map[error]struct{})) + return appendUnwrapped(nil, err, make(map[error]struct{}), 0) } // appendUnwrapped flattens an error chain, guarding against cyclic chains that @@ -281,7 +281,13 @@ func unwrapAll(err error) []error { // Only comparable errors are tracked: using an incomparable error as a map key // would panic, so those simply skip cycle detection (mirroring how the standard // library's errors.Is guards its own comparisons). -func appendUnwrapped(errs []error, err error, visited map[error]struct{}) []error { +// +// Unwrapping stops at 1000 nest levels, which should cover most practical usage of it. +func appendUnwrapped(errs []error, err error, visited map[error]struct{}, depth int) []error { + if depth > maxDepth { + return nil + } + if err != nil && reflect.TypeOf(err).Comparable() { if _, ok := visited[err]; ok { return errs // cyclic error chain: stop here @@ -297,12 +303,13 @@ func appendUnwrapped(errs []error, err error, visited map[error]struct{}) []erro if next == nil { return errs } - errs = appendUnwrapped(errs, next, visited) + errs = appendUnwrapped(errs, next, visited, depth+1) case interface{ Unwrap() []error }: for _, next := range x.Unwrap() { - errs = appendUnwrapped(errs, next, visited) + errs = appendUnwrapped(errs, next, visited, depth+1) } } + return errs } diff --git a/internal/assertions/file.go b/internal/assertions/file.go index 54dcae64e..dcadc8bb6 100644 --- a/internal/assertions/file.go +++ b/internal/assertions/file.go @@ -145,6 +145,14 @@ func FileEmpty(t T, path string, msgAndArgs ...any) bool { h.Helper() } + return fileEmpty(t, path, 0, msgAndArgs...) +} + +func fileEmpty(t T, path string, depth int, msgAndArgs ...any) bool { + if depth > maxDepth { + return Fail(t, fmt.Sprintf("chain of symlinks too long, stopped at %d nested symlinks", maxDepth), msgAndArgs...) + } + info, err := lstat(path, "file") if err != nil { return Fail(t, err.Error(), msgAndArgs...) @@ -159,7 +167,7 @@ func FileEmpty(t T, path string, msgAndArgs ...any) bool { return Fail(t, err.Error(), msgAndArgs...) } if isSymlink { - return FileEmpty(t, target, msgAndArgs...) + return fileEmpty(t, target, depth+1, msgAndArgs...) } if info.Size() > 0 { @@ -186,6 +194,14 @@ func FileNotEmpty(t T, path string, msgAndArgs ...any) bool { h.Helper() } + return fileNotEmpty(t, path, 0, msgAndArgs...) +} + +func fileNotEmpty(t T, path string, depth int, msgAndArgs ...any) bool { + if depth > maxDepth { + return Fail(t, fmt.Sprintf("chain of symlinks too long, stopped at %d nested symlinks", maxDepth), msgAndArgs...) + } + info, err := lstat(path, "file") if err != nil { return Fail(t, err.Error(), msgAndArgs...) @@ -200,7 +216,7 @@ func FileNotEmpty(t T, path string, msgAndArgs ...any) bool { return Fail(t, err.Error(), msgAndArgs...) } if isSymlink { - return FileNotEmpty(t, target, msgAndArgs...) + return fileNotEmpty(t, target, depth+1, msgAndArgs...) } if info.Size() == 0 { diff --git a/internal/assertions/number.go b/internal/assertions/number.go index 94acb2938..12dc2ecbc 100644 --- a/internal/assertions/number.go +++ b/internal/assertions/number.go @@ -347,7 +347,10 @@ func InDeltaSlice(t T, expected, actual any, delta float64, msgAndArgs ...any) b lenActual := actualSlice.Len() lenExpected := expectedSlice.Len() if lenActual != lenExpected { - return Fail(t, "Parameters must be slice", msgAndArgs...) + return Fail(t, + fmt.Sprintf("Parameters must be slices of equal length to be compared, but got len(expected)=%d != len(actual)=%d", lenExpected, lenActual), + msgAndArgs..., + ) } for i := range lenActual { diff --git a/internal/assertions/order.go b/internal/assertions/order.go index 5d087c297..beb17f6df 100644 --- a/internal/assertions/order.go +++ b/internal/assertions/order.go @@ -215,7 +215,6 @@ func IsDecreasing(t T, collection any, msgAndArgs ...any) bool { 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...) } @@ -320,6 +319,8 @@ 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. +// +// The returned slice always contains either 0 or 2 values. func isStrictlyOrdered(object any, reverseOrder bool) ([]any, bool, error) { objType := reflect.TypeOf(object) if objType == nil { diff --git a/internal/assertions/testing.go b/internal/assertions/testing.go index aa77f72f2..6dda3a4a3 100644 --- a/internal/assertions/testing.go +++ b/internal/assertions/testing.go @@ -115,18 +115,21 @@ func errorWithCallerInfo(t T, offset int, failureMessage string, msgAndArgs ...a } func callerInfo(offset int) []string { - var pc uintptr - var file string - var line int - var name string + var ( + pc uintptr + file string + line int + name string + ) const stackFrameBufferSize = 10 pcs := make([]uintptr, stackFrameBufferSize) callers := []string{} + newOffset := offset for { - n := runtime.Callers(offset, pcs) + n := runtime.Callers(newOffset, pcs) if n == 0 { break @@ -184,7 +187,7 @@ func callerInfo(offset int) []string { } // Next batch - offset += cap(pcs) + newOffset += cap(pcs) } return callers diff --git a/internal/assertions/type.go b/internal/assertions/type.go index c2e7567f4..584b4e58a 100644 --- a/internal/assertions/type.go +++ b/internal/assertions/type.go @@ -25,9 +25,10 @@ func Implements(t T, interfaceObject any, object any, msgAndArgs ...any) bool { h.Helper() } ifType := reflect.TypeOf(interfaceObject) - if ifType == nil { + if ifType == nil || ifType.Kind() != reflect.Pointer { return Fail(t, "interfaceObject must be a pointer to an interface type", msgAndArgs...) } + // ifType is a pointer: Elem() works interfaceType := ifType.Elem() if object == nil { @@ -56,7 +57,7 @@ func NotImplements(t T, interfaceObject any, object any, msgAndArgs ...any) bool h.Helper() } ifType := reflect.TypeOf(interfaceObject) - if ifType == nil { + if ifType == nil || ifType.Kind() != reflect.Pointer { return Fail(t, "interfaceObject must be a pointer to an interface type", msgAndArgs...) } interfaceType := ifType.Elem() diff --git a/internal/assertions/type_test.go b/internal/assertions/type_test.go index f1cb363bb..fa100b362 100644 --- a/internal/assertions/type_test.go +++ b/internal/assertions/type_test.go @@ -24,6 +24,9 @@ func TestTypeImplements(t *testing.T) { if Implements(mock, (*AssertionTesterInterface)(nil), nil) { t.Error("Implements method should return false: nil does not implement AssertionTesterInterface") } + if Implements(mock, "not a pointer", nil) { + t.Error("Implements method should return false: expected must be a pointer") + } } func TestTypeNotImplements(t *testing.T) { @@ -39,6 +42,9 @@ func TestTypeNotImplements(t *testing.T) { if NotImplements(mock, (*AssertionTesterInterface)(nil), nil) { t.Error("NotImplements method should return false: nil can't be checked to be implementing AssertionTesterInterface or not") } + if NotImplements(mock, "not a pointer", nil) { + t.Error("Implements method should return false: expected must be a pointer") + } } func TestTypeIsType(t *testing.T) { diff --git a/internal/difflib/matcher.go b/internal/difflib/matcher.go index 68a797ca9..7ec9339a7 100644 --- a/internal/difflib/matcher.go +++ b/internal/difflib/matcher.go @@ -3,6 +3,8 @@ package difflib +import "slices" + type Match struct { A int B int @@ -76,16 +78,16 @@ func (m *SequenceMatcher) SetSeqs(a, b []string) { // SetSeq1 sets the first sequence to be compared. The second sequence to be compared is not changed. // -// [SequenceMatcher] computes and caches detailed information about the second -// sequence, so if you want to compare one sequence S against many sequences, -// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other -// sequences. +// [SequenceMatcher] computes and caches detailed information about the second sequence, +// so if you want to compare one sequence S against many sequences, +// use [SequenceMatcher.SetSeq2] once and call .SetSeq1(x) repeatedly for each of the other sequences. // // See also [SequenceMatcher.SetSeqs] and [SequenceMatcher.SetSeq2]. func (m *SequenceMatcher) SetSeq1(a []string) { - if &a == &m.a { + if slices.Compare(a, m.a) == 0 { return } + m.a = a m.matchingBlocks = nil m.opCodes = nil @@ -93,9 +95,10 @@ func (m *SequenceMatcher) SetSeq1(a []string) { // SetSeq2 sets the second sequence to be compared. The first sequence to be compared is not changed. func (m *SequenceMatcher) SetSeq2(b []string) { - if &b == &m.b { + if slices.Compare(b, m.b) == 0 { return } + m.b = b m.matchingBlocks = nil m.opCodes = nil diff --git a/internal/spew/common.go b/internal/spew/common.go index 8f6bbd460..cdf5c52f7 100644 --- a/internal/spew/common.go +++ b/internal/spew/common.go @@ -286,7 +286,7 @@ func printHexPtr(w io.Writer, p uintptr) { // Stringer/error methods and invokes them if so. Returns true if the value // was handled. func tryHandleMethods(cs *ConfigState, w io.Writer, v reflect.Value, kind reflect.Kind) bool { - if cs.DisableMethods && (!cs.EnableTimeStringer || !isTime(v)) { + if cs.DisableMethods && (!cs.EnableTimeStringer || !isTime(v, 0)) { return false } if kind == reflect.Invalid || kind == reflect.Interface { @@ -384,7 +384,7 @@ type valuesSorter struct { func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { vs := &valuesSorter{values: values, cs: cs} v0 := vs.values[0] - if canSortSimply(v0.Kind()) || isTime(v0) { + if canSortSimply(v0.Kind()) || isTime(v0, 0) { return vs } @@ -459,9 +459,9 @@ func (s *valuesSorter) Less(i, j int) bool { return s.strings[i] < s.strings[j] } -// valueSortLess returns whether the first value should sort before the second -// value. It is used by valueSorter.Less as part of the sort.Interface -// implementation. +// valueSortLess returns whether the first value should sort before the second value. +// +// It is used by valueSorter.Less as part of the sort.Interface implementation. func valueSortLess(a, b reflect.Value) bool { switch a.Kind() { case reflect.Bool: @@ -495,8 +495,8 @@ func valueSortLess(a, b reflect.Value) bool { } fallthrough default: - isTimeA := isTime(a) - isTimeB := isTime(b) + isTimeA := isTime(a, 0) + isTimeB := isTime(b, 0) if !isTimeA || !isTimeB { return a.String() < b.String() } @@ -552,7 +552,7 @@ func timeLess(a, b reflect.Value) bool { return a.String() < b.String() } - return tA.Compare(tB) <= 0 + return tA.Compare(tB) < 0 } // sortValues is a sort function that handles both native types and any type that diff --git a/internal/spew/config.go b/internal/spew/config.go index 7021e491c..81a037af8 100644 --- a/internal/spew/config.go +++ b/internal/spew/config.go @@ -42,7 +42,7 @@ type ConfigState struct { Indent string // MaxDepth controls the maximum number of levels to descend into nested - // data structures. The default, 0, means there is no limit. + // data structures. The default is 1000. The value 0 means there is no limit. // // NOTE: Circular data structures are properly detected, so it is not // necessary to set this value unless you specifically want to limit deeply @@ -107,22 +107,24 @@ type ConfigState struct { } // Config is the active configuration of the top-level functions. +// // The configuration can be changed by modifying the contents of [spew.Config]. var Config = ConfigState{ //nolint:gochecknoglobals // this is global configuration and we leave it for backward-compatibility Indent: " ", EnableTimeStringer: true, + MaxDepth: maxDepth, } // NewDefaultConfig returns a [ConfigState] with the following default settings. // // Indent: " " -// MaxDepth: 0 +// MaxDepth: 1000 // DisableMethods: false // DisablePointerMethods: false // ContinueOnMethod: false // SortKeys: false func NewDefaultConfig() *ConfigState { - return &ConfigState{Indent: " "} + return &ConfigState{Indent: " ", MaxDepth: maxDepth} } // Errorf is a wrapper for [fmt.Errorf] that treats each argument as if it were diff --git a/internal/spew/dump.go b/internal/spew/dump.go index c51f8cbad..c73401d9f 100644 --- a/internal/spew/dump.go +++ b/internal/spew/dump.go @@ -58,8 +58,7 @@ type dumpState struct { cs *ConfigState } -// indent performs indentation according to the depth level and cs.Indent -// option. +// indent performs indentation according to the depth level and cs.Indent option. func (d *dumpState) indent() { if d.ignoreNextIndent { d.ignoreNextIndent = false @@ -69,6 +68,7 @@ func (d *dumpState) indent() { } // unpackValue returns values inside of non-nil interfaces when possible. +// // This is useful for data types like structs, arrays, slices, and maps which // can contain varying types packed inside an interface. func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { diff --git a/internal/spew/time.go b/internal/spew/time.go index 1ab10641d..d1dc87548 100644 --- a/internal/spew/time.go +++ b/internal/spew/time.go @@ -5,6 +5,8 @@ import ( "time" ) +const maxDepth = 1000 + // isTime detects if a value may be assimilated to time.Time. // // It may be time.Time or a *time.Time, @@ -12,7 +14,15 @@ import ( // // Conversely, a struct that embeds a time.Time or *time.Time is not considered a time.Time // and we'll have to dig the individual fields. -func isTime(v reflect.Value) bool { +// +// Drilling down pointers indirections stops at depth 1000. Over that limit, the check assumes +// that this is (probably) not a time.Time... That's a theoretical bound, nothing practical +// describes a **** ... ***time.Time with 1000 stars. +func isTime(v reflect.Value, depth int) bool { + if depth > maxDepth { + return false + } + if !v.IsValid() { return false } @@ -22,7 +32,7 @@ func isTime(v reflect.Value) bool { // for pointers, we reason about the pointer type, because the value may be nil if k == reflect.Pointer && t.Elem().Kind() == reflect.Pointer { - return isTime(v.Elem()) + return isTime(v.Elem(), depth+1) } if k == reflect.Struct || (k == reflect.Pointer && t.Elem().Kind() == reflect.Struct) { diff --git a/internal/spew/time_test.go b/internal/spew/time_test.go index 0075c3113..84376af29 100644 --- a/internal/spew/time_test.go +++ b/internal/spew/time_test.go @@ -16,9 +16,12 @@ func TestIsTime(t *testing.T) { t.Parallel() val := reflect.ValueOf(tt.value) - if result := isTime(val); result != tt.expectedTime { + if result := isTime(val, 0); result != tt.expectedTime { t.Errorf("expected %v to be considered a time.Time", tt.value) } + if result := isTime(val, 1001); result == true { + t.Errorf("expected %v to NOT be considered a time.Time (even if it is: we simulate a depth-bound check)", tt.value) + } }) } }