Skip to content

Commit 01a6fff

Browse files
committed
Integrate go test transformer into Flakeguard
1 parent eaef14f commit 01a6fff

File tree

3 files changed

+78
-40
lines changed

3 files changed

+78
-40
lines changed

tools/flakeguard/cmd/run.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var RunTestsCmd = &cobra.Command{
4141
useShuffle, _ := cmd.Flags().GetBool("shuffle")
4242
shuffleSeed, _ := cmd.Flags().GetString("shuffle-seed")
4343
omitOutputsOnSuccess, _ := cmd.Flags().GetBool("omit-test-outputs-on-success")
44+
ignoreParentFailuresOnSubtests, _ := cmd.Flags().GetBool("ignore-parent-failures-on-subtests")
4445

4546
outputDir := filepath.Dir(outputPath)
4647
initialDirSize, err := getDirSize(outputDir)
@@ -63,7 +64,6 @@ var RunTestsCmd = &cobra.Command{
6364
// Determine test packages
6465
var testPackages []string
6566
if len(testCmdStrings) == 0 {
66-
// No custom command -> parse packages
6767
if testPackagesJson != "" {
6868
if err := json.Unmarshal([]byte(testPackagesJson), &testPackages); err != nil {
6969
log.Error().Err(err).Msg("Error decoding test packages JSON")
@@ -79,18 +79,19 @@ var RunTestsCmd = &cobra.Command{
7979

8080
// Initialize the runner
8181
testRunner := runner.Runner{
82-
ProjectPath: projectPath,
83-
Verbose: true,
84-
RunCount: runCount,
85-
Timeout: timeout,
86-
Tags: tags,
87-
UseRace: useRace,
88-
SkipTests: skipTests,
89-
SelectTests: selectTests,
90-
UseShuffle: useShuffle,
91-
ShuffleSeed: shuffleSeed,
92-
OmitOutputsOnSuccess: omitOutputsOnSuccess,
93-
MaxPassRatio: maxPassRatio,
82+
ProjectPath: projectPath,
83+
Verbose: true,
84+
RunCount: runCount,
85+
Timeout: timeout,
86+
Tags: tags,
87+
UseRace: useRace,
88+
SkipTests: skipTests,
89+
SelectTests: selectTests,
90+
UseShuffle: useShuffle,
91+
ShuffleSeed: shuffleSeed,
92+
OmitOutputsOnSuccess: omitOutputsOnSuccess,
93+
MaxPassRatio: maxPassRatio,
94+
IgnoreParentFailuresOnSubtests: ignoreParentFailuresOnSubtests,
9495
}
9596

9697
// Run the tests
@@ -105,7 +106,6 @@ var RunTestsCmd = &cobra.Command{
105106
os.Exit(ErrorExitCode)
106107
}
107108
} else {
108-
// Otherwise, use the normal go test approach
109109
testReport, err = testRunner.RunTestPackages(testPackages)
110110
if err != nil {
111111
log.Fatal().Err(err).Msg("Error running test packages")
@@ -180,6 +180,7 @@ func init() {
180180
RunTestsCmd.Flags().StringSlice("select-tests", nil, "Comma-separated list of test names to specifically run")
181181
RunTestsCmd.Flags().Float64("max-pass-ratio", 1.0, "The maximum pass ratio threshold for a test to be considered flaky. Any tests below this pass rate will be considered flaky.")
182182
RunTestsCmd.Flags().Bool("omit-test-outputs-on-success", true, "Omit test outputs and package outputs for tests that pass")
183+
RunTestsCmd.Flags().Bool("ignore-parent-failures-on-subtests", false, "Ignore failures in parent tests when only subtests fail")
183184
}
184185

185186
func checkDependencies(projectPath string) error {

tools/flakeguard/go-test-transform/pkg/transformer/transformer.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type TestNode struct {
3737
}
3838

3939
// TransformJSON transforms go test -json output according to the options
40-
func TransformJSON(input io.Reader, output io.Writer, opts *Options) (int, error) {
40+
func TransformJSON(input io.Reader, output io.Writer, opts *Options) error {
4141
// Create scanner for JSON input
4242
scanner := bufio.NewScanner(input)
4343
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024) // 10MB max buffer
@@ -47,13 +47,13 @@ func TransformJSON(input io.Reader, output io.Writer, opts *Options) (int, error
4747
for scanner.Scan() {
4848
var event TestEvent
4949
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
50-
return 1, fmt.Errorf("failed to parse JSON: %v", err)
50+
return fmt.Errorf("failed to parse JSON: %v", err)
5151
}
5252
events = append(events, event)
5353
}
5454

5555
if err := scanner.Err(); err != nil {
56-
return 1, fmt.Errorf("error reading input: %v", err)
56+
return fmt.Errorf("error reading input: %v", err)
5757
}
5858

5959
// Build test tree
@@ -69,22 +69,18 @@ func TransformJSON(input io.Reader, output io.Writer, opts *Options) (int, error
6969
propagateIgnoreStatus(testTree, opts)
7070

7171
// Transform events
72-
transformedEvents, anyRemainingFailures := transformEvents(events, testTree)
72+
transformedEvents, _ := transformEvents(events, testTree)
7373

7474
// Output transformed events
7575
for _, event := range transformedEvents {
7676
eventJSON, err := json.Marshal(event)
7777
if err != nil {
78-
return 1, fmt.Errorf("failed to marshal JSON: %v", err)
78+
return fmt.Errorf("failed to marshal JSON: %v", err)
7979
}
8080
fmt.Fprintln(output, string(eventJSON))
8181
}
8282

83-
// Return appropriate exit code
84-
if anyRemainingFailures {
85-
return 1, nil
86-
}
87-
return 0, nil
83+
return nil
8884
}
8985

9086
// buildTestTree builds a tree of tests from the events

tools/flakeguard/runner/runner.go

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"time"
1717

1818
"github.com/rs/zerolog/log"
19+
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/go-test-transform/pkg/transformer"
1920
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/reports"
2021
)
2122

@@ -26,22 +27,23 @@ var (
2627

2728
// Runner describes the test run parameters and raw test outputs
2829
type Runner struct {
29-
ProjectPath string // Path to the Go project directory.
30-
prettyProjectPath string // Go project package path, formatted for pretty printing.
31-
Verbose bool // If true, provides detailed logging.
32-
RunCount int // Number of times to run the tests.
33-
UseRace bool // Enable race detector.
34-
Timeout time.Duration // Test timeout
35-
Tags []string // Build tags.
36-
UseShuffle bool // Enable test shuffling. -shuffle=on flag.
37-
ShuffleSeed string // Set seed for test shuffling -shuffle={seed} flag. Must be used with UseShuffle.
38-
FailFast bool // Stop on first test failure.
39-
SkipTests []string // Test names to exclude.
40-
SelectTests []string // Test names to include.
41-
CollectRawOutput bool // Set to true to collect test output for later inspection.
42-
OmitOutputsOnSuccess bool // Set to true to omit test outputs on success.
43-
MaxPassRatio float64 // Maximum pass ratio threshold for a test to be considered flaky.
44-
rawOutputs map[string]*bytes.Buffer
30+
ProjectPath string // Path to the Go project directory.
31+
prettyProjectPath string // Go project package path, formatted for pretty printing.
32+
Verbose bool // If true, provides detailed logging.
33+
RunCount int // Number of times to run the tests.
34+
UseRace bool // Enable race detector.
35+
Timeout time.Duration // Test timeout
36+
Tags []string // Build tags.
37+
UseShuffle bool // Enable test shuffling. -shuffle=on flag.
38+
ShuffleSeed string // Set seed for test shuffling -shuffle={seed} flag. Must be used with UseShuffle.
39+
FailFast bool // Stop on first test failure.
40+
SkipTests []string // Test names to exclude.
41+
SelectTests []string // Test names to include.
42+
CollectRawOutput bool // Set to true to collect test output for later inspection.
43+
OmitOutputsOnSuccess bool // Set to true to omit test outputs on success.
44+
MaxPassRatio float64 // Maximum pass ratio threshold for a test to be considered flaky.
45+
IgnoreParentFailuresOnSubtests bool // Ignore failures in parent tests when only subtests fail.
46+
rawOutputs map[string]*bytes.Buffer
4547
}
4648

4749
// RunTestPackages executes the tests for each provided package and aggregates all results.
@@ -283,6 +285,15 @@ func (e entry) String() string {
283285
// Subtests add more complexity, as panics in subtests are only reported in their parent's output,
284286
// and cannot be accurately attributed to the subtest that caused them.
285287
func (r *Runner) parseTestResults(filePaths []string) ([]reports.TestResult, error) {
288+
// If the option is enabled, transform each JSON output file before parsing.
289+
if r.IgnoreParentFailuresOnSubtests {
290+
transformedPaths, err := r.transformTestOutputFiles(filePaths)
291+
if err != nil {
292+
return nil, err
293+
}
294+
filePaths = transformedPaths
295+
}
296+
286297
var (
287298
testDetails = make(map[string]*reports.TestResult) // Holds run, pass counts, and other details for each test
288299
panickedPackages = map[string]struct{}{} // Packages with tests that panicked
@@ -595,6 +606,36 @@ func (r *Runner) parseTestResults(filePaths []string) ([]reports.TestResult, err
595606
return results, nil
596607
}
597608

609+
// transformTestOutputFiles transforms the test output JSON files to ignore parent failures when only subtests fail.
610+
// It returns the paths to the transformed files.
611+
func (r *Runner) transformTestOutputFiles(filePaths []string) ([]string, error) {
612+
transformedPaths := make([]string, len(filePaths))
613+
for i, origPath := range filePaths {
614+
inFile, err := os.Open(origPath)
615+
if err != nil {
616+
return nil, fmt.Errorf("failed to open original file %s: %w", origPath, err)
617+
}
618+
// Create a temporary file for the transformed output.
619+
outFile, err := os.CreateTemp("", "transformed-output-*.json")
620+
if err != nil {
621+
inFile.Close()
622+
return nil, fmt.Errorf("failed to create transformed temp file: %w", err)
623+
}
624+
// Transform the JSON output.
625+
// The transformer option is set to ignore parent failures when only subtests fail.
626+
err = transformer.TransformJSON(inFile, outFile, transformer.NewOptions(true))
627+
inFile.Close()
628+
outFile.Close()
629+
if err != nil {
630+
return nil, fmt.Errorf("failed to transform output file %s: %v", origPath, err)
631+
}
632+
// Use the transformed file path.
633+
transformedPaths[i] = outFile.Name()
634+
os.Remove(origPath)
635+
}
636+
return transformedPaths, nil
637+
}
638+
598639
// attributePanicToTest properly attributes panics to the test that caused them.
599640
func attributePanicToTest(panicPackage string, panicEntries []entry) (test string, timeout bool, err error) {
600641
regexSanitizePanicPackage := filepath.Base(panicPackage)

0 commit comments

Comments
 (0)