|
26 | 26 |
|
27 | 27 | type Runner struct { |
28 | 28 | ProjectPath string // Path to the Go project directory. |
| 29 | + prettyProjectPath string // Go project package path, formatted for pretty printing. |
29 | 30 | Verbose bool // If true, provides detailed logging. |
30 | 31 | RunCount int // Number of times to run the tests. |
31 | 32 | UseRace bool // Enable race detector. |
@@ -66,21 +67,12 @@ func (r *Runner) RunTests() (*reports.TestReport, error) { |
66 | 67 | } |
67 | 68 | } |
68 | 69 |
|
69 | | - results, err := parseTestResults(jsonFilePaths) |
| 70 | + results, err := parseTestResults(r.RunCount, jsonFilePaths) |
70 | 71 | if err != nil { |
71 | 72 | return nil, fmt.Errorf("failed to parse test results: %w", err) |
72 | 73 | } |
73 | | - for index, report := range results { |
74 | | - if report.Runs > r.RunCount { // Panics can introduce double-counting test failures, this is a hacky correction for it |
75 | | - if report.Panic && report.Failures > report.Runs { |
76 | | - results[index].Failures = report.Runs |
77 | | - } else { |
78 | | - fmt.Printf("WARN: '%s' has %d test runs, exceeding expected amount in an unexpected way, this may be due to oddly presented panics\n", report.TestName, report.Runs) |
79 | | - } |
80 | | - } |
81 | | - } |
82 | 74 | return &reports.TestReport{ |
83 | | - GoProject: r.ProjectPath, |
| 75 | + GoProject: r.prettyProjectPath, |
84 | 76 | TestRunCount: r.RunCount, |
85 | 77 | RaceDetection: r.UseRace, |
86 | 78 | ExcludedTests: r.SkipTests, |
@@ -132,6 +124,10 @@ func (r *Runner) runTests(packageName string) (string, bool, error) { |
132 | 124 | } |
133 | 125 | defer tmpFile.Close() |
134 | 126 |
|
| 127 | + r.prettyProjectPath, err = prettyProjectPath(r.ProjectPath) |
| 128 | + if err != nil { |
| 129 | + return "", false, fmt.Errorf("failed to get pretty project path: %w", err) |
| 130 | + } |
135 | 131 | // Run the command with output directed to the file |
136 | 132 | cmd := exec.Command("go", args...) |
137 | 133 | cmd.Dir = r.ProjectPath |
@@ -179,7 +175,7 @@ func (e entry) String() string { |
179 | 175 | // panics and failures at that point. |
180 | 176 | // Subtests add more complexity, as panics in subtests are only reported in their parent's output, |
181 | 177 | // and cannot be accurately attributed to the subtest that caused them. |
182 | | -func parseTestResults(filePaths []string) ([]reports.TestResult, error) { |
| 178 | +func parseTestResults(expectedRuns int, filePaths []string) ([]reports.TestResult, error) { |
183 | 179 | var ( |
184 | 180 | testDetails = make(map[string]*reports.TestResult) // Holds run, pass counts, and other details for each test |
185 | 181 | panickedPackages = map[string]struct{}{} // Packages with tests that panicked |
@@ -385,15 +381,23 @@ func parseTestResults(filePaths []string) ([]reports.TestResult, error) { |
385 | 381 | testDetails[subTestKey].Outputs = append(testDetails[subTestKey].Outputs, "Panic in parent test") |
386 | 382 | } |
387 | 383 | } else { |
388 | | - fmt.Printf("WARN: subtest %s has no results\n", subTestKey) |
| 384 | + fmt.Printf("WARN: expected to fine subtest '%s' inside parent test '%s', but not found\n", parentTestKey, subTestKey) |
389 | 385 | } |
390 | 386 | } |
391 | 387 | } |
392 | 388 | } else { |
393 | | - fmt.Printf("WARN: parent test %s has no results\n", parentTestKey) |
| 389 | + fmt.Printf("WARN: expected to find parent test '%s' for sub tests, but not found\n", parentTestKey) |
394 | 390 | } |
395 | 391 | } |
396 | 392 | for _, result := range testDetails { |
| 393 | + if result.Runs > expectedRuns { // Panics can introduce double-counting test failures, this is a hacky correction for it |
| 394 | + if result.Panic { |
| 395 | + result.Failures = expectedRuns |
| 396 | + result.Runs = expectedRuns |
| 397 | + } else { |
| 398 | + fmt.Printf("WARN: '%s' has %d test runs, exceeding expected amount of %d in an unexpected way, this may be due to oddly presented panics\n", result.TestName, result.Runs, expectedRuns) |
| 399 | + } |
| 400 | + } |
397 | 401 | // If a package panicked, all tests in that package will be marked as panicking |
398 | 402 | if _, panicked := panickedPackages[result.TestPackage]; panicked { |
399 | 403 | result.PackagePanic = true |
@@ -445,3 +449,41 @@ func parseSubTest(testName string) (parentTestName, subTestName string) { |
445 | 449 | } |
446 | 450 | return parts[0], parts[1] |
447 | 451 | } |
| 452 | + |
| 453 | +// prettyProjectPath returns the project path formatted for pretty printing in results |
| 454 | +func prettyProjectPath(projectPath string) (string, error) { |
| 455 | + // Walk up the directory structure to find go.mod |
| 456 | + absPath, err := filepath.Abs(projectPath) |
| 457 | + if err != nil { |
| 458 | + return "", fmt.Errorf("failed to resolve absolute path: %w", err) |
| 459 | + } |
| 460 | + dir := absPath |
| 461 | + for { |
| 462 | + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { |
| 463 | + break |
| 464 | + } |
| 465 | + |
| 466 | + parent := filepath.Dir(dir) |
| 467 | + if parent == dir { // Reached the root without finding go.mod |
| 468 | + return "", fmt.Errorf("go.mod not found in project path, started at %s, ended at %s", projectPath, dir) |
| 469 | + } |
| 470 | + dir = parent |
| 471 | + } |
| 472 | + |
| 473 | + // Read go.mod to extract the module path |
| 474 | + goModPath := filepath.Join(dir, "go.mod") |
| 475 | + goModData, err := os.ReadFile(goModPath) |
| 476 | + if err != nil { |
| 477 | + return "", fmt.Errorf("failed to read go.mod: %w", err) |
| 478 | + } |
| 479 | + |
| 480 | + for _, line := range strings.Split(string(goModData), "\n") { |
| 481 | + if strings.HasPrefix(line, "module ") { |
| 482 | + goProject := strings.TrimSpace(strings.TrimPrefix(line, "module ")) |
| 483 | + projectPath = strings.TrimPrefix(projectPath, goProject) |
| 484 | + return filepath.Join(goProject, projectPath), nil |
| 485 | + } |
| 486 | + } |
| 487 | + |
| 488 | + return "", fmt.Errorf("module path not found in go.mod") |
| 489 | +} |
0 commit comments