mirror of https://github.com/sbt/sbt.git
[2.x] feat: ClassLoaderStrategy.Raw (#9161)
**Problem** In sbt 2.x, forking still creates layered classloader in the worker process, which doesn't work for some tests. **Solution** This provides an escape hatch to emulate the sbt 1.x semantics of using the system classpath for testing.
This commit is contained in:
parent
06e53a0ae4
commit
532edd1716
|
|
@ -37,6 +37,10 @@ import sbt.internal.WorkerConnection
|
|||
private[sbt] object ForkTests:
|
||||
val r = Random()
|
||||
|
||||
/**
|
||||
* virtualClasspath can be controlled by setting
|
||||
* Test / classLoaderLayeringStrategy to ClassLoaderLayeringStrategy.Raw.
|
||||
*/
|
||||
def apply(
|
||||
runners: Map[TestFramework, Runner],
|
||||
opts: ProcessedOptions,
|
||||
|
|
@ -46,6 +50,7 @@ private[sbt] object ForkTests:
|
|||
fork: ForkOptions,
|
||||
log: Logger,
|
||||
parallelism: Option[Int],
|
||||
virtualClasspath: Boolean,
|
||||
tags: (Tag, Int)*
|
||||
): Task[TestOutput] = {
|
||||
import std.TaskExtra.*
|
||||
|
|
@ -57,44 +62,22 @@ private[sbt] object ForkTests:
|
|||
if opts.tests.isEmpty then
|
||||
constant(TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty))
|
||||
else
|
||||
mainTestTask(runners, opts, classpath, converter, fork, log, config.parallel, parallelism)
|
||||
.tagw(
|
||||
config.tags*
|
||||
)
|
||||
mainTestTask(
|
||||
runners = runners,
|
||||
opts = opts,
|
||||
classpath = classpath,
|
||||
converter = converter,
|
||||
fork = fork,
|
||||
log = log,
|
||||
parallel = config.parallel,
|
||||
parallelism = parallelism,
|
||||
virtualClasspath = virtualClasspath,
|
||||
).tagw(config.tags*)
|
||||
main.tagw(tags*).dependsOn(all(opts.setup)*) flatMap { results =>
|
||||
all(opts.cleanup).join.map(_ => results)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
runners: Map[TestFramework, Runner],
|
||||
tests: Vector[TestDefinition],
|
||||
config: Execution,
|
||||
classpath: Seq[HashedVirtualFileRef],
|
||||
converter: FileConverter,
|
||||
fork: ForkOptions,
|
||||
log: Logger,
|
||||
parallelism: Option[Int],
|
||||
tags: (Tag, Int)*
|
||||
): Task[TestOutput] = {
|
||||
val opts = processOptions(config, tests, log)
|
||||
apply(runners, opts, config, classpath, converter, fork, log, parallelism, tags*)
|
||||
}
|
||||
|
||||
def apply(
|
||||
runners: Map[TestFramework, Runner],
|
||||
tests: Vector[TestDefinition],
|
||||
config: Execution,
|
||||
classpath: Seq[HashedVirtualFileRef],
|
||||
converter: FileConverter,
|
||||
fork: ForkOptions,
|
||||
log: Logger,
|
||||
parallelism: Option[Int],
|
||||
tag: Tag
|
||||
): Task[TestOutput] = {
|
||||
apply(runners, tests, config, classpath, converter, fork, log, parallelism, tag -> 1)
|
||||
}
|
||||
|
||||
private def mainTestTask(
|
||||
runners: Map[TestFramework, Runner],
|
||||
opts: ProcessedOptions,
|
||||
|
|
@ -103,7 +86,8 @@ private[sbt] object ForkTests:
|
|||
fork: ForkOptions,
|
||||
log: Logger,
|
||||
parallel: Boolean,
|
||||
parallelism: Option[Int]
|
||||
parallelism: Option[Int],
|
||||
virtualClasspath: Boolean,
|
||||
): Task[TestOutput] =
|
||||
std.TaskExtra.task {
|
||||
val testListeners = opts.testListeners.flatMap:
|
||||
|
|
@ -131,8 +115,6 @@ private[sbt] object ForkTests:
|
|||
ArrayList(mainRunner.remoteArgs().toList.asJava)
|
||||
)
|
||||
val g = WorkerMain.mkGson()
|
||||
// virtualize classloading by using ClassLoader
|
||||
val useClassLoader = true
|
||||
val cpList = ArrayList[FilePath](
|
||||
(classpath
|
||||
.map: vf =>
|
||||
|
|
@ -146,7 +128,7 @@ private[sbt] object ForkTests:
|
|||
true, /* jvm */
|
||||
RunInfo.JvmRunInfo(
|
||||
ArrayList(),
|
||||
if useClassLoader then cpList else ArrayList(),
|
||||
if virtualClasspath then cpList else ArrayList(),
|
||||
"",
|
||||
false /*connectInput*/,
|
||||
),
|
||||
|
|
@ -160,7 +142,7 @@ private[sbt] object ForkTests:
|
|||
testListeners.foreach(_.doInit())
|
||||
val result =
|
||||
val ct = WorkerConnection.Tcp
|
||||
val w = WorkerExchange.startWorker(fork, if useClassLoader then Nil else cpFiles, ct)
|
||||
val w = WorkerExchange.startWorker(fork, if virtualClasspath then Nil else cpFiles, ct)
|
||||
val wl = React(randomId, log, opts.testListeners, resultsAcc, w.process)
|
||||
try
|
||||
WorkerExchange.registerListener(wl)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ object ClassLoaderLayeringStrategy {
|
|||
*/
|
||||
case object Flat extends ClassLoaderLayeringStrategy
|
||||
|
||||
/**
|
||||
* Use the system loader. This applises only for forked tests.
|
||||
*/
|
||||
case object Raw extends ClassLoaderLayeringStrategy
|
||||
|
||||
/**
|
||||
* Add a layer for the scala library class loader.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1573,6 +1573,7 @@ object Defaults extends BuildCommon {
|
|||
opts,
|
||||
s.log,
|
||||
forkedParallelism,
|
||||
strategy != ClassLoaderLayeringStrategy.Raw,
|
||||
(Tags.ForkedTestGroup, 1) +: group.tags*
|
||||
)
|
||||
case Tests.InProcess =>
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ private[sbt] object ClassLoaders {
|
|||
): ClassLoader = {
|
||||
val cpFiles = fullCP.map(_._1)
|
||||
strategy match {
|
||||
case Flat => new FlatLoader(cpFiles.urls, interfaceLoader, tmp, close, allowZombies, logger)
|
||||
case Flat | Raw =>
|
||||
new FlatLoader(cpFiles.urls, interfaceLoader, tmp, close, allowZombies, logger)
|
||||
case _ =>
|
||||
val layerDependencies = strategy match {
|
||||
case _: AllLibraryJars => true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
val munit = "org.scalameta" %% "munit" % "1.0.4"
|
||||
|
||||
scalaVersion := "3.8.2"
|
||||
libraryDependencies += munit % Test
|
||||
|
||||
Test / fork := true
|
||||
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Raw
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package testpkg
|
||||
|
||||
import munit.*
|
||||
|
||||
class ATest extends FunSuite:
|
||||
test("sum"):
|
||||
assert(1 + 1 == 2)
|
||||
end ATest
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package testpkg
|
||||
|
||||
import munit.*
|
||||
|
||||
class ATest extends FunSuite:
|
||||
test("sum"):
|
||||
assert(1 + 1 == 3)
|
||||
end ATest
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-> testFull
|
||||
|
||||
$ copy-file changes/Good.scala src/test/scala/ATest.scala
|
||||
> testFull
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
ThisBuild / scalaVersion := "2.12.21"
|
||||
val munit = "org.scalameta" %% "munit" % "1.0.4"
|
||||
scalaVersion := "3.8.2"
|
||||
|
||||
lazy val munit = "org.scalameta" %% "munit" % "0.7.22"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
lazy val root = rootProject
|
||||
.settings(
|
||||
Compile / scalacOptions += "-Yrangepos",
|
||||
libraryDependencies += munit % Test
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package testpkg
|
||||
|
||||
import munit._
|
||||
import munit.*
|
||||
|
||||
class ClueSuite extends FunSuite {
|
||||
class ClueSuite extends FunSuite:
|
||||
val x = 42
|
||||
val y = 32
|
||||
checkPrint(
|
||||
|
|
@ -31,10 +31,10 @@ class ClueSuite extends FunSuite {
|
|||
options: TestOptions,
|
||||
clues: Clues,
|
||||
expected: String
|
||||
)(implicit loc: Location): Unit = {
|
||||
)(using loc: Location): Unit = {
|
||||
test(options) {
|
||||
val obtained = munitPrint(clues)
|
||||
assertNoDiff(obtained, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
end ClueSuite
|
||||
|
|
|
|||
|
|
@ -188,8 +188,13 @@ public final class WorkerMain {
|
|||
if (info.jvm) {
|
||||
RunInfo.JvmRunInfo jvmRunInfo = info.jvmRunInfo;
|
||||
ClassLoader parent = new ForkTestMain().getClass().getClassLoader();
|
||||
try (URLClassLoader cl = createClassLoader(jvmRunInfo, parent)) {
|
||||
ForkTestMain.main(id, info, this.jsonOut, cl);
|
||||
// empty virtual classpath means raw mode
|
||||
if (jvmRunInfo.classpath.isEmpty()) {
|
||||
ForkTestMain.main(id, info, this.jsonOut, parent);
|
||||
} else {
|
||||
try (URLClassLoader cl = createClassLoader(jvmRunInfo, parent)) {
|
||||
ForkTestMain.main(id, info, this.jsonOut, cl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("only jvm is supported");
|
||||
|
|
|
|||
Loading…
Reference in New Issue