diff --git a/bleep-cli/src/scala/bleep/Main.scala b/bleep-cli/src/scala/bleep/Main.scala index 58a559d0..879cc82e 100644 --- a/bleep-cli/src/scala/bleep/Main.scala +++ b/bleep-cli/src/scala/bleep/Main.scala @@ -90,7 +90,8 @@ object Main { .orNone .map(started.chosenTestProjects) - val testSuites: Opts[Option[NonEmptyList[String]]] = Opts.options[String]("test-only", "Test only a subset of test suites", "o").orNone + val testSuitesOnly: Opts[Option[NonEmptyList[String]]] = Opts.options[String]("only", "Test only a subset of test suites", "o").orNone + val testSuitesExclude: Opts[Option[NonEmptyList[String]]] = Opts.options[String]("exclude", "Test only a subset of test suites", "x").orNone val hasSourcegenProjectNames: Opts[Array[model.CrossProjectName]] = Opts @@ -174,8 +175,8 @@ object Main { (watch, hasSourcegenProjectNames).mapN { case (watch, projectNames) => commands.SourceGen(watch, projectNames) } ), Opts.subcommand("test", "test projects")( - (watch, testProjectNames, testSuites).mapN { case (watch, projectNames, testSuites) => - commands.Test(watch, projectNames, testSuites) + (watch, testProjectNames, testSuitesOnly, testSuitesExclude).mapN { case (watch, projectNames, testSuitesOnly, testSuitesExclude) => + commands.Test(watch, projectNames, testSuitesOnly, testSuitesExclude) } ), Opts.subcommand("list-tests", "list tests in projects")( diff --git a/bleep-core/src/scala/bleep/Commands.scala b/bleep-core/src/scala/bleep/Commands.scala index 17122aeb..2572ba35 100644 --- a/bleep-core/src/scala/bleep/Commands.scala +++ b/bleep-core/src/scala/bleep/Commands.scala @@ -22,8 +22,13 @@ class Commands(started: Started) { ): Unit = force(commands.Run(project, maybeOverriddenMain, args, raw, watch)) - def test(projects: List[model.CrossProjectName], watch: Boolean = false, classes: Option[NonEmptyList[String]]): Unit = - force(commands.Test(watch, projects.toArray, classes)) + def test( + projects: List[model.CrossProjectName], + watch: Boolean = false, + testOnlyClasses: Option[NonEmptyList[String]], + testExcludeClasses: Option[NonEmptyList[String]] + ): Unit = + force(commands.Test(watch, projects.toArray, testOnlyClasses, testExcludeClasses)) def script(name: model.ScriptName, args: List[String], watch: Boolean = false): Unit = force(commands.Script(name, args, watch)) diff --git a/bleep-core/src/scala/bleep/commands/Test.scala b/bleep-core/src/scala/bleep/commands/Test.scala index 638717ed..5bd694ed 100644 --- a/bleep-core/src/scala/bleep/commands/Test.scala +++ b/bleep-core/src/scala/bleep/commands/Test.scala @@ -10,16 +10,24 @@ import bloop.rifle.BuildServer import cats.data.NonEmptyList import bleep.BleepException.TestSuitesNotFound -case class Test(watch: Boolean, projects: Array[model.CrossProjectName], testSuites: Option[NonEmptyList[String]]) - extends BleepCommandRemote(watch) +case class Test( + watch: Boolean, + projects: Array[model.CrossProjectName], + testSuitesOnly: Option[NonEmptyList[String]], + testSuitesExclude: Option[NonEmptyList[String]] +) extends BleepCommandRemote(watch) with BleepCommandRemote.OnlyChanged { + private def isSameSuite(fromBuild: String, fromCli: String) = { + val fromBuildFqn = fromBuild.split('.') + val fromCliFqn = fromCli.split('.') + fromBuildFqn.sameElements(fromCliFqn) || fromBuildFqn.last == fromCliFqn.last + } + private def intersectTestSuites(fromBuild: List[String], fromCli: List[String]): List[String] = fromBuild.filter { fullyQualifiedCls => fromCli.exists { fromCliCls => - val fromBuildFqn = fullyQualifiedCls.split('.') - val fromCliFqn = fromCliCls.split('.') - fromBuildFqn.sameElements(fromCliFqn) || fromBuildFqn.last == fromCliFqn.last + isSameSuite(fullyQualifiedCls, fromCliCls) } } @@ -33,25 +41,47 @@ case class Test(watch: Boolean, projects: Array[model.CrossProjectName], testSui DoSourceGen(started, bloop, watchableProjects(started)).flatMap { _ => val targets = BleepCommandRemote.buildTargets(started.buildPaths, projects) val tests = bloop.buildTargetScalaTestClasses(new bsp4j.ScalaTestClassesParams(targets)).get() - val testClasses: List[String] = tests.getItems().asScala.flatMap(_.getClasses.asScala).toList + val testClassesUnfiltered: List[String] = tests.getItems().asScala.flatMap(_.getClasses.asScala).toList + + val testClasses = testSuitesExclude.map { exclude => + val filtered = testClassesUnfiltered + .filter(fromBuildCls => exclude.toList.find(fromCliCls => isSameSuite(fromBuildCls, fromCliCls)).isDefined) + if (filtered.nonEmpty) testClassesUnfiltered.diff(filtered) else List.empty + } val testParams = new bsp4j.TestParams(targets) - val hasSuites: Boolean = testSuites - .map { classes => - val testClassesData = new bsp4j.ScalaTestSuites( - intersectTestSuites(testClasses, classes.toList) - .map(cls => new bsp4j.ScalaTestSuiteSelection(cls, List.empty[String].asJava)) - .asJava, - List.empty[String].asJava, - List.empty[String].asJava - ) - testParams.setData(testClassesData) - testParams.setDataKind(bsp4j.TestParamsDataKind.SCALA_TEST_SUITES_SELECTION) + val hasSuites: Boolean = + testClasses match { + case None => + testSuitesOnly + .map { classes => + val testClassesData = new bsp4j.ScalaTestSuites( + intersectTestSuites(testClassesUnfiltered, classes.toList) + .map(cls => new bsp4j.ScalaTestSuiteSelection(cls, List.empty[String].asJava)) + .asJava, + List.empty[String].asJava, + List.empty[String].asJava + ) + testParams.setData(testClassesData) + testParams.setDataKind(bsp4j.TestParamsDataKind.SCALA_TEST_SUITES_SELECTION) - !testClassesData.getSuites().isEmpty + !testClassesData.getSuites().isEmpty + } + .getOrElse(true) + case Some(value) => + if (value.isEmpty) false + else { + val testClassesData = new bsp4j.ScalaTestSuites( + value.map(suite => new bsp4j.ScalaTestSuiteSelection(suite, List.empty[String].asJava)).asJava, + List.empty[String].asJava, + List.empty[String].asJava + ) + testParams.setData(testClassesData) + testParams.setDataKind(bsp4j.TestParamsDataKind.SCALA_TEST_SUITES_SELECTION) + true + } } - .getOrElse(true) if (hasSuites) { val result = bloop.buildTargetTest(testParams).get() @@ -65,7 +95,7 @@ case class Test(watch: Boolean, projects: Array[model.CrossProjectName], testSui Left(new BspCommandFailed("tests", projects, BspCommandFailed.StatusCode(errorCode))) } } else { - Left(new TestSuitesNotFound(testSuites.map(_.toList).getOrElse(List.empty[String]))) + Left(new TestSuitesNotFound(testSuitesOnly.map(_.toList).getOrElse(List.empty[String]))) } } }