Skip to content

Commit 9b2c859

Browse files
committed
Handles subtests and panics
1 parent 990bf47 commit 9b2c859

File tree

8 files changed

+453
-188
lines changed

8 files changed

+453
-188
lines changed

tools/flakeguard/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
tmp_test_flaky*
22
test_results_*.json
3-
debug_outputs/
3+
debug_outputs
44
example_results/
55
example*.json
66
example*.md

tools/flakeguard/cmd/run.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var RunTestsCmd = &cobra.Command{
2525
outputPath, _ := cmd.Flags().GetString("output-json")
2626
maxPassRatio, _ := cmd.Flags().GetFloat64("max-pass-ratio")
2727
skipTests, _ := cmd.Flags().GetStringSlice("skip-tests")
28+
selectTests, _ := cmd.Flags().GetStringSlice("select-tests")
2829
printFailedTests, _ := cmd.Flags().GetBool("print-failed-tests")
2930
useShuffle, _ := cmd.Flags().GetBool("shuffle")
3031
shuffleSeed, _ := cmd.Flags().GetString("shuffle-seed")
@@ -51,6 +52,7 @@ var RunTestsCmd = &cobra.Command{
5152
RunCount: runCount,
5253
UseRace: useRace,
5354
SkipTests: skipTests,
55+
SelectTests: selectTests,
5456
SelectedTestPackages: testPackages,
5557
UseShuffle: useShuffle,
5658
ShuffleSeed: shuffleSeed,
@@ -102,6 +104,7 @@ func init() {
102104
RunTestsCmd.Flags().Bool("fail-fast", false, "Stop on the first test failure")
103105
RunTestsCmd.Flags().String("output-json", "", "Path to output the test results in JSON format")
104106
RunTestsCmd.Flags().StringSlice("skip-tests", nil, "Comma-separated list of test names to skip from running")
107+
RunTestsCmd.Flags().StringSlice("select-tests", nil, "Comma-separated list of test names to specifically run")
105108
RunTestsCmd.Flags().Bool("print-failed-tests", true, "Print failed test results to the console")
106109
RunTestsCmd.Flags().Float64("max-pass-ratio", 1.0, "The maximum (non-inclusive) pass ratio threshold for a test to be considered a failure. Any tests below this pass rate will be considered flaky.")
107110
}

tools/flakeguard/reports/reports.go

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,20 @@ type TestReport struct {
2323

2424
// TestResult represents the result of a single test being run through flakeguard.
2525
type TestResult struct {
26-
TestName string
27-
TestPackage string
28-
Panicked bool // Indicates a test-level panic
29-
Skipped bool // Indicates if the test was skipped
30-
PackagePanicked bool // Indicates a package-level panic
31-
PassRatio float64 // Pass ratio in decimal format like 0.5
32-
Runs int // Count of how many times the test was run
33-
Failures int // Count of how many times the test failed
34-
Successes int // Count of how many times the test passed
35-
Panics int // Count of how many times the test panicked
36-
Races int // Count of how many times the test encountered a data race
37-
Skips int // Count of how many times the test was skipped
38-
Outputs []string `json:"outputs,omitempty"` // Stores outputs for a test
39-
Durations []time.Duration // Stores elapsed time for each run of the test
40-
PackageOutputs []string `json:"package_outputs,omitempty"` // Stores package-level outputs
26+
TestName string
27+
TestPackage string
28+
PackagePanic bool // Indicates a package-level panic
29+
Panic bool // Indicates a test-level panic
30+
Race bool // Indicates if the test caused a data race
31+
Skipped bool // Indicates if the test was skipped
32+
PassRatio float64 // Pass ratio in decimal format like 0.5
33+
Runs int // Count of how many times the test was run
34+
Failures int // Count of how many times the test failed
35+
Successes int // Count of how many times the test passed
36+
Skips int // Count of how many times the test was skipped
37+
Outputs []string `json:"outputs,omitempty"` // Stores outputs for a test
38+
Durations []time.Duration // Stores elapsed time for each run of the test
39+
PackageOutputs []string `json:"package_outputs,omitempty"` // Stores package-level outputs
4140
}
4241

4342
// FilterFailedTests returns a slice of TestResult where the pass ratio is below the specified threshold.
@@ -131,8 +130,8 @@ func AggregateTestResults(folderPath string) (*TestReport, error) {
131130
existingResult.PackageOutputs = append(existingResult.PackageOutputs, result.PackageOutputs...)
132131
existingResult.Successes += result.Successes
133132
existingResult.Failures += result.Failures
134-
existingResult.Panics += result.Panics
135-
existingResult.Races += result.Races
133+
existingResult.Panic = existingResult.Panic || result.Panic
134+
existingResult.Race = existingResult.Race || result.Race
136135
existingResult.Skips += result.Skips
137136
existingResult.PassRatio = 1.0
138137
if existingResult.Runs > 0 {
@@ -178,9 +177,23 @@ func AggregateTestResults(folderPath string) (*TestReport, error) {
178177
}
179178

180179
// PrintTests prints tests in a pretty format
181-
func PrintTests(w io.Writer, tests []TestResult, maxPassRatio float64) (allRuns, passes, fails, panics, skips, races, flakes int) {
180+
func PrintTests(
181+
w io.Writer,
182+
tests []TestResult,
183+
maxPassRatio float64,
184+
) (runs, passes, fails, skips, panickedTests, racedTests, flakyTests int) {
182185
headers := []string{
183-
"**Test Name**", "**Test Package**", "**Package Panicked**", "**Pass Ratio**", "**Runs**", "**Successes**", "**Failures**", "**Panics**", "**Races**", "**Skips**", "**Avg Duration**",
186+
"**Test**",
187+
"**Pass Ratio**",
188+
"**Runs**",
189+
"**Test Panicked?**",
190+
"**Test Race?**",
191+
"**Successes**",
192+
"**Failures**",
193+
"**Skips**",
194+
"**Package**",
195+
"**Package Panicked?**",
196+
"**Avg Duration**",
184197
}
185198

186199
// Build test rows and summary data
@@ -189,40 +202,46 @@ func PrintTests(w io.Writer, tests []TestResult, maxPassRatio float64) (allRuns,
189202
if test.PassRatio < maxPassRatio {
190203
rows = append(rows, []string{
191204
test.TestName,
192-
test.TestPackage,
193-
fmt.Sprintf("%t", test.PackagePanicked),
194205
fmt.Sprintf("%.2f%%", test.PassRatio*100),
195206
fmt.Sprintf("%d", test.Runs),
207+
fmt.Sprintf("%t", test.Panic),
208+
fmt.Sprintf("%t", test.Race),
196209
fmt.Sprintf("%d", test.Successes),
197210
fmt.Sprintf("%d", test.Failures),
198-
fmt.Sprintf("%d", test.Panics),
199-
fmt.Sprintf("%d", test.Races),
200211
fmt.Sprintf("%d", test.Skips),
212+
test.TestPackage,
213+
fmt.Sprintf("%t", test.PackagePanic),
201214
avgDuration(test.Durations).String(),
202215
})
203216
}
204217

205-
allRuns += test.Runs
218+
runs += test.Runs
206219
passes += test.Successes
207220
fails += test.Failures
208221
skips += test.Skips
209-
races += test.Races
210-
panics += test.Panics
211-
if test.PassRatio < maxPassRatio {
212-
flakes++
222+
if test.Panic {
223+
panickedTests++
224+
flakyTests++
225+
} else if test.Race {
226+
racedTests++
227+
flakyTests++
228+
} else if test.PassRatio < maxPassRatio {
229+
flakyTests++
213230
}
214231
}
215232

216233
// Print out summary data
217234
summaryData := [][]string{
218235
{"**Category**", "**Total**"},
219-
{"**Runs**", fmt.Sprint(allRuns)},
236+
{"**Tests**", fmt.Sprint(len(tests))},
237+
{"**Panicked Tests**", fmt.Sprint(panickedTests)},
238+
{"**Raced Tests**", fmt.Sprint(racedTests)},
239+
{"**Flaky Tests**", fmt.Sprint(flakyTests)},
240+
{"**Pass Ratio**", fmt.Sprintf("%.2f%%", float64(passes)/float64(runs)*100)},
241+
{"**Runs**", fmt.Sprint(runs)},
220242
{"**Passes**", fmt.Sprint(passes)},
221243
{"**Failures**", fmt.Sprint(fails)},
222-
{"**Panics**", fmt.Sprint(panics)},
223244
{"**Skips**", fmt.Sprint(skips)},
224-
{"**Races**", fmt.Sprint(races)},
225-
{"**Flaky Tests**", fmt.Sprint(flakes)},
226245
}
227246
colWidths := make([]int, len(rows[0]))
228247

@@ -276,11 +295,12 @@ func PrintTests(w io.Writer, tests []TestResult, maxPassRatio float64) (allRuns,
276295
fmt.Fprintln(w, "|"+buffer.String())
277296
}
278297

279-
// Print table
280-
printRow(headers)
281-
printSeparator()
282-
for _, row := range rows {
283-
printRow(row)
298+
if len(rows) == 0 {
299+
printRow(headers)
300+
printSeparator()
301+
for _, row := range rows {
302+
printRow(row)
303+
}
284304
}
285305
return
286306
}
@@ -332,7 +352,7 @@ func MarkdownSummary(w io.Writer, testReport *TestReport, maxPassRatio float64)
332352
return
333353
}
334354

335-
allRuns, passes, _, _, _, _, flakes := PrintTests(testsData, tests, maxPassRatio)
355+
allRuns, passes, _, _, _, _, _ := PrintTests(testsData, tests, maxPassRatio)
336356
if allRuns > 0 {
337357
avgPassRatio = float64(passes) / float64(allRuns)
338358
}
@@ -341,11 +361,7 @@ func MarkdownSummary(w io.Writer, testReport *TestReport, maxPassRatio float64)
341361
} else {
342362
fmt.Fprint(w, "## No Flakes Found :white_check_mark:\n\n")
343363
}
344-
fmt.Fprintf(w, "Ran `%d` tests `%d` times, and found `%d` flaky tests with an overall `%.2f%%` pass ratio\n\n", len(tests), testReport.TestRunCount, flakes, avgPassRatio*100)
345-
if avgPassRatio < maxPassRatio {
346-
fmt.Fprint(w, "### Flakes\n\n")
347-
fmt.Fprint(w, testsData.String())
348-
}
364+
fmt.Fprint(w, testsData.String())
349365
}
350366

351367
// Helper function to save filtered results and logs to specified paths
@@ -356,7 +372,8 @@ func SaveFilteredResultsAndLogs(outputResultsPath, outputLogsPath string, report
356372
}
357373
jsonFileName := strings.TrimSuffix(outputResultsPath, filepath.Ext(outputResultsPath)) + ".json"
358374
mdFileName := strings.TrimSuffix(outputResultsPath, filepath.Ext(outputResultsPath)) + ".md"
359-
if err := saveReportNoLogs(jsonFileName, report); err != nil {
375+
// no pointer to avoid destroying the original report
376+
if err := saveReportNoLogs(jsonFileName, *report); err != nil {
360377
return fmt.Errorf("error writing filtered results to file: %w", err)
361378
}
362379
summaryFile, err := os.Create(mdFileName)
@@ -374,7 +391,8 @@ func SaveFilteredResultsAndLogs(outputResultsPath, outputLogsPath string, report
374391
if err := os.MkdirAll(filepath.Dir(outputLogsPath), 0755); err != nil { //nolint:gosec
375392
return fmt.Errorf("error creating output directory: %w", err)
376393
}
377-
if err := saveReport(outputLogsPath, report); err != nil {
394+
// no pointer to avoid destroying the original report
395+
if err := saveReport(outputLogsPath, *report); err != nil {
378396
return fmt.Errorf("error writing filtered logs to file: %w", err)
379397
}
380398
fmt.Printf("Test logs saved to %s\n", outputLogsPath)
@@ -385,7 +403,7 @@ func SaveFilteredResultsAndLogs(outputResultsPath, outputLogsPath string, report
385403
// saveReportNoLogs saves the test results to JSON without logs
386404
// as outputs can take up a lot of space and are not always needed.
387405
// Outputs can be saved separately using saveTestOutputs
388-
func saveReportNoLogs(filePath string, report *TestReport) error {
406+
func saveReportNoLogs(filePath string, report TestReport) error {
389407
var filteredResults []TestResult
390408
for _, r := range report.Results {
391409
filteredResult := r
@@ -403,7 +421,7 @@ func saveReportNoLogs(filePath string, report *TestReport) error {
403421
}
404422

405423
// saveReport saves the test results to JSON
406-
func saveReport(filePath string, report *TestReport) error {
424+
func saveReport(filePath string, report TestReport) error {
407425
data, err := json.MarshalIndent(report, "", " ")
408426
if err != nil {
409427
return fmt.Errorf("error marshaling outputs: %v", err)

tools/flakeguard/reports/reports_test.go

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,65 @@ func TestFilterSkippedTests(t *testing.T) {
6969
}
7070

7171
func TestPrintTests(t *testing.T) {
72-
var (
73-
tests = []TestResult{
74-
{
75-
TestName: "Test1",
76-
TestPackage: "package1",
77-
PassRatio: 0.75,
78-
Skipped: false,
79-
Runs: 4,
80-
Outputs: []string{"Output1", "Output2"},
81-
Durations: []time.Duration{time.Millisecond * 1200, time.Millisecond * 900, time.Millisecond * 1100, time.Second},
72+
testcases := []struct {
73+
name string
74+
testResults []TestResult
75+
expectedRuns int
76+
expectedPasses int
77+
expectedFails int
78+
expectedSkippedTests int
79+
expectedPanickedTests int
80+
expectedRacedTests int
81+
expectedFlakyTests int
82+
expectedStringsContain []string
83+
}{
84+
{
85+
name: "single flaky test",
86+
testResults: []TestResult{
87+
{
88+
TestName: "Test1",
89+
TestPackage: "package1",
90+
PassRatio: 0.75,
91+
Skipped: false,
92+
Runs: 4,
93+
Durations: []time.Duration{time.Millisecond * 1200, time.Millisecond * 900, time.Millisecond * 1100, time.Second},
94+
},
8295
},
83-
}
84-
buf bytes.Buffer
85-
)
96+
expectedRuns: 4,
97+
expectedPasses: 3,
98+
expectedFails: 1,
99+
expectedSkippedTests: 0,
100+
expectedPanickedTests: 0,
101+
expectedRacedTests: 0,
102+
expectedFlakyTests: 1,
103+
expectedStringsContain: []string{"Test1", "package1", "75.00%", "false", "1.05s", "4", "0"},
104+
},
105+
}
106+
107+
for _, testCase := range testcases {
108+
tc := testCase
109+
t.Run(tc.name, func(t *testing.T) {
110+
t.Parallel()
111+
var buf bytes.Buffer
112+
113+
runs, passes, fails, skips, panickedTests, racedTests, flakyTests := PrintTests(&buf, tc.testResults, 1.0)
114+
assert.Equal(t, tc.expectedRuns, runs, "wrong number of runs")
115+
assert.Equal(t, tc.expectedPasses, passes, "wrong number of passes")
116+
assert.Equal(t, tc.expectedFails, fails, "wrong number of failures")
117+
assert.Equal(t, tc.expectedSkippedTests, skips, "wrong number of skips")
118+
assert.Equal(t, tc.expectedPanickedTests, panickedTests, "wrong number of panicked tests")
119+
assert.Equal(t, tc.expectedRacedTests, racedTests, "wrong number of raced tests")
120+
assert.Equal(t, tc.expectedFlakyTests, flakyTests, "wrong number of flaky tests")
86121

87-
PrintTests(&buf, tests, 1.0)
122+
// Get the output as a string
123+
output := buf.String()
124+
expectedContains := []string{"Test1", "package1", "75.00%", "false", "1.05s", "4", "0"}
125+
for _, expected := range expectedContains {
126+
assert.Contains(t, output, expected, "output does not contain expected string")
127+
}
128+
})
129+
}
88130

89-
// Get the output as a string
90-
output := buf.String()
91-
expectedContains := "| Test1 | package1 | 75.00% | false | 4 | 0 | 0 | 0 | 0 | 0 | 1.05s |"
92-
assert.Contains(t, output, expectedContains, "output does not contain expected string")
93131
}
94132

95133
// Sorts TestResult slice by TestName and TestPackage for consistent comparison

0 commit comments

Comments
 (0)