runTests runs the tests in pt and sends the results on ch. It sends a testAttempt for each test and a final testAttempt per pkg with pkgFinished set to true. Package build errors will not emit a testAttempt (as no valid JSON is produced) but the [os/exec.ExitError] will be returned. It calls close(c
(ctx context.Context, attempt int, pt *packageTests, goTestArgs, testArgs []string, ch chan<- *testAttempt)
| 262 | // JSON is produced) but the [os/exec.ExitError] will be returned. |
| 263 | // It calls close(ch) when it's done. |
| 264 | func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, testArgs []string, ch chan<- *testAttempt) error { |
| 265 | defer close(ch) |
| 266 | args := []string{"test"} |
| 267 | args = append(args, goTestArgs...) |
| 268 | args = append(args, pt.Pattern) |
| 269 | if len(pt.Tests) > 0 { |
| 270 | // Specific tests requested (e.g. flaky test retry). |
| 271 | runArg := strings.Join(pt.Tests, "|") |
| 272 | args = append(args, "--run", runArg) |
| 273 | } else if shardSpec := os.Getenv("TS_TEST_SHARD"); shardSpec != "" { |
| 274 | // Automatic test-name sharding: list tests and filter by hash. |
| 275 | shardTests, err := testsForShard(ctx, pt.Pattern, shardSpec) |
| 276 | if err != nil { |
| 277 | return err |
| 278 | } |
| 279 | if len(shardTests) == 0 { |
| 280 | ch <- &testAttempt{pkg: pt.Pattern, outcome: outcomeSkip, pkgFinished: true} |
| 281 | return nil |
| 282 | } |
| 283 | quoted := make([]string, len(shardTests)) |
| 284 | for i, name := range shardTests { |
| 285 | quoted[i] = regexp.QuoteMeta(name) |
| 286 | } |
| 287 | args = append(args, "--run", "^("+strings.Join(quoted, "|")+")$") |
| 288 | } |
| 289 | args = append(args, testArgs...) |
| 290 | args = append(args, "-json") |
| 291 | if debug { |
| 292 | fmt.Println("running", strings.Join(args, " ")) |
| 293 | } |
| 294 | cmd := exec.CommandContext(ctx, "go", args...) |
| 295 | r, err := cmd.StdoutPipe() |
| 296 | if err != nil { |
| 297 | log.Printf("error creating stdout pipe: %v", err) |
| 298 | } |
| 299 | defer r.Close() |
| 300 | cmd.Stderr = os.Stderr |
| 301 | |
| 302 | cmd.Env = slices.DeleteFunc(os.Environ(), func(s string) bool { |
| 303 | return strings.HasPrefix(s, "TS_TEST_SHARD=") |
| 304 | }) |
| 305 | cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", flakytest.FlakeAttemptEnv, attempt)) |
| 306 | |
| 307 | if err := cmd.Start(); err != nil { |
| 308 | log.Printf("error starting test: %v", err) |
| 309 | os.Exit(1) |
| 310 | } |
| 311 | |
| 312 | pkgCached := map[string]bool{} |
| 313 | |
| 314 | s := bufio.NewScanner(r) |
| 315 | resultMap := make(map[string]map[string]*testAttempt) // pkg -> test -> testAttempt |
| 316 | for s.Scan() { |
| 317 | var goOutput goTestOutput |
| 318 | if err := json.Unmarshal(s.Bytes(), &goOutput); err != nil { |
| 319 | return fmt.Errorf("failed to parse go test output %q: %w", s.Bytes(), err) |
| 320 | } |
| 321 | pkg := cmp.Or( |
no test coverage detected
searching dependent graphs…