[testsharder] distribute tests evenly amongst shards
E.g. sharding (1,2,3,4,5) with max size 4 would result in:
Before:
(1,2,3,4), (5)
After:
(1,2,3), (4,5)
Bug: IN-1433
Change-Id: I042630ba46d3c63eaaea00eab9b9603029478525
diff --git a/testsharder/shard.go b/testsharder/shard.go
index b5c71b2..64ff3ae 100644
--- a/testsharder/shard.go
+++ b/testsharder/shard.go
@@ -105,6 +105,14 @@
return b
}
+func divRoundUp(a, b int) int {
+ if a % b == 0 {
+ return a / b
+ } else {
+ return (a / b) + 1
+ }
+}
+
// WithMaxSize returns a list of shards such that each shard contains fewer than maxShardSize tests.
// If maxShardSize <= 0, just returns its input.
func WithMaxSize(shards []*Shard, maxShardSize int) []*Shard {
@@ -113,11 +121,18 @@
}
output := make([]*Shard, 0, len(shards))
for _, shard := range shards {
- for i := 0; i*maxShardSize < len(shard.Tests); i++ {
- sliceStart := i * maxShardSize
- sliceLimit := min((i+1)*maxShardSize, len(shard.Tests))
+ numNewShards := divRoundUp(len(shard.Tests), maxShardSize)
+ // Evenly distribute the tests between the new shards.
+ maxTestsPerNewShard := divRoundUp(len(shard.Tests), numNewShards)
+ for i := 0; i < numNewShards; i++ {
+ sliceStart := i * maxTestsPerNewShard
+ sliceLimit := min((i+1)*maxTestsPerNewShard, len(shard.Tests))
+ newName := shard.Name
+ if numNewShards > 1 {
+ newName = fmt.Sprintf("%s-(%d)", shard.Name, i+1)
+ }
output = append(output, &Shard{
- Name: fmt.Sprintf("%s-(%d)", shard.Name, i),
+ Name: newName,
Tests: shard.Tests[sliceStart:sliceLimit],
Env: shard.Env,
})
diff --git a/testsharder/shard_test.go b/testsharder/shard_test.go
index eea167c..9c5545e 100644
--- a/testsharder/shard_test.go
+++ b/testsharder/shard_test.go
@@ -279,6 +279,13 @@
})
}
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
func TestWithMaxSize(t *testing.T) {
env1 := Environment{
Tags: []string{"env1"},
@@ -301,22 +308,29 @@
}
}
}
- t.Run("max is larger than all shards", func(t *testing.T) {
- maxSize := len(input[0].Tests)+len(input[1].Tests)
+ t.Run("max is larger greater or equal to all shards", func(t *testing.T) {
+ maxSize := max(len(input[0].Tests), len(input[1].Tests))
actual := WithMaxSize(input, maxSize)
- assertEqual(t, []*Shard{
- // Returns equivalent shards, but renamed.
- namedShard(env1, "env1-(0)", 1, 2, 3, 4, 5), namedShard(env2, "env2-(0)", 6, 7, 8)},
- actual)
- assertShardsLessThanSize(t, actual, maxSize)
+ assertEqual(t, input, actual)
+ assertShardsLessThanSize(t, actual, maxSize)
})
+
t.Run("applies max", func(t *testing.T) {
maxSize := 2
actual := WithMaxSize(input, maxSize)
assertEqual(t, []*Shard{
- namedShard(env1, "env1-(0)", 1, 2), namedShard(env1, "env1-(1)", 3, 4),
- namedShard(env1, "env1-(2)", 5),
- namedShard(env2, "env2-(0)", 6, 7), namedShard(env2, "env2-(1)", 8)},
+ namedShard(env1, "env1-(1)", 1, 2), namedShard(env1, "env1-(2)", 3, 4),
+ namedShard(env1, "env1-(3)", 5),
+ namedShard(env2, "env2-(1)", 6, 7), namedShard(env2, "env2-(2)", 8)},
+ actual)
+ assertShardsLessThanSize(t, actual, maxSize)
+ })
+ t.Run("evenly distributes tests", func(t *testing.T) {
+ maxSize := 4
+ actual := WithMaxSize(input, maxSize)
+ assertEqual(t, []*Shard{
+ namedShard(env1, "env1-(1)", 1, 2, 3), namedShard(env1, "env1-(2)", 4, 5),
+ namedShard(env2, "env2", 6, 7, 8)},
actual)
assertShardsLessThanSize(t, actual, maxSize)
})