@@ -13,10 +13,12 @@ use collector::Bound;
13
13
use serde:: { Deserialize , Serialize } ;
14
14
15
15
use database:: CommitType ;
16
+ use std:: cmp;
16
17
use std:: collections:: { HashMap , HashSet } ;
17
18
use std:: error:: Error ;
18
19
use std:: fmt:: Write ;
19
20
use std:: hash:: Hash ;
21
+ use std:: iter;
20
22
use std:: sync:: Arc ;
21
23
22
24
type BoxedError = Box < dyn Error + Send + Sync > ;
@@ -330,7 +332,7 @@ impl ArtifactComparisonSummary {
330
332
let cmp = |b1 : & TestResultComparison , b2 : & TestResultComparison | {
331
333
b1. relative_change ( )
332
334
. partial_cmp ( & b2. relative_change ( ) )
333
- . unwrap_or ( std :: cmp:: Ordering :: Equal )
335
+ . unwrap_or ( cmp:: Ordering :: Equal )
334
336
} ;
335
337
relevant_comparisons. sort_by ( cmp) ;
336
338
@@ -498,7 +500,7 @@ impl ArtifactComparisonSummary {
498
500
let most_neg = self . most_negative_change ( ) . unwrap ( ) ;
499
501
let most_pos_abs = most_pos. relative_change ( ) . abs ( ) ;
500
502
let most_neg_abs = most_neg. relative_change ( ) . abs ( ) ;
501
- if most_neg_abs. partial_cmp ( & most_pos_abs) == Some ( std :: cmp:: Ordering :: Greater ) {
503
+ if most_neg_abs. partial_cmp ( & most_pos_abs) == Some ( cmp:: Ordering :: Greater ) {
502
504
Some ( most_neg)
503
505
} else {
504
506
Some ( most_pos)
@@ -568,7 +570,7 @@ pub fn write_summary_table(
568
570
. map ( |m| format ! ( "({})" , m. metric. as_str( ) ) )
569
571
} )
570
572
. flatten ( )
571
- . unwrap_or_else ( || String :: from ( " " ) ) ;
573
+ . unwrap_or_else ( || " " . to_string ( ) ) ;
572
574
573
575
fn render_stat < F : FnOnce ( ) -> Option < f64 > > ( count : usize , calculate : F ) -> String {
574
576
let value = if count > 0 { calculate ( ) } else { None } ;
@@ -587,10 +589,10 @@ pub fn write_summary_table(
587
589
}
588
590
589
591
// (label, mean, max, count)
590
- let mut column_data = vec ! [ ] ;
592
+ let mut columns = vec ! [ ] ;
591
593
592
594
// label
593
- column_data . push ( vec ! [
595
+ columns . push ( vec ! [
594
596
"Regressions ❌ <br /> (primary)" . to_string( ) ,
595
597
"Regressions ❌ <br /> (secondary)" . to_string( ) ,
596
598
"Improvements ✅ <br /> (primary)" . to_string( ) ,
@@ -599,7 +601,7 @@ pub fn write_summary_table(
599
601
] ) ;
600
602
601
603
// mean
602
- column_data . push ( vec ! [
604
+ columns . push ( vec ! [
603
605
render_stat( primary. num_regressions, || {
604
606
Some ( primary. arithmetic_mean_of_regressions( ) )
605
607
} ) ,
@@ -621,7 +623,7 @@ pub fn write_summary_table(
621
623
622
624
// range
623
625
let rel_change = |r : Option < & TestResultComparison > | r. unwrap ( ) . relative_change ( ) * 100.0 ;
624
- column_data . push ( vec ! [
626
+ columns . push ( vec ! [
625
627
render_range( primary. num_regressions, || {
626
628
(
627
629
rel_change( primary. smallest_regression( ) ) ,
@@ -655,7 +657,7 @@ pub fn write_summary_table(
655
657
] ) ;
656
658
657
659
// count
658
- column_data . push ( vec ! [
660
+ columns . push ( vec ! [
659
661
primary. num_regressions. to_string( ) ,
660
662
secondary. num_regressions. to_string( ) ,
661
663
primary. num_improvements. to_string( ) ,
@@ -667,25 +669,65 @@ pub fn write_summary_table(
667
669
// easy to read for anyone who is viewing the Markdown source.
668
670
let column_labels = [
669
671
metric,
670
- format ! ( "mean" ) ,
672
+ "mean" . to_string ( ) ,
671
673
"range" . to_string ( ) ,
672
- format ! ( "count" ) ,
674
+ "count" . to_string ( ) ,
673
675
] ;
674
- let counts: Vec < usize > = column_labels. iter ( ) . map ( |s| s. chars ( ) . count ( ) ) . collect ( ) ;
675
- for column in & column_labels {
676
- write ! ( result, "| {} " , column) . unwrap ( ) ;
676
+
677
+ // Calculate the console width of a string, allowing for double-width
678
+ // chars. The `unicode_width` crate does this properly, but with only two
679
+ // emoji in use we can do it easily ourselves.
680
+ let width = |s : & str | {
681
+ s. chars ( )
682
+ . map ( |c| if c == '❌' || c == '✅' { 2 } else { 1 } )
683
+ . sum ( )
684
+ } ;
685
+
686
+ // Get the width of each column.
687
+ let column_widths: Vec < usize > = column_labels
688
+ . iter ( )
689
+ . zip ( columns. iter ( ) )
690
+ . map ( |( column_label, column) | {
691
+ // Get the maximum width of the column data cells and the column label.
692
+ column
693
+ . iter ( )
694
+ . chain ( iter:: once ( column_label) )
695
+ . map ( |cell| width ( cell) )
696
+ . max ( )
697
+ . unwrap ( )
698
+ } )
699
+ . collect ( ) ;
700
+
701
+ // Write column labels.
702
+ for ( column_label, & column_width) in column_labels. iter ( ) . zip ( column_widths. iter ( ) ) {
703
+ write ! (
704
+ result,
705
+ "| {}{} " ,
706
+ column_label,
707
+ " " . repeat( column_width - width( column_label) )
708
+ )
709
+ . unwrap ( ) ;
677
710
}
678
711
result. push_str ( "|\n " ) ;
679
- for & count in & counts {
680
- write ! ( result, "|:{}:" , "-" . repeat( count) ) . unwrap ( ) ;
712
+
713
+ // Write lines under the column labels.
714
+ for & column_width in & column_widths {
715
+ write ! ( result, "|:{}:" , "-" . repeat( column_width) ) . unwrap ( ) ;
681
716
}
682
717
result. push_str ( "|\n " ) ;
683
718
684
- for row in 0 ..5 {
685
- let row_data = column_data. iter ( ) . map ( |rows| rows[ row] . clone ( ) ) ;
686
- debug_assert_eq ! ( row_data. len( ) , column_labels. len( ) ) ;
687
- for ( column, & count) in row_data. zip ( & counts) {
688
- write ! ( result, "| {:<1$} " , column, count) . unwrap ( ) ;
719
+ // Write the column data.
720
+ for row_idx in 0 ..5 {
721
+ let row = columns. iter ( ) . map ( |rows| rows[ row_idx] . clone ( ) ) ;
722
+ assert_eq ! ( row. len( ) , column_labels. len( ) ) ;
723
+ for ( cell, & column_width) in row. zip ( column_widths. iter ( ) ) {
724
+ write ! (
725
+ result,
726
+ "| {}{} " ,
727
+ cell,
728
+ " " . repeat( column_width - width( & cell) )
729
+ )
730
+ . unwrap ( ) ;
689
731
}
690
732
result. push_str ( "|\n " ) ;
691
733
}
@@ -1040,7 +1082,7 @@ impl HistoricalData {
1040
1082
. zip ( self . data . iter ( ) )
1041
1083
. map ( |( d, & r) | d / r)
1042
1084
. collect :: < Vec < _ > > ( ) ;
1043
- deltas. sort_by ( |d1, d2| d1. partial_cmp ( d2) . unwrap_or ( std :: cmp:: Ordering :: Equal ) ) ;
1085
+ deltas. sort_by ( |d1, d2| d1. partial_cmp ( d2) . unwrap_or ( cmp:: Ordering :: Equal ) ) ;
1044
1086
deltas
1045
1087
}
1046
1088
@@ -1242,15 +1284,15 @@ impl TestResultComparison {
1242
1284
}
1243
1285
}
1244
1286
1245
- impl std :: cmp:: PartialEq for TestResultComparison {
1287
+ impl cmp:: PartialEq for TestResultComparison {
1246
1288
fn eq ( & self , other : & Self ) -> bool {
1247
1289
self . benchmark == other. benchmark
1248
1290
&& self . profile == other. profile
1249
1291
&& self . scenario == other. scenario
1250
1292
}
1251
1293
}
1252
1294
1253
- impl std :: cmp:: Eq for TestResultComparison { }
1295
+ impl cmp:: Eq for TestResultComparison { }
1254
1296
1255
1297
impl std:: hash:: Hash for TestResultComparison {
1256
1298
fn hash < H : std:: hash:: Hasher > ( & self , state : & mut H ) {
@@ -1426,11 +1468,13 @@ mod tests {
1426
1468
( Category :: Primary , 1.0 , 3.0 ) ,
1427
1469
] ,
1428
1470
r#"
1429
- | Regressions ❌ <br /> (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1430
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1431
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1432
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1433
- | All ❌✅ (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1471
+ | (instructions:u) | mean | range | count |
1472
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1473
+ | Regressions ❌ <br /> (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1474
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1475
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1476
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1477
+ | All ❌✅ (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1434
1478
"#
1435
1479
. trim_start ( ) ,
1436
1480
) ;
@@ -1445,11 +1489,13 @@ mod tests {
1445
1489
( Category :: Primary , 4.0 , 1.0 ) ,
1446
1490
] ,
1447
1491
r#"
1448
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1449
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1450
- | Improvements ✅ <br /> (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1451
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1452
- | All ❌✅ (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1492
+ | (instructions:u) | mean | range | count |
1493
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1494
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1495
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1496
+ | Improvements ✅ <br /> (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1497
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1498
+ | All ❌✅ (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1453
1499
"#
1454
1500
. trim_start ( ) ,
1455
1501
) ;
@@ -1464,11 +1510,13 @@ mod tests {
1464
1510
( Category :: Secondary , 4.0 , 1.0 ) ,
1465
1511
] ,
1466
1512
r#"
1467
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1468
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1469
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1513
+ | (instructions:u) | mean | range | count |
1514
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1515
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1516
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1517
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1470
1518
| Improvements ✅ <br /> (secondary) | -71.7% | [-80.0%, -60.0%] | 3 |
1471
- | All ❌✅ (primary) | - | - | 0 |
1519
+ | All ❌✅ (primary) | - | - | 0 |
1472
1520
"#
1473
1521
. trim_start ( ) ,
1474
1522
) ;
@@ -1483,11 +1531,13 @@ mod tests {
1483
1531
( Category :: Secondary , 1.0 , 3.0 ) ,
1484
1532
] ,
1485
1533
r#"
1486
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1487
- | Regressions ❌ <br /> (secondary) | 146.7% | [100.0%, 200.0%] | 3 |
1488
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1489
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1490
- | All ❌✅ (primary) | - | - | 0 |
1534
+ | (instructions:u) | mean | range | count |
1535
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1536
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1537
+ | Regressions ❌ <br /> (secondary) | 146.7% | [100.0%, 200.0%] | 3 |
1538
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1539
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1540
+ | All ❌✅ (primary) | - | - | 0 |
1491
1541
"#
1492
1542
. trim_start ( ) ,
1493
1543
) ;
@@ -1503,11 +1553,13 @@ mod tests {
1503
1553
( Category :: Primary , 4.0 , 1.0 ) ,
1504
1554
] ,
1505
1555
r#"
1506
- | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1507
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1508
- | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1509
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1510
- | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1556
+ | (instructions:u) | mean | range | count |
1557
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1558
+ | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1559
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1560
+ | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1561
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1562
+ | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1511
1563
"#
1512
1564
. trim_start ( ) ,
1513
1565
) ;
@@ -1525,11 +1577,13 @@ mod tests {
1525
1577
( Category :: Primary , 4.0 , 1.0 ) ,
1526
1578
] ,
1527
1579
r#"
1528
- | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1529
- | Regressions ❌ <br /> (secondary) | 100.0% | [100.0%, 100.0%] | 1 |
1530
- | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1580
+ | (instructions:u) | mean | range | count |
1581
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1582
+ | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1583
+ | Regressions ❌ <br /> (secondary) | 100.0% | [100.0%, 100.0%] | 1 |
1584
+ | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1531
1585
| Improvements ✅ <br /> (secondary) | -66.7% | [-66.7%, -66.7%] | 1 |
1532
- | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1586
+ | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1533
1587
"#
1534
1588
. trim_start ( ) ,
1535
1589
) ;
@@ -1543,11 +1597,13 @@ mod tests {
1543
1597
( Category :: Primary , 5.0 , 6.0 ) ,
1544
1598
] ,
1545
1599
r#"
1546
- | Regressions ❌ <br /> (primary) | 20.0% | [20.0%, 20.0%] | 1 |
1547
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1548
- | Improvements ✅ <br /> (primary) | -50.0% | [-50.0%, -50.0%] | 1 |
1549
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1550
- | All ❌✅ (primary) | -15.0% | [-50.0%, 20.0%] | 2 |
1600
+ | (instructions:u) | mean | range | count |
1601
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1602
+ | Regressions ❌ <br /> (primary) | 20.0% | [20.0%, 20.0%] | 1 |
1603
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1604
+ | Improvements ✅ <br /> (primary) | -50.0% | [-50.0%, -50.0%] | 1 |
1605
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1606
+ | All ❌✅ (primary) | -15.0% | [-50.0%, 20.0%] | 2 |
1551
1607
"#
1552
1608
. trim_start ( ) ,
1553
1609
) ;
@@ -1561,11 +1617,13 @@ mod tests {
1561
1617
( Category :: Primary , 6.0 , 5.0 ) ,
1562
1618
] ,
1563
1619
r#"
1564
- | Regressions ❌ <br /> (primary) | 100.0% | [100.0%, 100.0%] | 1 |
1565
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1566
- | Improvements ✅ <br /> (primary) | -16.7% | [-16.7%, -16.7%] | 1 |
1567
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1568
- | All ❌✅ (primary) | 41.7% | [-16.7%, 100.0%] | 2 |
1620
+ | (instructions:u) | mean | range | count |
1621
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1622
+ | Regressions ❌ <br /> (primary) | 100.0% | [100.0%, 100.0%] | 1 |
1623
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1624
+ | Improvements ✅ <br /> (primary) | -16.7% | [-16.7%, -16.7%] | 1 |
1625
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1626
+ | All ❌✅ (primary) | 41.7% | [-16.7%, 100.0%] | 2 |
1569
1627
"#
1570
1628
. trim_start ( ) ,
1571
1629
) ;
@@ -1615,7 +1673,13 @@ mod tests {
1615
1673
1616
1674
let mut result = String :: new ( ) ;
1617
1675
write_summary_table ( & primary, & secondary, true , & mut result) ;
1618
- let header = "| (instructions:u) | mean | range | count |\n |:----------------:|:----:|:-----:|:-----:|\n " ;
1619
- assert_eq ! ( result, format!( "{header}{expected}" ) ) ;
1676
+ // We don't use `assert_eq!` here because it stringifies the arguments,
1677
+ // making the tables hard to read when printed.
1678
+ if result != expected {
1679
+ panic ! (
1680
+ "output mismatch:\n expected:\n {}actual:\n {}" ,
1681
+ expected, result
1682
+ ) ;
1683
+ }
1620
1684
}
1621
1685
}
0 commit comments