runOneTest runs a single test in a single package via `go test -run` with a per-attempt -timeout. It returns the test's outcome (outcomePass / outcomeFail / outcomeSkip), the wall-clock time spent on this attempt (used for the per-test retry budget), and any captured test logs. On panic, timeout, o
(ctx context.Context, pkg, testName string, perAttemptTimeout time.Duration, attemptNum int, goTestArgs, testArgs []string)
| 461 | // On panic, timeout, or any other failure mode where the test does not emit a |
| 462 | // pass/fail/skip JSON event, outcome is reported as outcomeFail. |
| 463 | func runOneTest(ctx context.Context, pkg, testName string, perAttemptTimeout time.Duration, attemptNum int, goTestArgs, testArgs []string) (outcome testOutcome, wallDur time.Duration, logs bytes.Buffer, err error) { |
| 464 | goTestArgs, perAttemptTimeout = extractTimeout(goTestArgs, perAttemptTimeout) |
| 465 | testArgs, perAttemptTimeout = extractTimeout(testArgs, perAttemptTimeout) |
| 466 | args := []string{"test", "-json"} |
| 467 | args = append(args, goTestArgs...) |
| 468 | args = append(args, "-timeout", perAttemptTimeout.String()) |
| 469 | args = append(args, pkg) |
| 470 | args = append(args, "--run", "^("+regexp.QuoteMeta(testName)+")$") |
| 471 | args = append(args, testArgs...) |
| 472 | |
| 473 | if debug { |
| 474 | fmt.Println("running", strings.Join(args, " ")) |
| 475 | } |
| 476 | cmd := exec.CommandContext(ctx, "go", args...) |
| 477 | // Strip TS_TEST_SHARD so the child doesn't try to shard inside a |
| 478 | // single-test retry — we are telling it exactly what to run. |
| 479 | cmd.Env = slices.DeleteFunc(os.Environ(), func(s string) bool { |
| 480 | return strings.HasPrefix(s, "TS_TEST_SHARD=") |
| 481 | }) |
| 482 | cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", flakytest.FlakeAttemptEnv, attemptNum)) |
| 483 | r, perr := cmd.StdoutPipe() |
| 484 | if perr != nil { |
| 485 | return "", 0, logs, fmt.Errorf("stdout pipe: %w", perr) |
| 486 | } |
| 487 | defer r.Close() |
| 488 | cmd.Stderr = os.Stderr |
| 489 | |
| 490 | wallStart := time.Now() |
| 491 | if err := cmd.Start(); err != nil { |
| 492 | return "", 0, logs, fmt.Errorf("starting go test: %w", err) |
| 493 | } |
| 494 | |
| 495 | s := bufio.NewScanner(r) |
| 496 | for s.Scan() { |
| 497 | var ev goTestOutput |
| 498 | if err := json.Unmarshal(s.Bytes(), &ev); err != nil { |
| 499 | continue |
| 500 | } |
| 501 | if ev.Test == "" { |
| 502 | continue // package-level events ignored for single-test runs |
| 503 | } |
| 504 | // Collapse subtests to parent. |
| 505 | parent, _, _ := strings.Cut(ev.Test, "/") |
| 506 | if parent != testName { |
| 507 | continue |
| 508 | } |
| 509 | switch ev.Action { |
| 510 | case "pass", "fail", "skip": |
| 511 | if ev.Test == testName { |
| 512 | outcome = testOutcome(ev.Action) |
| 513 | } |
| 514 | case "output": |
| 515 | logs.WriteString(ev.Output) |
| 516 | } |
| 517 | } |
| 518 | waitErr := cmd.Wait() |
| 519 | wallDur = time.Since(wallStart) |
| 520 | if scanErr := s.Err(); scanErr != nil && err == nil { |
no test coverage detected
searching dependent graphs…