cmd/testwrapper: include flake URL in JSON metadata

Updates tailscale/corp#14975

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/9585/head
Brad Fitzpatrick 1 year ago committed by Brad Fitzpatrick
parent 2a7b3ada58
commit 856d32b4a9

@ -38,7 +38,7 @@ func Mark(t testing.TB, issue string) {
// We're being run under cmd/testwrapper so send our sentinel message // We're being run under cmd/testwrapper so send our sentinel message
// to stderr. (We avoid doing this when the env is absent to avoid // to stderr. (We avoid doing this when the env is absent to avoid
// spamming people running tests without the wrapper) // spamming people running tests without the wrapper)
fmt.Fprintln(os.Stderr, FlakyTestLogMessage) fmt.Fprintf(os.Stderr, "%s: %s\n", FlakyTestLogMessage, issue)
} }
t.Logf("flakytest: issue tracking this flaky test: %s", issue) t.Logf("flakytest: issue tracking this flaky test: %s", issue)
} }

@ -24,6 +24,7 @@ import (
"time" "time"
xmaps "golang.org/x/exp/maps" xmaps "golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"tailscale.com/cmd/testwrapper/flakytest" "tailscale.com/cmd/testwrapper/flakytest"
) )
@ -34,11 +35,16 @@ type testAttempt struct {
testName string // "TestFoo" testName string // "TestFoo"
outcome string // "pass", "fail", "skip" outcome string // "pass", "fail", "skip"
logs bytes.Buffer logs bytes.Buffer
isMarkedFlaky bool // set if the test is marked as flaky isMarkedFlaky bool // set if the test is marked as flaky
issueURL string // set if the test is marked as flaky
pkgFinished bool pkgFinished bool
} }
// packageTests describes what to run.
// It's also JSON-marshalled to output for analysys tools to parse
// so the fields are all exported.
// TODO(bradfitz): move this type to its own types package?
type packageTests struct { type packageTests struct {
// Pattern is the package Pattern to run. // Pattern is the package Pattern to run.
// Must be a single Pattern, not a list of patterns. // Must be a single Pattern, not a list of patterns.
@ -46,6 +52,8 @@ type packageTests struct {
// Tests is a list of Tests to run. If empty, all Tests in the package are // Tests is a list of Tests to run. If empty, all Tests in the package are
// run. // run.
Tests []string // ["TestFoo", "TestBar"] Tests []string // ["TestFoo", "TestBar"]
// IssueURLs maps from a test name to a URL tracking its flake.
IssueURLs map[string]string // "TestFoo" => "https://github.com/foo/bar/issue/123"
} }
type goTestOutput struct { type goTestOutput struct {
@ -152,8 +160,9 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []st
pkgTests[testName].outcome = goOutput.Action pkgTests[testName].outcome = goOutput.Action
ch <- pkgTests[testName] ch <- pkgTests[testName]
case "output": case "output":
if strings.TrimSpace(goOutput.Output) == flakytest.FlakyTestLogMessage { if suffix, ok := strings.CutPrefix(strings.TrimSpace(goOutput.Output), flakytest.FlakyTestLogMessage); ok {
pkgTests[testName].isMarkedFlaky = true pkgTests[testName].isMarkedFlaky = true
pkgTests[testName].issueURL = strings.TrimPrefix(suffix, ": ")
} else { } else {
pkgTests[testName].logs.WriteString(goOutput.Output) pkgTests[testName].logs.WriteString(goOutput.Output)
} }
@ -244,12 +253,11 @@ func main() {
os.Exit(1) os.Exit(1)
} }
if thisRun.attempt > 1 { if thisRun.attempt > 1 {
fmt.Printf("\n\nAttempt #%d: Retrying flaky tests:\n\n", thisRun.attempt)
j, _ := json.Marshal(thisRun.tests) j, _ := json.Marshal(thisRun.tests)
fmt.Printf("\n\nflakytest failures JSON: %s\n\n", j) fmt.Printf("\n\nAttempt #%d: Retrying flaky tests:\n\nflakytest failures JSON: %s\n\n", thisRun.attempt, j)
} }
toRetry := make(map[string][]string) // pkg -> tests to retry toRetry := make(map[string][]*testAttempt) // pkg -> tests to retry
for _, pt := range thisRun.tests { for _, pt := range thisRun.tests {
ch := make(chan *testAttempt) ch := make(chan *testAttempt)
runErr := make(chan error, 1) runErr := make(chan error, 1)
@ -284,7 +292,7 @@ func main() {
continue continue
} }
if tr.isMarkedFlaky { if tr.isMarkedFlaky {
toRetry[tr.pkg] = append(toRetry[tr.pkg], tr.testName) toRetry[tr.pkg] = append(toRetry[tr.pkg], tr)
} else { } else {
failed = true failed = true
} }
@ -317,10 +325,17 @@ func main() {
} }
for _, pkg := range pkgs { for _, pkg := range pkgs {
tests := toRetry[pkg] tests := toRetry[pkg]
sort.Strings(tests) slices.SortFunc(tests, func(a, b *testAttempt) int { return strings.Compare(a.testName, b.testName) })
issueURLs := map[string]string{} // test name => URL
var testNames []string
for _, ta := range tests {
issueURLs[ta.testName] = ta.issueURL
testNames = append(testNames, ta.testName)
}
nextRun.tests = append(nextRun.tests, &packageTests{ nextRun.tests = append(nextRun.tests, &packageTests{
Pattern: pkg, Pattern: pkg,
Tests: tests, Tests: testNames,
IssueURLs: issueURLs,
}) })
} }
toRun = append(toRun, nextRun) toRun = append(toRun, nextRun)

Loading…
Cancel
Save