Merge pull request #5367 from eatkins/scripted-run-source-from-main

Run all scripted tests using RunFromSourceMain
This commit is contained in:
eugene yokota 2020-01-20 10:34:10 -05:00 committed by GitHub
commit 996ee5f0d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 911 additions and 550 deletions

109
build.sbt
View File

@ -567,23 +567,11 @@ val sbtProjDepsCompileScopeFilter =
)
lazy val scriptedSbtReduxProj = (project in file("scripted-sbt-redux"))
.dependsOn(commandProj, utilLogging, utilScripted)
.dependsOn(sbtProj % "compile;test->test", commandProj, utilLogging, utilScripted)
.settings(
baseSettings,
name := "Scripted sbt Redux",
libraryDependencies ++= Seq(launcherInterface % "provided"),
resourceGenerators in Compile += Def task {
val mainClassDir = (classDirectory in Compile in LocalProject("sbtProj")).value
val testClassDir = (classDirectory in Test in LocalProject("sbtProj")).value
val classDirs = (classDirectory all sbtProjDepsCompileScopeFilter).value
val extDepsCp = (externalDependencyClasspath in Compile in LocalProject("sbtProj")).value
val cpStrings = (mainClassDir +: testClassDir +: classDirs) ++ extDepsCp.files map (_.toString)
val file = (resourceManaged in Compile).value / "RunFromSource.classpath"
if (!file.exists || Try(IO.readLines(file)).getOrElse(Nil).toSet != cpStrings.toSet) {
IO.writeLines(file, cpStrings)
}
List(file)
},
mimaSettings,
scriptedSbtReduxMimaSettings,
)
@ -777,17 +765,13 @@ lazy val mainSettingsProj = (project in file("main-settings"))
.settings(
testedBaseSettings,
name := "Main Settings",
BuildInfoPlugin.buildInfoDefaultSettings,
addBuildInfoToConfig(Test),
buildInfoObject in Test := "TestBuildInfo",
buildInfoKeys in Test := Seq[BuildInfoKey](
classDirectory in Compile,
classDirectory in Test,
// WORKAROUND https://github.com/sbt/sbt-buildinfo/issues/117
BuildInfoKey.map((dependencyClasspath in Compile).taskValue) {
case (ident, cp) => ident -> cp.files
},
),
testOptions in Test ++= {
val cp = (Test / fullClasspathAsJars).value.map(_.data).mkString(java.io.File.pathSeparator)
val framework = TestFrameworks.ScalaTest
Tests.Argument(framework, s"-Dsbt.server.classpath=$cp") ::
Tests.Argument(framework, s"-Dsbt.server.version=${version.value}") ::
Tests.Argument(framework, s"-Dsbt.server.scala.version=${scalaVersion.value}") :: Nil
},
mimaSettings,
mimaBinaryIssueFilters ++= Seq(
exclude[IncompatibleSignatureProblem]("sbt.Previous#References.getReferences"),
@ -829,15 +813,11 @@ lazy val mainSettingsProj = (project in file("main-settings"))
)
lazy val zincLmIntegrationProj = (project in file("zinc-lm-integration"))
.enablePlugins(BuildInfoPlugin)
.settings(
name := "Zinc LM Integration",
testedBaseSettings,
buildInfo in Compile := Nil, // Only generate build info for tests
BuildInfoPlugin.buildInfoScopedSettings(Test),
buildInfoPackage in Test := "sbt.internal.inc",
buildInfoObject in Test := "ZincLmIntegrationBuildInfo",
buildInfoKeys in Test := List[BuildInfoKey]("zincVersion" -> zincVersion),
testOptions in Test +=
Tests.Argument(TestFrameworks.ScalaTest, s"-Dsbt.zinc.version=$zincVersion"),
mimaSettingsSince(sbt13Plus),
libraryDependencies += launcherInterface,
)
@ -853,7 +833,6 @@ lazy val mainProj = (project in file("main"))
runProj,
commandProj,
collectionProj,
scriptedSbtReduxProj,
scriptedPluginProj,
zincLmIntegrationProj,
utilLogging,
@ -963,7 +942,7 @@ lazy val mainProj = (project in file("main"))
// technically, we need a dependency on all of mainProj's dependencies, but we don't do that since this is strictly an integration project
// with the sole purpose of providing certain identifiers without qualification (with a package object)
lazy val sbtProj = (project in file("sbt"))
.dependsOn(mainProj, scriptedSbtReduxProj % "test->test")
.dependsOn(mainProj)
.settings(
testedBaseSettings,
name := "sbt",
@ -973,25 +952,16 @@ lazy val sbtProj = (project in file("sbt"))
javaOptions ++= Seq("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"),
mimaSettings,
mimaBinaryIssueFilters ++= sbtIgnoredProblems,
BuildInfoPlugin.buildInfoDefaultSettings,
addBuildInfoToConfig(Test),
BuildInfoPlugin.buildInfoDefaultSettings,
buildInfoObject in Test := "TestBuildInfo",
buildInfoKeys in Test := Seq[BuildInfoKey](
version,
// WORKAROUND https://github.com/sbt/sbt-buildinfo/issues/117
BuildInfoKey.map((fullClasspath in Compile).taskValue) {
case (ident, cp) => ident -> cp.files
},
BuildInfoKey.map((dependencyClasspath in Compile).taskValue) {
case (ident, cp) => ident -> cp.files
},
classDirectory in Compile,
classDirectory in Test,
),
Test / run / connectInput := true,
Test / run / outputStrategy := Some(StdoutOutput),
Test / run / fork := true,
testOptions in Test ++= {
val cp = (Test / fullClasspathAsJars).value.map(_.data).mkString(java.io.File.pathSeparator)
val framework = TestFrameworks.ScalaTest
Tests.Argument(framework, s"-Dsbt.server.classpath=$cp") ::
Tests.Argument(framework, s"-Dsbt.server.version=${version.value}") ::
Tests.Argument(framework, s"-Dsbt.server.scala.version=${scalaVersion.value}") :: Nil
},
)
.configure(addSbtIO, addSbtCompilerBridge)
@ -1129,33 +1099,19 @@ lazy val vscodePlugin = (project in file("vscode-sbt-scala"))
}
)
def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
// publishLocalBinAll.value // TODO: Restore scripted needing only binary jars.
publishAll.value
(sbtProj / Test / compile).value // make sure sbt.RunFromSourceMain is compiled
def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTask {
publishLocalBinAll.value
val launchJar = s"-Dsbt.launch.jar=${(bundledLauncherProj / Compile / packageBin).value}"
Scripted.doScripted(
(sbtLaunchJar in bundledLauncherProj).value,
(fullClasspath in scriptedSbtReduxProj in Test).value,
(scalaInstance in scriptedSbtReduxProj).value,
scriptedSource.value,
scriptedBufferLog.value,
Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed,
scriptedPrescripted.value,
scriptedLaunchOpts.value,
streams.value.log
)
}
def scriptedUnpublishedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
Scripted.doScripted(
(sbtLaunchJar in bundledLauncherProj).value,
(fullClasspath in scriptedSbtReduxProj in Test).value,
(scalaInstance in scriptedSbtReduxProj).value,
scriptedSource.value,
scriptedBufferLog.value,
Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed,
scriptedPrescripted.value,
scriptedLaunchOpts.value,
scriptedLaunchOpts.value ++ (if (launch) Some(launchJar) else None),
scalaVersion.value,
version.value,
(scriptedSbtReduxProj / Test / fullClasspathAsJars).value.map(_.data),
streams.value.log
)
}
@ -1206,18 +1162,18 @@ ThisBuild / scriptedPrescripted := { _ =>
def otherRootSettings =
Seq(
scripted := scriptedTask.evaluated,
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
scripted := scriptedTask(false).evaluated,
scriptedUnpublished := scriptedTask(false).evaluated,
scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test",
watchTriggers in scripted += scriptedSource.value.toGlob / **,
watchTriggers in scriptedUnpublished := (watchTriggers in scripted).value,
scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server") :::
(sys.props.get("sbt.ivy.home") match {
case Some(home) => List(s"-Dsbt.ivy.home=$home")
case _ => Nil
}),
publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value },
publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value },
aggregate in bintrayRelease := false
publishLocalBinAll := (Compile / publishLocalBin).all(scriptedProjects).value,
aggregate in bintrayRelease := false,
) ++ inConfig(Scripted.RepoOverrideTest)(
Seq(
scriptedLaunchOpts := List(
@ -1231,8 +1187,8 @@ def otherRootSettings =
case Some(home) => List(s"-Dsbt.ivy.home=$home")
case _ => Nil
}),
scripted := scriptedTask.evaluated,
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
scripted := scriptedTask(true).evaluated,
scriptedUnpublished := scriptedTask(true).evaluated,
scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test"
)
)
@ -1262,6 +1218,7 @@ lazy val otherProjects: ScopeFilter = ScopeFilter(
inConfigurations(Test)
)
lazy val javafmtOnCompile = taskKey[Unit]("Formats java sources before compile")
lazy val scriptedProjects = ScopeFilter(inAnyProject -- inProjects(vscodePlugin))
def customCommands: Seq[Setting[_]] = Seq(
commands += Command.command("setupBuildScala212") { state =>

View File

@ -7,19 +7,18 @@
package sbt.std
import org.scalatest.FunSuite
import org.scalatest.{ TestData, fixture }
import sbt.std.TestUtil._
import scala.tools.reflect.{ FrontEnd, ToolBoxError }
class TaskConfigSpec extends FunSuite {
class TaskConfigSpec extends fixture.FunSuite with fixture.TestDataFixture {
private def expectError(
errorSnippet: String,
compileOptions: String = "",
baseCompileOptions: String = s"-cp $toolboxClasspath",
)(code: String) = {
)(code: String)(implicit td: TestData) = {
val errorMessage = intercept[ToolBoxError] {
eval(code, s"$compileOptions $baseCompileOptions")
eval(code, s"$compileOptions -cp ${toolboxClasspath(td)}")
println(s"Test failed -- compilation was successful! Expected:\n$errorSnippet")
}.getMessage
val userMessage =
@ -29,7 +28,7 @@ class TaskConfigSpec extends FunSuite {
""".stripMargin
assert(errorMessage.contains(errorSnippet), userMessage)
}
private class CachingToolbox {
private class CachingToolbox(implicit td: TestData) {
private[this] val m = scala.reflect.runtime.currentMirror
private[this] var _infos: List[FrontEnd#Info] = Nil
private[this] val frontEnd = new FrontEnd {
@ -38,7 +37,7 @@ class TaskConfigSpec extends FunSuite {
}
import scala.tools.reflect.ToolBox
val toolbox = m.mkToolBox(frontEnd, options = s"-cp $toolboxClasspath")
val toolbox = m.mkToolBox(frontEnd, options = s"-cp ${toolboxClasspath(td)}")
def eval(code: String): Any = toolbox.eval(toolbox.parse(code))
def infos: List[FrontEnd#Info] = _infos
}
@ -59,16 +58,17 @@ class TaskConfigSpec extends FunSuite {
""".stripMargin
private val fooNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")
test("Fail on task invocation inside if it is used inside a regular task") {
test("Fail on task invocation inside if it is used inside a regular task") { implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")
expectError(List(fooNegError).mkString("\n"))(taskDef(Some("import sbt.dsl.LinterLevel.Abort")))
val code = taskDef(Some("import sbt.dsl.LinterLevel.Abort"))
expectError(List(fooNegError).mkString("\n"))(code)
}
test("Succeed if the linter level is set to warn") {
test("Succeed if the linter level is set to warn") { implicit td =>
val toolbox = new CachingToolbox
assert(toolbox.eval(taskDef(None)) == ((): Unit))
assert(toolbox.infos.exists(i => i.severity.count == 1 && i.msg.contains(fooNegError)))
}
test("Succeed if the linter level is set to ignore") {
test("Succeed if the linter level is set to ignore") { implicit td =>
val toolbox = new CachingToolbox
assert(toolbox.eval(taskDef(Some("import sbt.dsl.LinterLevel.Ignore"))) == ((): Unit))
assert(toolbox.infos.isEmpty)

View File

@ -7,6 +7,8 @@
package sbt.std
import org.scalatest.TestData
import scala.tools.reflect.ToolBox
object TestUtil {
@ -21,10 +23,9 @@ object TestUtil {
m.mkToolBox(options = compileOptions)
}
lazy val toolboxClasspath: String = {
val mainClassesDir = buildinfo.TestBuildInfo.classDirectory
val testClassesDir = buildinfo.TestBuildInfo.test_classDirectory
val depsClasspath = buildinfo.TestBuildInfo.dependencyClasspath
mainClassesDir +: testClassesDir +: depsClasspath mkString java.io.File.pathSeparator
}
def toolboxClasspath(td: TestData): String =
td.configMap.get("sbt.server.classpath") match {
case Some(s: String) => s
case _ => throw new IllegalStateException("No classpath specified.")
}
}

View File

@ -8,19 +8,17 @@
package sbt.std.neg
import scala.tools.reflect.ToolBoxError
import org.scalatest.FunSuite
import sbt.std.TaskLinterDSLFeedback
import org.scalatest.{ TestData, fixture }
import sbt.std.{ TaskLinterDSLFeedback, TestUtil }
import sbt.std.TestUtil._
class TaskNegSpec extends FunSuite {
class TaskNegSpec extends fixture.FunSuite with fixture.TestDataFixture {
def expectError(
errorSnippet: String,
compileOptions: String = "",
baseCompileOptions: String = s"-cp $toolboxClasspath",
)(code: String) = {
)(code: String)(implicit td: TestData) = {
val errorMessage = intercept[ToolBoxError] {
val baseCompileOptions = s"-cp ${TestUtil.toolboxClasspath(td)}"
eval(code, s"$compileOptions $baseCompileOptions")
println(s"Test failed -- compilation was successful! Expected:\n$errorSnippet")
}.getMessage
@ -32,7 +30,7 @@ class TaskNegSpec extends FunSuite {
assert(errorMessage.contains(errorSnippet), userMessage)
}
test("Fail on task invocation inside if it is used inside a regular task") {
test("Fail on task invocation inside if it is used inside a regular task") { implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")
val barNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg")
expectError(List(fooNegError, barNegError).mkString("\n")) {
@ -53,7 +51,7 @@ class TaskNegSpec extends FunSuite {
}
}
test("Fail on task invocation inside `if` if it is used inside a regular task") {
test("Fail on task invocation inside `if` if it is used inside a regular task") { implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")
val barNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg")
expectError(List(fooNegError, barNegError).mkString("\n")) {
@ -75,7 +73,7 @@ class TaskNegSpec extends FunSuite {
}
}
test("Fail on task invocation inside `if` of task returned by dynamic task") {
test("Fail on task invocation inside `if` of task returned by dynamic task") { implicit td =>
expectError(TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")) {
"""
|import sbt._
@ -100,10 +98,11 @@ class TaskNegSpec extends FunSuite {
}
test("Fail on task invocation inside nested `if` of task returned by dynamic task") {
val fooNegCatch = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")
val barNegCatch = TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg")
expectError(List(fooNegCatch, barNegCatch).mkString("\n")) {
"""
implicit td =>
val fooNegCatch = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")
val barNegCatch = TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg")
expectError(List(fooNegCatch, barNegCatch).mkString("\n")) {
"""
|import sbt._
|import sbt.Def._
|import sbt.dsl.LinterLevel.Abort
@ -128,10 +127,10 @@ class TaskNegSpec extends FunSuite {
| } else Def.task("")
|}
""".stripMargin
}
}
}
test("Fail on task invocation inside else of task returned by dynamic task") {
test("Fail on task invocation inside else of task returned by dynamic task") { implicit td =>
expectError(TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg")) {
"""
|import sbt._
@ -155,9 +154,10 @@ class TaskNegSpec extends FunSuite {
}
test("Fail on task invocation inside anonymous function returned by regular task") {
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
expectError(fooNegError) {
"""
implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
expectError(fooNegError) {
"""
|import sbt._
|import sbt.Def._
|import sbt.dsl.LinterLevel.Abort
@ -172,14 +172,15 @@ class TaskNegSpec extends FunSuite {
| else anon()
|}
""".stripMargin
}
}
}
test("Fail on task invocation inside nested anonymous function returned by regular task") {
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
val barNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("barNeg")
expectError(List(fooNegError, barNegError).mkString("\n")) {
"""
implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
val barNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("barNeg")
expectError(List(fooNegError, barNegError).mkString("\n")) {
"""
|import sbt._
|import sbt.Def._
|import sbt.dsl.LinterLevel.Abort
@ -194,13 +195,14 @@ class TaskNegSpec extends FunSuite {
| else anon()
|}
""".stripMargin
}
}
}
test("Fail on task invocation inside complex anonymous function returned by regular task") {
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
expectError(fooNegError) {
"""
implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
expectError(fooNegError) {
"""
|import sbt._
|import sbt.Def._
|import sbt.dsl.LinterLevel.Abort
@ -214,13 +216,14 @@ class TaskNegSpec extends FunSuite {
| else anon()
|}
""".stripMargin
}
}
}
test("Fail on task invocation inside anonymous function returned by dynamic task") {
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
expectError(fooNegError) {
"""
implicit td =>
val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg")
expectError(fooNegError) {
"""
|import sbt._
|import sbt.Def._
|import sbt.dsl.LinterLevel.Abort
@ -236,10 +239,10 @@ class TaskNegSpec extends FunSuite {
| } else Def.task("")
|}
""".stripMargin
}
}
}
test("Detect a missing `.value` inside a task") {
test("Detect a missing `.value` inside a task") { implicit td =>
expectError(TaskLinterDSLFeedback.missingValueForKey("fooNeg")) {
"""
|import sbt._
@ -257,7 +260,7 @@ class TaskNegSpec extends FunSuite {
}
}
test("Detect a missing `.value` inside a val definition of a task") {
test("Detect a missing `.value` inside a val definition of a task") { implicit td =>
expectError(TaskLinterDSLFeedback.missingValueForKey("fooNeg2")) {
"""
|import sbt._
@ -276,8 +279,9 @@ class TaskNegSpec extends FunSuite {
}
test("Detect a missing `.value` inside a val definition of an inner method of a task") {
expectError(TaskLinterDSLFeedback.missingValueForKey("fooNeg2")) {
"""
implicit td =>
expectError(TaskLinterDSLFeedback.missingValueForKey("fooNeg2")) {
"""
|import sbt._
|import sbt.Def._
|import sbt.dsl.LinterLevel.Abort
@ -293,10 +297,10 @@ class TaskNegSpec extends FunSuite {
| inner
|}
""".stripMargin
}
}
}
test("Detect a missing `.value` inside an inner method of a task") {
test("Detect a missing `.value` inside an inner method of a task") { implicit td =>
expectError(TaskLinterDSLFeedback.missingValueForKey("fooNeg3")) {
"""
|import sbt._
@ -316,7 +320,7 @@ class TaskNegSpec extends FunSuite {
}
}
test("Detect a missing `.value` inside a task whose return type is Unit") {
test("Detect a missing `.value` inside a task whose return type is Unit") { implicit td =>
expectError(TaskLinterDSLFeedback.missingValueForKey("fooNeg4")) {
"""
|import sbt._

View File

@ -0,0 +1,14 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal;
public class ClassLoaderClose {
public static void close(ClassLoader classLoader) throws Exception {
if (classLoader instanceof AutoCloseable) ((AutoCloseable) classLoader).close();
}
}

View File

@ -11,6 +11,8 @@ import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;
import xsbti.AppProvider;
import xsbti.ScalaProvider;
@ -61,19 +63,27 @@ public final class MetaBuildLoader extends URLClassLoader {
final Pattern pattern = Pattern.compile("test-interface-[0-9.]+\\.jar");
final File[] cp = appProvider.mainClasspath();
final URL[] interfaceURL = new URL[1];
final URL[] rest = new URL[cp.length - 1];
final File[] extra =
appProvider.id().classpathExtra() == null ? new File[0] : appProvider.id().classpathExtra();
final Set<File> bottomClasspath = new LinkedHashSet<>();
{
int i = 0;
int j = 0; // index into rest
while (i < cp.length) {
final File file = cp[i];
for (final File file : cp) {
if (pattern.matcher(file.getName()).find()) {
interfaceURL[0] = file.toURI().toURL();
} else {
rest[j] = file.toURI().toURL();
j += 1;
bottomClasspath.add(file);
}
}
for (final File file : extra) {
bottomClasspath.add(file);
}
}
final URL[] rest = new URL[bottomClasspath.size()];
{
int i = 0;
for (final File file : bottomClasspath) {
rest[i] = file.toURI().toURL();
i += 1;
}
}

View File

@ -11,6 +11,7 @@ import java.io.File
import java.lang.reflect.InvocationTargetException
import java.net.URL
import java.util.concurrent.{ ExecutorService, Executors }
import ClassLoaderClose.close
import sbt.plugins.{ CorePlugin, IvyPlugin, JvmPlugin }
import sbt.util.LogExchange
@ -56,10 +57,6 @@ private[internal] object ClassLoaderWarmup {
* in this file.
*/
private[sbt] class XMainConfiguration {
private def close(classLoader: ClassLoader): Unit = classLoader match {
case a: AutoCloseable => a.close()
case _ =>
}
def run(moduleName: String, configuration: xsbti.AppConfiguration): xsbti.MainResult = {
val updatedConfiguration =
if (configuration.provider.scalaProvider.launcher.topLoader.getClass.getCanonicalName
@ -86,9 +83,11 @@ private[sbt] class XMainConfiguration {
private def makeConfiguration(configuration: xsbti.AppConfiguration): xsbti.AppConfiguration = {
val baseLoader = classOf[XMainConfiguration].getClassLoader
val url = baseLoader.getResource("sbt/internal/XMainConfiguration.class")
val className = "sbt/internal/XMainConfiguration.class"
val url = baseLoader.getResource(className)
val path = url.toString.replaceAll(s"$className$$", "")
val urlArray = new Array[URL](1)
urlArray(0) = new URL(url.getPath.replaceAll("[!][^!]*class", ""))
urlArray(0) = new URL(path)
val topLoader = configuration.provider.scalaProvider.launcher.topLoader
// This loader doesn't have the scala library in it so it's critical that none of the code
// in this file use the scala library.

View File

@ -5,8 +5,6 @@ import java.lang.reflect.InvocationTargetException
import sbt._
import sbt.internal.inc.ScalaInstance
import sbt.internal.inc.classpath.{ ClasspathUtilities, FilteredLoader }
import sbt.ScriptedPlugin.autoImport._
import sbt.util.Level
object LocalScriptedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
@ -15,7 +13,6 @@ object LocalScriptedPlugin extends AutoPlugin {
}
trait ScriptedKeys {
val publishAll = taskKey[Unit]("")
val publishLocalBinAll = taskKey[Unit]("")
val scriptedUnpublished = inputKey[Unit](
"Execute scripted without publishing sbt first. " +
@ -93,14 +90,15 @@ object Scripted {
}
def doScripted(
launcher: File,
scriptedSbtClasspath: Seq[Attributed[File]],
scriptedSbtInstance: ScalaInstance,
sourcePath: File,
bufferLog: Boolean,
args: Seq[String],
prescripted: File => Unit,
launchOpts: Seq[String],
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File],
logger: Logger
): Unit = {
logger.info(s"About to run tests: ${args.mkString("\n * ", "\n * ", "\n")}")
@ -110,7 +108,7 @@ object Scripted {
sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true"
val noJLine = new FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil)
val loader = ClasspathUtilities.toLoader(scriptedSbtClasspath.files, noJLine)
val loader = ClasspathUtilities.toLoader(classpath, noJLine)
val bridgeClass = Class.forName("sbt.scriptedtest.ScriptedRunner", true, loader)
// Interface to cross class loader
@ -119,9 +117,11 @@ object Scripted {
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
bootProperties: File,
launchOpts: Array[String],
prescripted: java.util.List[File],
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File],
instances: Int
): Unit
}
@ -150,9 +150,11 @@ object Scripted {
sourcePath,
bufferLog,
args.toArray,
launcher,
launchOpts.toArray,
callback,
scalaVersion,
sbtVersion,
classpath,
instances
)
} catch { case ite: InvocationTargetException => throw ite.getCause }

View File

@ -6,7 +6,6 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.0")
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.4")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0")
addSbtPlugin("com.lightbend" % "sbt-whitesource" % "0.1.14")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.6.1")

View File

@ -7,17 +7,17 @@
package sbt
import org.scalatest.FunSuite
import org.scalatest
import org.scalatest.{ TestData, fixture }
import scala.tools.reflect.{ FrontEnd, ToolBoxError }
class IllegalReferenceSpec extends FunSuite {
lazy val toolboxClasspath: String = {
val mainClassesDir = buildinfo.TestBuildInfo.classDirectory
val testClassesDir = buildinfo.TestBuildInfo.test_classDirectory
val depsClasspath = buildinfo.TestBuildInfo.dependencyClasspath
mainClassesDir +: testClassesDir +: depsClasspath mkString java.io.File.pathSeparator
}
class IllegalReferenceSpec extends fixture.FunSuite with fixture.TestDataFixture {
private def toolboxClasspath(td: TestData): String =
td.configMap.get("sbt.server.classpath") match {
case Some(s: String) => s
case _ => throw new IllegalStateException("No classpath specified.")
}
def eval(code: String, compileOptions: String = ""): Any = {
val m = scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
@ -26,11 +26,10 @@ class IllegalReferenceSpec extends FunSuite {
}
private def expectError(
errorSnippet: String,
compileOptions: String = "",
baseCompileOptions: String = s"-cp $toolboxClasspath",
)(code: String) = {
compileOptions: String = ""
)(code: String)(implicit td: TestData): scalatest.Assertion = {
val errorMessage = intercept[ToolBoxError] {
eval(code, s"$compileOptions $baseCompileOptions")
eval(code, s"$compileOptions -cp ${toolboxClasspath(td)}")
println(s"Test failed -- compilation was successful! Expected:\n$errorSnippet")
}.getMessage
val userMessage =
@ -40,7 +39,7 @@ class IllegalReferenceSpec extends FunSuite {
""".stripMargin
assert(errorMessage.contains(errorSnippet), userMessage)
}
private class CachingToolbox {
private class CachingToolbox(implicit td: TestData) {
private[this] val m = scala.reflect.runtime.currentMirror
private[this] var _infos: List[FrontEnd#Info] = Nil
private[this] val frontEnd = new FrontEnd {
@ -49,12 +48,12 @@ class IllegalReferenceSpec extends FunSuite {
}
import scala.tools.reflect.ToolBox
val toolbox = m.mkToolBox(frontEnd, options = s"-cp $toolboxClasspath")
val toolbox = m.mkToolBox(frontEnd, options = s"-cp ${toolboxClasspath(td)}")
def eval(code: String): Any = toolbox.eval(toolbox.parse(code))
def infos: List[FrontEnd#Info] = _infos
}
test("Def.sequential should be legal within Def.taskDyn") {
test("Def.sequential should be legal within Def.taskDyn") { implicit td =>
val toolbox = new CachingToolbox
// This example was taken from @dos65 in https://github.com/sbt/sbt/issues/3110
val build =
@ -74,7 +73,7 @@ class IllegalReferenceSpec extends FunSuite {
assert(toolbox.eval(build) == Some("baseDirectory"))
assert(toolbox.infos.isEmpty)
}
test("Local task defs should be illegal within Def.task") {
test("Local task defs should be illegal within Def.task") { implicit td =>
val build =
s"""
|import sbt._

View File

@ -7,23 +7,33 @@
package sbt
import java.io.File.pathSeparator
import sbt.internal.scriptedtest.ScriptedLauncher
import sbt.util.LogExchange
import scala.annotation.tailrec
import buildinfo.TestBuildInfo
import xsbti._
import scala.sys.process.Process
object RunFromSourceMain {
private val sbtVersion = TestBuildInfo.version
private val scalaVersion = "2.12.6"
def fork(workingDirectory: File): Process = {
def fork(
workingDirectory: File,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File]
): Process = {
val fo = ForkOptions()
.withOutputStrategy(OutputStrategy.StdoutOutput)
fork(fo, workingDirectory)
fork(fo, workingDirectory, scalaVersion, sbtVersion, classpath)
}
def fork(fo0: ForkOptions, workingDirectory: File): Process = {
def fork(
fo0: ForkOptions,
workingDirectory: File,
scalaVersion: String,
sbtVersion: String,
cp: Seq[File]
): Process = {
val fo = fo0
.withWorkingDirectory(workingDirectory)
.withRunJVMOptions(sys.props.get("sbt.ivy.home") match {
@ -31,54 +41,84 @@ object RunFromSourceMain {
case _ => Vector()
})
implicit val runner = new ForkRun(fo)
val cp = {
TestBuildInfo.test_classDirectory +: TestBuildInfo.fullClasspath
}
val options = Vector(workingDirectory.toString)
val options =
Vector(workingDirectory.toString, scalaVersion, sbtVersion, cp.mkString(pathSeparator))
val log = LogExchange.logger("RunFromSourceMain.fork", None, None)
runner.fork("sbt.RunFromSourceMain", cp, options, log)
}
def main(args: Array[String]): Unit = args match {
case Array() => sys.error(s"Must specify working directory as the first argument")
case Array(wd, args @ _*) => run(file(wd), args)
case Array() =>
sys.error(
s"Must specify working directory, scala version and sbt version and classpath as the first three arguments"
)
case Array(wd, scalaVersion, sbtVersion, classpath, args @ _*) =>
System.setProperty("jna.nosys", "true")
run(file(wd), scalaVersion, sbtVersion, classpath, args)
}
// this arrangement is because Scala does not always properly optimize away
// the tail recursion in a catch statement
@tailrec private[sbt] def run(baseDir: File, args: Seq[String]): Unit =
runImpl(baseDir, args) match {
case Some((baseDir, args)) => run(baseDir, args)
@tailrec private[sbt] def run(
baseDir: File,
scalaVersion: String,
sbtVersion: String,
classpath: String,
args: Seq[String],
): Unit =
runImpl(baseDir, scalaVersion, sbtVersion, classpath, args) match {
case Some((baseDir, args)) => run(baseDir, scalaVersion, sbtVersion, classpath, args)
case None => ()
}
private def runImpl(baseDir: File, args: Seq[String]): Option[(File, Seq[String])] =
try launch(getConf(baseDir, args)) map exit
private def runImpl(
baseDir: File,
scalaVersion: String,
sbtVersion: String,
classpath: String,
args: Seq[String],
): Option[(File, Seq[String])] = {
try launch(defaultBootDirectory, baseDir, scalaVersion, sbtVersion, classpath, args) map exit
catch {
case r: xsbti.FullReload => Some((baseDir, r.arguments()))
case scala.util.control.NonFatal(e) => e.printStackTrace(); errorAndExit(e.toString)
}
@tailrec private def launch(conf: AppConfiguration): Option[Int] =
xMain.run(conf) match {
case e: xsbti.Exit => Some(e.code)
case _: xsbti.Continue => None
case r: xsbti.Reboot => launch(getConf(conf.baseDirectory(), r.arguments()))
case x => handleUnknownMainResult(x)
}
private val noGlobalLock = new GlobalLock {
def apply[T](lockFile: File, run: java.util.concurrent.Callable[T]) = run.call()
}
private lazy val bootDirectory: File = file(sys.props("user.home")) / ".sbt" / "boot"
private lazy val scalaHome: File = {
private def launch(
bootDirectory: File,
baseDirectory: File,
scalaVersion: String,
sbtVersion: String,
classpath: String,
arguments: Seq[String],
): Option[Int] = {
ScriptedLauncher
.launch(
scalaHome(bootDirectory, scalaVersion),
sbtVersion,
scalaVersion,
bootDirectory,
baseDirectory,
classpath.split(java.io.File.pathSeparator).map(file),
arguments.toArray
)
.orElse(null) match {
case null => None
case i if i == Int.MaxValue => None
case i => Some(i)
}
}
private lazy val defaultBootDirectory: File =
file(sys.props("user.home")) / ".sbt" / "scripted" / "boot"
private def scalaHome(bootDirectory: File, scalaVersion: String): File = {
val log = sbt.util.LogExchange.logger("run-from-source")
val scalaHome0 = bootDirectory / s"scala-$scalaVersion"
if ((scalaHome0 / "lib").exists) scalaHome0
else {
log.info(s"""scalaHome ($scalaHome0) wasn't found""")
val fakeboot = file(sys.props("user.home")) / ".sbt" / "fakeboot"
val fakeboot = bootDirectory / "fakeboot"
val scalaHome1 = fakeboot / s"scala-$scalaVersion"
val scalaHome1Lib = scalaHome1 / "lib"
val scalaHome1Temp = scalaHome1 / "temp"
@ -106,99 +146,6 @@ object RunFromSourceMain {
}
}
private def getConf(baseDir: File, args: Seq[String]): AppConfiguration = new AppConfiguration {
def baseDirectory = baseDir
def arguments = args.toArray
def provider = new AppProvider { appProvider =>
def scalaProvider = new ScalaProvider { scalaProvider =>
def scalaOrg = "org.scala-lang"
def launcher = new Launcher {
def getScala(version: String) = getScala(version, "")
def getScala(version: String, reason: String) = getScala(version, reason, scalaOrg)
def getScala(version: String, reason: String, scalaOrg: String) = scalaProvider
def app(id: xsbti.ApplicationID, version: String) = appProvider
def topLoader = new java.net.URLClassLoader(Array(), null)
def globalLock = noGlobalLock
def bootDirectory = RunFromSourceMain.bootDirectory
def ivyHome: File = sys.props.get("sbt.ivy.home") match {
case Some(home) => file(home)
case _ => file(sys.props("user.home")) / ".ivy2"
}
case class PredefRepo(id: Predefined) extends PredefinedRepository
import Predefined._
def ivyRepositories = Array(PredefRepo(Local), PredefRepo(MavenCentral))
def appRepositories = Array(PredefRepo(Local), PredefRepo(MavenCentral))
def isOverrideRepositories = false
def checksums = Array("sha1", "md5")
}
def version = scalaVersion
lazy val libDir: File = RunFromSourceMain.scalaHome / "lib"
def jar(name: String): File = libDir / s"$name.jar"
lazy val libraryJar = jar("scala-library")
lazy val compilerJar = jar("scala-compiler")
lazy val jars = {
assert(libDir.exists)
libDir.listFiles(f => !f.isDirectory && f.getName.endsWith(".jar"))
}
def loader = new java.net.URLClassLoader(jars map (_.toURI.toURL), null)
def app(id: xsbti.ApplicationID) = appProvider
}
def id = ApplicationID(
"org.scala-sbt",
"sbt",
sbtVersion,
"sbt.xMain",
Seq("xsbti", "extra"),
CrossValue.Disabled,
Nil
)
def appHome: File = scalaHome / id.groupID / id.name / id.version
def mainClasspath = buildinfo.TestBuildInfo.fullClasspath.toArray
def loader = new java.net.URLClassLoader(mainClasspath map (_.toURI.toURL), null)
def entryPoint = classOf[xMain]
def mainClass = classOf[xMain]
def newMain = new xMain
def components = new ComponentProvider {
def componentLocation(id: String) = appHome / id
def component(id: String) = IO.listFiles(componentLocation(id), _.isFile)
def defineComponent(id: String, files: Array[File]) = {
val location = componentLocation(id)
if (location.exists)
sys error s"Cannot redefine component. ID: $id, files: ${files mkString ","}"
else {
copy(files.toList, location)
()
}
}
def addToComponent(id: String, files: Array[File]) =
copy(files.toList, componentLocation(id))
def lockFile = appHome / "sbt.components.lock"
private def copy(files: List[File], toDirectory: File): Boolean =
files exists (copy(_, toDirectory))
private def copy(file: File, toDirectory: File): Boolean = {
val to = toDirectory / file.getName
val missing = !to.exists
IO.copyFile(file, to)
missing
}
}
}
}
private def handleUnknownMainResult(x: MainResult): Nothing = {
val clazz = if (x eq null) "" else " (class: " + x.getClass + ")"
errorAndExit("Invalid main result: " + x + clazz)
}
private def errorAndExit(msg: String): Nothing = { System.err.println(msg); exit(1) }
private def exit(code: Int): Nothing = System.exit(code).asInstanceOf[Nothing]
}

View File

@ -0,0 +1,547 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.scriptedtest;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import xsbti.AppConfiguration;
import xsbti.AppMain;
import xsbti.AppProvider;
import xsbti.ApplicationID;
import xsbti.ComponentProvider;
import xsbti.CrossValue;
import xsbti.GlobalLock;
import xsbti.Launcher;
import xsbti.Predefined;
import xsbti.PredefinedRepository;
import xsbti.Repository;
import xsbti.ScalaProvider;
public class ScriptedLauncher {
private static URL URLForClass(final Class<?> clazz)
throws MalformedURLException, ClassNotFoundException {
final String path = clazz.getCanonicalName().replace('.', '/') + ".class";
final URL url = clazz.getClassLoader().getResource(path);
if (url == null) throw new ClassNotFoundException(clazz.getCanonicalName());
return new URL(url.toString().replaceAll(path + "$", ""));
}
public static Optional<Integer> launch(
final File scalaHome,
final String sbtVersion,
final String scalaVersion,
final File bootDirectory,
final File baseDir,
final File[] classpath,
String[] arguments)
throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, IOException {
String[] args = arguments;
Object appID = null;
if (System.getProperty("sbt.launch.jar") == null) {
final ClassLoader previous = Thread.currentThread().getContextClassLoader();
final URL configURL = URLForClass(xsbti.AppConfiguration.class);
final URL mainURL = URLForClass(sbt.xMain.class);
final URL scriptedURL = URLForClass(ScriptedLauncher.class);
final ClassLoader topLoader = new URLClassLoader(new URL[] {configURL}, top());
final URLClassLoader loader = new URLClassLoader(new URL[] {mainURL, scriptedURL}, topLoader);
try {
while (true) {
final Class<?> clazz = loader.loadClass("sbt.internal.scriptedtest.ScriptedLauncher");
final Class<?> reboot = loader.loadClass("xsbti.Reboot");
final Class<?> exit = loader.loadClass("xsbti.Exit");
Method method =
clazz.getDeclaredMethod(
"getConf",
ClassLoader.class,
File.class,
String.class,
String.class,
File.class,
File.class,
File[].class,
String[].class,
loader.loadClass("xsbti.ApplicationID"));
Thread.currentThread().setContextClassLoader(loader);
try {
final Object conf =
method.invoke(
null,
topLoader,
scalaHome,
sbtVersion,
scalaVersion,
bootDirectory,
baseDir,
classpath,
args,
appID);
final Object launchResult =
clazz
.getDeclaredMethod(
"launchImpl", ClassLoader.class, ClassLoader.class, Object.class)
.invoke(null, topLoader, loader, conf);
if (reboot.isAssignableFrom(launchResult.getClass())) {
final Object a = reboot.getDeclaredMethod("arguments").invoke(launchResult);
final int length = Array.getLength(a);
args = new String[length];
for (int j = 0; j < length; ++j) {
args[j] = (String) Array.get(a, j);
}
appID = reboot.getDeclaredMethod("app").invoke(launchResult);
} else if (exit.isAssignableFrom(launchResult.getClass())) {
return Optional.of((Integer) exit.getDeclaredMethod("code").invoke(launchResult));
}
} catch (final InvocationTargetException e) {
Throwable t = e.getCause();
while (t != null && !t.getClass().getCanonicalName().equals("xsbti.FullReload"))
t = t.getCause();
final RuntimeException reload = t == null ? null : (RuntimeException) t;
if (reload != null) {
final boolean clean =
(boolean) reload.getClass().getDeclaredMethod("clean").invoke(reload);
if (clean) deleteRecursive(bootDirectory);
final Object reloadArgs =
reload.getClass().getDeclaredMethod("arguments").invoke(reload);
throw new xsbti.FullReload((String[]) reloadArgs, true);
}
if (e.getCause() instanceof RuntimeException) throw (RuntimeException) e.getCause();
throw new RuntimeException(e.getCause());
}
}
} finally {
swap(loader, previous);
}
} else {
final URL url = new URL("file:" + System.getProperty("sbt.launch.jar"));
final URLClassLoader loader = new URLClassLoader(new URL[] {url}, top());
final Class<?> boot = loader.loadClass("xsbt.boot.Boot");
// If we don't initialize the arguments this way, then the call to invoke on
// xsbt.boot.Boot.main fails with an IllegalArgumentException
final Object newArgs = Array.newInstance(loader.loadClass("java.lang.String"), args.length);
for (int i = 0; i < args.length; ++i) ((String[]) newArgs)[i] = args[i];
final ClassLoader previous = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(loader);
boot.getDeclaredMethod("main", newArgs.getClass()).invoke(null, newArgs);
return Optional.empty();
} finally {
swap(loader, previous);
}
}
}
private static ClassLoader top() {
ClassLoader result = ClassLoader.getSystemClassLoader();
while (result.getParent() != null) result = result.getParent();
return result;
}
private static void swap(final URLClassLoader old, final ClassLoader stashed) {
try {
old.close();
} catch (final Exception e) {
}
Thread.currentThread().setContextClassLoader(stashed);
}
private static boolean copy(final File[] files, final File toDirectory) throws IOException {
boolean result = true;
for (final File file : files) {
try {
Files.createDirectories(toDirectory.toPath());
Files.copy(file.toPath(), toDirectory.toPath().resolve(file.getName()));
} catch (final FileAlreadyExistsException e) {
result = false;
}
}
return result;
}
@SuppressWarnings("unused")
public static Object launchImpl(
final ClassLoader topLoader, final ClassLoader loader, final Object conf)
throws ClassNotFoundException, InvocationTargetException, IllegalAccessException,
NoSuchMethodException, InstantiationException {
final Class<?> clazz = loader.loadClass("sbt.xMain");
final Object instance = clazz.getConstructor().newInstance();
final Method run = clazz.getDeclaredMethod("run", loader.loadClass("xsbti.AppConfiguration"));
return run.invoke(instance, conf);
}
@SuppressWarnings("unused")
public static AppConfiguration getConf(
final ClassLoader topLoader,
final File scalaHome,
final String sbtVersion,
final String scalaVersion,
final File bootDirectory,
final File baseDir,
final File[] classpath,
String[] args,
final ApplicationID appID) {
final File libDir = new File(scalaHome, "lib");
final AtomicReference<File[]> classpathExtra = new AtomicReference<>(new File[0]);
final ApplicationID id =
appID != null
? appID
: new ApplicationID() {
@Override
public String groupID() {
return "org.scala-sbt";
}
@Override
public String name() {
return "sbt";
}
@Override
public String version() {
return sbtVersion;
}
@Override
public String mainClass() {
return "sbt.xMain";
}
@Override
public String[] mainComponents() {
return new String[] {"xsbti", "extra"};
}
@Deprecated
@Override
public boolean crossVersioned() {
return false;
}
@Override
public CrossValue crossVersionedValue() {
return CrossValue.Disabled;
}
@Override
public File[] classpathExtra() {
return classpathExtra.get();
}
};
final File appHome =
scalaHome.toPath().resolve(id.groupID()).resolve(id.name()).resolve(id.version()).toFile();
final ComponentProvider provider =
new ComponentProvider() {
@Override
public File componentLocation(String id) {
return new File(appHome, id);
}
@Override
public File[] component(String componentID) {
final File dir = componentLocation(componentID);
final File[] files = dir.listFiles(File::isFile);
return files == null ? new File[0] : files;
}
@Override
public void defineComponent(String componentID, File[] components) {
final File dir = componentLocation(componentID);
if (dir.exists()) {
final StringBuilder files = new StringBuilder();
for (final File file : components) {
if (files.length() > 0) {
files.append(',');
}
files.append(file.toString());
}
throw new RuntimeException(
"Cannot redefine component. ID: " + id + ", files: " + files);
} else {
try {
copy(components, dir);
} catch (final IOException e) {
e.printStackTrace(System.err);
}
}
}
@Override
public boolean addToComponent(String componentID, File[] components) {
try {
boolean result = copy(components, componentLocation(componentID));
final File[] extra = componentLocation(componentID).listFiles();
classpathExtra.set(extra == null ? new File[0] : extra);
return result;
} catch (final IOException e) {
return true;
}
}
@Override
public File lockFile() {
return new File(appHome, "sbt.components.lock");
}
};
assert (libDir.exists());
final File[] jars = libDir.listFiles(f -> f.isFile() && f.getName().endsWith(".jar"));
final URL[] urls = new URL[jars.length];
for (int i = 0; i < jars.length; ++i) {
try {
urls[i] = jars[i].toURI().toURL();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
return new AppConfiguration() {
@Override
public String[] arguments() {
return args;
}
@Override
public File baseDirectory() {
return baseDir;
}
@Override
public AppProvider provider() {
return new AppProvider() {
final AppProvider self = this;
final ScalaProvider scalaProvider =
new ScalaProvider() {
private final ScalaProvider sp = this;
private final String scalaOrg = "org.scala-lang";
private final Repository[] repos =
new PredefinedRepository[] {
() -> Predefined.Local, () -> Predefined.MavenCentral
};
private final Launcher launcher =
new Launcher() {
@Override
public ScalaProvider getScala(String version) {
return getScala(version, "");
}
@Override
public ScalaProvider getScala(String version, String reason) {
return getScala(version, reason, scalaOrg);
}
@Override
public ScalaProvider getScala(
String version, String reason, String scalaOrg) {
return sp;
}
@Override
public AppProvider app(ApplicationID id, String version) {
return self;
}
@Override
public ClassLoader topLoader() {
return topLoader;
}
class foo extends Throwable {
foo(final Exception e) {
super(e.getMessage(), null, true, false);
}
}
@Override
public GlobalLock globalLock() {
return new GlobalLock() {
@Override
public <T> T apply(File lockFile, Callable<T> run) {
try {
return run.call();
} catch (final Exception e) {
throw new RuntimeException(new foo(e)) {
@Override
public StackTraceElement[] getStackTrace() {
return new StackTraceElement[0];
}
};
}
}
};
}
@Override
public File bootDirectory() {
return bootDirectory;
}
@Override
public Repository[] ivyRepositories() {
return repos;
}
@Override
public Repository[] appRepositories() {
return repos;
}
@Override
public boolean isOverrideRepositories() {
return false;
}
@Override
public File ivyHome() {
final String home = System.getProperty("sbt.ivy.home");
return home == null
? new File(System.getProperty("user.home"), ".ivy2")
: new File(home);
}
@Override
public String[] checksums() {
return new String[] {"sha1", "md5"};
}
};
@Override
public Launcher launcher() {
return launcher;
}
@Override
public String version() {
return scalaVersion;
}
@Override
public ClassLoader loader() {
return new URLClassLoader(urls, topLoader);
}
@Override
public File[] jars() {
return jars;
}
@Deprecated
@Override
public File libraryJar() {
return new File(libDir, "scala-library.jar");
}
@Deprecated
@Override
public File compilerJar() {
return new File(libDir, "scala-compiler.jar");
}
@Override
public AppProvider app(ApplicationID id) {
return self;
}
};
@Override
public ScalaProvider scalaProvider() {
return scalaProvider;
}
@Override
public ApplicationID id() {
return id;
}
@Override
public ClassLoader loader() {
return new URLClassLoader(urls, topLoader);
}
@Deprecated
@Override
public Class<? extends AppMain> mainClass() {
return AppMain.class;
}
@Override
public Class<?> entryPoint() {
return AppMain.class;
}
@Override
public AppMain newMain() {
try {
return (AppMain) loader().loadClass("sbt.xMain").getConstructor().newInstance();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@Override
public File[] mainClasspath() {
return classpath;
}
@Override
public ComponentProvider components() {
return provider;
}
};
}
};
}
private static void deleteRecursive(final File directory) {
try {
Files.walkFileTree(
directory.toPath(),
new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (attrs.isRegularFile()) Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (final IOException e) {
}
}
}

View File

@ -159,8 +159,20 @@ object TestServer {
def withTestServer(testBuild: String, baseDirectory: File)(
f: TestServer => Future[Assertion]
)(implicit td: TestData): Future[Assertion] = {
val classpath = td.configMap.get("sbt.server.classpath") match {
case Some(s: String) => s.split(java.io.File.pathSeparator).map(file)
case _ => throw new IllegalStateException("No server classpath was specified.")
}
val sbtVersion = td.configMap.get("sbt.server.version") match {
case Some(v: String) => v
case _ => throw new IllegalStateException("No server version was specified.")
}
val scalaVersion = td.configMap.get("sbt.server.scala.version") match {
case Some(v: String) => v
case _ => throw new IllegalStateException("No server scala version was specified.")
}
// Each test server instance will be executed in a Thread pool separated from the tests
val testServer = TestServer(baseDirectory)
val testServer = TestServer(baseDirectory, scalaVersion, sbtVersion, classpath)
// checking last log message after initialization
// if something goes wrong here the communication streams are corrupted, restarting
val init =
@ -194,7 +206,12 @@ object TestServer {
}
}
case class TestServer(baseDirectory: File) {
case class TestServer(
baseDirectory: File,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File]
) {
import TestServer.hostLog
val readBuffer = new Array[Byte](40960)
@ -204,7 +221,7 @@ case class TestServer(baseDirectory: File) {
private val RetByte = '\r'.toByte
hostLog("fork to a new sbt instance")
val process = RunFromSourceMain.fork(baseDirectory)
val process = RunFromSourceMain.fork(baseDirectory, scalaVersion, sbtVersion, classpath)
lazy val portfile = baseDirectory / "project" / "target" / "active.json"

View File

@ -8,55 +8,28 @@
package sbt
package scriptedtest
import java.io.File
import xsbt.IPC
import scala.sys.process.{ BasicIO, Process }
import sbt.io.IO
import sbt.util.Logger
import xsbt.IPC
private[sbt] sealed trait RemoteSbtCreatorKind
private[sbt] object RemoteSbtCreatorKind {
case object LauncherBased extends RemoteSbtCreatorKind
case object RunFromSourceBased extends RemoteSbtCreatorKind
}
abstract class RemoteSbtCreator private[sbt] {
def newRemote(server: IPC.Server): Process
}
final class LauncherBasedRemoteSbtCreator(
directory: File,
launcher: File,
log: Logger,
launchOpts: Seq[String] = Nil,
) extends RemoteSbtCreator {
def newRemote(server: IPC.Server) = {
val launcherJar = launcher.getAbsolutePath
val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath
val args = List("<" + server.port)
val cmd = "java" :: launchOpts.toList ::: globalBase :: "-jar" :: launcherJar :: args ::: Nil
val io = BasicIO(false, log).withInput(_.close())
val p = Process(cmd, directory) run (io)
val thread = new Thread() { override def run() = { p.exitValue(); server.close() } }
thread.start()
p
}
}
final class RunFromSourceBasedRemoteSbtCreator(
directory: File,
log: Logger,
launchOpts: Seq[String] = Nil,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File],
) extends RemoteSbtCreator {
def newRemote(server: IPC.Server) = {
val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath
val cp = IO readLinesURL (getClass getResource "/RunFromSource.classpath")
val cpString = cp mkString File.pathSeparator
def newRemote(server: IPC.Server): Process = {
val globalBase = "-Dsbt.global.base=" + new File(directory, "global").getAbsolutePath
val mainClassName = "sbt.RunFromSourceMain"
val args = List(mainClassName, directory.toString, "<" + server.port)
val cpString = classpath.mkString(java.io.File.pathSeparator)
val args =
List(mainClassName, directory.toString, scalaVersion, sbtVersion, cpString, "<" + server.port)
val cmd = "java" :: launchOpts.toList ::: globalBase :: "-cp" :: cpString :: args ::: Nil
val io = BasicIO(false, log).withInput(_.close())
val p = Process(cmd, directory) run (io)

View File

@ -8,7 +8,7 @@
package sbt
package scriptedtest
import java.io.{ File, FileNotFoundException, IOException }
import java.io.{ FileNotFoundException, IOException }
import java.net.SocketException
import java.nio.file.Files
import java.util.Properties
@ -16,44 +16,33 @@ import java.util.concurrent.ForkJoinPool
import sbt.internal.io.Resources
import sbt.internal.scripted._
import sbt.internal.util.{ BufferedLogger, ConsoleOut, FullLogger, Util }
import sbt.io.FileFilter._
import sbt.io.syntax._
import sbt.io.{ DirectoryFilter, HiddenFileFilter, IO }
import sbt.nio.file._
import sbt.nio.file.syntax._
import sbt.util.{ AbstractLogger, Level, Logger }
import scala.collection.{ GenSeq, mutable }
import scala.collection.parallel.ForkJoinTaskSupport
import scala.collection.{ GenSeq, mutable }
import scala.util.Try
import scala.util.control.NonFatal
final class ScriptedTests(
resourceBaseDirectory: File,
bufferLog: Boolean,
launcher: File,
launchOpts: Seq[String],
) {
import ScriptedTests.{ TestRunner, emptyCallback }
import ScriptedTests.TestRunner
private val testResources = new Resources(resourceBaseDirectory)
val ScriptFilename = "test"
val PendingScriptFilename = "pending"
def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[TestRunner] =
scriptedTest(group, name, Logger.xlog2Log(log))
def scriptedTest(group: String, name: String, log: Logger): Seq[TestRunner] =
singleScriptedTest(group, name, emptyCallback, log)
/** Returns a sequence of test runners that have to be applied in the call site. */
def singleScriptedTest(
group: String,
name: String,
prescripted: File => Unit,
log: Logger,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File]
): Seq[TestRunner] = {
// Test group and names may be file filters (like '*')
@ -71,7 +60,7 @@ final class ScriptedTests(
val buffer = new BufferedLogger(new FullLogger(log))
val singleTestRunner = () => {
val handlers =
createScriptedHandlers(testDirectory, buffer, RemoteSbtCreatorKind.LauncherBased)
createScriptedHandlers(testDirectory, buffer, scalaVersion, sbtVersion, classpath)
val runner = new BatchScriptRunner
val states = new mutable.HashMap[StatementHandler, StatementHandler#State]()
try commonRunTest(label, testDirectory, prescripted, handlers, runner, states, buffer)
@ -87,15 +76,20 @@ final class ScriptedTests(
private def createScriptedHandlers(
testDir: File,
buffered: Logger,
remoteSbtCreatorKind: RemoteSbtCreatorKind,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File]
): Map[Char, StatementHandler] = {
val fileHandler = new FileCommands(testDir)
val remoteSbtCreator = remoteSbtCreatorKind match {
case RemoteSbtCreatorKind.LauncherBased =>
new LauncherBasedRemoteSbtCreator(testDir, launcher, buffered, launchOpts)
case RemoteSbtCreatorKind.RunFromSourceBased =>
new RunFromSourceBasedRemoteSbtCreator(testDir, buffered, launchOpts)
}
val remoteSbtCreator =
new RunFromSourceBasedRemoteSbtCreator(
testDir,
buffered,
launchOpts,
scalaVersion,
sbtVersion,
classpath
)
val sbtHandler = new SbtHandler(remoteSbtCreator)
Map('$' -> fileHandler, '>' -> sbtHandler, '#' -> CommentHandler)
}
@ -105,7 +99,10 @@ final class ScriptedTests(
testGroupAndNames: Seq[(String, String)],
prescripted: File => Unit,
sbtInstances: Int,
log: Logger
log: Logger,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File]
): Seq[TestRunner] = {
// Test group and names may be file filters (like '*')
val groupAndNameDirs = {
@ -134,15 +131,7 @@ final class ScriptedTests(
case s => s
}
val (launcherBasedTestsUnfiltered, runFromSourceBasedTestsUnfiltered) =
labelsAndDirs.partition {
case (testName, _) =>
determineRemoteSbtCreatorKind(testName) match {
case RemoteSbtCreatorKind.LauncherBased => true
case RemoteSbtCreatorKind.RunFromSourceBased => false
}
}
val launcherBasedTests = launcherBasedTestsUnfiltered.filterNot(windowsExclude)
val runFromSourceBasedTestsUnfiltered = labelsAndDirs
val runFromSourceBasedTests = runFromSourceBasedTestsUnfiltered.filterNot(windowsExclude)
def logTests(size: Int, how: String) =
@ -150,24 +139,19 @@ final class ScriptedTests(
f"Running $size / $totalSize (${size * 100d / totalSize}%3.2f%%) scripted tests with $how"
)
logTests(runFromSourceBasedTests.size, "RunFromSourceMain")
logTests(launcherBasedTests.size, "sbt/launcher")
def createTestRunners(
tests: Seq[TestInfo],
remoteSbtCreatorKind: RemoteSbtCreatorKind,
): Seq[TestRunner] = {
def createTestRunners(tests: Seq[TestInfo]): Seq[TestRunner] = {
tests
.grouped(batchSize)
.map { batch => () =>
IO.withTemporaryDirectory {
runBatchedTests(batch, _, prescripted, remoteSbtCreatorKind, log)
runBatchedTests(batch, _, prescripted, log, scalaVersion, sbtVersion, classpath)
}
}
.toList
}
createTestRunners(runFromSourceBasedTests, RemoteSbtCreatorKind.RunFromSourceBased) ++
createTestRunners(launcherBasedTests, RemoteSbtCreatorKind.LauncherBased)
createTestRunners(runFromSourceBasedTests)
}
}
@ -183,81 +167,6 @@ final class ScriptedTests(
}
}
else _ => false
private def determineRemoteSbtCreatorKind(testName: (String, String)): RemoteSbtCreatorKind = {
import RemoteSbtCreatorKind._
val (group, name) = testName
s"$group/$name" match {
case "actions/add-alias" => LauncherBased // sbt/Package$
case "actions/cross-incremental" => LauncherBased // tbd
case "actions/cross-multiproject" => LauncherBased // tbd
case "actions/cross-multi-parser" =>
LauncherBased // java.lang.ClassNotFoundException: javax.tools.DiagnosticListener when run with java 11 and an old sbt launcher
case "actions/multi-command" =>
LauncherBased // java.lang.ClassNotFoundException: javax.tools.DiagnosticListener when run with java 11 and an old sbt launcher
case "actions/cross-test-only" => LauncherBased // tbd
case "actions/external-doc" => LauncherBased // sbt/Package$
case "actions/input-task" => LauncherBased // sbt/Package$
case "actions/input-task-dyn" => LauncherBased // sbt/Package$
case gn if gn.startsWith("classloader-cache/") =>
LauncherBased // This should be tested using launcher
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$
case gn if gn.startsWith("dependency-management/") => LauncherBased // sbt/Package$
case gn if gn.startsWith("plugins/") => LauncherBased // sbt/Package$
case "java/argfile" => LauncherBased // sbt/Package$
case "java/cross" => LauncherBased // sbt/Package$
case "java/basic" => LauncherBased // sbt/Package$
case "java/varargs-main" => LauncherBased // sbt/Package$
case "package/lazy-name" => LauncherBased // sbt/Package$
case "package/manifest" => LauncherBased // sbt/Package$
case "package/mappings" => LauncherBased // sbt/Package$
case "package/resources" => LauncherBased // sbt/Package$
case "project/Class.forName" => LauncherBased // sbt/Package$
case "project/binary-plugin" => LauncherBased // sbt/Package$
case "project/default-settings" => LauncherBased // sbt/Package$
case "project/extra" => LauncherBased // tbd
case "project/flatten" => LauncherBased // sbt/Package$
case "project/generated-root-no-publish" => LauncherBased // tbd
case "project/giter8-plugin" => LauncherBased // tbd
case "project/lib" => LauncherBased // sbt/Package$
case "project/scripted-plugin" => LauncherBased // tbd
case "project/scripted-skip-incompatible" => LauncherBased // sbt/Package$
case "project/session-update-from-cmd" => LauncherBased // tbd
case "project/transitive-plugins" => LauncherBased // tbd
case "run/awt" => LauncherBased // sbt/Package$
case "run/classpath" => LauncherBased // sbt/Package$
case "run/daemon" => LauncherBased // sbt/Package$
case "run/daemon-exit" => LauncherBased // sbt/Package$
case "run/error" => LauncherBased // sbt/Package$
case "run/fork" => LauncherBased // sbt/Package$
case "run/fork-loader" => LauncherBased // sbt/Package$
case "run/non-local-main" => LauncherBased // sbt/Package$
case "run/spawn" => LauncherBased // sbt/Package$
case "run/spawn-exit" => LauncherBased // sbt/Package$
case "source-dependencies/binary" => LauncherBased // sbt/Package$
case "source-dependencies/export-jars" => LauncherBased // sbt/Package$
case "source-dependencies/implicit-search" => LauncherBased // sbt/Package$
case "source-dependencies/java-basic" => LauncherBased // sbt/Package$
case "source-dependencies/less-inter-inv" => LauncherBased // sbt/Package$
case "source-dependencies/less-inter-inv-java" => LauncherBased // sbt/Package$
case "source-dependencies/linearization" => LauncherBased // sbt/Package$
case "source-dependencies/named" => LauncherBased // sbt/Package$
case "source-dependencies/specialized" => LauncherBased // sbt/Package$
case gn if gn.startsWith("watch/") && Util.isWindows =>
LauncherBased // there is an issue with jansi and coursier
case "watch/commands" =>
LauncherBased // java.lang.ClassNotFoundException: javax.tools.DiagnosticListener when run with java 11 and an old sbt launcher
case "watch/managed" => LauncherBased // sbt/Package$
case "tests/scalatest" => LauncherBased
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$)
// Typically from Compile / packageBin / packageOptions
}
/** Defines an auto plugin that is injected to sbt between every scripted session.
*
@ -314,13 +223,15 @@ final class ScriptedTests(
groupedTests: Seq[((String, String), File)],
tempTestDir: File,
preHook: File => Unit,
remoteSbtCreatorKind: RemoteSbtCreatorKind,
log: Logger,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File]
): Seq[Option[String]] = {
val runner = new BatchScriptRunner
val buffer = new BufferedLogger(new FullLogger(log))
val handlers = createScriptedHandlers(tempTestDir, buffer, remoteSbtCreatorKind)
val handlers = createScriptedHandlers(tempTestDir, buffer, scalaVersion, sbtVersion, classpath)
val states = new BatchScriptRunner.States
val seqHandlers = handlers.values.toList
runner.initStates(states, seqHandlers)
@ -362,7 +273,7 @@ final class ScriptedTests(
// Run the test and delete files (except global that holds local scala jars)
val result = runOrHandleDisabled(label, tempTestDir, runTest, buffer)
val view = FileTreeView.default
val view = sbt.nio.file.FileTreeView.default
val base = tempTestDir.getCanonicalFile.toGlob
val global = base / "global"
val globalLogging = base / ** / "global-logging"
@ -423,7 +334,7 @@ final class ScriptedTests(
}
}
val pendingMark = if (pending) PendingLabel else ""
val pendingMark: String = if (pending) PendingLabel else ""
def testFailed(t: Throwable): Option[String] = {
if (pending) log.clear() else log.stop()
@ -462,18 +373,28 @@ object ScriptedTests extends ScriptedRunner {
/** Represents the function that runs the scripted tests, both in single or batch mode. */
type TestRunner = () => Seq[Option[String]]
val emptyCallback: File => Unit = _ => ()
def main(args: Array[String]): Unit = {
val directory = new File(args(0))
val buffer = args(1).toBoolean
// val sbtVersion = args(2)
// val defScalaVersion = args(3)
val sbtVersion = args(2)
val defScalaVersion = args(3)
// val buildScalaVersions = args(4)
val bootProperties = new File(args(5))
//val bootProperties = new File(args(5))
val tests = args.drop(6)
val logger = TestConsoleLogger()
run(directory, buffer, tests, logger, bootProperties, Array(), emptyCallback)
val cp = System.getProperty("java.class.path", "").split(java.io.File.pathSeparator).map(file)
runInParallel(
directory,
buffer,
tests,
logger,
Array(),
new java.util.ArrayList[File],
defScalaVersion,
sbtVersion,
cp.toSeq,
1
)
}
}
@ -482,102 +403,62 @@ object ScriptedTests extends ScriptedRunner {
class ScriptedRunner {
// This is called by project/Scripted.scala
// Using java.util.List[File] to encode File => Unit
def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
bootProperties: File,
launchOpts: Array[String],
prescripted: java.util.List[File],
): Unit = {
val logger = new TestConsoleLogger()
val addTestFile = (f: File) => { prescripted.add(f); () }
run(resourceBaseDirectory, bufferLog, tests, logger, bootProperties, launchOpts, addTestFile)
//new FullLogger(Logger.xlog2Log(log)))
}
// This is called by sbt-scripted 0.13.x and 1.x (see https://github.com/sbt/sbt/issues/3245)
def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
bootProperties: File,
launchOpts: Array[String],
): Unit = {
val logger = TestConsoleLogger()
val prescripted = ScriptedTests.emptyCallback
run(resourceBaseDirectory, bufferLog, tests, logger, bootProperties, launchOpts, prescripted)
}
def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
logger: AbstractLogger,
bootProperties: File,
launchOpts: Array[String],
prescripted: File => Unit,
): Unit = {
// Force Log4J to not use a thread context classloader otherwise it throws a CCE
sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true"
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts)
val sbtVersion = bootProperties.getName.dropWhile(!_.isDigit).dropRight(".jar".length)
val accept = isTestCompatible(resourceBaseDirectory, sbtVersion) _
val allTests = get(tests, resourceBaseDirectory, accept, logger) flatMap {
case ScriptedTest(group, name) =>
runner.singleScriptedTest(group, name, prescripted, logger)
}
runAll(allTests)
}
def runInParallel(
baseDir: File,
bufferLog: Boolean,
tests: Array[String],
bootProps: File,
launchOpts: Array[String],
prescripted: java.util.List[File],
): Unit = {
runInParallel(baseDir, bufferLog, tests, bootProps, launchOpts, prescripted, 1)
}
// This is used by sbt-scripted sbt 1.x
def runInParallel(
baseDir: File,
bufferLog: Boolean,
tests: Array[String],
bootProps: File,
logger: Logger,
launchOpts: Array[String],
prescripted: java.util.List[File],
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File],
instances: Int
): Unit = {
val logger = TestConsoleLogger()
val addTestFile = (f: File) => { prescripted.add(f); () }
runInParallel(baseDir, bufferLog, tests, logger, bootProps, launchOpts, addTestFile, instances)
}
def runInParallel(
baseDir: File,
bufferLog: Boolean,
tests: Array[String],
logger: AbstractLogger,
bootProps: File,
launchOpts: Array[String],
prescripted: File => Unit,
instances: Int
): Unit = {
val runner = new ScriptedTests(baseDir, bufferLog, bootProps, launchOpts)
val sbtVersion = bootProps.getName.dropWhile(!_.isDigit).dropRight(".jar".length)
val runner = new ScriptedTests(baseDir, bufferLog, launchOpts)
val accept = isTestCompatible(baseDir, sbtVersion) _
// The scripted tests mapped to the inputs that the user wrote after `scripted`.
val scriptedTests =
get(tests, baseDir, accept, logger).map(st => (st.group, st.name))
val scriptedRunners = runner.batchScriptedRunner(scriptedTests, prescripted, instances, logger)
val scriptedRunners =
runner.batchScriptedRunner(
scriptedTests,
addTestFile,
instances,
logger,
scalaVersion,
sbtVersion,
classpath
)
val parallelRunners = scriptedRunners.toParArray
parallelRunners.tasksupport = new ForkJoinTaskSupport(new ForkJoinPool(instances))
runAll(parallelRunners)
}
def runInParallel(
baseDir: File,
bufferLog: Boolean,
tests: Array[String],
launchOpts: Array[String],
prescripted: java.util.List[File],
sbtVersion: String,
scalaVersion: String,
classpath: Seq[File],
instances: Int
): Unit =
runInParallel(
baseDir,
bufferLog,
tests,
TestConsoleLogger(),
launchOpts,
prescripted,
sbtVersion,
scalaVersion,
classpath,
instances
)
private def reportErrors(errors: GenSeq[String]): Unit =
if (errors.nonEmpty) sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n")) else ()

View File

@ -23,7 +23,10 @@ import org.scalatest._
*
* This is a very good example on how to instantiate the compiler bridge provider.
*/
abstract class IvyBridgeProviderSpecification extends FlatSpec with Matchers {
abstract class IvyBridgeProviderSpecification
extends fixture.FlatSpec
with fixture.TestDataFixture
with Matchers {
def currentBase: File = new File(".")
def currentTarget: File = currentBase / "target" / "ivyhome"
def currentManaged: File = currentBase / "target" / "lib_managed"
@ -50,13 +53,21 @@ abstract class IvyBridgeProviderSpecification extends FlatSpec with Matchers {
ZincComponentCompiler.interfaceProvider(bridge, manager, dependencyResolution, currentManaged)
}
def getCompilerBridge(targetDir: File, log: Logger, scalaVersion: String): File = {
def getCompilerBridge(
targetDir: File,
log: Logger,
scalaVersion: String,
)(implicit td: TestData): File = {
val zincVersion = td.configMap.get("sbt.zinc.version") match {
case Some(v: String) => v
case _ => throw new IllegalStateException("No zinc version specified")
}
val bridge0 = ZincComponentCompiler.getDefaultBridgeModule(scalaVersion)
// redefine the compiler bridge version
// using the version of zinc used during testing
// this way when building with zinc as a source dependency
// these specs don't go looking for some SHA-suffixed compiler bridge
val bridge1 = bridge0.withRevision(ZincLmIntegrationBuildInfo.zincVersion)
val bridge1 = bridge0.withRevision(zincVersion)
val provider = getZincProvider(bridge1, targetDir, log)
val scalaInstance = provider.fetchScalaInstance(scalaVersion, log)
val bridge = provider.fetchCompiledBridge(scalaInstance, log)

View File

@ -25,31 +25,31 @@ class ZincComponentCompilerSpec extends IvyBridgeProviderSpecification {
val logger = ConsoleLogger()
it should "compile the bridge for Scala 2.10.5 and 2.10.6" in {
it should "compile the bridge for Scala 2.10.5 and 2.10.6" in { implicit td =>
if (isJava8) {
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2105) should exist)
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2106) should exist)
} else ()
}
it should "compile the bridge for Scala 2.11.8 and 2.11.11" in {
it should "compile the bridge for Scala 2.11.8 and 2.11.11" in { implicit td =>
if (isJava8) {
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2118) should exist)
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala21111) should exist)
} else ()
}
it should "compile the bridge for Scala 2.12.2" in {
it should "compile the bridge for Scala 2.12.2" in { implicit td =>
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2121) should exist)
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2122) should exist)
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2123) should exist)
}
it should "compile the bridge for Scala 2.13.0-M2" in {
it should "compile the bridge for Scala 2.13.0-M2" in { implicit td =>
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2130M2) should exist)
}
it should "compile the bridge for Scala 2.13.0-RC1" in {
it should "compile the bridge for Scala 2.13.0-RC1" in { implicit td =>
IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2130RC1) should exist)
}
}