1- // Copyright 2023 Martin Pool
2-
31//! Sharding parameters.
42
53use std:: str:: FromStr ;
64
75use anyhow:: { anyhow, ensure, Context , Error } ;
6+ use clap:: ValueEnum ;
7+ use schemars:: JsonSchema ;
8+ use serde:: Deserialize ;
89
910/// Select mutants for a particular shard of the total list.
1011#[ derive( Debug , Clone , Copy , Eq , PartialEq ) ]
@@ -15,17 +16,6 @@ pub struct Shard {
1516 pub n : usize ,
1617}
1718
18- impl Shard {
19- /// Select the mutants that should be run for this shard.
20- pub fn select < M , I : IntoIterator < Item = M > > ( & self , mutants : I ) -> Vec < M > {
21- mutants
22- . into_iter ( )
23- . enumerate ( )
24- . filter_map ( |( i, m) | if i % self . n == self . k { Some ( m) } else { None } )
25- . collect ( )
26- }
27- }
28-
2919impl FromStr for Shard {
3020 type Err = Error ;
3121
@@ -38,6 +28,57 @@ impl FromStr for Shard {
3828 }
3929}
4030
31+ /// Method for sharding.
32+ #[ derive( Debug , Default , Clone , Copy , PartialEq , Eq , Deserialize , ValueEnum , JsonSchema ) ]
33+ #[ serde( rename_all = "snake_case" ) ]
34+ pub enum Sharding {
35+ /// Run mutant `i` on shard `i % k`.
36+ ///
37+ /// This was the default up to cargo-mutants 25.3.1.
38+ ///
39+ /// This distributes mutants more evenly and will likely generate more equal completion
40+ /// times, but it has less locality of reference within each shard's cache and so may
41+ /// cause more build time.
42+ #[ default]
43+ RoundRobin ,
44+
45+ /// Run consecutive ranges of mutants on each shard: `0..(n/k)` on shard 0, etc. (default)
46+ ///
47+ /// This makes the incremental change between each build likely to be smaller and
48+ /// so may reduce build time, but it may also lead to more unbalanced shards.
49+ Slice ,
50+ }
51+
52+ impl Sharding {
53+ /// Select the mutants that should be run for this shard.
54+ pub fn shard < M > ( self , shard : Shard , mut mutants : Vec < M > ) -> Vec < M > {
55+ match self {
56+ Sharding :: RoundRobin => mutants
57+ . into_iter ( )
58+ . enumerate ( )
59+ . filter_map ( |( i, m) | {
60+ if i % shard. n == shard. k {
61+ Some ( m)
62+ } else {
63+ None
64+ }
65+ } )
66+ . collect ( ) ,
67+ Sharding :: Slice => {
68+ let total = mutants. len ( ) ;
69+ let chunk_size = total. div_ceil ( shard. n ) ;
70+ let start = shard. k * chunk_size;
71+ let end = ( ( shard. k + 1 ) * chunk_size) . min ( total) ;
72+ if start >= total {
73+ Vec :: new ( )
74+ } else {
75+ mutants. drain ( start..end) . collect ( )
76+ }
77+ }
78+ }
79+ }
80+ }
81+
4182#[ cfg( test) ]
4283mod tests {
4384 use super :: * ;
@@ -74,10 +115,21 @@ mod tests {
74115 }
75116
76117 #[ test]
77- fn shard_select ( ) {
78- assert_eq ! (
79- Shard :: from_str( "1/4" ) . unwrap( ) . select( 0 ..10 ) . as_slice( ) ,
80- & [ 1 , 5 , 9 ]
81- ) ;
118+ fn shard_round_robin ( ) {
119+ // This test works on ints instead of real mutants just for ease of testing.
120+ let fake_mutants: Vec < usize > = ( 0 ..10 ) . collect ( ) ;
121+ for ( k, expect) in [
122+ ( 0 , [ 0 , 4 , 8 ] . as_slice ( ) ) ,
123+ ( 1 , & [ 1 , 5 , 9 ] ) ,
124+ ( 2 , & [ 2 , 6 ] ) ,
125+ ( 3 , & [ 3 , 7 ] ) ,
126+ ] {
127+ assert_eq ! (
128+ Sharding :: RoundRobin
129+ . shard( Shard { k, n: 4 } , fake_mutants. clone( ) )
130+ . as_slice( ) ,
131+ expect
132+ ) ;
133+ }
82134 }
83135}
0 commit comments