Merge pull request #4601 from eatkins/improve-layers

Improve layers
This commit is contained in:
Ethan Atkins 2019-04-02 21:46:35 -07:00 committed by GitHub
commit 5b1756fe60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 232 additions and 140 deletions

View File

@ -112,7 +112,9 @@ object ClassLoaderLayeringStrategy {
* Add the TestDependencies layer on top of the RuntimeDependencies layer on top of the
* ScalaInstance layer. This differs from TestDependencies in that it will not reload the
* runtime classpath. The drawback to using this is that if the test dependencies evict
* classes provided in the runtime layer, then tests can fail.
* classes provided in the runtime layer, then tests can fail. In order for sharing the runtime
* layer to work, it is necessary to set [[Keys.bgCopyClasspath]] to false. Otherwise the
* runtime and test classpaths are completely different.
*/
case object ShareRuntimeDependenciesLayerWithTestDependencies
extends ScalaInstance

View File

@ -8,7 +8,7 @@
package sbt
import java.io.{ File, PrintWriter }
import java.net.{ URI, URL }
import java.net.{ URI, URL, URLClassLoader }
import java.util.Optional
import java.util.concurrent.{ Callable, TimeUnit }
@ -787,9 +787,14 @@ object Defaults extends BuildCommon {
// ((streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel, javaOptions in test) flatMap allTestGroupsTask).value,
testResultLogger in (Test, test) :== TestResultLogger.SilentWhenNoTests, // https://github.com/sbt/sbt/issues/1185
test := {
val loader = testLoader.value match { case u: URLClassLoader => Some(u); case _ => None }
val trl = (testResultLogger in (Test, test)).value
val taskName = Project.showContextKey(state.value).show(resolvedScoped.value)
trl.run(streams.value.log, executeTests.value, taskName)
try {
trl.run(streams.value.log, executeTests.value, taskName)
} finally {
loader.foreach(_.close())
}
},
testOnly := inputTests(testOnly).evaluated,
testQuick := inputTests(testQuick).evaluated
@ -1056,22 +1061,21 @@ object Defaults extends BuildCommon {
val result = output map { out =>
out.events.foreach {
case (suite, e) =>
e.throwables
.collectFirst {
case t
if t
.isInstanceOf[NoClassDefFoundError] && strategy != ClassLoaderLayeringStrategy.Flat =>
t
}
.foreach { t =>
s.log.error(
s"Test suite $suite failed with $t. This may be due to the ClassLoaderLayeringStrategy"
+ s" ($strategy) used by your task. This issue may be resolved by changing the"
+ " ClassLoaderLayeringStrategy in your configuration (generally Test or IntegrationTest),"
+ "e.g.:\nTest / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat\n"
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
)
}
if (strategy != ClassLoaderLayeringStrategy.Flat) {
e.throwables
.find { t =>
t.isInstanceOf[NoClassDefFoundError] || t.isInstanceOf[IllegalAccessError]
}
.foreach { t =>
s.log.error(
s"Test suite $suite failed with $t. This may be due to the ClassLoaderLayeringStrategy"
+ s" ($strategy) used by your task. This issue may be resolved by changing the"
+ " ClassLoaderLayeringStrategy in your configuration (generally Test or IntegrationTest),"
+ " e.g.:\nTest / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat\n"
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
)
}
}
}
val summaries =
runners map {
@ -1772,18 +1776,26 @@ object Defaults extends BuildCommon {
Classpaths.addUnmanagedLibrary ++
Vector(
TaskRepository.proxy(
classLoaderCache,
Compile / classLoaderCache,
// We need a cache of size four so that the subset of the runtime dependencies that are used
// by the test task layers may be cached without evicting the runtime classloader layers. The
// cache size should be a multiple of two to support snapshot layers.
ClassLoaderCache(4)
)
),
bgCopyClasspath in bgRun := {
val old = (bgCopyClasspath in bgRun).value
old && (Test / classLoaderLayeringStrategy).value != ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
},
bgCopyClasspath in bgRunMain := {
val old = (bgCopyClasspath in bgRunMain).value
old && (Test / classLoaderLayeringStrategy).value != ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
},
)
lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks ++
Vector(
TaskRepository.proxy(
classLoaderCache,
Test / classLoaderCache,
// We need a cache of size two for the test dependency layers (regular and snapshot).
ClassLoaderCache(2)
)

View File

@ -503,7 +503,7 @@ object Keys {
val resolvedScoped = Def.resolvedScoped
val pluginData = taskKey[PluginData]("Information from the plugin build needed in the main build definition.").withRank(DTask)
val globalPluginUpdate = taskKey[UpdateReport]("A hook to get the UpdateReport of the global plugin.").withRank(DTask)
val classLoaderCache = taskKey[internal.ClassLoaderCache]("The cache of ClassLoaders to be used for layering in tasks that invoke other java code").withRank(DTask)
private[sbt] val classLoaderCache = taskKey[internal.ClassLoaderCache]("The cache of ClassLoaders to be used for layering in tasks that invoke other java code").withRank(DTask)
private[sbt] val taskRepository = AttributeKey[TaskRepository.Repr]("task-repository", "A repository that can be used to cache arbitrary values for a given task key that can be read or filled during task evaluation.", 10000)
private[sbt] val taskCancelStrategy = settingKey[State => TaskCancellationStrategy]("Experimental task cancellation handler.").withRank(DTask)

View File

@ -8,6 +8,7 @@
package sbt
import java.io.{ File, IOException }
import java.lang.reflect.InvocationTargetException
import java.net.URI
import java.util.concurrent.atomic.AtomicBoolean
import java.util.{ Locale, Properties }
@ -27,6 +28,7 @@ import sbt.io._
import sbt.io.syntax._
import sbt.util.{ Level, Logger, Show }
import xsbti.compile.CompilerCache
import xsbti.{ AppMain, AppProvider, ComponentProvider, ScalaProvider }
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext
@ -35,6 +37,47 @@ import scala.util.control.NonFatal
/** This class is the entry point for sbt. */
final class xMain extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
val modifiedConfiguration = new ModifiedConfiguration(configuration)
val loader = modifiedConfiguration.provider.loader
// No need to memoize the old class loader. It is reset by the launcher anyway.
Thread.currentThread.setContextClassLoader(loader)
val clazz = loader.loadClass("sbt.xMainImpl$")
val instance = clazz.getField("MODULE$").get(null)
val runMethod = clazz.getMethod("run", classOf[xsbti.AppConfiguration])
try {
runMethod.invoke(instance, modifiedConfiguration).asInstanceOf[xsbti.MainResult]
} catch {
case e: InvocationTargetException =>
// This propogates xsbti.FullReload to the launcher
throw e.getCause
}
}
/*
* Replaces the AppProvider.loader method with a new loader that puts the sbt test interface
* jar ahead of the rest of the sbt classpath in the classloading hierarchy.
*/
private class ModifiedConfiguration(val configuration: xsbti.AppConfiguration)
extends xsbti.AppConfiguration {
private[this] val initLoader = configuration.provider.loader
private[this] val scalaLoader = configuration.provider.scalaProvider.loader
private[this] val metaLoader: ClassLoader = SbtMetaBuildClassLoader(scalaLoader, initLoader)
private class ModifiedAppProvider(val appProvider: AppProvider) extends AppProvider {
override def scalaProvider(): ScalaProvider = appProvider.scalaProvider
override def id(): xsbti.ApplicationID = appProvider.id()
override def loader(): ClassLoader = metaLoader
override def mainClass(): Class[_ <: AppMain] = appProvider.mainClass()
override def entryPoint(): Class[_] = appProvider.entryPoint()
override def newMain(): AppMain = appProvider.newMain()
override def mainClasspath(): Array[File] = appProvider.mainClasspath()
override def components(): ComponentProvider = appProvider.components()
}
override def arguments(): Array[String] = configuration.arguments
override def baseDirectory(): File = configuration.baseDirectory
override def provider(): AppProvider = new ModifiedAppProvider(configuration.provider)
}
}
private[sbt] object xMainImpl {
private[sbt] def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
import BasicCommandStrings.{ DashClient, DashDashClient, runEarly }
import BasicCommands.early
import BuiltinCommands.defaults

View File

@ -14,17 +14,17 @@ import sbt.internal.util.TypeFunctions.Id
import scala.annotation.tailrec
sealed trait ClassLoaderCache
private[sbt] sealed trait ClassLoaderCache
extends Repository[Id, (Seq[File], ClassLoader, Map[String, String], File), ClassLoader]
object ClassLoaderCache {
private[sbt] object ClassLoaderCache {
private type Resources = Map[String, String]
private sealed trait CachedClassLoader extends ClassLoader {
def close(): Unit
}
private sealed trait StableClassLoader extends CachedClassLoader
private sealed trait SnapshotClassLoader extends CachedClassLoader
def apply(maxSize: Int): ClassLoaderCache =
def apply(maxSize: Int): ClassLoaderCache = {
new ClassLoaderCache {
private final def mktmp(tmp: File): File =
if (maxSize > 0) Files.createTempDirectory("sbt-jni").toFile else tmp
@ -89,10 +89,5 @@ object ClassLoaderCache {
}
}
}
def empty(newLoader: (Seq[File], ClassLoader, Resources, File) => ClassLoader): ClassLoaderCache =
new ClassLoaderCache {
override def get(key: (Seq[File], ClassLoader, Resources, File)): ClassLoader =
newLoader.tupled(key)
override def close(): Unit = {}
}
}
}

View File

@ -9,36 +9,20 @@ package sbt
package internal
import java.io.File
import java.net.URLClassLoader
import java.net.{ URL, URLClassLoader }
import sbt.ClassLoaderLayeringStrategy.{ ScalaInstance => ScalaInstanceLayer, _ }
import sbt.Keys._
import sbt.SlashSyntax0._
import sbt.internal.inc.ScalaInstance
import sbt.internal.inc.classpath.{ ClasspathUtilities, DualLoader, NullLoader }
import sbt.internal.inc.classpath.ClasspathUtilities
import sbt.internal.util.Attributed
import sbt.internal.util.Attributed.data
import sbt.io.IO
import sbt.librarymanagement.Configurations.Runtime
import PrettyPrint.indent
import scala.annotation.tailrec
import ClassLoaderLayeringStrategy.{ ScalaInstance => ScalaInstanceLayer, _ }
import sbt.librarymanagement.Configurations.{ Runtime, Test }
private[sbt] object ClassLoaders {
private[this] lazy val interfaceLoader =
combine(
classOf[sbt.testing.Framework].getClassLoader,
new NullLoader,
toString = "sbt.testing.Framework interface ClassLoader"
)
private[this] lazy val baseLoader = {
@tailrec
def getBase(classLoader: ClassLoader): ClassLoader = classLoader.getParent match {
case null => classLoader
case loader => getBase(loader)
}
getBase(ClassLoaders.getClass.getClassLoader)
}
private[this] val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader
/*
* Get the class loader for a test task. The configuration could be IntegrationTest or Test.
*/
@ -46,19 +30,19 @@ private[sbt] object ClassLoaders {
val si = scalaInstance.value
val rawCP = data(fullClasspath.value)
val fullCP = if (si.isManagedVersion) rawCP else si.allJars.toSeq ++ rawCP
val exclude = dependencyJars(exportedProducts).value.toSet ++ si.allJars.toSeq
val exclude = dependencyJars(exportedProducts).value.toSet ++ si.allJars
buildLayers(
classLoaderLayeringStrategy.value,
si,
fullCP,
dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude),
dependencyJars(dependencyClasspath).value.filterNot(exclude).toSet,
interfaceLoader,
(Runtime / classLoaderCache).value,
classLoaderCache.value,
ClasspathUtilities.createClasspathResources(fullCP, si),
IO.createUniqueDirectory(taskTemporaryDirectory.value),
resolvedScoped.value.scope
strategy = classLoaderLayeringStrategy.value,
si = si,
fullCP = fullCP,
rawRuntimeDependencies =
dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude),
allDependencies = dependencyJars(dependencyClasspath).value.filterNot(exclude),
runtimeCache = (Runtime / classLoaderCache).value,
testCache = (Test / classLoaderCache).value,
resources = ClasspathUtilities.createClasspathResources(fullCP, si),
tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value),
scope = resolvedScoped.value.scope
)
}
@ -88,23 +72,23 @@ private[sbt] object ClassLoaders {
s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false")
}
val runtimeCache = (Runtime / classLoaderCache).value
val testCache = classLoaderCache.value
val testCache = (Test / classLoaderCache).value
val exclude = dependencyJars(exportedProducts).value.toSet ++ instance.allJars
val runtimeDeps = dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude)
val allDeps = dependencyJars(dependencyClasspath).value.filterNot(exclude)
val newLoader =
(classpath: Seq[File]) => {
buildLayers(
classLoaderLayeringStrategy.value: @sbtUnchecked,
instance,
classpath,
(dependencyJars(Runtime / dependencyClasspath).value: @sbtUnchecked)
.filterNot(exclude),
(dependencyJars(dependencyClasspath).value: @sbtUnchecked).filterNot(exclude).toSet,
baseLoader,
runtimeCache,
testCache,
ClasspathUtilities.createClasspathResources(classpath, instance),
taskTemporaryDirectory.value: @sbtUnchecked,
resolvedScope
strategy = classLoaderLayeringStrategy.value: @sbtUnchecked,
si = instance,
fullCP = classpath,
rawRuntimeDependencies = runtimeDeps,
allDependencies = allDeps,
runtimeCache = runtimeCache,
testCache = testCache,
resources = ClasspathUtilities.createClasspathResources(classpath, instance),
tmp = taskTemporaryDirectory.value: @sbtUnchecked,
scope = resolvedScope
)
}
new Run(newLoader, trapExit.value)
@ -127,8 +111,7 @@ private[sbt] object ClassLoaders {
si: ScalaInstance,
fullCP: Seq[File],
rawRuntimeDependencies: Seq[File],
allDependencies: Set[File],
base: ClassLoader,
allDependencies: Seq[File],
runtimeCache: ClassLoaderCache,
testCache: ClassLoaderCache,
resources: Map[String, String],
@ -137,7 +120,7 @@ private[sbt] object ClassLoaders {
): ClassLoader = {
val isTest = scope.config.toOption.map(_.name) == Option("test")
val raw = strategy match {
case Flat => flatLoader(fullCP, base)
case Flat => flatLoader(fullCP, interfaceLoader)
case _ =>
val (layerDependencies, layerTestDependencies) = strategy match {
case ShareRuntimeDependenciesLayerWithTestDependencies if isTest => (true, true)
@ -150,30 +133,31 @@ private[sbt] object ClassLoaders {
"Flat, ScalaInstance, RuntimeDependencies }"
throw new IllegalArgumentException(msg)
}
val allDependenciesSet = allDependencies.toSet
// The raw declarations are to avoid having to make a dynamic task. The
// allDependencies and allTestDependencies create a mutually exclusive list of jar
// dependencies for layers 2 and 3. Note that in the Runtime or Compile configs, it
// should always be the case that allTestDependencies == Nil
val allTestDependencies = if (layerTestDependencies) allDependencies else Set.empty[File]
val allTestDependencies = if (layerTestDependencies) allDependenciesSet else Set.empty[File]
val allRuntimeDependencies = (if (layerDependencies) rawRuntimeDependencies else Nil).toSet
val scalaInstanceLayer = new ScalaInstanceLoader(si)
// layer 2
val runtimeDependencies = allDependencies intersect allRuntimeDependencies
val runtimeLayer =
layer(runtimeDependencies.toSeq, loader(si), runtimeCache, resources, tmp)
val runtimeDependencySet = allDependenciesSet intersect allRuntimeDependencies
val runtimeDependencies = rawRuntimeDependencies.filter(runtimeDependencySet)
lazy val runtimeLayer =
if (layerDependencies)
layer(runtimeDependencies, scalaInstanceLayer, runtimeCache, resources, tmp)
else scalaInstanceLayer
// layer 3 (optional if testDependencies are empty)
// The top layer needs to include the interface jar or else the test task cannot be created.
// It needs to be separated from the runtimeLayer or else the runtimeLayer cannot be
// shared between the runtime and test tasks.
val top = combine(base, runtimeLayer)
val testDependencies = allTestDependencies diff runtimeDependencies
val testLayer = layer(testDependencies.toSeq, top, testCache, resources, tmp)
val testDependencySet = allTestDependencies diff runtimeDependencySet
val testDependencies = allDependencies.filter(testDependencySet)
val testLayer = layer(testDependencies, runtimeLayer, testCache, resources, tmp)
// layer 4
val dynamicClasspath =
fullCP.filterNot(testDependencies ++ runtimeDependencies ++ si.allJars)
fullCP.filterNot(testDependencySet ++ runtimeDependencies ++ si.allJars)
if (dynamicClasspath.nonEmpty)
new LayeredClassLoader(dynamicClasspath, testLayer, resources, tmp)
else testLayer
@ -201,49 +185,42 @@ private[sbt] object ClassLoaders {
if (snapshots.isEmpty) jarLoader else cache.get((snapshots, jarLoader, resources, tmp))
}
// Code related to combining two classloaders that primarily exists so the test loader correctly
// loads the testing framework using the same classloader as sbt itself.
private val interfaceFilter = (name: String) =>
name.startsWith("org.scalatools.testing.") || name.startsWith("sbt.testing.") || name
.startsWith("java.") || name.startsWith("sun.")
private val notInterfaceFilter = (name: String) => !interfaceFilter(name)
private class WrappedDualLoader(
val parent: ClassLoader,
val child: ClassLoader,
string: => String
) extends ClassLoader(
new DualLoader(parent, interfaceFilter, _ => false, child, notInterfaceFilter, _ => true)
) {
private class ScalaInstanceLoader(val instance: ScalaInstance)
extends URLClassLoader(instance.allJars.map(_.toURI.toURL), interfaceLoader) {
override def equals(o: Any): Boolean = o match {
case that: WrappedDualLoader => this.parent == that.parent && this.child == that.child
case _ => false
case that: ScalaInstanceLoader => this.instance.allJars.sameElements(that.instance.allJars)
case _ => false
}
override def hashCode: Int = (parent.hashCode * 31) ^ child.hashCode
override lazy val toString: String = string
override def hashCode: Int = instance.hashCode
override lazy val toString: String =
s"ScalaInstanceLoader($interfaceLoader, jars = {${instance.allJars.mkString(", ")}})"
}
private def combine(parent: ClassLoader, child: ClassLoader, toString: String): ClassLoader =
new WrappedDualLoader(parent, child, toString)
private def combine(parent: ClassLoader, child: ClassLoader): ClassLoader =
new WrappedDualLoader(
parent,
child,
s"WrappedDualLoader(\n parent =\n${indent(parent, 4)}"
+ s"\n child =\n${indent(child, 4)}\n)"
)
// helper methods
private def flatLoader(classpath: Seq[File], parent: ClassLoader): ClassLoader =
new URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent)
// This makes the toString method of the ScalaInstance classloader much more readable, but
// it is not strictly necessary.
private def loader(si: ScalaInstance): ClassLoader = new ClassLoader(si.loader) {
override lazy val toString: String =
"ScalaInstanceClassLoader(\n instance = " +
s"${indent(si.toString.split(",").mkString("\n ", ",\n ", "\n"), 4)}\n)"
// Delegate equals to that.equals in case that is itself some kind of wrapped classloader that
// needs to delegate its equals method to the delegated ClassLoader.
override def equals(that: Any): Boolean = if (that != null) that.equals(si.loader) else false
override def hashCode: Int = si.loader.hashCode
}
private[sbt] object SbtMetaBuildClassLoader {
private[this] implicit class Ops(val c: ClassLoader) {
def urls: Array[URL] = c match {
case u: URLClassLoader => u.getURLs
case cl =>
throw new IllegalStateException(s"sbt was launched with a non URLClassLoader: $cl")
}
}
def apply(libraryLoader: ClassLoader, fullLoader: ClassLoader): ClassLoader = {
val interfaceFilter: URL => Boolean = _.getFile.endsWith("test-interface-1.0.jar")
val (interfaceURL, rest) = fullLoader.urls.partition(interfaceFilter)
val interfaceLoader = new URLClassLoader(interfaceURL, libraryLoader.getParent) {
override def toString: String = s"SbtTestInterfaceClassLoader(${getURLs.head})"
}
val updatedLibraryLoader = new URLClassLoader(libraryLoader.urls, interfaceLoader) {
override def toString: String = s"ScalaClassLoader(jars = {${getURLs.mkString(", ")}}"
}
new URLClassLoader(rest, updatedLibraryLoader) {
override def toString: String = s"SbtMetaBuildClassLoader"
}
}
}

View File

@ -10,14 +10,15 @@ package sbt
import java.io.File
import java.lang.reflect.{ Method, Modifier }
import Modifier.{ isPublic, isStatic }
import sbt.internal.inc.classpath.ClasspathUtilities
import sbt.internal.inc.ScalaInstance
import sbt.internal.util.MessageOnlyException
import sbt.io.Path
import sbt.util.Logger
import scala.util.{ Try, Success, Failure }
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
import scala.util.{ Failure, Success, Try }
import scala.util.control.NonFatal
import scala.sys.process.Process
@ -90,14 +91,36 @@ class Run(newLoader: Seq[File] => ClassLoader, trapExit: Boolean) extends ScalaR
): Unit = {
log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
val loader = newLoader(classpath)
val main = getMainMethod(mainClassName, loader)
invokeMain(loader, main, options)
try {
val main = getMainMethod(mainClassName, loader)
invokeMain(loader, main, options)
} finally {
loader match {
case u: URLClassLoader => u.close()
case _ =>
}
}
}
private def invokeMain(loader: ClassLoader, main: Method, options: Seq[String]): Unit = {
private def invokeMain(
loader: ClassLoader,
main: Method,
options: Seq[String]
): Unit = {
val currentThread = Thread.currentThread
val oldLoader = Thread.currentThread.getContextClassLoader
currentThread.setContextClassLoader(loader)
try { main.invoke(null, options.toArray[String]); () } finally {
try { main.invoke(null, options.toArray[String]); () } catch {
case t: Throwable =>
t.getCause match {
case e: java.lang.IllegalAccessError =>
val msg = s"Error running $main.\n$e\n" +
"If using a layered classloader, this can occur if jvm package private classes are " +
"accessed across layers. This can be fixed by changing to the Flat or " +
"ScalaInstance class loader layering strategies."
throw new IllegalAccessError(msg)
case _ => throw t
}
} finally {
currentThread.setContextClassLoader(oldLoader)
}
}

View File

@ -0,0 +1 @@
libraryDependencies += "org.scala-sbt.ivy" % "ivy" % "2.3.0-sbt-cb9cc189e9f3af519f9f102e6c5d446488ff6832"

View File

@ -0,0 +1,12 @@
package org.apache.ivy.plugins.parser.m2
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
object Run {
def main(args: Array[String]): Unit = {
new PomModuleDescriptorBuilder.ConfMapper {
override def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {}
}
()
}
}

View File

@ -0,0 +1,9 @@
-> run
> set Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
> run
> set Runtime / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaInstance
> run

View File

@ -48,9 +48,10 @@ Provided by:
Defined at:
\t(sbt.Defaults.testTasks) Defaults.scala:670
Dependencies:
\tTest / executeTests
\tTest / test / streams
\tTest / state
\tTest / testLoader
\tTest / test / streams
\tTest / executeTests
\tTest / test / testResultLogger
Delegates:
\tTest / test

View File

@ -56,7 +56,7 @@ object RunFromSourceMain {
}
@tailrec private def launch(conf: AppConfiguration): Option[Int] =
new xMain().run(conf) match {
xMainImpl.run(conf) match {
case e: xsbti.Exit => Some(e.code)
case _: xsbti.Continue => None
case r: xsbti.Reboot => launch(getConf(conf.baseDirectory(), r.arguments()))

View File

@ -177,6 +177,7 @@ final class ScriptedTests(
case "classloader-cache/jni" => LauncherBased // sbt/Package$
case "classloader-cache/library-mismatch" => LauncherBased // sbt/Package$
case "classloader-cache/runtime-layers" => LauncherBased // sbt/Package$
case "classloader-cache/package-private" => LauncherBased // sbt/Package$
case "compiler-project/dotty-compiler-plugin" => LauncherBased // sbt/Package$
case "compiler-project/run-test" => LauncherBased // sbt/Package$
case "compiler-project/src-dep-plugin" => LauncherBased // sbt/Package$

View File

@ -122,9 +122,25 @@ final class TestRunner(
val results = new scala.collection.mutable.ListBuffer[Event]
val handler = new EventHandler { def handle(e: Event): Unit = { results += e } }
val loggers: Vector[ContentLogger] = listeners.flatMap(_.contentLogger(testDefinition))
def errorEvents(e: Throwable): Array[sbt.testing.Task] = {
val taskDef = testTask.taskDef
val event = new Event {
val status = Status.Error
val throwable = new OptionalThrowable(e)
val fullyQualifiedName = taskDef.fullyQualifiedName
val selector = new TestSelector(name)
val fingerprint = taskDef.fingerprint
val duration = -1L
}
results += event
Array.empty
}
val nestedTasks =
try testTask.execute(handler, loggers.map(_.log).toArray)
finally {
catch {
case NonFatal(e) => errorEvents(e)
case e: IllegalAccessError => errorEvents(e)
} finally {
loggers.foreach(_.flush())
}
val event = TestEvent(results)