Skip to content

Commit ea12ffb

Browse files
committed
Add java-test-runner module to support running tests with pure Java
1 parent 9169742 commit ea12ffb

13 files changed

Lines changed: 949 additions & 5 deletions

File tree

build.mill

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ object runner extends Cross[Runner](Scala.runnerScalaVersions)
112112
with CrossScalaDefaultToRunner
113113
object `test-runner` extends Cross[TestRunner](Scala.runnerScalaVersions)
114114
with CrossScalaDefaultToRunner
115+
object `java-test-runner` extends JavaTestRunner
116+
with LocatedInModules
115117
object `tasty-lib` extends Cross[TastyLib](Scala.scala3MainVersions)
116118
with CrossScalaDefaultToInternal
117119

@@ -452,12 +454,18 @@ trait Core extends ScalaCliCrossSbtModule
452454
val runnerMainClass = build.runner(crossScalaVersion)
453455
.mainClass()
454456
.getOrElse(sys.error("No main class defined for runner"))
457+
val javaTestRunnerMainClass = `java-test-runner`
458+
.mainClass()
459+
.getOrElse(sys.error("No main class defined for java-test-runner"))
455460
val detailedVersionValue =
456461
if (`local-repo`.developingOnStubModules) s"""Some("${vcsState()}")"""
457462
else "None"
458463
val testRunnerOrganization = `test-runner`(crossScalaVersion)
459464
.pomSettings()
460465
.organization
466+
val javaTestRunnerOrganization = `java-test-runner`
467+
.pomSettings()
468+
.organization
461469
val code =
462470
s"""package scala.build.internal
463471
|
@@ -479,6 +487,11 @@ trait Core extends ScalaCliCrossSbtModule
479487
| def testRunnerVersion = "${`test-runner`(crossScalaVersion).publishVersion()}"
480488
| def testRunnerMainClass = "$testRunnerMainClass"
481489
|
490+
| def javaTestRunnerOrganization = "$javaTestRunnerOrganization"
491+
| def javaTestRunnerModuleName = "${`java-test-runner`.artifactName()}"
492+
| def javaTestRunnerVersion = "${`java-test-runner`.publishVersion()}"
493+
| def javaTestRunnerMainClass = "$javaTestRunnerMainClass"
494+
|
482495
| def runnerOrganization = "${build.runner(crossScalaVersion).pomSettings().organization}"
483496
| def runnerModuleName = "${build.runner(crossScalaVersion).artifactName()}"
484497
| def runnerVersion = "${build.runner(crossScalaVersion).publishVersion()}"
@@ -1323,6 +1336,16 @@ trait TestRunner extends CrossSbtModule
13231336
override def mainClass: T[Option[String]] = Some("scala.build.testrunner.DynamicTestRunner")
13241337
}
13251338

1339+
trait JavaTestRunner extends JavaModule
1340+
with ScalaCliPublishModule
1341+
with LocatedInModules {
1342+
override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq(
1343+
Deps.asm,
1344+
Deps.testInterface
1345+
)
1346+
override def mainClass: T[Option[String]] = Some("scala.build.testrunner.JavaDynamicTestRunner")
1347+
}
1348+
13261349
trait TastyLib extends ScalaCliCrossSbtModule
13271350
with ScalaCliPublishModule
13281351
with ScalaCliScalafixModule
@@ -1357,7 +1380,7 @@ object `local-repo` extends LocalRepo {
13571380
def developingOnStubModules = false
13581381

13591382
override def stubsModules: Seq[PublishLocalNoFluff] =
1360-
Seq(runner(Scala.runnerScala3), `test-runner`(Scala.runnerScala3))
1383+
Seq(runner(Scala.runnerScala3), `test-runner`(Scala.runnerScala3), `java-test-runner`)
13611384

13621385
override def version: T[String] = runner(Scala.runnerScala3).publishVersion()
13631386
}

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,8 +1105,7 @@ object Build {
11051105
either {
11061106

11071107
val options0 =
1108-
// FIXME: don't add Scala to pure Java test builds (need to add pure Java test runner)
1109-
if sources.hasJava && !sources.hasScala && scope != Scope.Test
1108+
if sources.hasJava && !sources.hasScala
11101109
then
11111110
options.copy(
11121111
scalaOptions = options.scalaOptions.copy(
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package scala.build.tests
2+
3+
import com.eed3si9n.expecty.Expecty.assert as expect
4+
5+
import scala.build.options.*
6+
7+
class JavaTestRunnerTests extends TestUtil.ScalaCliBuildSuite {
8+
9+
private def makeOptions(
10+
scalaVersionOpt: Option[MaybeScalaVersion],
11+
addTestRunner: Boolean
12+
): BuildOptions =
13+
BuildOptions(
14+
scalaOptions = ScalaOptions(
15+
scalaVersion = scalaVersionOpt
16+
),
17+
internalDependencies = InternalDependenciesOptions(
18+
addTestRunnerDependencyOpt = Some(addTestRunner)
19+
)
20+
)
21+
22+
test("pure Java build has no scalaParams") {
23+
val opts = makeOptions(Some(MaybeScalaVersion.none), addTestRunner = false)
24+
val params = opts.scalaParams.toOption.flatten
25+
expect(params.isEmpty, "Pure Java build should have no scalaParams")
26+
}
27+
28+
test("Scala build has scalaParams") {
29+
val opts = makeOptions(None, addTestRunner = false)
30+
val params = opts.scalaParams.toOption.flatten
31+
expect(params.isDefined, "Scala build should have scalaParams")
32+
}
33+
34+
test("pure Java test build gets addJvmJavaTestRunner=true in Artifacts params") {
35+
val opts = makeOptions(Some(MaybeScalaVersion.none), addTestRunner = true)
36+
val isJava = opts.scalaParams.toOption.flatten.isEmpty
37+
expect(isJava, "Expected pure Java build to have no scalaParams")
38+
}
39+
40+
test("Scala test build gets addJvmTestRunner=true in Artifacts params") {
41+
val opts = makeOptions(None, addTestRunner = true)
42+
val isJava = opts.scalaParams.toOption.flatten.isEmpty
43+
expect(!isJava, "Expected Scala build to have scalaParams")
44+
}
45+
46+
test("mixed Scala+Java build still gets Scala test runner") {
47+
val opts = makeOptions(None, addTestRunner = true)
48+
val isJava = opts.scalaParams.toOption.flatten.isEmpty
49+
expect(!isJava, "Mixed Scala+Java build should still use Scala test runner")
50+
}
51+
}

modules/cli/src/main/scala/scala/cli/commands/test/Test.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,16 @@ object Test extends ScalaCommand[TestOptions] {
256256
testOnly.map(to => s"--test-only=$to").toSeq ++
257257
Seq("--") ++ args
258258

259+
val testRunnerMainClass =
260+
if build.artifacts.hasJavaTestRunner
261+
then Constants.javaTestRunnerMainClass
262+
else Constants.testRunnerMainClass
263+
259264
Runner.runJvm(
260265
build.options.javaHome().value.javaCommand,
261266
build.options.javaOptions.javaOpts.toSeq.map(_.value.value),
262267
classPath,
263-
Constants.testRunnerMainClass,
268+
testRunnerMainClass,
264269
extraArgs,
265270
logger,
266271
allowExecve = allowExecve

modules/integration/src/test/scala/scala/cli/integration/RunTestsDefault.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,30 @@ class RunTestsDefault extends RunTestDefinitions
236236
expect(res.err.trim().contains(expectedWarning))
237237
}
238238
}
239+
240+
for {
241+
buildServerOptions <- Seq(Nil, Seq("--server=false"))
242+
buildServerDesc =
243+
if buildServerOptions.isEmpty then "with build server" else "without build server"
244+
}
245+
test(s"pure Java run has no Scala on classpath $buildServerDesc") {
246+
TestInputs(
247+
os.rel / "Main.java" ->
248+
"""public class Main {
249+
| public static void main(String[] args) {
250+
| try {
251+
| Class.forName("scala.Predef");
252+
| throw new RuntimeException("Scala should not be on the classpath");
253+
| } catch (ClassNotFoundException e) {
254+
| System.out.println("No Scala on classpath!");
255+
| }
256+
| }
257+
|}
258+
|""".stripMargin
259+
).fromRoot { root =>
260+
val res =
261+
os.proc(TestUtil.cli, "run", buildServerOptions, extraOptions, ".").call(cwd = root)
262+
expect(res.out.text().contains("No Scala on classpath!"))
263+
}
264+
}
239265
}

modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,38 @@ class TestTestsDefault extends TestTestDefinitions with TestDefault {
9898
expect(err.countOccurrences(expectedWarning) == 1)
9999
}
100100
}
101+
102+
for {
103+
buildServerOptions <- Seq(Nil, Seq("--server=false"))
104+
buildServerDesc =
105+
if buildServerOptions.isEmpty then "with build server" else "without build server"
106+
}
107+
test(s"pure Java test with JUnit has no Scala on classpath $buildServerDesc") {
108+
TestInputs(
109+
os.rel / "test" / "MyTests.java" ->
110+
"""//> using test.dep junit:junit:4.13.2
111+
|//> using test.dep com.novocode:junit-interface:0.11
112+
|import org.junit.Test;
113+
|import static org.junit.Assert.assertEquals;
114+
|
115+
|public class MyTests {
116+
| @Test
117+
| public void foo() {
118+
| try {
119+
| Class.forName("scala.Predef");
120+
| throw new AssertionError("Scala should not be on the classpath");
121+
| } catch (ClassNotFoundException e) {
122+
| // expected
123+
| }
124+
| assertEquals(4, 2 + 2);
125+
| System.out.println("No Scala on classpath!");
126+
| }
127+
|}
128+
|""".stripMargin
129+
).fromRoot { root =>
130+
val res =
131+
os.proc(TestUtil.cli, "test", extraOptions, buildServerOptions, ".").call(cwd = root)
132+
expect(res.out.text().contains("No Scala on classpath!"))
133+
}
134+
}
101135
}

0 commit comments

Comments
 (0)