@ -29,7 +29,8 @@ import (
const maxAttempts = 3
const maxAttempts = 3
type testAttempt struct {
type testAttempt struct {
name testName
pkg string // "tailscale.com/types/key"
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
@ -37,11 +38,6 @@ type testAttempt struct {
pkgFinished bool
pkgFinished bool
}
}
type testName struct {
pkg string // "tailscale.com/types/key"
name string // "TestFoo"
}
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.
@ -98,7 +94,7 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []st
} ( )
} ( )
jd := json . NewDecoder ( r )
jd := json . NewDecoder ( r )
resultMap := make ( map [ testName ] * testAttempt )
resultMap := make ( map [ string ] map [ string ] * testAttempt ) // pkg -> test -> testAttempt
for {
for {
var goOutput goTestOutput
var goOutput goTestOutput
if err := jd . Decode ( & goOutput ) ; err != nil {
if err := jd . Decode ( & goOutput ) ; err != nil {
@ -116,27 +112,34 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []st
}
}
panic ( err )
panic ( err )
}
}
pkg := goOutput . Package
pkgTests := resultMap [ pkg ]
if goOutput . Test == "" {
if goOutput . Test == "" {
switch goOutput . Action {
switch goOutput . Action {
case "fail" , "pass" , "skip" :
case "fail" , "pass" , "skip" :
for _ , test := range pkgTests {
if test . outcome == "" {
test . outcome = "fail"
ch <- test
}
}
ch <- & testAttempt {
ch <- & testAttempt {
name : testName {
pkg : goOutput . Package ,
pkg : goOutput . Package ,
} ,
outcome : goOutput . Action ,
outcome : goOutput . Action ,
pkgFinished : true ,
pkgFinished : true ,
}
}
}
}
continue
continue
}
}
name := testName {
if pkgTests == nil {
pkg : goOutput . Package ,
pkg Tests = make ( map [ string ] * testAttempt )
name: goOutput . Test ,
resultMap[ pkg ] = pkgTests
}
}
testName := goOutput . Test
if test , _ , isSubtest := strings . Cut ( goOutput . Test , "/" ) ; isSubtest {
if test , _ , isSubtest := strings . Cut ( goOutput . Test , "/" ) ; isSubtest {
name. n ame = test
testN ame = test
if goOutput . Action == "output" {
if goOutput . Action == "output" {
resultMap [ n ame] . logs . WriteString ( goOutput . Output )
resultMap [ pkg] [ testN ame] . logs . WriteString ( goOutput . Output )
}
}
continue
continue
}
}
@ -144,17 +147,18 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []st
case "start" :
case "start" :
// ignore
// ignore
case "run" :
case "run" :
resultMap [ name ] = & testAttempt {
pkgTests [ testName ] = & testAttempt {
name : name ,
pkg : pkg ,
testName : testName ,
}
}
case "skip" , "pass" , "fail" :
case "skip" , "pass" , "fail" :
resultMap[ n ame] . outcome = goOutput . Action
pkgTests[ testN ame] . outcome = goOutput . Action
ch <- resultMap[ n ame]
ch <- pkgTests[ testN ame]
case "output" :
case "output" :
if strings . TrimSpace ( goOutput . Output ) == flakytest . FlakyTestLogMessage {
if strings . TrimSpace ( goOutput . Output ) == flakytest . FlakyTestLogMessage {
resultMap[ n ame] . isMarkedFlaky = true
pkgTests[ testN ame] . isMarkedFlaky = true
} else {
} else {
resultMap[ n ame] . logs . WriteString ( goOutput . Output )
pkgTests[ testN ame] . logs . WriteString ( goOutput . Output )
}
}
}
}
}
}
@ -247,13 +251,13 @@ func main() {
go runTests ( ctx , thisRun . attempt , pt , otherArgs , ch )
go runTests ( ctx , thisRun . attempt , pt , otherArgs , ch )
for tr := range ch {
for tr := range ch {
if tr . pkgFinished {
if tr . pkgFinished {
if tr . outcome == "fail" && len ( toRetry [ tr . name. pkg] ) == 0 {
if tr . outcome == "fail" && len ( toRetry [ tr . pkg] ) == 0 {
// If a package fails and we don't have any tests to
// If a package fails and we don't have any tests to
// retry, then we should fail. This typically happens
// retry, then we should fail. This typically happens
// when a package times out.
// when a package times out.
failed = true
failed = true
}
}
printPkgOutcome ( tr . name. pkg, tr . outcome , thisRun . attempt )
printPkgOutcome ( tr . pkg, tr . outcome , thisRun . attempt )
continue
continue
}
}
if * v || tr . outcome == "fail" {
if * v || tr . outcome == "fail" {
@ -263,7 +267,7 @@ func main() {
continue
continue
}
}
if tr . isMarkedFlaky {
if tr . isMarkedFlaky {
toRetry [ tr . name. pkg] = append ( toRetry [ tr . name. pkg ] , tr . name . n ame)
toRetry [ tr . pkg] = append ( toRetry [ tr . pkg] , tr . testN ame)
} else {
} else {
failed = true
failed = true
}
}