diff --git a/modules/build/src/main/scala/scala/build/Bloop.scala b/modules/build/src/main/scala/scala/build/Bloop.scala index a849a99fd6..f1f2550dbc 100644 --- a/modules/build/src/main/scala/scala/build/Bloop.scala +++ b/modules/build/src/main/scala/scala/build/Bloop.scala @@ -19,6 +19,12 @@ import scala.jdk.CollectionConverters.* object Bloop { + final case class BloopTestOptions( + testOnly: Option[String] = None, + selectedTestClasses: Seq[String] = Nil, + extraArgs: Seq[String] = Nil + ) + private object BrokenPipeInCauses { @tailrec def unapply(ex: Throwable): Option[IOException] = @@ -68,6 +74,77 @@ object Bloop { ) Left(ex) } + + def test( + projectName: String, + buildServer: BuildServer, + logger: Logger, + buildTargetsTimeout: FiniteDuration, + testOptions: BloopTestOptions = BloopTestOptions() + ): Either[Throwable, Int] = + try retry()(logger) { + logger.debug("Listing BSP build targets for test") + val results = buildServer.workspaceBuildTargets() + .get(buildTargetsTimeout.length, buildTargetsTimeout.unit) + val buildTargetOpt = results.getTargets.asScala.find(_.getDisplayName == projectName) + + val buildTarget = buildTargetOpt.getOrElse { + throw new Exception( + s"Expected to find project '$projectName' in build targets (only got ${results.getTargets + .asScala.map("'" + _.getDisplayName + "'").mkString(", ")})" + ) + } + + logger.debug(s"Testing $projectName with Bloop") + val testParams = buildTestParams(buildTarget.getId, testOptions) + val testRes = buildServer.buildTargetTest(testParams).get() + + val statusCode = testRes.getStatusCode + val exitCode = statusCode match { + case bsp4j.StatusCode.OK => 0 + case bsp4j.StatusCode.ERROR => 1 + case bsp4j.StatusCode.CANCELLED => 1 + } + logger.debug(if (exitCode == 0) "Tests succeeded" else "Tests failed") + Right(exitCode) + } + catch { + case ex @ BrokenPipeInCauses(_) => + logger.debug(s"Caught $ex while exchanging with Bloop server, assuming Bloop server exited") + Left(ex) + case ex: ExecutionException => + logger.debug( + s"Caught $ex while exchanging with Bloop server, you may consider restarting the build server" + ) + Left(ex) + } + + private def buildTestParams( + buildTargetId: bsp4j.BuildTargetIdentifier, + testOptions: BloopTestOptions + ): bsp4j.TestParams = { + val params = new bsp4j.TestParams(List(buildTargetId).asJava) + val classes = testOptions.selectedTestClasses + if classes.nonEmpty then + if testOptions.extraArgs.nonEmpty then { + val selections = classes.map { className => + new bsp4j.ScalaTestSuiteSelection(className, testOptions.extraArgs.asJava) + } + val suites = new bsp4j.ScalaTestSuites( + selections.asJava, + List.empty[String].asJava, + List.empty[String].asJava + ) + params.setDataKind(bsp4j.TestParamsDataKind.SCALA_TEST_SUITES_SELECTION) + params.setData(suites) + } + else { + params.setDataKind(bsp4j.TestParamsDataKind.SCALA_TEST_SUITES) + params.setData(classes.asJava) + } + params + } + def bloopClassPath( dep: AnyDependency, params: ScalaParameters, diff --git a/modules/build/src/main/scala/scala/build/BloopTestBuildClient.scala b/modules/build/src/main/scala/scala/build/BloopTestBuildClient.scala new file mode 100644 index 0000000000..58678c7ce8 --- /dev/null +++ b/modules/build/src/main/scala/scala/build/BloopTestBuildClient.scala @@ -0,0 +1,42 @@ +package scala.build + +import ch.epfl.scala.bsp4j + +class BloopTestBuildClient(logger: Logger) + extends ConsoleBloopBuildClient(logger, keepDiagnostics = false) { + private var testsRanCount: Int = 0 + + def testsRan: Int = testsRanCount + + override def onBuildLogMessage(params: bsp4j.LogMessageParams): Unit = { + logger.debug("Received onBuildLogMessage from bloop: " + params) + System.out.println(params.getMessage) + } + + override def onBuildTaskStart(params: bsp4j.TaskStartParams): Unit = { + logger.debug("Received onBuildTaskStart from bloop: " + params) + Option(params.getMessage).foreach(System.out.println) + } + + override def onBuildTaskFinish(params: bsp4j.TaskFinishParams): Unit = { + logger.debug("Received onBuildTaskFinish from bloop: " + params) + Option(params.getMessage).foreach(System.out.println) + if params.getDataKind == "test-report" then + params.getData match { + case report: bsp4j.TestReport => + testsRanCount += report.getPassed + report.getFailed + report.getIgnored + + report.getCancelled + report.getSkipped + case _ => + } + } + + override def onBuildShowMessage(params: bsp4j.ShowMessageParams): Unit = { + logger.debug("Received onBuildShowMessage from bloop: " + params) + System.out.println(params.getMessage) + } +} + +object BloopTestBuildClient { + def create(logger: Logger): BloopTestBuildClient = + new BloopTestBuildClient(logger) +} diff --git a/modules/build/src/main/scala/scala/build/BloopTestClassDiscovery.scala b/modules/build/src/main/scala/scala/build/BloopTestClassDiscovery.scala new file mode 100644 index 0000000000..99b63df739 --- /dev/null +++ b/modules/build/src/main/scala/scala/build/BloopTestClassDiscovery.scala @@ -0,0 +1,33 @@ +package scala.build + +import java.nio.file.Path +import java.util.regex.Pattern + +import scala.build.testrunner.FrameworkUtils.listClasses +import scala.build.testrunner.Logger as TestRunnerLogger + +object BloopTestClassDiscovery { + + /** Glob pattern matching only `*` wildcards, same semantics as the test-runner. */ + def globPattern(expr: String): Pattern = { + val parts = expr.split("\\*", -1) + val b = new StringBuilder() + for (i <- parts.indices) { + if (i != 0) b.append(".*") + if (parts(i).nonEmpty) b.append(Pattern.quote(parts(i).replaceAll("\n", "\\n"))) + } + Pattern.compile(b.toString) + } + + def matchingTestClasses( + classPath: Seq[Path], + testOnlyGlob: String, + logger: Logger + ): Seq[String] = { + val pattern = globPattern(testOnlyGlob) + listClasses(classPath, keepJars = false, TestRunnerLogger(logger.verbosity)) + .filter(pattern.matcher(_).matches) + .toVector + .sorted + } +} diff --git a/modules/build/src/main/scala/scala/build/BloopTestRunner.scala b/modules/build/src/main/scala/scala/build/BloopTestRunner.scala new file mode 100644 index 0000000000..921927cbf2 --- /dev/null +++ b/modules/build/src/main/scala/scala/build/BloopTestRunner.scala @@ -0,0 +1,93 @@ +package scala.build + +import bloop.rifle.{BloopRifleConfig, BloopServer} + +import scala.build.EitherCps.{either, value} +import scala.build.errors.BuildException +import scala.build.internal.Constants +import scala.concurrent.duration.DurationInt +import scala.util.Try + +object BloopTestRunner { + private final class BloopTestFailedError(message: String, cause: Throwable = null) + extends BuildException(message, cause = cause) + + def run( + build: Build.Successful, + bloopConfig: BloopRifleConfig, + threads: BuildThreads, + logger: Logger, + requireTests: Boolean, + args: Seq[String] + ): Either[BuildException, Int] = either { + val buildClient = BloopTestBuildClient.create(logger) + val workspace = build.inputs.workspace / Constants.workspaceDirName + val classesDir = Build.classesRootDir(build.inputs.workspace, build.inputs.projectName) + + val server = value { + Try { + retry()(logger) { + BloopServer.buildServer( + bloopConfig, + "scala-cli", + Constants.version, + workspace.toNIO, + classesDir.toNIO, + buildClient, + threads.bloop, + logger.bloopRifleLogger + ) + } + }.toEither.left.map(ex => + new BloopTestFailedError("Failed to connect to Bloop for running tests", ex) + ) + } + + try { + val testOptions = value(prepareTestOptions(build, args, logger)) + val exitCode = value { + Bloop + .test( + build.project.projectName, + server.server, + logger, + 20.seconds, + testOptions + ) + .left + .map(ex => new BloopTestFailedError("Bloop test execution failed", ex)) + } + if requireTests && buildClient.testsRan == 0 && testOptions.selectedTestClasses.isEmpty + then { + logger.error("Error: no tests were run.") + 1 + } + else exitCode + } + finally server.shutdown() + } + + private def prepareTestOptions( + build: Build.Successful, + args: Seq[String], + logger: Logger + ): Either[BuildException, Bloop.BloopTestOptions] = { + val testOnly = build.options.testOptions.testOnly + val selectedTestClasses = testOnly match { + case None => Nil + case Some(glob) => + BloopTestClassDiscovery.matchingTestClasses( + build.fullClassPath.map(_.toNIO), + glob, + logger + ) + } + Right( + Bloop.BloopTestOptions( + testOnly = testOnly, + selectedTestClasses = selectedTestClasses, + extraArgs = args + ) + ) + } +} diff --git a/modules/build/src/main/scala/scala/build/Build.scala b/modules/build/src/main/scala/scala/build/Build.scala index f868b60dd6..145ac23837 100644 --- a/modules/build/src/main/scala/scala/build/Build.scala +++ b/modules/build/src/main/scala/scala/build/Build.scala @@ -65,6 +65,11 @@ object Build { sources.resourceDirs ++ artifacts.compileClassPath def fullClassPath: Seq[os.Path] = Seq(output) ++ dependencyClassPath def fullCompileClassPath: Seq[os.Path] = fullClassPath ++ dependencyCompileClassPath + def isLegacyScala3: Boolean = + scalaParams.exists { params => + params.scalaVersion.startsWith("3") && + params.scalaVersion.coursierVersion < "3.3.0".coursierVersion + } private lazy val mainClassesFoundInProject: Seq[String] = MainClass.find(output, logger).sorted private lazy val mainClassesFoundOnExtraClasspath: Seq[String] = options.classPathOptions.extraClassPath.flatMap(MainClass.find(_, logger)).sorted @@ -1090,7 +1095,9 @@ object Build { resourceDirs = sources.resourceDirs, scope = scope, javaHomeOpt = Option(options.javaHomeLocation().value), - javacOptions = javacOptions.toList + javacOptions = javacOptions.toList, + testFrameworkNames = + if scope == Scope.Test then options.testOptions.frameworks.map(_.value) else Nil ) project } diff --git a/modules/build/src/main/scala/scala/build/Project.scala b/modules/build/src/main/scala/scala/build/Project.scala index 8b8e8c32cd..5af185832a 100644 --- a/modules/build/src/main/scala/scala/build/Project.scala +++ b/modules/build/src/main/scala/scala/build/Project.scala @@ -28,7 +28,8 @@ final case class Project( resourceDirs: Seq[os.Path], javaHomeOpt: Option[os.Path], scope: Scope, - javacOptions: List[String] + javacOptions: List[String], + testFrameworkNames: Seq[String] = Nil ) { import Project._ @@ -55,7 +56,8 @@ final case class Project( directory.toNIO, (directory / ".bloop" / projectName).toNIO, classesDir.toNIO, - scope + scope, + testFrameworkNames ) .copy( workspaceDir = Some(workspace.toNIO), @@ -169,12 +171,30 @@ object Project { BloopConfig.Resolution(modules) } - private def setProjectTestConfig(p: BloopConfig.Project): BloopConfig.Project = + private val jupiterFramework = + BloopConfig.TestFramework(List("com.github.sbt.junit.jupiter.api.JupiterFramework")) + private val zioTestFramework = + BloopConfig.TestFramework(List("zio.test.sbt.ZTestFramework")) + private val weaverFramework = + BloopConfig.TestFramework(List("weaver.framework.CatsEffect")) + + private def bloopTestFrameworks(names: Seq[String]): List[BloopConfig.TestFramework] = + names match { + case Nil => + BloopConfig.TestFramework.DefaultFrameworks ++ + List(jupiterFramework, zioTestFramework, weaverFramework) + case ns => ns.map(name => BloopConfig.TestFramework(List(name))).toList + } + + private def setProjectTestConfig( + p: BloopConfig.Project, + testFrameworkNames: Seq[String] + ): BloopConfig.Project = p.copy( dependencies = List(p.name.stripSuffix("-test")), test = Some( BloopConfig.Test( - frameworks = BloopConfig.TestFramework.DefaultFrameworks, + frameworks = bloopTestFrameworks(testFrameworkNames), options = BloopConfig.TestOptions.empty ) ), @@ -186,7 +206,8 @@ object Project { directory: Path, out: Path, classesDir: Path, - scope: Scope + scope: Scope, + testFrameworkNames: Seq[String] ): BloopConfig.Project = { val project = BloopConfig.Project( name = name, @@ -210,7 +231,7 @@ object Project { sourceGenerators = None ) if (scope == Scope.Test) - setProjectTestConfig(project) + setProjectTestConfig(project, testFrameworkNames) else project } diff --git a/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala b/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala index c222cf330f..06ea128bb7 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala @@ -116,7 +116,9 @@ object Test extends ScalaCommand[TestOptions] { args.unparsed, logger, allowExecve = allowExit && buildsLen <= 1, - asJar = options.shared.asJar + asJar = options.shared.asJar, + shared = options.shared, + threads = threads ) if (printBeforeAfterMessages && idx < buildsLen - 1) System.err.println() @@ -162,27 +164,69 @@ object Test extends ScalaCommand[TestOptions] { maybeTest(builds, allowExit = false) } try WatchUtil.waitForCtrlC(() => watcher.schedule()) - finally watcher.dispose() - } - else { - val builds = - Build.build( - inputs, - initialBuildOptions, - compilerMaker, - None, - logger, - crossBuilds = cross, - buildTests = true, - partial = None, - actionableDiagnostics = actionableDiagnostics - ) - .orExit(logger) - maybeTest(builds, allowExit = true) + finally { + watcher.dispose() + threads.shutdown() + } } + else + try { + val builds = + Build.build( + inputs, + initialBuildOptions, + compilerMaker, + None, + logger, + crossBuilds = cross, + buildTests = true, + partial = None, + actionableDiagnostics = actionableDiagnostics + ) + .orExit(logger) + maybeTest(builds, allowExit = true) + } + finally threads.shutdown() } private def testOnce( + build: Build.Successful, + requireTests: Boolean, + args: Seq[String], + logger: Logger, + asJar: Boolean, + allowExecve: Boolean, + shared: SharedOptions, + threads: BuildThreads + ): Either[BuildException, Int] = + if shouldRunTestsViaBloop(build, args) then + either { + val bloopConfig = value(shared.bloopRifleConfig(Some(build.options))) + value(BloopTestRunner.run(build, bloopConfig, threads, logger, requireTests, args)) + } + else + testOnceWithoutBloop(build, requireTests, args, logger, asJar, allowExecve) + + /** Bloop can run JVM tests compiled into the test project; fall back to the subprocess test + * runner for other platforms and cases Bloop does not handle yet (JUnit 5, JS/Native toolchains, + * markdown or main-scope test sources, explicit frameworks, etc.). + */ + private def shouldRunTestsViaBloop(build: Build.Successful, args: Seq[String]): Boolean = + build.options.useBuildServer.getOrElse(true) && + build.options.platform.value == Platform.JVM && + build.project.sources.nonEmpty && + !usesJupiter(build.options.testOptions.frameworks, build.fullClassPath) && + !build.artifacts.hasJavaTestRunner && + args.isEmpty && + build.options.testOptions.frameworks.isEmpty && + !hasZioTestWithoutSbt(build.fullClassPath) + + private def hasZioTestWithoutSbt(classPath: Seq[os.Path]): Boolean = { + val classPathStr = classPath.map(_.toString) + classPathStr.exists(_.contains("zio-test")) && !classPathStr.exists(_.contains("zio-test-sbt")) + } + + private def testOnceWithoutBloop( build: Build.Successful, requireTests: Boolean, args: Seq[String], @@ -257,6 +301,15 @@ object Test extends ScalaCommand[TestOptions] { case Nil => findTestFramework(classPath.map(_.toNIO), logger).map(Positioned.none).toList } + + if build.options.useBuildServer.contains(false) && + build.isLegacyScala3 && + usesJupiter(predefinedTestFrameworks0, classPath) + then + logger.message( + s"JUnit 5 on Scala < 3.3.0 is only supported with the build server enabled (without --server=false)." + ) + val testOnly = build.options.testOptions.testOnly val extraArgs = @@ -283,6 +336,16 @@ object Test extends ScalaCommand[TestOptions] { } } + private def usesJupiter( + frameworks: Seq[Positioned[String]], + classPath: Seq[os.Path] + ): Boolean = { + val classPathStr = classPath.map(_.toString) + frameworks.exists(_.value.contains("Jupiter")) || + classPathStr.exists(_.contains("jupiter-interface")) || + classPathStr.exists(_.contains("junit-jupiter")) + } + private def findTestFramework(classPath: Seq[Path], logger: Logger): Option[String] = { val classPath0 = classPath.map(_.toString) diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestBuildServer.scala b/modules/integration/src/test/scala/scala/cli/integration/TestBuildServer.scala new file mode 100644 index 0000000000..6f68e8bdd0 --- /dev/null +++ b/modules/integration/src/test/scala/scala/cli/integration/TestBuildServer.scala @@ -0,0 +1,15 @@ +package scala.cli.integration + +sealed trait TestBuildServer { + def buildServerOptions: Seq[String] + def buildServerDescriptionSuffix: String + def usesBloop: Boolean = buildServerOptions.isEmpty +} +trait TestWithBloop extends TestBuildServer { + override def buildServerOptions: Seq[String] = Nil + override def buildServerDescriptionSuffix: String = "with Bloop" +} +trait TestWithoutBloop extends TestBuildServer { + override def buildServerOptions: Seq[String] = Seq("--server=false") + override def buildServerDescriptionSuffix: String = "without build server" +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala index ede9d615ac..09fad6c089 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala @@ -9,10 +9,14 @@ import scala.concurrent.duration.DurationInt import scala.util.Properties abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionArgs { - this: TestScalaVersion => - protected lazy val extraOptions: Seq[String] = scalaVersionArgs ++ TestUtil.extraOptions - private val utestVersion = "0.8.3" - private val zioTestVersion = "2.1.17" + this: TestScalaVersion & TestBuildServer => + protected lazy val extraOptions: Seq[String] = + scalaVersionArgs ++ buildServerOptions ++ TestUtil.extraOptions + private def supportsJupiterTests: Boolean = + actualScalaVersion.coursierVersion >= "3.3.0".coursierVersion || + (usesBloop && actualScalaVersion.startsWith("3.")) + private val utestVersion = "0.8.3" + private val zioTestVersion = "2.1.17" def successfulTestInputs(directivesString: String = s"//> using dep org.scalameta::munit::$munitVersion"): TestInputs = TestInputs( @@ -629,7 +633,23 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr } } - if actualScalaVersion.coursierVersion >= "3.3.0".coursierVersion then + if !usesBloop && actualScalaVersion.coursierVersion < "3.3.0".coursierVersion then + for javaVersion <- Constants.allJavaVersions.filter(_ >= 17) + do + test(s"jupiter warns without build server on Java $javaVersion") { + successfulJupiterInputs.fromRoot { root => + val res = + os.proc(TestUtil.cli, "test", extraOptions, ".", "--jvm", javaVersion) + .call(cwd = root, stderr = os.Pipe) + expect( + res.err.text().contains( + "JUnit 5 on Scala < 3.3 is only supported with the build server enabled" + ) + ) + } + } + + if supportsJupiterTests then for javaVersion <- Constants.allJavaVersions.filter(_ >= 17) do test(s"jupiter on Java $javaVersion") { diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTests212.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTests212.scala index 8ad023f460..95abd59af3 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTests212.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTests212.scala @@ -3,32 +3,42 @@ package scala.cli.integration import com.eed3si9n.expecty.Expecty.expect import scala.cli.integration.TestUtil.StringOps +import scala.util.Properties -class TestTests212 extends TestTestDefinitions with Test212 { - test(s"run a simple test with Scala $actualScalaVersion (legacy)") { - val expectedMessage = "Hello, world!" - TestInputs(os.rel / "example.test.scala" -> - s"""//> using dep com.novocode:junit-interface:0.11 - |import org.junit.Test - | - |class MyTests { - | @Test - | def foo(): Unit = { - | assert(2 + 2 == 4) - | println("$expectedMessage") - | } - |} - |""".stripMargin).fromRoot { root => - val expectedWarning = - s"Defaulting to a legacy test-runner module version: ${Constants.runnerScala2LegacyVersion}" - val res = - os.proc(TestUtil.cli, "test", ".", extraOptions) - .call(cwd = root, stderr = os.Pipe) - val out = res.out.trim() - expect(out.contains(expectedMessage)) - val err = res.err.trim() - expect(err.contains(expectedWarning)) - expect(err.countOccurrences(expectedWarning) == 1) +trait TestTests212 { this: TestTestDefinitions & Test212 & TestBuildServer => + if !usesBloop then + test(s"run a simple test with Scala $actualScalaVersion (legacy)") { + val expectedMessage = "Hello, world!" + TestInputs(os.rel / "example.test.scala" -> + s"""//> using dep com.novocode:junit-interface:0.11 + |import org.junit.Test + | + |class MyTests { + | @Test + | def foo(): Unit = { + | assert(2 + 2 == 4) + | println("$expectedMessage") + | } + |} + |""".stripMargin).fromRoot { root => + val expectedWarning = + s"Defaulting to a legacy test-runner module version: ${Constants.runnerScala2LegacyVersion}" + val res = + os.proc(TestUtil.cli, "test", ".", extraOptions) + .call(cwd = root, stderr = os.Pipe) + val out = res.out.trim() + expect(out.contains(expectedMessage)) + val err = res.err.trim() + expect(err.contains(expectedWarning)) + expect(err.countOccurrences(expectedWarning) == 1) + } } - } +} + +class TestTests212WithBloop + extends TestTestDefinitions with Test212 with TestWithBloop with TestTests212 + +class TestTests212WithoutBloop + extends TestTestDefinitions with Test212 with TestWithoutBloop with TestTests212 { + override def munitIgnore: Boolean = super.munitIgnore || Properties.isWin } diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTests213.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTests213.scala index d8fde38561..4b9cf7db79 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTests213.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTests213.scala @@ -3,33 +3,42 @@ package scala.cli.integration import com.eed3si9n.expecty.Expecty.expect import scala.cli.integration.TestUtil.StringOps +import scala.util.Properties -class TestTests213 extends TestTestDefinitions with Test213 { - test(s"run a simple test with Scala $actualScalaVersion (legacy)") { - val expectedMessage = "Hello, world!" - TestInputs(os.rel / "example.test.scala" -> - s"""//> using dep com.novocode:junit-interface:0.11 - |import org.junit.Test - | - |class MyTests { - | @Test - | def foo(): Unit = { - | assert(2 + 2 == 4) - | println("$expectedMessage") - | } - |} - |""".stripMargin).fromRoot { root => - val expectedWarning = - s"Defaulting to a legacy test-runner module version: ${Constants.runnerScala2LegacyVersion}" - val res = - os.proc(TestUtil.cli, "test", ".", extraOptions) - .call(cwd = root, stderr = os.Pipe) - val out = res.out.trim() - expect(out.contains(expectedMessage)) - val err = res.err.trim() - expect(err.contains(expectedWarning)) - expect(err.countOccurrences(expectedWarning) == 1) +trait TestTests213 { this: TestTestDefinitions & Test213 & TestBuildServer => + if !usesBloop then + test(s"run a simple test with Scala $actualScalaVersion (legacy)") { + val expectedMessage = "Hello, world!" + TestInputs(os.rel / "example.test.scala" -> + s"""//> using dep com.novocode:junit-interface:0.11 + |import org.junit.Test + | + |class MyTests { + | @Test + | def foo(): Unit = { + | assert(2 + 2 == 4) + | println("$expectedMessage") + | } + |} + |""".stripMargin).fromRoot { root => + val expectedWarning = + s"Defaulting to a legacy test-runner module version: ${Constants.runnerScala2LegacyVersion}" + val res = + os.proc(TestUtil.cli, "test", ".", extraOptions) + .call(cwd = root, stderr = os.Pipe) + val out = res.out.trim() + expect(out.contains(expectedMessage)) + val err = res.err.trim() + expect(err.contains(expectedWarning)) + expect(err.countOccurrences(expectedWarning) == 1) + } } - } +} + +class TestTests213WithBloop + extends TestTestDefinitions with Test213 with TestWithBloop with TestTests213 +class TestTests213WithoutBloop + extends TestTestDefinitions with Test213 with TestWithoutBloop with TestTests213 { + override def munitIgnore: Boolean = super.munitIgnore || Properties.isWin } diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTests3Lts.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTests3Lts.scala index 0cccc1f1c1..d611510d05 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTests3Lts.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTests3Lts.scala @@ -1,3 +1,9 @@ package scala.cli.integration -class TestTests3Lts extends TestTestDefinitions with Test3Lts +import scala.util.Properties + +class TestTests3LtsWithBloop extends TestTestDefinitions with Test3Lts with TestWithBloop + +class TestTests3LtsWithoutBloop extends TestTestDefinitions with Test3Lts with TestWithoutBloop { + override def munitIgnore: Boolean = super.munitIgnore || Properties.isWin +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTests3NextRc.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTests3NextRc.scala index 9406030d37..24dd7ce2ba 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTests3NextRc.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTests3NextRc.scala @@ -1,3 +1,10 @@ package scala.cli.integration -class TestTests3NextRc extends TestTestDefinitions with Test3NextRc +import scala.util.Properties + +class TestTests3NextRcWithBloop extends TestTestDefinitions with Test3NextRc with TestWithBloop + +class TestTests3NextRcWithoutBloop extends TestTestDefinitions with Test3NextRc + with TestWithoutBloop { + override def munitIgnore: Boolean = super.munitIgnore || Properties.isWin +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala index c38e260b44..ba10dd9186 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala @@ -6,8 +6,10 @@ import java.io.File import scala.cli.integration.Constants.munitVersion import scala.cli.integration.TestUtil.StringOps +import scala.util.Properties + +trait TestTestsDefault { this: TestTestDefinitions & TestDefault & TestBuildServer => -class TestTestsDefault extends TestTestDefinitions with TestDefault { test("Pure Java with Scala tests") { val inputs = TestInputs( os.rel / "Messages.java" -> @@ -73,6 +75,7 @@ class TestTestsDefault extends TestTestDefinitions with TestDefault { expectedMessage = "Hello, world!" expectedWarning = s"Defaulting to a legacy test-runner module version: ${Constants.runnerScala30LegacyVersion}" + if !usesBloop } test(s"run a simple test with Scala $scalaVersion (legacy)") { TestInputs(os.rel / "example.test.scala" -> @@ -89,7 +92,7 @@ class TestTestsDefault extends TestTestDefinitions with TestDefault { |} |""".stripMargin).fromRoot { root => val res = - os.proc(TestUtil.cli, "test", ".", "-S", scalaVersion, TestUtil.extraOptions) + os.proc(TestUtil.cli, "test", ".", "-S", scalaVersion, extraOptions) .call(cwd = root, stderr = os.Pipe) val out = res.out.trim() expect(out.contains(expectedMessage)) @@ -99,37 +102,40 @@ class TestTestsDefault extends TestTestDefinitions with TestDefault { } } - for { - buildServerOptions <- Seq(Nil, Seq("--server=false")) - buildServerDesc = - if buildServerOptions.isEmpty then "with build server" else "without build server" - } - test(s"pure Java test with JUnit has no Scala on classpath $buildServerDesc") { - TestInputs( - os.rel / "test" / "MyTests.java" -> - """//> using test.dep junit:junit:4.13.2 - |//> using test.dep com.novocode:junit-interface:0.11 - |import org.junit.Test; - |import static org.junit.Assert.assertEquals; - | - |public class MyTests { - | @Test - | public void foo() { - | try { - | Class.forName("scala.Predef"); - | throw new AssertionError("Scala should not be on the classpath"); - | } catch (ClassNotFoundException e) { - | // expected - | } - | assertEquals(4, 2 + 2); - | System.out.println("No Scala on classpath!"); - | } - |} - |""".stripMargin - ).fromRoot { root => - val res = - os.proc(TestUtil.cli, "test", extraOptions, buildServerOptions, ".").call(cwd = root) - expect(res.out.text().contains("No Scala on classpath!")) - } + test(s"pure Java test with JUnit has no Scala on classpath $buildServerDescriptionSuffix") { + TestInputs( + os.rel / "test" / "MyTests.java" -> + """//> using test.dep junit:junit:4.13.2 + |//> using test.dep com.novocode:junit-interface:0.11 + |import org.junit.Test; + |import static org.junit.Assert.assertEquals; + | + |public class MyTests { + | @Test + | public void foo() { + | try { + | Class.forName("scala.Predef"); + | throw new AssertionError("Scala should not be on the classpath"); + | } catch (ClassNotFoundException e) { + | // expected + | } + | assertEquals(4, 2 + 2); + | System.out.println("No Scala on classpath!"); + | } + |} + |""".stripMargin + ).fromRoot { root => + val res = + os.proc(TestUtil.cli, "test", extraOptions, ".").call(cwd = root) + expect(res.out.text().contains("No Scala on classpath!")) } + } +} + +class TestTestsDefaultWithBloop + extends TestTestDefinitions with TestDefault with TestWithBloop with TestTestsDefault + +class TestTestsDefaultWithoutBloop + extends TestTestDefinitions with TestDefault with TestWithoutBloop with TestTestsDefault { + override def munitIgnore: Boolean = super.munitIgnore || Properties.isWin }