@@ -421,6 +421,61 @@ impl ComparisonSummary {
421
421
. unwrap ( ) ;
422
422
}
423
423
}
424
+
425
+ /// Writes a Markdown table containing summary of relevant results.
426
+ pub fn write_summary_table ( & self , result : & mut String ) {
427
+ use std:: fmt:: Write ;
428
+
429
+ fn render_stat < F : FnOnce ( ) -> Option < f64 > > ( count : usize , calculate : F ) -> String {
430
+ let value = if count > 0 {
431
+ calculate ( )
432
+ } else {
433
+ None
434
+ } ;
435
+ value. map ( |value| format ! ( "{value:.1}%" ) ) . unwrap_or_else ( || "N/A" . to_string ( ) )
436
+ }
437
+
438
+ writeln ! (
439
+ result,
440
+ r#"| | Regressions 😿 | Improvements 🎉 | All relevant changes |
441
+ |:---:|:---:|:---:|:---:|"#
442
+ )
443
+ . unwrap ( ) ;
444
+ writeln ! (
445
+ result,
446
+ "| count[^1] | {} | {} | {} |" ,
447
+ self . num_regressions,
448
+ self . num_improvements,
449
+ self . num_regressions + self . num_improvements
450
+ )
451
+ . unwrap ( ) ;
452
+ writeln ! (
453
+ result,
454
+ "| mean[^2] | {} | {} | {:.1}% |" ,
455
+ render_stat( self . num_regressions, || Some ( self . arithmetic_mean_of_regressions( ) ) ) ,
456
+ render_stat( self . num_improvements, || Some ( self . arithmetic_mean_of_improvements( ) ) ) ,
457
+ self . arithmetic_mean_of_changes( )
458
+ )
459
+ . unwrap ( ) ;
460
+
461
+ let largest_change = self . most_relevant_changes ( ) . iter ( ) . fold ( 0.0 , |accum : f64 , item| {
462
+ let change = item. map ( |v| v. relative_change ( ) * 100.0 ) . unwrap_or ( 0.0 ) ;
463
+ accum. max ( change)
464
+ } ) ;
465
+
466
+ writeln ! (
467
+ result,
468
+ "| max | {} | {} | {:.1}% |" ,
469
+ render_stat( self . num_regressions, || self . largest_regression( ) . map( |r| r. relative_change( ) * 100.0 ) ) ,
470
+ render_stat( self . num_improvements, || self . largest_improvement( ) . map( |r| r. relative_change( ) * 100.0 ) ) ,
471
+ largest_change
472
+ )
473
+ . unwrap ( ) ;
474
+
475
+ writeln ! ( result, r#"
476
+ [^1]: *number of relevant changes*
477
+ [^2]: *the arithmetic mean of the percent change*"# ) . unwrap ( ) ;
478
+ }
424
479
}
425
480
426
481
/// The amount of confidence we have that a comparison actually represents a real
@@ -1233,3 +1288,102 @@ fn compare_link(start: &ArtifactId, end: &ArtifactId) -> String {
1233
1288
start, end
1234
1289
)
1235
1290
}
1291
+
1292
+ #[ cfg( test) ]
1293
+ mod tests {
1294
+ use std:: collections:: HashSet ;
1295
+
1296
+ use database:: { ArtifactId , Profile , Scenario } ;
1297
+
1298
+ use crate :: comparison:: {
1299
+ ArtifactDescription , Comparison , ComparisonSummary , TestResultComparison ,
1300
+ } ;
1301
+
1302
+ #[ test]
1303
+ fn summary_table_only_improvements ( ) {
1304
+ let summary = create_summary ( vec ! [ ( 10.0 , 5.0 ) , ( 8.0 , 2.0 ) ] ) ;
1305
+ check_table (
1306
+ summary, r#"
1307
+ | | Regressions 😿 | Improvements 🎉 | All relevant changes |
1308
+ |:---:|:---:|:---:|:---:|
1309
+ | count[^1] | 0 | 2 | 2 |
1310
+ | mean[^2] | N/A | -62.5% | -62.5% |
1311
+ | max | N/A | -75.0% | 0.0% |
1312
+
1313
+ [^1]: *number of relevant changes*
1314
+ [^2]: *the arithmetic mean of the percent change*
1315
+ "# . trim_start ( ) ,
1316
+ ) ;
1317
+ }
1318
+
1319
+ #[ test]
1320
+ fn summary_table_only_regressions ( ) {
1321
+ let summary = create_summary ( vec ! [ ( 5.0 , 10.0 ) , ( 1.0 , 3.0 ) ] ) ;
1322
+ check_table (
1323
+ summary, r#"
1324
+ | | Regressions 😿 | Improvements 🎉 | All relevant changes |
1325
+ |:---:|:---:|:---:|:---:|
1326
+ | count[^1] | 2 | 0 | 2 |
1327
+ | mean[^2] | 150.0% | N/A | 150.0% |
1328
+ | max | 200.0% | N/A | 200.0% |
1329
+
1330
+ [^1]: *number of relevant changes*
1331
+ [^2]: *the arithmetic mean of the percent change*
1332
+ "# . trim_start ( ) ,
1333
+ ) ;
1334
+ }
1335
+
1336
+ #[ test]
1337
+ fn summary_table_mixed ( ) {
1338
+ let summary = create_summary ( vec ! [ ( 10.0 , 5.0 ) , ( 5.0 , 10.0 ) , ( 1.0 , 3.0 ) , ( 4.0 , 1.0 ) ] ) ;
1339
+ check_table (
1340
+ summary, r#"
1341
+ | | Regressions 😿 | Improvements 🎉 | All relevant changes |
1342
+ |:---:|:---:|:---:|:---:|
1343
+ | count[^1] | 2 | 2 | 4 |
1344
+ | mean[^2] | 150.0% | -62.5% | 43.8% |
1345
+ | max | 200.0% | -75.0% | 200.0% |
1346
+
1347
+ [^1]: *number of relevant changes*
1348
+ [^2]: *the arithmetic mean of the percent change*
1349
+ "# . trim_start ( ) ,
1350
+ ) ;
1351
+ }
1352
+
1353
+ fn check_table ( summary : ComparisonSummary , expected : & str ) {
1354
+ let mut result = String :: new ( ) ;
1355
+ summary. write_summary_table ( & mut result) ;
1356
+ assert_eq ! ( result, expected) ;
1357
+ }
1358
+
1359
+ fn create_summary ( values : Vec < ( f64 , f64 ) > ) -> ComparisonSummary {
1360
+ let mut statistics = HashSet :: new ( ) ;
1361
+ for ( index, diff) in values. into_iter ( ) . enumerate ( ) {
1362
+ statistics. insert ( TestResultComparison {
1363
+ benchmark : index. to_string ( ) . as_str ( ) . into ( ) ,
1364
+ profile : Profile :: Check ,
1365
+ scenario : Scenario :: Empty ,
1366
+ variance : None ,
1367
+ results : diff,
1368
+ } ) ;
1369
+ }
1370
+
1371
+ let comparison = Comparison {
1372
+ a : ArtifactDescription {
1373
+ artifact : ArtifactId :: Tag ( "a" . to_string ( ) ) ,
1374
+ pr : None ,
1375
+ bootstrap : Default :: default ( ) ,
1376
+ bootstrap_total : 0 ,
1377
+ } ,
1378
+ b : ArtifactDescription {
1379
+ artifact : ArtifactId :: Tag ( "b" . to_string ( ) ) ,
1380
+ pr : None ,
1381
+ bootstrap : Default :: default ( ) ,
1382
+ bootstrap_total : 0 ,
1383
+ } ,
1384
+ statistics,
1385
+ new_errors : Default :: default ( ) ,
1386
+ } ;
1387
+ ComparisonSummary :: summarize_comparison ( & comparison) . unwrap ( )
1388
+ }
1389
+ }
0 commit comments