From 85a8e24ea42901cf055267439a77e5aa87a02e45 Mon Sep 17 00:00:00 2001 From: Kristian Nedrevold-Hansen Date: Thu, 27 Jun 2024 00:33:23 +0200 Subject: [PATCH 1/2] Add support for BSP TestParamsData kinds Adds support for TestParamsData kinds as defined in the scala extension of BSP. This allows clients to specify which test classes / tests to run --- .../scala/bloop/bsp/BloopBspServices.scala | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala b/frontend/src/main/scala/bloop/bsp/BloopBspServices.scala index 717ebd988..c59638d3f 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 @@ -707,15 +709,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 ) From 4f71845e5d2608004669204a33190d32560130e3 Mon Sep 17 00:00:00 2001 From: Kristian Nedrevold-Hansen Date: Thu, 27 Jun 2024 14:38:08 +0200 Subject: [PATCH 2/2] Fixed a bug in the TestTask. TestTask.discoverTestSuites where it did not filter on which test should should run when explicitly passing list of test classes (fully qualified) Included tests for running specific test suites, and tests within suites --- .../scala/bloop/engine/tasks/TestTask.scala | 41 ++- .../test/scala/bloop/bsp/BspTestSpec.scala | 287 ++++++++++++++++++ 2 files changed, 314 insertions(+), 14 deletions(-) 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(