diff --git a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala index b11f08e50..d1e7f7dcd 100644 --- a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala +++ b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala @@ -74,7 +74,9 @@ import bloop.util.JavaRuntime import com.github.plokhotnyuk.jsoniter_scala.core._ import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker +import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec import jsonrpc4s._ +import com.github.plokhotnyuk.jsoniter_scala.core.readFromArray import monix.execution.Cancelable import monix.execution.CancelablePromise import monix.execution.Scheduler @@ -711,15 +713,54 @@ final class BloopBspServices( } def test(params: bsp.TestParams): BspEndpointResponse[bsp.TestResult] = { + def scalaTestSuitesByKind(kind: String): bsp.ScalaTestSuites = + kind match { + case bsp.TestParamsDataKind.ScalaTest => + val scalaTestParams: Option[bsp.ScalaTestParams] = + params.data.map(raw => readFromArray[bsp.ScalaTestParams](raw.value)) + + val suites: List[bsp.ScalaTestSuiteSelection] = + scalaTestParams + .flatMap( + _.testClasses + .map(_.flatMap(item => item.classes)) + .map(_.map(cls => bsp.ScalaTestSuiteSelection.apply(cls, Nil))) + ) + .getOrElse(Nil) + bsp.ScalaTestSuites(suites, Nil, Nil) + + case bsp.TestParamsDataKind.ScalaTestSuites => + val scalaTestSuites: Option[List[bsp.ScalaTestSuiteSelection]] = params.data + .map { raw => + readFromArray[List[String]](raw.value)(JsonCodecMaker.make[List[String]]) + } + .map(_.map(className => bsp.ScalaTestSuiteSelection(className, Nil))) + bsp.ScalaTestSuites(scalaTestSuites.getOrElse(Nil), Nil, Nil) + + case bsp.TestParamsDataKind.ScalaTestSuitesSelection => + params.data + .map(raw => readFromArray[bsp.ScalaTestSuites](raw.value)) + .getOrElse(bsp.ScalaTestSuites(Nil, Nil, Nil)) + + case _ => bsp.ScalaTestSuites(Nil, Nil, Nil) + } + def test(project: Project, state: State): Task[Tasks.TestRuns] = { - val testFilter = TestInternals.parseFilters(Nil) // Don't support test only for now + val scalaTestSuites: bsp.ScalaTestSuites = params.dataKind match { + case None => bsp.ScalaTestSuites(Nil, Nil, Nil) + case Some(kind) => scalaTestSuitesByKind(kind) + } + + val testFilter = + TestInternals.parseFilters(Nil) // Does not handle filtering of tests, yet + val handler = new LoggingEventHandler(state.logger) Tasks.test( state, List(project), Nil, testFilter, - bsp.ScalaTestSuites(Nil, Nil, Nil), + scalaTestSuites, handler, mode = RunMode.Normal ) diff --git a/frontend/src/main/scala/bloop/engine/tasks/TestTask.scala b/frontend/src/main/scala/bloop/engine/tasks/TestTask.scala index e7b92d6a7..b94ef4650 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/TestTask.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/TestTask.scala @@ -306,6 +306,7 @@ object TestTask { val fqn = taskDef.fullyQualifiedName() !excluded(fqn) && testFilter(fqn) } + if (logger.isVerbose) { val allNames = ungroupedTests.map(_.taskDef.fullyQualifiedName).mkString(", ") val includedNames = includedTests.map(_.taskDef.fullyQualifiedName).mkString(", ") @@ -321,21 +322,33 @@ object TestTask { // usually it is a Array(new SuiteSelector). However, if only subset of test are supposed to // be run, then it can be altered to Array[TestSelector] val selectedTests = testClasses.suites.map(entry => (entry.className, entry.tests)).toMap - includedTests.groupBy(_.framework).mapValues { taskDefs => - taskDefs.map { - case TaskDefWithFramework(taskDef, _) => - selectedTests.get(taskDef.fullyQualifiedName()).getOrElse(Nil) match { - case Nil => taskDef - case selectedTests => - new TaskDef( - taskDef.fullyQualifiedName(), - taskDef.fingerprint(), - false, - selectedTests.map(test => new TestSelector(test)).toList.toArray - ) - } + + val testsToRun = + if (testClasses.suites.nonEmpty) { + includedTests + .filter(t => testClasses.suites.map(_.className).contains(t.taskDef.fullyQualifiedName)) + } else { + includedTests + } + + testsToRun + .groupBy(_.framework) + .mapValues { taskDefs => + taskDefs.map { + case TaskDefWithFramework(taskDef, _) => + selectedTests.get(taskDef.fullyQualifiedName()) match { + case None => + taskDef + case Some(value) => + new TaskDef( + taskDef.fullyQualifiedName(), + taskDef.fingerprint(), + false, + value.map(test => new TestSelector(test)).toList.toArray + ) + } + } } - } } private[bloop] def discoverTests( diff --git a/frontend/src/test/scala/bloop/bsp/BspTestSpec.scala b/frontend/src/test/scala/bloop/bsp/BspTestSpec.scala index 632a3fcb6..03f64a44b 100644 --- a/frontend/src/test/scala/bloop/bsp/BspTestSpec.scala +++ b/frontend/src/test/scala/bloop/bsp/BspTestSpec.scala @@ -7,6 +7,13 @@ import bloop.io.AbsolutePath import bloop.logging.RecordingLogger import bloop.util.TestProject import bloop.util.TestUtil +import com.github.plokhotnyuk.jsoniter_scala.core.writeToArray +import jsonrpc4s.RawJson +import ch.epfl.scala.bsp.ScalaTestParams +import ch.epfl.scala.bsp.ScalaTestClassesItem +import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker +import ch.epfl.scala.bsp.ScalaTestSuites +import ch.epfl.scala.bsp.ScalaTestSuiteSelection object TcpBspTestSpec extends BspTestSpec(BspProtocol.Tcp) object LocalBspTestSpec extends BspTestSpec(BspProtocol.Local) @@ -35,6 +42,286 @@ class BspTestSpec(override val protocol: BspProtocol) extends BspBaseSuite { } } + test("Test only when datakind is scala-test succeeds in testing only one test suite") { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + loadBspState(workspace, List(A), logger) { state => + val cls = + state.testClasses(A) + + val classes = + Some( + List( + ScalaTestClassesItem(cls.items.head.target, cls.items.head.framework, List("BarTest")) + ) + ) + + val data = RawJson(writeToArray(ScalaTestParams(classes, None))) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.Ok) + } + } + } + + test("Test only when datakind is scala-test fails in testing only one test suite") { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + loadBspState(workspace, List(A), logger) { state => + val cls = + state.testClasses(A) + + val classes = + Some( + List( + ScalaTestClassesItem(cls.items.head.target, cls.items.head.framework, List("BarTest")) + ) + ) + + val data = RawJson(writeToArray(ScalaTestParams(classes, None))) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.TestExecutionError) + } + } + } + + test("Test only when datakind is scala-test-suites succeeds in testing only one test suite") { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + loadBspState(workspace, List(A), logger) { state => + val classes = writeToArray(List("BarTest"))(JsonCodecMaker.make[List[String]]) + val data = RawJson(classes) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test-suites"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.Ok) + } + } + } + + test("Test only when datakind is scala-test-suites fails in testing only one test suite") { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + loadBspState(workspace, List(A), logger) { state => + val classes = writeToArray(List("BarTest"))(JsonCodecMaker.make[List[String]]) + val data = RawJson(classes) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test-suites"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.TestExecutionError) + } + } + } + + test( + "Test only when datakind is scala-test-suites-selection succeeds in testing only one test suite" + ) { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + + loadBspState(workspace, List(A), logger) { state => + val testSuites = ScalaTestSuites( + List(ScalaTestSuiteSelection("BarTest", Nil)), + Nil, + Nil + ) + val data = RawJson(writeToArray(testSuites)) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test-suites-selection"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.Ok) + } + } + } + + test( + "Test only when datakind is scala-test-suites-selection fails in testing only one test suite" + ) { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + + loadBspState(workspace, List(A), logger) { state => + val testSuites = ScalaTestSuites( + List(ScalaTestSuiteSelection("BarTest", Nil)), + Nil, + Nil + ) + val data = RawJson(writeToArray(testSuites)) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test-suites-selection"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.TestExecutionError) + } + } + } + + test( + "Test only when datakind is scala-test-suites-selection succeeds in testing only one test suite with specific test" + ) { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + | @org.junit.Test def bar(): Unit = org.junit.Assert.assertTrue(true) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + + loadBspState(workspace, List(A), logger) { state => + val testSuites = ScalaTestSuites( + List(ScalaTestSuiteSelection("BarTest", List("bar"))), + Nil, + Nil + ) + val data = RawJson(writeToArray(testSuites)) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test-suites-selection"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.Ok) + } + } + } + + test( + "Test only when datakind is scala-test-suites-selection fails in testing only one test suite with specific test" + ) { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/FooTest.scala + |class FooTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin, + """/BarTest.scala + |class BarTest { + | @org.junit.Test def foo(): Unit = org.junit.Assert.assertTrue(true) + | @org.junit.Test def bar(): Unit = org.junit.Assert.assertTrue(false) + |} + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + + val A = TestProject(workspace, "a", sources, enableTests = true, jars = junitJars) + + loadBspState(workspace, List(A), logger) { state => + val testSuites = ScalaTestSuites( + List(ScalaTestSuiteSelection("BarTest", List("bar"))), + Nil, + Nil + ) + val data = RawJson(writeToArray(testSuites)) + + val testResult: ManagedBspTestState = + state.test(A, dataKind = Some("scala-test-suites-selection"), data = Some(data)) + assertExitStatus(testResult, ExitStatus.TestExecutionError) + } + } + } + test("bsp test fails") { TestUtil.withinWorkspace { workspace => val sources = List(