From c9aec02d059c0ce5638df65421805d15c739c0bd Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 7 Apr 2019 14:54:11 -0700 Subject: [PATCH 1/2] Improve toString for flat classloader It can be helpful to see what jars are available to the underlying url classloader as well as what the parent classloader is. --- main/src/main/scala/sbt/internal/ClassLoaders.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/ClassLoaders.scala b/main/src/main/scala/sbt/internal/ClassLoaders.scala index 8e9e927f8..4bd40814c 100644 --- a/main/src/main/scala/sbt/internal/ClassLoaders.scala +++ b/main/src/main/scala/sbt/internal/ClassLoaders.scala @@ -198,7 +198,10 @@ private[sbt] object ClassLoaders { // helper methods private def flatLoader(classpath: Seq[File], parent: ClassLoader): ClassLoader = - new URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent) + new URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent) { + override def toString: String = + s"FlatClassLoader(parent = $interfaceLoader, jars =\n${classpath.mkString("\n")}\n)" + } } From fc715cab44b4d68fb36710d0823bdb6d73364cc2 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 7 Apr 2019 14:56:19 -0700 Subject: [PATCH 2/2] Don't leak the sbt boot scala library into tests It was reported in https://github.com/sbt/sbt/issues/4608 that there was a regression that tests run against scala 2.11 would fail. This was because the interface loader incorrectly contained the scala library. To fix this, I needed to find the xsbt.boot.BootFilteredLoader in the classloading hierarchy and put the sbt testing interface library in between that loader and the scala library loader. --- main/src/main/scala/sbt/internal/ClassLoaders.scala | 10 +++++++++- sbt/src/sbt-test/tests/test-cross/build.sbt | 8 ++++++++ .../tests/test-cross/src/test/scala/Test.scala | 8 ++++++++ sbt/src/sbt-test/tests/test-cross/test | 1 + .../main/scala/sbt/scriptedtest/ScriptedTests.scala | 4 +++- 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 sbt/src/sbt-test/tests/test-cross/build.sbt create mode 100644 sbt/src/sbt-test/tests/test-cross/src/test/scala/Test.scala create mode 100644 sbt/src/sbt-test/tests/test-cross/test diff --git a/main/src/main/scala/sbt/internal/ClassLoaders.scala b/main/src/main/scala/sbt/internal/ClassLoaders.scala index 4bd40814c..6366343c8 100644 --- a/main/src/main/scala/sbt/internal/ClassLoaders.scala +++ b/main/src/main/scala/sbt/internal/ClassLoaders.scala @@ -21,6 +21,8 @@ import sbt.internal.util.Attributed.data import sbt.io.IO import sbt.librarymanagement.Configurations.{ Runtime, Test } +import scala.annotation.tailrec + private[sbt] object ClassLoaders { private[this] val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader /* @@ -214,9 +216,15 @@ private[sbt] object SbtMetaBuildClassLoader { } } def apply(libraryLoader: ClassLoader, fullLoader: ClassLoader): ClassLoader = { + @tailrec + def bootLoader(classLoader: ClassLoader): ClassLoader = classLoader.getParent match { + case null => classLoader + case c if c.getClass.getCanonicalName == "xsbt.boot.BootFilteredLoader" => c + case c => bootLoader(c) + } 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) { + val interfaceLoader = new URLClassLoader(interfaceURL, bootLoader(libraryLoader)) { override def toString: String = s"SbtTestInterfaceClassLoader(${getURLs.head})" } val updatedLibraryLoader = new URLClassLoader(libraryLoader.urls, interfaceLoader) { diff --git a/sbt/src/sbt-test/tests/test-cross/build.sbt b/sbt/src/sbt-test/tests/test-cross/build.sbt new file mode 100644 index 000000000..dabf9b70b --- /dev/null +++ b/sbt/src/sbt-test/tests/test-cross/build.sbt @@ -0,0 +1,8 @@ +val scalatest = "org.scalatest" %% "scalatest" % "3.0.5" + +lazy val root = (project in file(".")) + .settings( + // Verifies that a different scala library version still works in test + scalaVersion := "2.11.12", + libraryDependencies += scalatest % Test + ) diff --git a/sbt/src/sbt-test/tests/test-cross/src/test/scala/Test.scala b/sbt/src/sbt-test/tests/test-cross/src/test/scala/Test.scala new file mode 100644 index 000000000..8a6430745 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-cross/src/test/scala/Test.scala @@ -0,0 +1,8 @@ +import java.io.File +import org.scalatest.FlatSpec + +class Test extends FlatSpec { + "a test" should "pass" in { + assert(true) + } +} diff --git a/sbt/src/sbt-test/tests/test-cross/test b/sbt/src/sbt-test/tests/test-cross/test new file mode 100644 index 000000000..dfffb838b --- /dev/null +++ b/sbt/src/sbt-test/tests/test-cross/test @@ -0,0 +1 @@ +> test diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 128e794ae..fc6dde494 100644 --- a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -255,7 +255,9 @@ final class ScriptedTests( case "source-dependencies/linearization" => LauncherBased // sbt/Package$ case "source-dependencies/named" => LauncherBased // sbt/Package$ case "source-dependencies/specialized" => LauncherBased // sbt/Package$ - case _ => RunFromSourceBased + case "tests/test-cross" => + LauncherBased // the sbt metabuild classpath leaks into the test interface classloader in older versions of sbt + case _ => RunFromSourceBased } // sbt/Package$ means: // java.lang.NoClassDefFoundError: sbt/Package$ (wrong name: sbt/package$)