@@ -74,6 +74,10 @@ pub struct GasSnapshotArgs {
74
74
) ]
75
75
tolerance : Option < u32 > ,
76
76
77
+ /// How to sort diff results.
78
+ #[ arg( long, value_name = "ORDER" ) ]
79
+ diff_sort : Option < DiffSortOrder > ,
80
+
77
81
/// All test arguments are supported
78
82
#[ command( flatten) ]
79
83
pub ( crate ) test : test:: TestArgs ,
@@ -105,7 +109,7 @@ impl GasSnapshotArgs {
105
109
if let Some ( path) = self . diff {
106
110
let snap = path. as_ref ( ) . unwrap_or ( & self . snap ) ;
107
111
let snaps = read_gas_snapshot ( snap) ?;
108
- diff ( tests, snaps) ?;
112
+ diff ( tests, snaps, self . diff_sort . unwrap_or_default ( ) ) ?;
109
113
} else if let Some ( path) = self . check {
110
114
let snap = path. as_ref ( ) . unwrap_or ( & self . snap ) ;
111
115
let snaps = read_gas_snapshot ( snap) ?;
@@ -162,6 +166,20 @@ struct GasSnapshotConfig {
162
166
max : Option < u64 > ,
163
167
}
164
168
169
+ /// Sort order for diff output
170
+ #[ derive( Clone , Debug , Default , clap:: ValueEnum ) ]
171
+ enum DiffSortOrder {
172
+ /// Sort by percentage change (smallest to largest) - default behavior
173
+ #[ default]
174
+ Percentage ,
175
+ /// Sort by percentage change (largest to smallest)
176
+ PercentageDesc ,
177
+ /// Sort by absolute gas change (smallest to largest)
178
+ Absolute ,
179
+ /// Sort by absolute gas change (largest to smallest)
180
+ AbsoluteDesc ,
181
+ }
182
+
165
183
impl GasSnapshotConfig {
166
184
fn is_in_gas_range ( & self , gas_used : u64 ) -> bool {
167
185
if let Some ( min) = self . min
@@ -387,42 +405,121 @@ fn check(
387
405
}
388
406
389
407
/// Compare the set of tests with an existing gas snapshot.
390
- fn diff ( tests : Vec < SuiteTestResult > , snaps : Vec < GasSnapshotEntry > ) -> Result < ( ) > {
408
+ fn diff (
409
+ tests : Vec < SuiteTestResult > ,
410
+ snaps : Vec < GasSnapshotEntry > ,
411
+ sort_order : DiffSortOrder ,
412
+ ) -> Result < ( ) > {
391
413
let snaps = snaps
392
414
. into_iter ( )
393
415
. map ( |s| ( ( s. contract_name , s. signature ) , s. gas_used ) )
394
416
. collect :: < HashMap < _ , _ > > ( ) ;
395
417
let mut diffs = Vec :: with_capacity ( tests. len ( ) ) ;
418
+ let mut new_tests = Vec :: new ( ) ;
419
+
396
420
for test in tests. into_iter ( ) {
397
421
if let Some ( target_gas_used) =
398
422
snaps. get ( & ( test. contract_name ( ) . to_string ( ) , test. signature . clone ( ) ) ) . cloned ( )
399
423
{
400
424
diffs. push ( GasSnapshotDiff {
401
425
source_gas_used : test. result . kind . report ( ) ,
402
- signature : test. signature ,
426
+ signature : format ! ( "{}::{}" , test. contract_name ( ) , test . signature) ,
403
427
target_gas_used,
404
428
} ) ;
429
+ } else {
430
+ // Track new tests
431
+ new_tests. push ( format ! ( "{}::{}" , test. contract_name( ) , test. signature) ) ;
405
432
}
406
433
}
434
+
435
+ let mut increased = 0 ;
436
+ let mut decreased = 0 ;
437
+ let mut unchanged = 0 ;
407
438
let mut overall_gas_change = 0i128 ;
408
439
let mut overall_gas_used = 0i128 ;
409
440
410
- diffs. sort_by ( |a, b| a. gas_diff ( ) . abs ( ) . total_cmp ( & b. gas_diff ( ) . abs ( ) ) ) ;
441
+ // Sort based on user preference
442
+ match sort_order {
443
+ DiffSortOrder :: Percentage => {
444
+ // Default: sort by percentage change (smallest to largest)
445
+ diffs. sort_by ( |a, b| a. gas_diff ( ) . abs ( ) . total_cmp ( & b. gas_diff ( ) . abs ( ) ) ) ;
446
+ }
447
+ DiffSortOrder :: PercentageDesc => {
448
+ // Sort by percentage change (largest to smallest)
449
+ diffs. sort_by ( |a, b| b. gas_diff ( ) . abs ( ) . total_cmp ( & a. gas_diff ( ) . abs ( ) ) ) ;
450
+ }
451
+ DiffSortOrder :: Absolute => {
452
+ // Sort by absolute gas change (smallest to largest)
453
+ diffs. sort_by_key ( |d| d. gas_change ( ) . abs ( ) ) ;
454
+ }
455
+ DiffSortOrder :: AbsoluteDesc => {
456
+ // Sort by absolute gas change (largest to smallest)
457
+ diffs. sort_by_key ( |d| std:: cmp:: Reverse ( d. gas_change ( ) . abs ( ) ) ) ;
458
+ }
459
+ }
411
460
412
- for diff in diffs {
461
+ for diff in & diffs {
413
462
let gas_change = diff. gas_change ( ) ;
414
463
overall_gas_change += gas_change;
415
464
overall_gas_used += diff. target_gas_used . gas ( ) as i128 ;
416
465
let gas_diff = diff. gas_diff ( ) ;
466
+
467
+ // Classify changes
468
+ if gas_change > 0 {
469
+ increased += 1 ;
470
+ } else if gas_change < 0 {
471
+ decreased += 1 ;
472
+ } else {
473
+ unchanged += 1 ;
474
+ }
475
+
476
+ // Display with icon and before/after values
477
+ let icon = if gas_change > 0 {
478
+ "↑" . red ( ) . to_string ( )
479
+ } else if gas_change < 0 {
480
+ "↓" . green ( ) . to_string ( )
481
+ } else {
482
+ "━" . to_string ( )
483
+ } ;
484
+
417
485
sh_println ! (
418
- "{} (gas: {} ({})) " ,
486
+ "{} {} (gas: {} → {} | {} {})" ,
487
+ icon,
419
488
diff. signature,
489
+ diff. target_gas_used. gas( ) ,
490
+ diff. source_gas_used. gas( ) ,
420
491
fmt_change( gas_change) ,
421
492
fmt_pct_change( gas_diff)
422
493
) ?;
423
494
}
424
495
425
- let overall_gas_diff = overall_gas_change as f64 / overall_gas_used as f64 ;
496
+ // Display new tests if any
497
+ if !new_tests. is_empty ( ) {
498
+ sh_println ! ( "\n {}" , "New tests:" . yellow( ) ) ?;
499
+ for test in new_tests {
500
+ sh_println ! ( " {} {}" , "+" . green( ) , test) ?;
501
+ }
502
+ }
503
+
504
+ // Summary separator
505
+ sh_println ! ( "\n {}" , "-" . repeat( 80 ) ) ?;
506
+
507
+ let overall_gas_diff = if overall_gas_used > 0 {
508
+ overall_gas_change as f64 / overall_gas_used as f64
509
+ } else {
510
+ 0.0
511
+ } ;
512
+
513
+ sh_println ! (
514
+ "Total tests: {}, {} {}, {} {}, {} {}" ,
515
+ diffs. len( ) ,
516
+ "↑" . red( ) . to_string( ) ,
517
+ increased,
518
+ "↓" . green( ) . to_string( ) ,
519
+ decreased,
520
+ "━" ,
521
+ unchanged
522
+ ) ?;
426
523
sh_println ! (
427
524
"Overall gas change: {} ({})" ,
428
525
fmt_change( overall_gas_change) ,
0 commit comments