From e07f58f1336fcfcf6cdbe3d07557ed51332b51e7 Mon Sep 17 00:00:00 2001 From: Kristian Nedrevold-Hansen Date: Thu, 8 Aug 2024 01:52:46 +0200 Subject: [PATCH] Added support for excluding tests. The way this works is mutually exclusive with defining a subset of tests that only should run. It hooks into the same bsp API with the logic inverted. Instead of saying run only these test that come from the user through the cli we say run all tets besides the ones the user specified in the cli --- bleep-cli/src/scala/bleep/Main.scala | 7 +- bleep-core/src/scala/bleep/Commands.scala | 9 ++- .../src/scala/bleep/commands/Test.scala | 70 +++++++++++++------ 3 files changed, 61 insertions(+), 25 deletions(-) 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]))) } } }