1
- use std:: collections:: { HashMap , HashSet } ;
2
- use std:: path:: PathBuf ;
1
+ use crate :: metrics;
2
+ use crate :: metrics:: { JobMetrics , JobName , get_test_suites} ;
3
+ use crate :: utils:: pluralize;
4
+ use build_helper:: metrics:: {
5
+ BuildStep , JsonRoot , TestOutcome , TestSuite , TestSuiteMetadata , format_build_steps,
6
+ } ;
7
+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
8
+
9
+ pub fn output_bootstrap_stats ( metrics : & JsonRoot ) {
10
+ if !metrics. invocations . is_empty ( ) {
11
+ println ! ( "# Bootstrap steps" ) ;
12
+ record_bootstrap_step_durations ( & metrics) ;
13
+ record_test_suites ( & metrics) ;
14
+ }
15
+ }
3
16
4
- use anyhow:: Context ;
5
- use build_helper:: metrics:: { JsonRoot , TestOutcome , TestSuiteMetadata } ;
17
+ fn record_bootstrap_step_durations ( metrics : & JsonRoot ) {
18
+ for invocation in & metrics. invocations {
19
+ let step = BuildStep :: from_invocation ( invocation) ;
20
+ let table = format_build_steps ( & step) ;
21
+ eprintln ! ( "Step `{}`\n {table}\n " , invocation. cmdline) ;
22
+ println ! (
23
+ r"<details>
24
+ <summary>{}</summary>
25
+ <pre><code>{table}</code></pre>
26
+ </details>
27
+ " ,
28
+ invocation. cmdline
29
+ ) ;
30
+ }
31
+ eprintln ! ( "Recorded {} bootstrap invocation(s)" , metrics. invocations. len( ) ) ;
32
+ }
6
33
7
- use crate :: jobs :: JobDatabase ;
8
- use crate :: metrics:: get_test_suites;
34
+ fn record_test_suites ( metrics : & JsonRoot ) {
35
+ let suites = metrics:: get_test_suites ( & metrics ) ;
9
36
10
- type Sha = String ;
11
- type JobName = String ;
37
+ if !suites. is_empty ( ) {
38
+ let aggregated = aggregate_test_suites ( & suites) ;
39
+ let table = render_table ( aggregated) ;
40
+ println ! ( "\n # Test results\n " ) ;
41
+ println ! ( "{table}" ) ;
42
+ } else {
43
+ eprintln ! ( "No test suites found in metrics" ) ;
44
+ }
45
+ }
12
46
13
- /// Computes a post merge CI analysis report between the `parent` and `current` commits.
14
- pub fn post_merge_report ( job_db : JobDatabase , parent : Sha , current : Sha ) -> anyhow:: Result < ( ) > {
15
- let jobs = download_all_metrics ( & job_db, & parent, & current) ?;
16
- let aggregated_test_diffs = aggregate_test_diffs ( & jobs) ?;
47
+ fn render_table ( suites : BTreeMap < String , TestSuiteRecord > ) -> String {
48
+ use std:: fmt:: Write ;
17
49
18
- println ! ( "Comparing {parent} (base) -> {current} (this PR) \n ") ;
19
- report_test_diffs ( aggregated_test_diffs ) ;
50
+ let mut table = "| Test suite | Passed ✅ | Ignored 🚫 | Failed ❌ | \n ". to_string ( ) ;
51
+ writeln ! ( table , "|:------|------:|------:|------:|" ) . unwrap ( ) ;
20
52
21
- Ok ( ( ) )
22
- }
53
+ fn compute_pct ( value : f64 , total : f64 ) -> f64 {
54
+ if total == 0.0 { 0.0 } else { value / total }
55
+ }
23
56
24
- struct JobMetrics {
25
- parent : Option < JsonRoot > ,
26
- current : JsonRoot ,
27
- }
57
+ fn write_row (
58
+ buffer : & mut String ,
59
+ name : & str ,
60
+ record : & TestSuiteRecord ,
61
+ surround : & str ,
62
+ ) -> std:: fmt:: Result {
63
+ let TestSuiteRecord { passed, ignored, failed } = record;
64
+ let total = ( record. passed + record. ignored + record. failed ) as f64 ;
65
+ let passed_pct = compute_pct ( * passed as f64 , total) * 100.0 ;
66
+ let ignored_pct = compute_pct ( * ignored as f64 , total) * 100.0 ;
67
+ let failed_pct = compute_pct ( * failed as f64 , total) * 100.0 ;
68
+
69
+ write ! ( buffer, "| {surround}{name}{surround} |" ) ?;
70
+ write ! ( buffer, " {surround}{passed} ({passed_pct:.0}%){surround} |" ) ?;
71
+ write ! ( buffer, " {surround}{ignored} ({ignored_pct:.0}%){surround} |" ) ?;
72
+ writeln ! ( buffer, " {surround}{failed} ({failed_pct:.0}%){surround} |" ) ?;
73
+
74
+ Ok ( ( ) )
75
+ }
28
76
29
- /// Download before/after metrics for all auto jobs in the job database.
30
- fn download_all_metrics (
31
- job_db : & JobDatabase ,
32
- parent : & str ,
33
- current : & str ,
34
- ) -> anyhow:: Result < HashMap < JobName , JobMetrics > > {
35
- let mut jobs = HashMap :: default ( ) ;
36
-
37
- for job in & job_db. auto_jobs {
38
- eprintln ! ( "Downloading metrics of job {}" , job. name) ;
39
- let metrics_parent = match download_job_metrics ( & job. name , parent) {
40
- Ok ( metrics) => Some ( metrics) ,
41
- Err ( error) => {
42
- eprintln ! (
43
- r#"Did not find metrics for job `{}` at `{}`: {error:?}.
44
- Maybe it was newly added?"# ,
45
- job. name, parent
46
- ) ;
47
- None
48
- }
49
- } ;
50
- let metrics_current = download_job_metrics ( & job. name , current) ?;
51
- jobs. insert (
52
- job. name . clone ( ) ,
53
- JobMetrics { parent : metrics_parent, current : metrics_current } ,
54
- ) ;
77
+ let mut total = TestSuiteRecord :: default ( ) ;
78
+ for ( name, record) in suites {
79
+ write_row ( & mut table, & name, & record, "" ) . unwrap ( ) ;
80
+ total. passed += record. passed ;
81
+ total. ignored += record. ignored ;
82
+ total. failed += record. failed ;
55
83
}
56
- Ok ( jobs)
84
+ write_row ( & mut table, "Total" , & total, "**" ) . unwrap ( ) ;
85
+ table
57
86
}
58
87
59
- /// Downloads job metrics of the given job for the given commit.
60
- /// Caches the result on the local disk.
61
- fn download_job_metrics ( job_name : & str , sha : & str ) -> anyhow:: Result < JsonRoot > {
62
- let cache_path = PathBuf :: from ( ".citool-cache" ) . join ( sha) . join ( job_name) . join ( "metrics.json" ) ;
63
- if let Some ( cache_entry) =
64
- std:: fs:: read_to_string ( & cache_path) . ok ( ) . and_then ( |data| serde_json:: from_str ( & data) . ok ( ) )
65
- {
66
- return Ok ( cache_entry) ;
67
- }
88
+ /// Computes a post merge CI analysis report of test differences
89
+ /// between the `parent` and `current` commits.
90
+ pub fn output_test_diffs ( job_metrics : HashMap < JobName , JobMetrics > ) {
91
+ let aggregated_test_diffs = aggregate_test_diffs ( & job_metrics) ;
92
+ report_test_diffs ( aggregated_test_diffs) ;
93
+ }
68
94
69
- let url = get_metrics_url ( job_name, sha) ;
70
- let mut response = ureq:: get ( & url) . call ( ) ?;
71
- if !response. status ( ) . is_success ( ) {
72
- return Err ( anyhow:: anyhow!(
73
- "Cannot fetch metrics from {url}: {}\n {}" ,
74
- response. status( ) ,
75
- response. body_mut( ) . read_to_string( ) ?
76
- ) ) ;
77
- }
78
- let data: JsonRoot = response
79
- . body_mut ( )
80
- . read_json ( )
81
- . with_context ( || anyhow:: anyhow!( "cannot deserialize metrics from {url}" ) ) ?;
82
-
83
- // Ignore errors if cache cannot be created
84
- if std:: fs:: create_dir_all ( cache_path. parent ( ) . unwrap ( ) ) . is_ok ( ) {
85
- if let Ok ( serialized) = serde_json:: to_string ( & data) {
86
- let _ = std:: fs:: write ( & cache_path, & serialized) ;
95
+ #[ derive( Default ) ]
96
+ struct TestSuiteRecord {
97
+ passed : u64 ,
98
+ ignored : u64 ,
99
+ failed : u64 ,
100
+ }
101
+
102
+ fn test_metadata_name ( metadata : & TestSuiteMetadata ) -> String {
103
+ match metadata {
104
+ TestSuiteMetadata :: CargoPackage { crates, stage, .. } => {
105
+ format ! ( "{} (stage {stage})" , crates. join( ", " ) )
106
+ }
107
+ TestSuiteMetadata :: Compiletest { suite, stage, .. } => {
108
+ format ! ( "{suite} (stage {stage})" )
87
109
}
88
110
}
89
- Ok ( data)
90
111
}
91
112
92
- fn get_metrics_url ( job_name : & str , sha : & str ) -> String {
93
- let suffix = if job_name. ends_with ( "-alt" ) { "-alt" } else { "" } ;
94
- format ! ( "https://ci-artifacts.rust-lang.org/rustc-builds{suffix}/{sha}/metrics-{job_name}.json" )
113
+ fn aggregate_test_suites ( suites : & [ & TestSuite ] ) -> BTreeMap < String , TestSuiteRecord > {
114
+ let mut records: BTreeMap < String , TestSuiteRecord > = BTreeMap :: new ( ) ;
115
+ for suite in suites {
116
+ let name = test_metadata_name ( & suite. metadata ) ;
117
+ let record = records. entry ( name) . or_default ( ) ;
118
+ for test in & suite. tests {
119
+ match test. outcome {
120
+ TestOutcome :: Passed => {
121
+ record. passed += 1 ;
122
+ }
123
+ TestOutcome :: Failed => {
124
+ record. failed += 1 ;
125
+ }
126
+ TestOutcome :: Ignored { .. } => {
127
+ record. ignored += 1 ;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ records
95
133
}
96
134
97
135
/// Represents a difference in the outcome of tests between a base and a current commit.
@@ -101,9 +139,7 @@ struct AggregatedTestDiffs {
101
139
diffs : HashMap < TestDiff , Vec < JobName > > ,
102
140
}
103
141
104
- fn aggregate_test_diffs (
105
- jobs : & HashMap < JobName , JobMetrics > ,
106
- ) -> anyhow:: Result < AggregatedTestDiffs > {
142
+ fn aggregate_test_diffs ( jobs : & HashMap < JobName , JobMetrics > ) -> AggregatedTestDiffs {
107
143
let mut diffs: HashMap < TestDiff , Vec < JobName > > = HashMap :: new ( ) ;
108
144
109
145
// Aggregate test suites
@@ -117,7 +153,7 @@ fn aggregate_test_diffs(
117
153
}
118
154
}
119
155
120
- Ok ( AggregatedTestDiffs { diffs } )
156
+ AggregatedTestDiffs { diffs }
121
157
}
122
158
123
159
#[ derive( Eq , PartialEq , Hash , Debug ) ]
@@ -312,7 +348,3 @@ fn report_test_diffs(diff: AggregatedTestDiffs) {
312
348
) ;
313
349
}
314
350
}
315
-
316
- fn pluralize ( text : & str , count : usize ) -> String {
317
- if count == 1 { text. to_string ( ) } else { format ! ( "{text}s" ) }
318
- }
0 commit comments