@@ -26,6 +26,8 @@ import sbt.Keys._
26
26
import sbt ._
27
27
import sbt .plugins .JvmPlugin
28
28
import scalaz .Dequeue
29
+ import scalaz .Heap
30
+ import scalaz .Order
29
31
30
32
// noinspection ScalaStyle
31
33
object CirclePlugin extends AutoPlugin {
@@ -149,12 +151,19 @@ object CirclePlugin extends AutoPlugin {
149
151
val tests = Dequeue [(TestKey , Double )](
150
152
allTestsTimings.toIndexedSeq.sortBy { case (key, runTime) => (runTime, key) } : _* )
151
153
154
+ case class Group (tests : List [TestKey ], runTime : Double )
155
+
156
+ implicit val groupOrder : Order [Group ] = {
157
+ import scalaz .std .anyVal ._
158
+ Order .orderBy(_.runTime)
159
+ }
160
+
152
161
@ tailrec
153
162
def process (tests : Dequeue [(TestKey , Double )],
154
163
soFar : Double = 0d ,
155
164
takeLeft : Boolean = true ,
156
165
acc : List [TestKey ] = Nil ,
157
- groups : List [List [ TestKey ]] = Nil ): List [List [ TestKey ] ] = {
166
+ groups : List [Group ] = Nil ): List [Group ] = {
158
167
159
168
if (groups.size == totalNodes || tests.isEmpty) {
160
169
// Short circuit the logic if we've just completed the last group
@@ -163,12 +172,17 @@ object CirclePlugin extends AutoPlugin {
163
172
return if (tests.isEmpty) {
164
173
groups
165
174
} else {
166
- // Fit all remaining tests in the last issued bucket.
167
- val lastGroup +: restGroups = groups
168
175
val toFit = tests.toStream.map(_._1).force
169
- log.info(s " Fitting remaining tests into first bucket (which already has " +
170
- s " ${lastGroup.size} tests): $toFit" )
171
- (toFit ++: lastGroup) :: restGroups
176
+ log.info(s " Fitting remaining tests into smallest buckets: $toFit" )
177
+ // Fit all remaining tests into the least used buckets.
178
+ // import needed for creating Heap from List (needs Foldable[List[_]])
179
+ import scalaz .std .list ._
180
+ tests.foldLeft(Heap .fromData(groups)) { case (heap, (test, runTime)) =>
181
+ heap.uncons match {
182
+ case Some ((group, rest)) =>
183
+ rest.insert(group.copy(test :: group.tests, runTime + group.runTime))
184
+ }
185
+ }.toList
172
186
}
173
187
}
174
188
@@ -192,7 +206,7 @@ object CirclePlugin extends AutoPlugin {
192
206
case x@ TestCandidate ((_, runTime), _, _) if soFar + runTime <= timePerNode => x
193
207
} match {
194
208
case None =>
195
- process(tests, 0d , takeLeft = true , Nil , acc :: groups)
209
+ process(tests, 0d , takeLeft = true , Nil , Group ( acc, soFar) :: groups)
196
210
case Some (TestCandidate ((key, runTime), rest, fromLeft)) =>
197
211
process(rest, soFar + runTime, fromLeft, key :: acc, groups)
198
212
}
@@ -202,11 +216,11 @@ object CirclePlugin extends AutoPlugin {
202
216
val rootTarget = (target in LocalRootProject ).value
203
217
val bucketsFile = rootTarget / " tests-by-bucket.json"
204
218
log.info(s " Saving test distribution into $totalNodes buckets to: $bucketsFile" )
205
- mapper.writeValue(bucketsFile, buckets)
206
- val timingsPerBucket = buckets.map(_.iterator.map(allTestsTimings.apply).sum)
219
+ mapper.writeValue(bucketsFile, buckets.map(_.tests) )
220
+ val timingsPerBucket = buckets.map(_.tests. iterator.map(allTestsTimings.apply).sum)
207
221
log.info(s " Estimated test timings per bucket: $timingsPerBucket" )
208
222
209
- val bucket = buckets.lift.apply(index).getOrElse(Nil )
223
+ val bucket = buckets.map(_.tests). lift.apply(index).getOrElse(Nil )
210
224
211
225
val groupedByProject = bucket.flatMap(testsByKey.apply)
212
226
.groupBy(_.project)
0 commit comments