Skip to content

Commit f96fe2e

Browse files
Consider targets used for test discovery in dependency checking
Before, we did test discovery at runtime, but we now do it at compile-time, for speed and to avoid having to serialize the analysis information returned by the compiler bridge. This commits marks targets used during test discovery as used, thereby subjecting them to depednency checking. In most cases, this won't make a difference as the target containing the implementation of the SBT test interface is the same one that the target being built would've depended on anyway. One important thing to note is that this will break a property of our dependency checker that's been thus-far guaranteed: that it only allows a dependency to be in `deps` or not in `deps`. When we do test discovery, iterate through a list of test "frameworks". If we find a test framework in the classpath, we look for tests that use that framework; otherwise, we don't. This means that, for example, if specs2 isn't on the classpath, but the target contains specs2 tests, the tests will silently fail to execute.
1 parent 4ff6c34 commit f96fe2e

File tree

3 files changed

+40
-24
lines changed

3 files changed

+40
-24
lines changed

src/main/scala/higherkindness/rules_scala/common/sbt-testing/Test.scala

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package higherkindness.rules_scala.common.sbt_testing
22

3+
import java.nio.file.{Path, Paths}
34
import play.api.libs.json.{Format, Json}
45
import sbt.testing.{Event, Framework, Logger, Runner, Status, Task, TaskDef, TestWildcardSelector}
56
import scala.collection.mutable
@@ -12,10 +13,27 @@ object TestDefinition {
1213
}
1314

1415
class TestFrameworkLoader(loader: ClassLoader) {
16+
private val loadedJars = mutable.ArrayBuffer[Path]()
17+
private def getClassJar(`class`: Class[?]): Option[Path] = for {
18+
codeSource <- Option(`class`.getProtectionDomain().getCodeSource())
19+
codeSourceUri = codeSource.getLocation().toURI()
20+
path <-
21+
try {
22+
Some(Paths.get(codeSourceUri))
23+
} catch {
24+
case _: IllegalArgumentException => None
25+
}
26+
} yield path
27+
28+
def getLoadedJars(): Array[Path] = loadedJars.toArray
1529
def load(className: String) = {
1630
val framework =
1731
try {
18-
Some(Class.forName(className, true, loader).getDeclaredConstructor().newInstance())
32+
val `class` = Class.forName(className, true, loader)
33+
34+
getClassJar(`class`).foreach(loadedJars += _)
35+
36+
Some(`class`.getDeclaredConstructor().newInstance())
1937
} catch {
2038
case _: ClassNotFoundException => None
2139
case NonFatal(e) => throw new Exception(s"Failed to load framework $className", e)

src/main/scala/higherkindness/rules_scala/workers/zinc/compile/ZincRunner.scala

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -211,19 +211,25 @@ object ZincRunner extends WorkerMain[Unit] {
211211
}
212212
}
213213

214-
private def maybeWriteTestsFile(parsedArguments: CommonArguments, analysis: AnnexAnalysis): Unit =
215-
parsedArguments.testsFile.foreach { path =>
216-
val classloader = ClassLoaders.sbtTestClassLoader(parsedArguments.classpath.map(_.toUri.toURL))
217-
val frameworkLoader = new TestFrameworkLoader(classloader)
218-
val testsFileData = TestsFileData(
219-
parsedArguments.testFrameworks
220-
.flatMap(frameworkName => frameworkLoader.load(frameworkName).map((frameworkName, _)))
221-
.map { case (frameworkName, framework) => frameworkName -> new TestDiscovery(framework)(analysis.apis.toSet) }
222-
.toMap,
223-
)
214+
private def maybeWriteTestsFile(parsedArguments: CommonArguments, analysis: AnnexAnalysis): Array[Path] =
215+
parsedArguments.testsFile
216+
.map { path =>
217+
val classloader = ClassLoaders.sbtTestClassLoader(parsedArguments.classpath.map(_.toUri.toURL))
218+
val frameworkLoader = new TestFrameworkLoader(classloader)
219+
val testsFileData = TestsFileData(
220+
parsedArguments.testFrameworks
221+
.flatMap(frameworkName => frameworkLoader.load(frameworkName).map((frameworkName, _)))
222+
.map { case (frameworkName, framework) =>
223+
frameworkName -> new TestDiscovery(framework)(analysis.apis.toSet)
224+
}
225+
.toMap,
226+
)
224227

225-
Files.write(path, Json.stringify(Json.toJson(testsFileData)).getBytes(StandardCharsets.UTF_8))
226-
}
228+
Files.write(path, Json.stringify(Json.toJson(testsFileData)).getBytes(StandardCharsets.UTF_8))
229+
230+
frameworkLoader.getLoadedJars()
231+
}
232+
.getOrElse(Array.empty)
227233

228234
protected def init(args: Option[Array[String]]): Unit = ()
229235

@@ -298,7 +304,7 @@ object ZincRunner extends WorkerMain[Unit] {
298304

299305
InterruptUtil.throwIfInterrupted(task.isCancelled)
300306

301-
maybeWriteTestsFile(workRequest, analysis)
307+
val usedDepsDuringTestDiscovery = maybeWriteTestsFile(workRequest, analysis)
302308

303309
InterruptUtil.throwIfInterrupted(task.isCancelled)
304310

@@ -310,7 +316,7 @@ object ZincRunner extends WorkerMain[Unit] {
310316
val usedDeps = workRequest.classpath.view
311317
.map(_.normalize().toAbsolutePath)
312318
.toSet
313-
.intersect(analysis.usedJars.toSet)
319+
.intersect((analysis.usedJars.view ++ usedDepsDuringTestDiscovery.view).toSet)
314320
// Filter out the Scala standard library as they should always be implicitly available we shouldn't be
315321
// bookkeeping them
316322
.view

tests/test-frameworks/mixed/BUILD

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,12 @@ load("@rules_scala_annex//rules:scala.bzl", "scala_test")
33
scala_test(
44
name = "mixed_2_13",
55
srcs = glob(["*.scala"]),
6-
deps_used_whitelist = [
7-
"@annex_test//:org_hamcrest_hamcrest_core",
8-
"@annex_test//:com_novocode_junit_interface",
9-
],
106
scala_toolchain_name = "test_zinc_2_13",
117
shard_count = 2,
128
tags = ["manual"],
139
deps = [
1410
"@annex_test//:com_novocode_junit_interface",
1511
"@annex_test//:junit_junit",
16-
"@annex_test//:org_hamcrest_hamcrest_core",
1712
"@annex_test//:org_scalacheck_scalacheck_2_13",
1813
"@annex_test//:org_scalactic_scalactic_2_13",
1914
"@annex_test//:org_scalatest_scalatest_compatible",
@@ -31,11 +26,8 @@ scala_test(
3126
scala_toolchain_name = "test_zinc_2_12",
3227
shard_count = 2,
3328
tags = ["manual"],
34-
runtime_deps = [
35-
"@annex_test//:com_novocode_junit_interface",
36-
"@annex_test//:org_hamcrest_hamcrest_core",
37-
],
3829
deps = [
30+
"@annex_test//:com_novocode_junit_interface",
3931
"@annex_test//:junit_junit",
4032
"@annex_test_2_12//:org_scalacheck_scalacheck_2_12",
4133
"@annex_test_2_12//:org_scalactic_scalactic_2_12",

0 commit comments

Comments
 (0)