diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 2d51e2d..b7c19a0 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - scala: [2.12.18] + scala: [2.12.19] java: [adopt@1.11, adopt@1.8] runs-on: ubuntu-latest steps: diff --git a/build.sbt b/build.sbt index 9c584f7..fae8cb3 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ inThisBuild( lazy val root = (project in file(".")).settings( name := "sbt-test-shards", organization := "com.github.reibitto", - scalaVersion := "2.12.18", + scalaVersion := "2.12.19", sbtPlugin := true, libraryDependencies ++= Seq( "org.scalameta" %% "munit" % "0.7.29" % Test, diff --git a/project/build.properties b/project/build.properties index e8a1e24..04267b1 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 +sbt.version=1.9.9 diff --git a/src/main/scala/sbttestshards/ShardingAlgorithm.scala b/src/main/scala/sbttestshards/ShardingAlgorithm.scala index 06ea81c..29b65a6 100644 --- a/src/main/scala/sbttestshards/ShardingAlgorithm.scala +++ b/src/main/scala/sbttestshards/ShardingAlgorithm.scala @@ -50,7 +50,7 @@ object ShardingAlgorithm { ): Balance = ShardingAlgorithm.Balance( JUnitReportParser.parseDirectoriesRecursively(reportDirectories).testReports.map { r => - SpecInfo(r.name, Some(Duration.ofMillis(r.timeTaken.toLong))) + SpecInfo(r.name, Some(Duration.ofMillis((r.timeTaken * 1000).toLong))) }, shardsInfo, fallbackShardingAlgorithm diff --git a/src/test/scala/sbttestshards/ShardingAlgorithmSpec.scala b/src/test/scala/sbttestshards/ShardingAlgorithmSpec.scala index 9d7a5a1..c673a13 100644 --- a/src/test/scala/sbttestshards/ShardingAlgorithmSpec.scala +++ b/src/test/scala/sbttestshards/ShardingAlgorithmSpec.scala @@ -13,7 +13,7 @@ class ShardingAlgorithmSpec extends ScalaCheckSuite { implicit val specInfoArbitrary: Arbitrary[SpecInfo] = Arbitrary { for { specName <- Gen.resize(30, Gen.alphaStr) - timeTaken <- Gen.choose(0L, 9 * 60 * 1000) + timeTaken <- Gen.choose(0L, 5 * 60 * 1000) } yield SpecInfo(specName, Some(Duration.ofMillis(timeTaken))) } @@ -50,4 +50,67 @@ class ShardingAlgorithmSpec extends ScalaCheckSuite { } } + property("only choose shard if initialDuration indicates node started early") { + forAll { (tests: List[SpecInfo]) => + val initialDurations = Map(0 -> Duration.ofMinutes(-1000)) + val algo = ShardingAlgorithm.Balance(tests, ShardingInfo(3, initialDurations)) + + val bucketMap = algo.distributeEvenly.toSeq.groupBy(_._2).map { case (k, v) => + k -> v.map(_._1.timeTaken).reduceOption(_.plus(_)).getOrElse(Duration.ZERO) + } + + if (tests.isEmpty) + bucketMap.isEmpty + else + bucketMap.keys.toSeq == Seq(0) + } + } + + property("not choose shard if initialDuration exceeds the available time") { + forAll { (tests: List[SpecInfo]) => + val initialDurations = Map(0 -> Duration.ofMinutes(1000)) + val algo = ShardingAlgorithm.Balance(tests, ShardingInfo(3, initialDurations)) + + val bucketMap = algo.distributeEvenly.toSeq.groupBy(_._2).map { case (k, v) => + k -> v.map(_._1.timeTaken).reduceOption(_.plus(_)).getOrElse(Duration.ZERO) + } + + !bucketMap.contains(0) + } + } + + test("take initialDurations into account when distributing specs") { + val initialDurations = Map(0 -> Duration.ofSeconds(10)) + val tests = List( + SpecInfo("spec1", timeTaken = Some(Duration.ofSeconds(1))), + SpecInfo("spec2", timeTaken = Some(Duration.ofSeconds(2))), + SpecInfo("spec3", timeTaken = Some(Duration.ofSeconds(3))), + SpecInfo("spec4", timeTaken = Some(Duration.ofSeconds(4))), + SpecInfo("spec5", timeTaken = Some(Duration.ofSeconds(5))), + SpecInfo("spec6", timeTaken = Some(Duration.ofSeconds(6))), + SpecInfo("spec7", timeTaken = Some(Duration.ofSeconds(7))), + SpecInfo("spec8", timeTaken = Some(Duration.ofSeconds(8))), + SpecInfo("spec9", timeTaken = Some(Duration.ofSeconds(9))) + ) + + val algo = ShardingAlgorithm.Balance(tests, ShardingInfo(3, initialDurations)) + + val bucketMap = algo.distributeEvenly.toSeq.groupBy(_._2).map { case (k, v) => + k -> v.map(_._1.timeTaken).reduceOption(_.plus(_)).getOrElse(Duration.ZERO) + } + + val bucketMapWithInitialDurations = bucketMap.map { case (k, v) => + k -> initialDurations.getOrElse(k, Duration.ZERO).plus(v) + } + + assertEquals( + bucketMapWithInitialDurations, + Map( + 0 -> Duration.ofSeconds(19), + 1 -> Duration.ofSeconds(18), + 2 -> Duration.ofSeconds(18) + ) + ) + } + }