@@ -2,99 +2,197 @@ package cmd
22
33import (
44 "encoding/json"
5- "log"
6- "os"
5+ "fmt"
76 "path/filepath"
7+ "time"
88
9+ "github.com/briandowns/spinner"
910 "github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/reports"
1011 "github.com/spf13/cobra"
1112)
1213
13- var (
14- resultsFolderPath string
15- outputResultsPath string
16- outputLogsPath string
17- codeOwnersPath string
18- projectPath string
19- maxPassRatio float64
20- filterFailed bool
21- )
22-
2314var AggregateResultsCmd = & cobra.Command {
2415 Use : "aggregate-results" ,
25- Short : "Aggregate test results and optionally filter failed tests based on a threshold " ,
16+ Short : "Aggregate test results into a single JSON report " ,
2617 RunE : func (cmd * cobra.Command , args []string ) error {
27- // Read test reports from files
28- var testReports []* reports.TestReport
29- err := filepath .Walk (resultsFolderPath , func (path string , info os.FileInfo , err error ) error {
30- if err != nil {
31- return err
32- }
33- if ! info .IsDir () && filepath .Ext (path ) == ".json" {
34- // Read file content
35- data , readErr := os .ReadFile (path )
36- if readErr != nil {
37- return readErr
38- }
39- var report * reports.TestReport
40- if jsonErr := json .Unmarshal (data , & report ); jsonErr != nil {
41- return jsonErr
42- }
43- testReports = append (testReports , report )
44- }
45- return nil
46- })
18+ fs := reports.OSFileSystem {}
19+
20+ // Get flag values
21+ resultsPath , _ := cmd .Flags ().GetString ("results-path" )
22+ outputDir , _ := cmd .Flags ().GetString ("output-path" )
23+ summaryFileName , _ := cmd .Flags ().GetString ("summary-file-name" )
24+ maxPassRatio , _ := cmd .Flags ().GetFloat64 ("max-pass-ratio" )
25+ codeOwnersPath , _ := cmd .Flags ().GetString ("codeowners-path" )
26+ repoPath , _ := cmd .Flags ().GetString ("repo-path" )
27+
28+ // Ensure the output directory exists
29+ if err := fs .MkdirAll (outputDir , 0755 ); err != nil {
30+ return fmt .Errorf ("error creating output directory: %w" , err )
31+ }
32+
33+ // Start spinner for loading test reports
34+ s := spinner .New (spinner .CharSets [11 ], 100 * time .Millisecond )
35+ s .Suffix = " Loading test reports..."
36+ s .Start ()
37+
38+ // Load test reports from JSON files
39+ testReports , err := reports .LoadReports (resultsPath )
4740 if err != nil {
48- log .Fatalf ("Error reading test reports: %v" , err )
41+ s .Stop ()
42+ return fmt .Errorf ("error loading test reports: %w" , err )
4943 }
44+ s .Stop ()
45+ fmt .Println ("Test reports loaded successfully." )
46+
47+ // Start spinner for aggregating reports
48+ s = spinner .New (spinner .CharSets [11 ], 100 * time .Millisecond )
49+ s .Suffix = " Aggregating test reports..."
50+ s .Start ()
5051
51- allReport , err := reports .Aggregate (testReports ... )
52+ // Aggregate the reports
53+ aggregatedReport , err := reports .Aggregate (testReports ... )
5254 if err != nil {
53- log .Fatalf ("Error aggregating results: %v" , err )
55+ s .Stop ()
56+ return fmt .Errorf ("error aggregating test reports: %w" , err )
5457 }
58+ s .Stop ()
59+ fmt .Println ("Test reports aggregated successfully." )
5560
56- // Map test results to paths
57- err = reports .MapTestResultsToPaths (allReport , projectPath )
61+ // Start spinner for mapping test results to paths
62+ s = spinner .New (spinner .CharSets [11 ], 100 * time .Millisecond )
63+ s .Suffix = " Mapping test results to paths..."
64+ s .Start ()
65+
66+ // Map test results to test paths
67+ err = reports .MapTestResultsToPaths (aggregatedReport , repoPath )
5868 if err != nil {
59- log .Fatalf ("Error mapping test results to paths: %v" , err )
69+ s .Stop ()
70+ return fmt .Errorf ("error mapping test results to paths: %w" , err )
6071 }
72+ s .Stop ()
73+ fmt .Println ("Test results mapped to paths successfully." )
6174
62- // Map test results to owners if CODEOWNERS path is provided
75+ // Map test results to code owners if codeOwnersPath is provided
6376 if codeOwnersPath != "" {
64- err = reports .MapTestResultsToOwners (allReport , codeOwnersPath )
77+ s = spinner .New (spinner .CharSets [11 ], 100 * time .Millisecond )
78+ s .Suffix = " Mapping test results to code owners..."
79+ s .Start ()
80+
81+ err = reports .MapTestResultsToOwners (aggregatedReport , codeOwnersPath )
6582 if err != nil {
66- log .Fatalf ("Error mapping test results to owners: %v" , err )
83+ s .Stop ()
84+ return fmt .Errorf ("error mapping test results to code owners: %w" , err )
6785 }
86+ s .Stop ()
87+ fmt .Println ("Test results mapped to code owners successfully." )
6888 }
6989
70- var resultsToSave []reports.TestResult
90+ // Save the aggregated report to the output directory
91+ aggregatedReportPath := filepath .Join (outputDir , "all-test-results.json" )
92+ if err := reports .SaveReport (fs , aggregatedReportPath , * aggregatedReport ); err != nil {
93+ return fmt .Errorf ("error saving aggregated test report: %w" , err )
94+ }
95+ fmt .Printf ("Aggregated test report saved to %s\n " , aggregatedReportPath )
96+
97+ // Filter failed tests (PassRatio < maxPassRatio and not skipped)
98+ s = spinner .New (spinner .CharSets [11 ], 100 * time .Millisecond )
99+ s .Suffix = " Filtering failed tests..."
100+ s .Start ()
101+
102+ failedTests := reports .FilterTests (aggregatedReport .Results , func (tr reports.TestResult ) bool {
103+ return ! tr .Skipped && tr .PassRatio < maxPassRatio
104+ })
105+ s .Stop ()
106+
107+ // Check if there are any failed tests
108+ if len (failedTests ) > 0 {
109+ fmt .Printf ("Found %d failed test(s).\n " , len (failedTests ))
110+
111+ // Create a new report for failed tests with logs
112+ failedReportWithLogs := & reports.TestReport {
113+ GoProject : aggregatedReport .GoProject ,
114+ TestRunCount : aggregatedReport .TestRunCount ,
115+ RaceDetection : aggregatedReport .RaceDetection ,
116+ ExcludedTests : aggregatedReport .ExcludedTests ,
117+ SelectedTests : aggregatedReport .SelectedTests ,
118+ Results : failedTests ,
119+ }
120+
121+ // Save the failed tests report with logs
122+ failedTestsReportWithLogsPath := filepath .Join (outputDir , "failed-test-results-with-logs.json" )
123+ if err := reports .SaveReport (fs , failedTestsReportWithLogsPath , * failedReportWithLogs ); err != nil {
124+ return fmt .Errorf ("error saving failed tests report with logs: %w" , err )
125+ }
126+ fmt .Printf ("Failed tests report with logs saved to %s\n " , failedTestsReportWithLogsPath )
71127
72- if filterFailed {
73- // Filter to only include tests that failed below the threshold
74- for _ , result := range allReport .Results {
75- if result .PassRatio < maxPassRatio && ! result .Skipped {
76- resultsToSave = append (resultsToSave , result )
77- }
128+ // Remove logs from test results for the report without logs
129+ for i := range failedReportWithLogs .Results {
130+ failedReportWithLogs .Results [i ].Outputs = nil
131+ failedReportWithLogs .Results [i ].PackageOutputs = nil
78132 }
133+
134+ // Save the failed tests report without logs
135+ failedTestsReportNoLogsPath := filepath .Join (outputDir , "failed-test-results.json" )
136+ if err := reports .SaveReport (fs , failedTestsReportNoLogsPath , * failedReportWithLogs ); err != nil {
137+ return fmt .Errorf ("error saving failed tests report without logs: %w" , err )
138+ }
139+ fmt .Printf ("Failed tests report without logs saved to %s\n " , failedTestsReportNoLogsPath )
79140 } else {
80- resultsToSave = allReport . Results
141+ fmt . Println ( "No failed tests found. Skipping generation of failed tests reports." )
81142 }
82- allReport .Results = resultsToSave
83143
84- // Output results to JSON files
85- if len (resultsToSave ) > 0 {
86- return reports .SaveFilteredResultsAndLogs (outputResultsPath , outputLogsPath , allReport , codeOwnersPath != "" )
144+ // Generate all-tests-summary.json
145+ if summaryFileName != "" {
146+ s = spinner .New (spinner .CharSets [11 ], 100 * time .Millisecond )
147+ s .Suffix = " Generating summary json..."
148+ s .Start ()
149+
150+ summaryFilePath := filepath .Join (outputDir , summaryFileName )
151+ err = generateAllTestsSummaryJSON (aggregatedReport , summaryFilePath , maxPassRatio )
152+ if err != nil {
153+ s .Stop ()
154+ return fmt .Errorf ("error generating summary json: %w" , err )
155+ }
156+ s .Stop ()
157+ fmt .Printf ("Summary generated at %s\n " , summaryFilePath )
87158 }
159+
160+ fmt .Println ("Aggregation complete." )
161+
88162 return nil
89163 },
90164}
91165
92166func init () {
93- AggregateResultsCmd .Flags ().StringVarP (& resultsFolderPath , "results-path" , "p" , "" , "Path to the folder containing JSON test result files" )
94- AggregateResultsCmd .Flags ().StringVarP (& outputResultsPath , "output-results" , "o" , "./results" , "Path to output the aggregated or filtered test results in JSON and markdown format" )
95- AggregateResultsCmd .Flags ().StringVarP (& outputLogsPath , "output-logs" , "l" , "" , "Path to output the filtered test logs in JSON format" )
96- AggregateResultsCmd .Flags ().Float64VarP (& maxPassRatio , "max-pass-ratio" , "m" , 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." )
97- AggregateResultsCmd .Flags ().BoolVarP (& filterFailed , "filter-failed" , "f" , false , "If true, filter and output only failed tests based on the max-pass-ratio threshold" )
98- AggregateResultsCmd .Flags ().StringVarP (& codeOwnersPath , "codeowners-path" , "c" , "" , "Path to the CODEOWNERS file" )
99- AggregateResultsCmd .Flags ().StringVarP (& projectPath , "project-path" , "r" , "." , "The path to the Go project. Default is the current directory. Useful for subprojects" )
167+ AggregateResultsCmd .Flags ().StringP ("results-path" , "p" , "" , "Path to the folder containing JSON test result files (required)" )
168+ AggregateResultsCmd .Flags ().StringP ("output-path" , "o" , "./report" , "Path to output the aggregated results (directory)" )
169+ AggregateResultsCmd .Flags ().StringP ("summary-file-name" , "s" , "all-test-summary.json" , "Name of the summary JSON file" )
170+ AggregateResultsCmd .Flags ().Float64P ("max-pass-ratio" , "" , 1.0 , "The maximum pass ratio threshold for a test to be considered flaky" )
171+ AggregateResultsCmd .Flags ().StringP ("codeowners-path" , "" , "" , "Path to the CODEOWNERS file" )
172+ AggregateResultsCmd .Flags ().StringP ("repo-path" , "" , "." , "The path to the root of the repository/project" )
173+
174+ AggregateResultsCmd .MarkFlagRequired ("results-path" )
175+ }
176+
177+ // New function to generate all-tests-summary.json
178+ func generateAllTestsSummaryJSON (report * reports.TestReport , outputPath string , maxPassRatio float64 ) error {
179+ summary := reports .GenerateSummaryData (report .Results , maxPassRatio )
180+ data , err := json .Marshal (summary )
181+ if err != nil {
182+ return fmt .Errorf ("error marshaling summary data to JSON: %w" , err )
183+ }
184+
185+ fs := reports.OSFileSystem {}
186+ jsonFile , err := fs .Create (outputPath )
187+ if err != nil {
188+ return fmt .Errorf ("error creating file: %w" , err )
189+ }
190+ defer jsonFile .Close ()
191+
192+ _ , err = jsonFile .Write (data )
193+ if err != nil {
194+ return fmt .Errorf ("error writing data to file: %w" , err )
195+ }
196+
197+ return nil
100198}
0 commit comments