Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions hack/migrate-testify/advisory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright 2026 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"
"go/ast"
"go/token"
)

// eventuallyWithAdvisoryMessage is the advisory text emitted for every
// EventuallyWith / EventuallyWithT (pre-rename) call encountered in pass 1.
//
// It is informational: no source rewrite is performed. The semantics of
// CollectT.FailNow in go-openapi/testify/v2.4 match stretchr/testify
// (tick-only abort, poller retries), so code migrating from stretchr does
// not need to change. The advisory exists to surface the new Cancel()
// escape hatch and to flag callers for whom the old fork behavior
// (whole-assertion abort on FailNow) may have been load-bearing.
const eventuallyWithAdvisoryMessage = "advisory: EventuallyWith — in v2.4, CollectT.FailNow() aborts only the current tick (matches stretchr). " +
"Use CollectT.Cancel() if you want to abort the whole assertion immediately. " +
"See: https://go-openapi.github.io/testify/usage/migration/index.html#collectt-failnow-vs-cancel"

// eventuallyWithNames lists the EventuallyWith-family call names to watch.
// Names are checked in their pre-rename form (stretchr spelling) and the
// post-rename form, so the advisory fires regardless of migration order.
var eventuallyWithNames = map[string]bool{ //nolint:gochecknoglobals // lookup table
"EventuallyWithT": true,
"EventuallyWithTf": true,
"EventuallyWith": true,
"EventuallyWithf": true,
}

// noteEventuallyWithCancel emits an informational diagnostic for every
// EventuallyWith-family call found in f that targets a testify package.
//
// It does not modify the AST. The advisory is emitted once per call site.
//
//nolint:unparam // return count left for future use.
func noteEventuallyWithCancel(f *ast.File, aliases map[string]string, fset *token.FileSet, rpt *report, filename string) int {
count := 0

ast.Inspect(f, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}

sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}

if !eventuallyWithNames[sel.Sel.Name] {
return true
}

if !isTestifySelector(sel, aliases) {
return true
}

pos := fset.Position(call.Pos())
rpt.warn(filename, pos.Line, fmt.Sprintf("%s(): %s", sel.Sel.Name, eventuallyWithAdvisoryMessage))
count++

return true
})

return count
}
2 changes: 2 additions & 0 deletions hack/migrate-testify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ func main() {
fmt.Fprintf(os.Stderr, "\nPost-migration checklist:\n")
fmt.Fprintf(os.Stderr, " - Run your linter: the migration may surface pre-existing unchecked linting issues.\n")
fmt.Fprintf(os.Stderr, " - Run your test suite to verify all tests still pass.\n")
fmt.Fprintf(os.Stderr, " - Review any 'advisory:' warnings — no rewrite is applied, but they may require manual attention\n")
fmt.Fprintf(os.Stderr, " (e.g. EventuallyWith advisory: v2.4 splits CollectT.FailNow and CollectT.Cancel).\n")
}

flag.Parse()
Expand Down
7 changes: 6 additions & 1 deletion hack/migrate-testify/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ func migrateFile(fset *token.FileSet, filename string, opts *options, rpt *repor
}
}

// 4. Rename functions and replace PanicTestFunc.
// 4. Advisory: note the new CollectT.Cancel() semantics for every
// EventuallyWith-family call. Runs BEFORE rename so stretchr spellings
// (EventuallyWithT) are also picked up in the diagnostic position.
_ = noteEventuallyWithCancel(f, aliases, fset, rpt, filename)

// 5. Rename functions and replace PanicTestFunc.
changes := renameFunctions(f, aliases, fset, rpt, filename, opts.verbose)
if changes > 0 {
changed = true
Expand Down
8 changes: 8 additions & 0 deletions hack/migrate-testify/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func migrateTestCases() iter.Seq[migrateTestCase] {
input: readTestdata("migrate_incompatible/input.go.txt"),
warnContains: "mock package is not available",
},
{
name: "EventuallyWith advisory",
input: readTestdata("migrate_eventuallywith/input.go.txt"),
warnContains: "CollectT.Cancel()",
},
{
name: "PanicTestFunc replacement",
input: readTestdata("migrate_panic_func/input.go.txt"),
Expand Down Expand Up @@ -92,6 +97,9 @@ func runMigrateSubtest(t *testing.T, c migrateTestCase) {
astutil.RewriteImport(fset, f, old, replacement)
}

// Advisory: note CollectT.Cancel semantics for EventuallyWith-family calls.
noteEventuallyWithCancel(f, aliases, fset, rpt, "test.go")

// Rename functions.
renameFunctions(f, aliases, fset, rpt, "test.go", true)

Expand Down
14 changes: 14 additions & 0 deletions hack/migrate-testify/testdata/migrate_eventuallywith/input.go.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package example

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestEventuallyWith(t *testing.T) {
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assert.True(c, true)
}, 100*time.Millisecond, 20*time.Millisecond)
}
Loading