Skip to content

Commit 36b10a8

Browse files
committed
Save raw output files
1 parent 42bd4bf commit 36b10a8

File tree

4 files changed

+104
-141
lines changed

4 files changed

+104
-141
lines changed

tools/flakeguard/.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ test_results_*.json
33
debug_outputs
44
example_results/
55
example*.json
6-
example*.md
6+
example*.md
7+
test-output-*.json
8+
transformed-output-*.json
9+
flakeguard_raw_output/
10+
flakeguard_raw_output_transformed/

tools/flakeguard/run_example.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ run_flakeguard_example() {
1616
--skip-tests=$skip_tests \
1717
--max-pass-ratio=1 \
1818
--race=false \
19-
--output-json=$output_file
19+
--main-results-path=$output_file
2020
local EXIT_CODE=$?
2121
if [ $EXIT_CODE -eq 2 ]; then
2222
echo "ERROR: Flakeguard encountered an error while running tests"

tools/flakeguard/runner/runner.go

Lines changed: 55 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package runner
22

33
import (
44
"bufio"
5-
"bytes"
65
"encoding/json"
76
"errors"
87
"fmt"
9-
"io"
108
"os"
119
"os/exec"
1210
"path/filepath"
@@ -21,6 +19,11 @@ import (
2119
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/reports"
2220
)
2321

22+
const (
23+
RawOutputDir = "./flakeguard_raw_output"
24+
RawOutputTransformedDir = "./flakeguard_raw_output_transformed"
25+
)
26+
2427
var (
2528
startPanicRe = regexp.MustCompile(`^panic:`)
2629
startRaceRe = regexp.MustCompile(`^WARNING: DATA RACE`)
@@ -40,44 +43,33 @@ type Runner struct {
4043
FailFast bool // Stop on first test failure.
4144
SkipTests []string // Test names to exclude.
4245
SelectTests []string // Test names to include.
43-
CollectRawOutput bool // Set to true to collect test output for later inspection.
4446
OmitOutputsOnSuccess bool // Set to true to omit test outputs on success.
4547
MaxPassRatio float64 // Maximum pass ratio threshold for a test to be considered flaky.
4648
IgnoreParentFailuresOnSubtests bool // Ignore failures in parent tests when only subtests fail.
47-
rawOutputs map[string]*bytes.Buffer
49+
rawOutputFiles []string // Raw output files for each test run.
50+
transformedOutputFiles []string // Transformed output files for each test run.
4851
}
4952

5053
// RunTestPackages executes the tests for each provided package and aggregates all results.
5154
// It returns all test results and any error encountered during testing.
5255
// RunTestPackages executes the tests for each provided package and aggregates all results.
5356
func (r *Runner) RunTestPackages(packages []string) ([]reports.TestResult, error) {
54-
var jsonFilePaths []string
5557
// Initial runs.
5658
for _, p := range packages {
57-
for i := 0; i < r.RunCount; i++ {
58-
if r.CollectRawOutput { // Collect raw output for debugging.
59-
if r.rawOutputs == nil {
60-
r.rawOutputs = make(map[string]*bytes.Buffer)
61-
}
62-
if _, exists := r.rawOutputs[p]; !exists {
63-
r.rawOutputs[p] = &bytes.Buffer{}
64-
}
65-
separator := strings.Repeat("-", 80)
66-
r.rawOutputs[p].WriteString(fmt.Sprintf("Run %d\n%s\n", i+1, separator))
67-
}
68-
jsonFilePath, passed, err := r.runTestPackage(p)
59+
for i := range r.RunCount {
60+
jsonFilePath, passed, err := r.runTestPackage(p, i)
6961
if err != nil {
7062
return nil, fmt.Errorf("failed to run tests in package %s: %w", p, err)
7163
}
72-
jsonFilePaths = append(jsonFilePaths, jsonFilePath)
64+
r.rawOutputFiles = append(r.rawOutputFiles, jsonFilePath)
7365
if !passed && r.FailFast {
7466
break
7567
}
7668
}
7769
}
7870

7971
// Parse initial results.
80-
results, err := r.parseTestResults(jsonFilePaths, "run", r.RunCount)
72+
results, err := r.parseTestResults("run", r.RunCount)
8173
if err != nil {
8274
return nil, fmt.Errorf("failed to parse test results: %w", err)
8375
}
@@ -89,39 +81,31 @@ func (r *Runner) RunTestPackages(packages []string) ([]reports.TestResult, error
8981
// that produces the same JSON lines that 'go test -json' would produce on stdout.
9082
// It captures those lines in a temp file, then parses them for pass/fail/panic/race data.
9183
func (r *Runner) RunTestCmd(testCmd []string) ([]reports.TestResult, error) {
92-
var jsonOutputPaths []string
93-
9484
for i := range r.RunCount {
9585
jsonOutputPath, passed, err := r.runCmd(testCmd, i)
9686
if err != nil {
9787
return nil, fmt.Errorf("failed to run test command: %w", err)
9888
}
99-
jsonOutputPaths = append(jsonOutputPaths, jsonOutputPath)
89+
r.rawOutputFiles = append(r.rawOutputFiles, jsonOutputPath)
10090
if !passed && r.FailFast {
10191
break
10292
}
10393
}
10494

105-
results, err := r.parseTestResults(jsonOutputPaths, "run", r.RunCount)
95+
results, err := r.parseTestResults("run", r.RunCount)
10696
if err != nil {
10797
return nil, fmt.Errorf("failed to parse test results: %w", err)
10898
}
10999

110100
return results, nil
111101
}
112102

113-
// RawOutputs retrieves the raw output from the test runs, if CollectRawOutput enabled.
114-
// packageName : raw output
115-
func (r *Runner) RawOutputs() map[string]*bytes.Buffer {
116-
return r.rawOutputs
117-
}
118-
119103
type exitCoder interface {
120104
ExitCode() int
121105
}
122106

123107
// runTestPackage runs the tests for a given package and returns the path to the output file.
124-
func (r *Runner) runTestPackage(packageName string) (string, bool, error) {
108+
func (r *Runner) runTestPackage(packageName string, runCount int) (string, bool, error) {
125109
args := []string{"test", packageName, "-json"}
126110
if r.GoTestCountFlag != nil {
127111
args = append(args, fmt.Sprintf("-count=%d", *r.GoTestCountFlag))
@@ -151,25 +135,26 @@ func (r *Runner) runTestPackage(packageName string) (string, bool, error) {
151135
args = append(args, fmt.Sprintf("-run=^%s$", selectPattern))
152136
}
153137

154-
if r.Verbose {
155-
log.Info().Str("command", fmt.Sprintf("go %s\n", strings.Join(args, " "))).Msg("Running command")
138+
err := os.MkdirAll(RawOutputDir, 0o755)
139+
if err != nil {
140+
return "", false, fmt.Errorf("failed to create raw output directory: %w", err)
156141
}
157-
158142
// Create a temporary file to store the output
159-
tmpFile, err := os.CreateTemp("", "test-output-*.json")
143+
saniPackageName := filepath.Base(packageName)
144+
tmpFile, err := os.CreateTemp(RawOutputDir, fmt.Sprintf("test-output-%s-%d-*.json", saniPackageName, runCount))
160145
if err != nil {
161146
return "", false, fmt.Errorf("failed to create temp file: %w", err)
162147
}
163148
defer tmpFile.Close()
164149

150+
if r.Verbose {
151+
log.Info().Str("raw output file", tmpFile.Name()).Str("command", fmt.Sprintf("go %s\n", strings.Join(args, " "))).Msg("Running command")
152+
}
153+
165154
// Run the command with output directed to the file
166155
cmd := exec.Command("go", args...)
167156
cmd.Dir = r.ProjectPath
168-
if r.CollectRawOutput {
169-
cmd.Stdout = io.MultiWriter(tmpFile, r.rawOutputs[packageName])
170-
} else {
171-
cmd.Stdout = tmpFile
172-
}
157+
cmd.Stdout = tmpFile
173158

174159
err = cmd.Run()
175160
if err != nil {
@@ -188,29 +173,21 @@ func (r *Runner) runTestPackage(packageName string) (string, bool, error) {
188173
// and returns the temp file path, whether the test passed, and an error if any.
189174
func (r *Runner) runCmd(testCmd []string, runIndex int) (tempFilePath string, passed bool, err error) {
190175
// Create temp file for JSON output
191-
tmpFile, err := os.CreateTemp("", fmt.Sprintf("test-output-cmd-run%d-*.json", runIndex+1))
176+
err = os.MkdirAll(RawOutputDir, 0o755)
177+
if err != nil {
178+
return "", false, fmt.Errorf("failed to create raw output directory: %w", err)
179+
}
180+
tmpFile, err := os.CreateTemp(RawOutputDir, fmt.Sprintf("test-output-cmd-run%d-*.json", runIndex+1))
192181
if err != nil {
193182
err = fmt.Errorf("failed to create temp file: %w", err)
194-
return
183+
return "", false, err
195184
}
196185
defer tmpFile.Close()
197186

198187
cmd := exec.Command(testCmd[0], testCmd[1:]...) //nolint:gosec
199188
cmd.Dir = r.ProjectPath
200189

201-
// If collecting raw output, write to both file & buffer
202-
if r.CollectRawOutput {
203-
if r.rawOutputs == nil {
204-
r.rawOutputs = make(map[string]*bytes.Buffer)
205-
}
206-
key := fmt.Sprintf("customCmd-run%d", runIndex+1)
207-
if _, exists := r.rawOutputs[key]; !exists {
208-
r.rawOutputs[key] = &bytes.Buffer{}
209-
}
210-
cmd.Stdout = io.MultiWriter(tmpFile, r.rawOutputs[key])
211-
} else {
212-
cmd.Stdout = tmpFile
213-
}
190+
cmd.Stdout = tmpFile
214191
cmd.Stderr = os.Stderr
215192

216193
err = cmd.Run()
@@ -231,12 +208,12 @@ func (r *Runner) runCmd(testCmd []string, runIndex int) (tempFilePath string, pa
231208
// Some other error that doesn't implement ExitCode() => real error
232209
tempFilePath = ""
233210
err = fmt.Errorf("error running test command: %w", err)
234-
return
211+
return tempFilePath, passed, err
235212
}
236213

237214
// Otherwise, test passed
238215
passed = true
239-
return
216+
return tempFilePath, passed, nil
240217
}
241218

242219
type entry struct {
@@ -262,14 +239,16 @@ func (e entry) String() string {
262239
// panics and failures at that point.
263240
// Subtests add more complexity, as panics in subtests are only reported in their parent's output,
264241
// and cannot be accurately attributed to the subtest that caused them.
265-
func (r *Runner) parseTestResults(jsonOutputPaths []string, runPrefix string, runCount int) ([]reports.TestResult, error) {
242+
func (r *Runner) parseTestResults(runPrefix string, runCount int) ([]reports.TestResult, error) {
243+
var parseFilePaths = r.rawOutputFiles
244+
266245
// If the option is enabled, transform each JSON output file before parsing.
267246
if r.IgnoreParentFailuresOnSubtests {
268-
transformedPaths, err := r.transformTestOutputFiles(jsonOutputPaths)
247+
err := r.transformTestOutputFiles(r.rawOutputFiles)
269248
if err != nil {
270249
return nil, err
271250
}
272-
jsonOutputPaths = transformedPaths
251+
parseFilePaths = r.transformedOutputFiles
273252
}
274253

275254
var (
@@ -286,7 +265,7 @@ func (r *Runner) parseTestResults(jsonOutputPaths []string, runPrefix string, ru
286265

287266
runNumber := 0
288267
// Process each file
289-
for _, filePath := range jsonOutputPaths {
268+
for _, filePath := range parseFilePaths {
290269
runNumber++
291270
runID := fmt.Sprintf("%s%d", runPrefix, runNumber)
292271
file, err := os.Open(filePath)
@@ -521,13 +500,9 @@ func (r *Runner) parseTestResults(jsonOutputPaths []string, runPrefix string, ru
521500
if err := scanner.Err(); err != nil {
522501
return nil, fmt.Errorf("reading test output file: %w", err)
523502
}
524-
// Clean up file after parsing
525503
if err = file.Close(); err != nil {
526504
log.Warn().Err(err).Str("file", filePath).Msg("failed to close file")
527505
}
528-
// if err = os.Remove(filePath); err != nil {
529-
// log.Warn().Err(err).Str("file", filePath).Msg("failed to delete file")
530-
// }
531506
}
532507

533508
var results []reports.TestResult
@@ -591,32 +566,34 @@ func (r *Runner) parseTestResults(jsonOutputPaths []string, runPrefix string, ru
591566

592567
// transformTestOutputFiles transforms the test output JSON files to ignore parent failures when only subtests fail.
593568
// It returns the paths to the transformed files.
594-
func (r *Runner) transformTestOutputFiles(filePaths []string) ([]string, error) {
595-
transformedPaths := make([]string, len(filePaths))
596-
for i, origPath := range filePaths {
569+
func (r *Runner) transformTestOutputFiles(filePaths []string) error {
570+
err := os.MkdirAll(RawOutputDir, 0o755)
571+
if err != nil {
572+
return fmt.Errorf("failed to create raw output directory: %w", err)
573+
}
574+
for _, origPath := range filePaths {
597575
inFile, err := os.Open(origPath)
598576
if err != nil {
599-
return nil, fmt.Errorf("failed to open original file %s: %w", origPath, err)
577+
return fmt.Errorf("failed to open original file %s: %w", origPath, err)
600578
}
601579
// Create a temporary file for the transformed output.
602-
outFile, err := os.CreateTemp("", "transformed-output-*.json")
580+
outFile, err := os.CreateTemp(RawOutputTransformedDir, "transformed-raw-output-*.json")
603581
if err != nil {
604582
inFile.Close()
605-
return nil, fmt.Errorf("failed to create transformed temp file: %w", err)
583+
return fmt.Errorf("failed to create transformed temp file: %w", err)
606584
}
607585
// Transform the JSON output.
608586
// The transformer option is set to ignore parent failures when only subtests fail.
609587
err = transformer.TransformJSON(inFile, outFile, transformer.NewOptions(true))
610588
inFile.Close()
611589
outFile.Close()
612590
if err != nil {
613-
return nil, fmt.Errorf("failed to transform output file %s: %v", origPath, err)
591+
return fmt.Errorf("failed to transform output file %s: %v", origPath, err)
614592
}
615593
// Use the transformed file path.
616-
transformedPaths[i] = outFile.Name()
617-
os.Remove(origPath)
594+
r.transformedOutputFiles = append(r.transformedOutputFiles, outFile.Name())
618595
}
619-
return transformedPaths, nil
596+
return nil
620597
}
621598

622599
// attributePanicToTest properly attributes panics to the test that caused them.
@@ -674,8 +651,6 @@ func (r *Runner) RerunFailedTests(failedTests []reports.TestResult, rerunCount i
674651
log.Info().Msgf("Rerunning failing tests grouped by package: %v", failingTestsByPackage)
675652
}
676653

677-
var rerunJsonOutputPaths []string
678-
679654
// Rerun each failing test package up to RerunCount times
680655
for i := range rerunCount {
681656
for pkg, tests := range failingTestsByPackage {
@@ -710,15 +685,15 @@ func (r *Runner) RerunFailedTests(failedTests []reports.TestResult, rerunCount i
710685
if err != nil {
711686
return nil, nil, fmt.Errorf("error on rerunCmd for package %s: %w", pkg, err)
712687
}
713-
rerunJsonOutputPaths = append(rerunJsonOutputPaths, jsonOutputPath)
688+
r.rawOutputFiles = append(r.rawOutputFiles, jsonOutputPath)
714689
}
715690
}
716691

717692
// Parse all rerun results at once with a consistent prefix
718-
rerunResults, err := r.parseTestResults(rerunJsonOutputPaths, "rerun", rerunCount)
693+
rerunResults, err := r.parseTestResults("rerun", rerunCount)
719694
if err != nil {
720-
return nil, rerunJsonOutputPaths, fmt.Errorf("failed to parse rerun results: %w", err)
695+
return nil, r.rawOutputFiles, fmt.Errorf("failed to parse rerun results: %w", err)
721696
}
722697

723-
return rerunResults, rerunJsonOutputPaths, nil
698+
return rerunResults, r.rawOutputFiles, nil
724699
}

0 commit comments

Comments
 (0)