mirror of https://github.com/sbt/sbt.git
[2.x] feat: Add scriptedKeepTempDirectory setting (#8621)
**Problem** When running scripted tests to debug sbt plugins, the temporary directories (`/private/var/folder/...`) are automatically deleted after tests complete. This makes it difficult to inspect the test state for debugging purposes, requiring workarounds like adding `$ pause` commands and manually copying directories. **Solution** Added a new `scriptedKeepTempDirectory` setting that allows users to preserve temporary directories after scripted tests complete. When enabled, the temporary directory path is logged so users can inspect it. Usage: ```scala scriptedKeepTempDirectory := true ```
This commit is contained in:
parent
64dadd6459
commit
f8704752e0
17
build.sbt
17
build.sbt
|
|
@ -507,8 +507,10 @@ lazy val scriptedSbtProj = (project in file("scripted-sbt"))
|
|||
name := "scripted-sbt",
|
||||
libraryDependencies ++= Seq(launcherInterface % "provided"),
|
||||
mimaSettings,
|
||||
scriptedSbtMimaSettings,
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Seq(
|
||||
exclude[IncompatibleMethTypeProblem]("sbt.scriptedtest.ScriptedTests.runInParallel"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.scriptedtest.ScriptedTests.batchScriptedRunner"),
|
||||
),
|
||||
)
|
||||
.dependsOn(lmCore)
|
||||
.configure(addSbtIO, addSbtCompilerInterface)
|
||||
|
|
@ -722,6 +724,13 @@ lazy val mainProj = (project in file("main"))
|
|||
mimaBinaryIssueFilters ++= Vector(
|
||||
exclude[DirectMissingMethodProblem]("sbt.internal.ConsoleProject.*"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.coursierint.LMCoursier.coursierConfiguration"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.ScriptedRun.run"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.ScriptedRun.invoke"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.ScriptedRun.invoke"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.ScriptedRun#RunInParallelV1.invoke"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.ScriptedRun#RunInParallelV2.invoke"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.ScriptedRun#RunV1.invoke"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.ScriptedRun#RunV2.invoke"),
|
||||
),
|
||||
)
|
||||
.dependsOn(lmCore, lmIvy, lmCoursierShadedPublishing)
|
||||
|
|
@ -946,7 +955,8 @@ def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTa
|
|||
.map(_.data)
|
||||
.filterNot(_.getName.contains("scala-compiler")),
|
||||
(bundledLauncherProj / Compile / packageBin).value,
|
||||
streams.value.log
|
||||
streams.value.log,
|
||||
scriptedKeepTempDirectory.value
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1001,6 +1011,7 @@ lazy val nonRoots = allProjects.map(p => LocalProject(p.id))
|
|||
|
||||
ThisBuild / scriptedBufferLog := true
|
||||
ThisBuild / scriptedPrescripted := { _ => }
|
||||
ThisBuild / scriptedKeepTempDirectory := false
|
||||
|
||||
def otherRootSettings =
|
||||
Seq(
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ object ScriptedPlugin extends AutoPlugin {
|
|||
val scriptedRun = taskKey[ScriptedRun]("")
|
||||
val scriptedLaunchOpts =
|
||||
settingKey[Seq[String]]("options to pass to jvm launching scripted tasks")
|
||||
val scriptedKeepTempDirectory =
|
||||
settingKey[Boolean](
|
||||
"If true, keeps the temporary directory after scripted tests complete for debugging."
|
||||
)
|
||||
val scriptedDependencies = taskKey[Unit]("")
|
||||
val scripted = inputKey[Unit]("")
|
||||
}
|
||||
|
|
@ -54,6 +58,7 @@ object ScriptedPlugin extends AutoPlugin {
|
|||
override lazy val globalSettings: Seq[Setting[?]] = Seq(
|
||||
scriptedBufferLog := true,
|
||||
scriptedLaunchOpts := Seq(),
|
||||
scriptedKeepTempDirectory := false,
|
||||
)
|
||||
|
||||
override lazy val projectSettings: Seq[Setting[?]] = Seq(
|
||||
|
|
@ -177,7 +182,8 @@ object ScriptedPlugin extends AutoPlugin {
|
|||
Fork.javaCommand((scripted / javaHome).value, "java").getAbsolutePath,
|
||||
scriptedLaunchOpts.value,
|
||||
new java.util.ArrayList[File](),
|
||||
scriptedParallelInstances.value
|
||||
scriptedParallelInstances.value,
|
||||
scriptedKeepTempDirectory.value
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ sealed trait ScriptedRun {
|
|||
launchOpts: Seq[String],
|
||||
prescripted: java.util.List[File],
|
||||
instances: Int,
|
||||
keepTempDirectory: Boolean,
|
||||
): Unit = {
|
||||
try {
|
||||
invoke(
|
||||
|
|
@ -33,6 +34,7 @@ sealed trait ScriptedRun {
|
|||
launchOpts.toArray,
|
||||
prescripted,
|
||||
instances,
|
||||
keepTempDirectory,
|
||||
)
|
||||
()
|
||||
} catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
|
||||
|
|
@ -47,6 +49,7 @@ sealed trait ScriptedRun {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
instances: java.lang.Integer,
|
||||
keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef
|
||||
|
||||
}
|
||||
|
|
@ -64,26 +67,45 @@ object ScriptedRun {
|
|||
val clazz = scriptedTests.getClass
|
||||
if (batchExecution)
|
||||
try
|
||||
new RunInParallelV2(
|
||||
new RunInParallelV3(
|
||||
scriptedTests,
|
||||
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls)
|
||||
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls, bCls)
|
||||
)
|
||||
catch {
|
||||
case _: NoSuchMethodException =>
|
||||
new RunInParallelV1(
|
||||
scriptedTests,
|
||||
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)
|
||||
)
|
||||
try
|
||||
new RunInParallelV2(
|
||||
scriptedTests,
|
||||
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls)
|
||||
)
|
||||
catch {
|
||||
case _: NoSuchMethodException =>
|
||||
new RunInParallelV1(
|
||||
scriptedTests,
|
||||
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)
|
||||
)
|
||||
}
|
||||
}
|
||||
else
|
||||
try
|
||||
new RunV2(
|
||||
new RunV3(
|
||||
scriptedTests,
|
||||
clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls)
|
||||
clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, bCls)
|
||||
)
|
||||
catch {
|
||||
case _: NoSuchMethodException =>
|
||||
new RunV1(scriptedTests, clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls))
|
||||
try
|
||||
new RunV2(
|
||||
scriptedTests,
|
||||
clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls)
|
||||
)
|
||||
catch {
|
||||
case _: NoSuchMethodException =>
|
||||
new RunV1(
|
||||
scriptedTests,
|
||||
clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +119,7 @@ object ScriptedRun {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
@unused instances: java.lang.Integer,
|
||||
@unused keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef =
|
||||
run.invoke(
|
||||
scriptedTests,
|
||||
|
|
@ -119,6 +142,7 @@ object ScriptedRun {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
instances: Integer,
|
||||
@unused keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef =
|
||||
runInParallel.invoke(
|
||||
scriptedTests,
|
||||
|
|
@ -142,6 +166,7 @@ object ScriptedRun {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
@unused instances: java.lang.Integer,
|
||||
@unused keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef =
|
||||
run.invoke(
|
||||
scriptedTests,
|
||||
|
|
@ -165,6 +190,7 @@ object ScriptedRun {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
instances: Integer,
|
||||
@unused keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef =
|
||||
runInParallel.invoke(
|
||||
scriptedTests,
|
||||
|
|
@ -179,4 +205,55 @@ object ScriptedRun {
|
|||
)
|
||||
}
|
||||
|
||||
private class RunV3(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
|
||||
override protected def invoke(
|
||||
resourceBaseDirectory: File,
|
||||
bufferLog: java.lang.Boolean,
|
||||
tests: Array[String],
|
||||
launcherJar: File,
|
||||
javaCommand: String,
|
||||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
@unused instances: java.lang.Integer,
|
||||
keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef =
|
||||
run.invoke(
|
||||
scriptedTests,
|
||||
resourceBaseDirectory,
|
||||
bufferLog,
|
||||
tests,
|
||||
launcherJar,
|
||||
javaCommand,
|
||||
launchOpts,
|
||||
prescripted,
|
||||
keepTempDirectory,
|
||||
)
|
||||
}
|
||||
|
||||
private class RunInParallelV3(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
|
||||
override protected def invoke(
|
||||
resourceBaseDirectory: File,
|
||||
bufferLog: java.lang.Boolean,
|
||||
tests: Array[String],
|
||||
launcherJar: File,
|
||||
javaCommand: String,
|
||||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
instances: Integer,
|
||||
keepTempDirectory: java.lang.Boolean,
|
||||
): AnyRef =
|
||||
runInParallel.invoke(
|
||||
scriptedTests,
|
||||
resourceBaseDirectory,
|
||||
bufferLog,
|
||||
tests,
|
||||
launcherJar,
|
||||
javaCommand,
|
||||
launchOpts,
|
||||
prescripted,
|
||||
instances,
|
||||
keepTempDirectory,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ trait ScriptedKeys {
|
|||
)
|
||||
val scriptedSource = settingKey[File]("")
|
||||
val scriptedPrescripted = taskKey[File => Unit]("")
|
||||
val scriptedKeepTempDirectory = settingKey[Boolean](
|
||||
"If true, keeps the temporary directory after scripted tests complete for debugging."
|
||||
)
|
||||
}
|
||||
|
||||
object Scripted {
|
||||
|
|
@ -102,7 +105,8 @@ object Scripted {
|
|||
sbtVersion: String,
|
||||
classpath: Seq[File],
|
||||
launcherJar: File,
|
||||
logger: Logger
|
||||
logger: Logger,
|
||||
keepTempDirectory: Boolean
|
||||
): Unit = {
|
||||
logger.info(s"Tests selected: ${args.mkString("\n * ", "\n * ", "\n")}")
|
||||
logger.info("")
|
||||
|
|
@ -137,6 +141,7 @@ object Scripted {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
instance: Int,
|
||||
keepTempDirectory: Boolean
|
||||
): Unit
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +185,8 @@ object Scripted {
|
|||
"java",
|
||||
launchOpts.toArray,
|
||||
callback,
|
||||
instances
|
||||
instances,
|
||||
keepTempDirectory
|
||||
)
|
||||
} catch { case ite: InvocationTargetException => throw ite.getCause }
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,8 @@ final class ScriptedTests(
|
|||
prescripted: File => Unit,
|
||||
sbtInstances: Int,
|
||||
prop: RemoteSbtCreatorProp,
|
||||
log: Logger
|
||||
log: Logger,
|
||||
keepTempDirectory: Boolean = false,
|
||||
): Seq[TestRunner] = {
|
||||
// Test group and names may be file filters (like '*')
|
||||
val groupAndNameDirs = {
|
||||
|
|
@ -146,13 +147,25 @@ final class ScriptedTests(
|
|||
)
|
||||
logTests(runFromSourceBasedTests.size, prop.toString)
|
||||
|
||||
if (keepTempDirectory && runFromSourceBasedTests.size > 1) {
|
||||
sys.error(
|
||||
s"scriptedKeepTempDirectory requires exactly one test, but ${runFromSourceBasedTests.size} tests were requested"
|
||||
)
|
||||
}
|
||||
|
||||
def createTestRunners(tests: Seq[TestInfo]): Seq[TestRunner] = {
|
||||
tests
|
||||
.sortBy(_._1)
|
||||
.grouped(batchSize)
|
||||
.map { batch => () =>
|
||||
IO.withTemporaryDirectory {
|
||||
runBatchedTests(batch, _, prescripted, prop, log)
|
||||
if (keepTempDirectory) {
|
||||
val tempDir = IO.createTemporaryDirectory
|
||||
log.info(s"Temporary directory for scripted tests: ${tempDir.getAbsolutePath}")
|
||||
runBatchedTests(batch, tempDir, prescripted, prop, log, keepTempDirectory)
|
||||
} else {
|
||||
IO.withTemporaryDirectory {
|
||||
runBatchedTests(batch, _, prescripted, prop, log, keepTempDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toList
|
||||
|
|
@ -202,7 +215,8 @@ final class ScriptedTests(
|
|||
tempTestDir: File,
|
||||
preHook: File => Unit,
|
||||
prop: RemoteSbtCreatorProp,
|
||||
log: Logger
|
||||
log: Logger,
|
||||
keepTempDirectory: Boolean = false
|
||||
): Seq[Option[String]] = {
|
||||
|
||||
val runner = new BatchScriptRunner
|
||||
|
|
@ -241,16 +255,18 @@ 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 = sbt.nio.file.FileTreeView.default
|
||||
val base = tempTestDir.getCanonicalFile.toGlob
|
||||
val global = base / "global"
|
||||
val globalLogging = base / ** / "global-logging"
|
||||
def recursiveFilter(glob: Glob): PathFilter = (glob: PathFilter) || glob / **
|
||||
val keep: PathFilter = recursiveFilter(global) || recursiveFilter(globalLogging)
|
||||
val toDelete = view.list(base / **, !keep).map(_._1).sorted.reverse
|
||||
toDelete.foreach { p =>
|
||||
try Files.deleteIfExists(p)
|
||||
catch { case _: IOException => }
|
||||
if (!keepTempDirectory) {
|
||||
val view = sbt.nio.file.FileTreeView.default
|
||||
val base = tempTestDir.getCanonicalFile.toGlob
|
||||
val global = base / "global"
|
||||
val globalLogging = base / ** / "global-logging"
|
||||
def recursiveFilter(glob: Glob): PathFilter = (glob: PathFilter) || glob / **
|
||||
val keep: PathFilter = recursiveFilter(global) || recursiveFilter(globalLogging)
|
||||
val toDelete = view.list(base / **, !keep).map(_._1).sorted.reverse
|
||||
toDelete.foreach { p =>
|
||||
try Files.deleteIfExists(p)
|
||||
catch { case _: IOException => }
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
@ -352,7 +368,7 @@ object ScriptedTests extends ScriptedRunner {
|
|||
buffer,
|
||||
tests,
|
||||
logger,
|
||||
Array(),
|
||||
Array[String](),
|
||||
new java.util.ArrayList[File],
|
||||
defScalaVersion,
|
||||
sbtVersion,
|
||||
|
|
@ -452,6 +468,32 @@ class ScriptedRunner {
|
|||
)
|
||||
}
|
||||
|
||||
def run(
|
||||
resourceBaseDirectory: File,
|
||||
bufferLog: Boolean,
|
||||
tests: Array[String],
|
||||
launcherJar: File,
|
||||
javaCommand: String,
|
||||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
keepTempDirectory: Boolean,
|
||||
): Unit = {
|
||||
val logger = TestConsoleLogger()
|
||||
run(
|
||||
resourceBaseDirectory,
|
||||
bufferLog,
|
||||
tests,
|
||||
logger,
|
||||
javaCommand,
|
||||
launchOpts,
|
||||
prescripted,
|
||||
LauncherBased(launcherJar),
|
||||
Int.MaxValue,
|
||||
parallelExecution = false,
|
||||
keepTempDirectory,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the entry point used by SbtPlugin in sbt 1.2.x, 1.3.x, 1.4.x etc.
|
||||
* Removing this method will break scripted and sbt plugin cross building.
|
||||
|
|
@ -510,6 +552,32 @@ class ScriptedRunner {
|
|||
)
|
||||
}
|
||||
|
||||
def runInParallel(
|
||||
resourceBaseDirectory: File,
|
||||
bufferLog: Boolean,
|
||||
tests: Array[String],
|
||||
launcherJar: File,
|
||||
javaCommand: String,
|
||||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
instance: Int,
|
||||
keepTempDirectory: Boolean,
|
||||
): Unit = {
|
||||
val logger = TestConsoleLogger()
|
||||
runInParallel(
|
||||
resourceBaseDirectory,
|
||||
bufferLog,
|
||||
tests,
|
||||
logger,
|
||||
javaCommand,
|
||||
launchOpts,
|
||||
prescripted,
|
||||
LauncherBased(launcherJar),
|
||||
instance,
|
||||
keepTempDirectory,
|
||||
)
|
||||
}
|
||||
|
||||
// This is called by project/Scripted.scala
|
||||
// Using java.util.List[File] to encode File => Unit
|
||||
def runInParallel(
|
||||
|
|
@ -545,7 +613,8 @@ class ScriptedRunner {
|
|||
launchOpts: Array[String],
|
||||
prescripted: java.util.List[File],
|
||||
prop: RemoteSbtCreatorProp,
|
||||
instances: Int
|
||||
instances: Int,
|
||||
keepTempDirectory: Boolean = false,
|
||||
): Unit =
|
||||
run(
|
||||
baseDir,
|
||||
|
|
@ -557,7 +626,8 @@ class ScriptedRunner {
|
|||
prescripted,
|
||||
prop,
|
||||
instances,
|
||||
parallelExecution = true
|
||||
parallelExecution = true,
|
||||
keepTempDirectory,
|
||||
)
|
||||
|
||||
@nowarn
|
||||
|
|
@ -572,6 +642,7 @@ class ScriptedRunner {
|
|||
prop: RemoteSbtCreatorProp,
|
||||
instances: Int,
|
||||
parallelExecution: Boolean,
|
||||
keepTempDirectory: Boolean = false,
|
||||
): Unit = {
|
||||
val addTestFile = (f: File) => { prescripted.add(f); () }
|
||||
val runner = new ScriptedTests(baseDir, bufferLog, javaCommand, launchOpts)
|
||||
|
|
@ -588,7 +659,14 @@ class ScriptedRunner {
|
|||
// Choosing Int.MaxValue will make the groupSize 1 in batchScriptedRunner
|
||||
val groupCount = if (parallelExecution) instances else Int.MaxValue
|
||||
val scriptedRunners =
|
||||
runner.batchScriptedRunner(scriptedTests, addTestFile, groupCount, prop, logger)
|
||||
runner.batchScriptedRunner(
|
||||
scriptedTests,
|
||||
addTestFile,
|
||||
groupCount,
|
||||
prop,
|
||||
logger,
|
||||
keepTempDirectory
|
||||
)
|
||||
// Fail if user provided test patterns but none matched any existing test directories
|
||||
if (tests.nonEmpty && scriptedRunners.isEmpty) {
|
||||
sys.error(s"No tests found matching: ${tests.mkString(", ")}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue