From 30aded8ced2b139b4937f77a5cba99657f078df3 Mon Sep 17 00:00:00 2001 From: Ali Rashid Date: Sun, 26 Apr 2026 21:31:42 +0300 Subject: [PATCH 1/2] [2.x] feat: Expose `scripted / excludeFilter` and `scripted / includeFilter` keys for scripted test filtering. (#9131) * [2.x] feat: Add scripted / includeFilter and scripted / excludeFilter * [2.x] Add scripted-exclude-filter scripted test. Remove stale item from Scripted.sbtWindowsExcludeFilter --- build.sbt | 6 +- main/src/main/scala/sbt/ScriptedPlugin.scala | 6 +- main/src/main/scala/sbt/ScriptedRun.scala | 247 ++++++++++++++++-- project/Scripted.scala | 49 ++-- .../project/scripted-exclude-filter/build.sbt | 5 + .../changes/broken-plugins.sbt | 1 + .../scripted-exclude-filter/changes/ok-test | 1 + .../project/scripted-exclude-filter/test | 23 ++ .../sbt/scriptedtest/ScriptedTests.scala | 154 +++++++++-- 9 files changed, 420 insertions(+), 72 deletions(-) create mode 100644 sbt-app/src/sbt-test/project/scripted-exclude-filter/build.sbt create mode 100644 sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/broken-plugins.sbt create mode 100644 sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/ok-test create mode 100644 sbt-app/src/sbt-test/project/scripted-exclude-filter/test diff --git a/build.sbt b/build.sbt index 0fbeeccb5..f462c4a4b 100644 --- a/build.sbt +++ b/build.sbt @@ -968,7 +968,9 @@ def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTa .filterNot(_.getName.contains("scala-compiler")), (bundledLauncherProj / Compile / packageBin).value, streams.value.log, - scriptedKeepTempDirectory.value + scriptedKeepTempDirectory.value, + (scripted / includeFilter).value, + (scripted / excludeFilter).value, ) } @@ -1046,6 +1048,8 @@ def otherRootSettings = scriptedSource := (sbtProj / sourceDirectory).value / "sbt-test", scripted / watchTriggers += scriptedSource.value.toGlob / **, scriptedUnpublished / watchTriggers := (scripted / watchTriggers).value, + scripted / includeFilter := AllPassFilter, + scripted / excludeFilter := Scripted.sbtWindowsExcludeFilter, scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server") ::: (sys.props.get("sbt.ivy.home") match { case Some(home) => List(s"-Dsbt.ivy.home=$home") diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 91f3c89a8..8b10dc479 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -63,6 +63,8 @@ object ScriptedPlugin extends AutoPlugin { override lazy val projectSettings: Seq[Setting[?]] = Seq( ivyConfigurations ++= Seq(ScriptedConf, ScriptedLaunchConf), + scripted / includeFilter := AllPassFilter, + scripted / excludeFilter := NothingFilter, scriptedSbt := (pluginCrossBuild / sbtVersion).value, sbtLauncher := Def.uncached( getJars(ScriptedLaunchConf) @@ -183,7 +185,9 @@ object ScriptedPlugin extends AutoPlugin { scriptedLaunchOpts.value, new java.util.ArrayList[File](), scriptedParallelInstances.value, - scriptedKeepTempDirectory.value + scriptedKeepTempDirectory.value, + (scripted / includeFilter).value, + (scripted / excludeFilter).value, ) } diff --git a/main/src/main/scala/sbt/ScriptedRun.scala b/main/src/main/scala/sbt/ScriptedRun.scala index 7735e584e..70d4e0b08 100644 --- a/main/src/main/scala/sbt/ScriptedRun.scala +++ b/main/src/main/scala/sbt/ScriptedRun.scala @@ -8,9 +8,11 @@ package sbt -import java.io.File +import java.io.{ File, FileFilter as JFileFilter } import java.lang.reflect.Method +import sbt.io.{ AllPassFilter, NothingFilter } + sealed trait ScriptedRun { final def run( resourceBaseDirectory: File, @@ -62,6 +64,37 @@ sealed trait ScriptedRun { } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause } } + final def run( + resourceBaseDirectory: File, + bufferLog: Boolean, + tests: Seq[String], + launcherJar: File, + javaCommand: String, + launchOpts: Seq[String], + prescripted: java.util.List[File], + instances: Int, + keepTempDirectory: Boolean, + includeFilter: JFileFilter, + excludeFilter: JFileFilter, + ): Unit = { + try { + invoke( + resourceBaseDirectory, + bufferLog, + tests.toArray, + launcherJar, + javaCommand, + launchOpts.toArray, + prescripted, + instances, + keepTempDirectory, + includeFilter, + excludeFilter, + ) + () + } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause } + } + protected def invoke( resourceBaseDirectory: File, bufferLog: java.lang.Boolean, @@ -97,6 +130,33 @@ sealed trait ScriptedRun { keepTempDirectory: java.lang.Boolean, ): AnyRef + // Default drops filters and calls V3 invoke so V1/V2/V3 subclasses need not 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: java.lang.Integer, + keepTempDirectory: java.lang.Boolean, + includeFilter: JFileFilter, + excludeFilter: JFileFilter, + ): AnyRef = { + invoke( + resourceBaseDirectory, + bufferLog, + tests, + launcherJar, + javaCommand, + launchOpts, + prescripted, + instances, + keepTempDirectory, + ) + } + } object ScriptedRun { @@ -108,48 +168,92 @@ object ScriptedRun { val sCls = classOf[String] val lfCls = classOf[java.util.List[File]] val iCls = classOf[Int] + val ffCls = classOf[JFileFilter] val clazz = scriptedTests.getClass if (batchExecution) try - new RunInParallelV3( + new RunInParallelV4( scriptedTests, - clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls, bCls) + clazz.getMethod( + "runInParallel", + fCls, + bCls, + asCls, + fCls, + sCls, + asCls, + lfCls, + iCls, + bCls, + ffCls, + ffCls, + ) ) catch { case _: NoSuchMethodException => 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 RunV3( + new RunV4( scriptedTests, - clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, bCls) + clazz.getMethod( + "run", + fCls, + bCls, + asCls, + fCls, + sCls, + asCls, + lfCls, + bCls, + ffCls, + ffCls, + ) ) catch { case _: NoSuchMethodException => 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) + ) + } } } } @@ -301,4 +405,113 @@ object ScriptedRun { ) } + private class RunV4(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], + instances: java.lang.Integer, + keepTempDirectory: java.lang.Boolean, + ): AnyRef = + invoke( + resourceBaseDirectory, + bufferLog, + tests, + launcherJar, + javaCommand, + launchOpts, + prescripted, + instances, + keepTempDirectory, + AllPassFilter, + NothingFilter, + ) + + 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: java.lang.Integer, + keepTempDirectory: java.lang.Boolean, + includeFilter: JFileFilter, + excludeFilter: JFileFilter, + ): AnyRef = + run.invoke( + scriptedTests, + resourceBaseDirectory, + bufferLog, + tests, + launcherJar, + javaCommand, + launchOpts, + prescripted, + keepTempDirectory, + includeFilter, + excludeFilter, + ) + } + + private class RunInParallelV4(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 = + invoke( + resourceBaseDirectory, + bufferLog, + tests, + launcherJar, + javaCommand, + launchOpts, + prescripted, + instances, + keepTempDirectory, + AllPassFilter, + NothingFilter, + ) + + 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, + includeFilter: JFileFilter, + excludeFilter: JFileFilter, + ): AnyRef = + runInParallel.invoke( + scriptedTests, + resourceBaseDirectory, + bufferLog, + tests, + launcherJar, + javaCommand, + launchOpts, + prescripted, + instances, + keepTempDirectory, + includeFilter, + excludeFilter, + ) + } + } diff --git a/project/Scripted.scala b/project/Scripted.scala index 635a1164c..247ba2911 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -32,6 +32,20 @@ object Scripted { val RepoOverrideTest = config("repoOverrideTest") extend Compile + val sbtWindowsExcludeFilter: FileFilter = + if (scala.util.Properties.isWin) + new SimpleFileFilter(f => + (f.getParentFile.getName, f.getName) match { + case ("classloader-cache", "jni") => true // no native lib is built for windows + case ("classloader-cache", "spark") => + true // the test spark server is unable to bind to a local socket on Visual Studio 2019 + case ("nio", "make-clone") => true // uses gcc which isn't set up on all systems + case ("watch", "symlinks") => true // symlinks don't work the same on windows + case _ => false + } + ) + else NothingFilter + import sbt.complete.* // Paging, 1-index based. @@ -106,7 +120,9 @@ object Scripted { classpath: Seq[File], launcherJar: File, logger: Logger, - keepTempDirectory: Boolean + keepTempDirectory: Boolean, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, ): Unit = { logger.info(s"Tests selected: ${args.mkString("\n * ", "\n * ", "\n")}") logger.info("") @@ -120,18 +136,6 @@ object Scripted { // Interface to cross class loader type SbtScriptedRunner = { - // def runInParallel( - // resourceBaseDirectory: File, - // bufferLog: Boolean, - // tests: Array[String], - // launchOpts: Array[String], - // prescripted: java.util.List[File], - // scalaVersion: String, - // sbtVersion: String, - // classpath: Array[File], - // instances: Int - // ): Unit - def runInParallel( resourceBaseDirectory: File, bufferLog: Boolean, @@ -141,7 +145,9 @@ object Scripted { launchOpts: Array[String], prescripted: java.util.List[File], instance: Int, - keepTempDirectory: Boolean + keepTempDirectory: Boolean, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, ): Unit } @@ -166,17 +172,6 @@ object Scripted { } import scala.language.reflectiveCalls - // bridge.runInParallel( - // sourcePath, - // bufferLog, - // args.toArray, - // launchOpts.toArray, - // callback, - // scalaVersion, - // sbtVersion, - // classpath.toArray, - // instances - // ) bridge.runInParallel( sourcePath, bufferLog, @@ -186,7 +181,9 @@ object Scripted { launchOpts.toArray, callback, instances, - keepTempDirectory + keepTempDirectory, + includeFilter, + excludeFilter, ) } catch { case ite: InvocationTargetException => throw ite.getCause } } finally { diff --git a/sbt-app/src/sbt-test/project/scripted-exclude-filter/build.sbt b/sbt-app/src/sbt-test/project/scripted-exclude-filter/build.sbt new file mode 100644 index 000000000..277cadc64 --- /dev/null +++ b/sbt-app/src/sbt-test/project/scripted-exclude-filter/build.sbt @@ -0,0 +1,5 @@ +lazy val root = (project in file(".")) + .enablePlugins(SbtPlugin) + .settings( + scripted / excludeFilter := new SimpleFileFilter(_.getName == "skipped") + ) diff --git a/sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/broken-plugins.sbt b/sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/broken-plugins.sbt new file mode 100644 index 000000000..80fd03938 --- /dev/null +++ b/sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/broken-plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("nonexistent.example" % "sbt-nonexistent" % "0.0.0") diff --git a/sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/ok-test b/sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/ok-test new file mode 100644 index 000000000..5df2af1f3 --- /dev/null +++ b/sbt-app/src/sbt-test/project/scripted-exclude-filter/changes/ok-test @@ -0,0 +1 @@ +> compile diff --git a/sbt-app/src/sbt-test/project/scripted-exclude-filter/test b/sbt-app/src/sbt-test/project/scripted-exclude-filter/test new file mode 100644 index 000000000..31b861f89 --- /dev/null +++ b/sbt-app/src/sbt-test/project/scripted-exclude-filter/test @@ -0,0 +1,23 @@ +# `passing` is a project that compiles; `skipped` has a project/plugins.sbt referencing a +# fictitious plugin so its sbt session fails to load if actually run. +$ copy-file changes/ok-test src/sbt-test/group/passing/test +$ copy-file changes/ok-test src/sbt-test/group/skipped/test +$ copy-file changes/broken-plugins.sbt src/sbt-test/group/skipped/project/plugins.sbt + +> scripted + +# Explicit selection of the un-filtered test. Succeeds. +> scripted group/passing + +# Explicit selection of a filtered test yields "No tests found matching" error. +-> scripted group/skipped + +# Replace the excludeFilter to let both run. `skipped` now fails. +> set scripted / excludeFilter := NothingFilter +-> scripted +-> scripted group/skipped + +# Flip to includeFilter: only accept tests whose name is "passing". +> set scripted / includeFilter := new SimpleFileFilter(_.getName == "passing") +> scripted +-> scripted group/skipped diff --git a/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 4c81e47b9..7c19e2934 100644 --- a/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -102,7 +102,6 @@ final class ScriptedTests( Map('$' -> fileHandler, '>' -> sbtHandler, '#' -> CommentHandler) } - /** Returns a sequence of test runners that have to be applied in the call site. */ def batchScriptedRunner( testGroupAndNames: Seq[(String, String)], prescripted: File => Unit, @@ -110,6 +109,28 @@ final class ScriptedTests( prop: RemoteSbtCreatorProp, log: Logger, keepTempDirectory: Boolean = false, + ): Seq[TestRunner] = + batchScriptedRunner( + testGroupAndNames, + prescripted, + sbtInstances, + prop, + log, + keepTempDirectory, + AllPassFilter, + NothingFilter, + ) + + /** Returns a sequence of test runners that have to be applied in the call site. */ + def batchScriptedRunner( + testGroupAndNames: Seq[(String, String)], + prescripted: File => Unit, + sbtInstances: Int, + prop: RemoteSbtCreatorProp, + log: Logger, + keepTempDirectory: Boolean, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, ): Seq[TestRunner] = { // Test group and names may be file filters (like '*') val groupAndNameDirs = { @@ -117,12 +138,14 @@ final class ScriptedTests( (group, name) <- testGroupAndNames groupDir <- (resourceBaseDirectory * group).get() testDir <- (groupDir * name).get() + if !testDir.isFile + if includeFilter.accept(testDir) && !excludeFilter.accept(testDir) } yield (groupDir, testDir) } type TestInfo = ((String, String), File) - val labelsAndDirs = groupAndNameDirs.filterNot(_._2.isFile).map { (groupDir, nameDir) => + val labelsAndDirs = groupAndNameDirs.map { (groupDir, nameDir) => val groupName = groupDir.getName val testName = nameDir.getName val testDirectory = testResources.readOnlyResourceDirectory(groupName, testName) @@ -137,18 +160,15 @@ final class ScriptedTests( case s => s } - val runFromSourceBasedTestsUnfiltered = labelsAndDirs - val runFromSourceBasedTests = runFromSourceBasedTestsUnfiltered.filterNot(windowsExclude) - def logTests(size: Int, how: String) = log.info( f"Running $size / $totalSize (${size * 100d / totalSize}%3.2f%%) scripted tests with $how" ) - logTests(runFromSourceBasedTests.size, prop.toString) + logTests(labelsAndDirs.size, prop.toString) - if (keepTempDirectory && runFromSourceBasedTests.size > 1) { + if (keepTempDirectory && labelsAndDirs.size > 1) { sys.error( - s"scriptedKeepTempDirectory requires exactly one test, but ${runFromSourceBasedTests.size} tests were requested" + s"scriptedKeepTempDirectory requires exactly one test, but ${labelsAndDirs.size} tests were requested" ) } @@ -170,27 +190,10 @@ final class ScriptedTests( .toList } - createTestRunners(runFromSourceBasedTests) + createTestRunners(labelsAndDirs) } } - private val windowsExclude: (((String, String), File)) => Boolean = - if (scala.util.Properties.isWin) { case (testName, _) => - testName match { - case ("classloader-cache", "jni") => true // no native lib is built for windows - case ("classloader-cache", "snapshot") => - true // the test overwrites a jar that is being used which is verboten in windows - // The test spark server is unable to bind to a local socket on Visual Studio 2019 - case ("classloader-cache", "spark") => true - case ("nio", "make-clone") => true // uses gcc which isn't set up on all systems - // symlinks don't work the same on windows. Symlink monitoring does work in many cases - // on windows but not to the same level as it does on osx and linux - case ("watch", "symlinks") => true - case _ => false - } - } - else _ => false - /** * Defines the batch execution of scripted tests. * @@ -498,6 +501,37 @@ class ScriptedRunner { ) } + /** Entry point with configurable include/exclude filters. */ + def run( + resourceBaseDirectory: File, + bufferLog: Boolean, + tests: Array[String], + launcherJar: File, + javaCommand: String, + launchOpts: Array[String], + prescripted: java.util.List[File], + keepTempDirectory: Boolean, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, + ): Unit = { + val logger = TestConsoleLogger() + run( + resourceBaseDirectory, + bufferLog, + tests, + logger, + javaCommand, + launchOpts, + prescripted, + LauncherBased(launcherJar), + Int.MaxValue, + parallelExecution = false, + keepTempDirectory, + includeFilter, + excludeFilter, + ) + } + /** * 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. @@ -582,6 +616,37 @@ class ScriptedRunner { ) } + /** Entry point with configurable include/exclude filters. */ + 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, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, + ): Unit = { + val logger = TestConsoleLogger() + runInParallel( + resourceBaseDirectory, + bufferLog, + tests, + logger, + javaCommand, + launchOpts, + prescripted, + LauncherBased(launcherJar), + instance, + keepTempDirectory, + includeFilter, + excludeFilter, + ) + } + // This is called by project/Scripted.scala // Using java.util.List[File] to encode File => Unit def runInParallel( @@ -619,6 +684,35 @@ class ScriptedRunner { prop: RemoteSbtCreatorProp, instances: Int, keepTempDirectory: Boolean = false, + ): Unit = + runInParallel( + baseDir, + bufferLog, + tests, + logger, + javaCommand, + launchOpts, + prescripted, + prop, + instances, + keepTempDirectory, + AllPassFilter, + NothingFilter, + ) + + private[sbt] def runInParallel( + baseDir: File, + bufferLog: Boolean, + tests: Array[String], + logger: Logger, + javaCommand: String, + launchOpts: Array[String], + prescripted: java.util.List[File], + prop: RemoteSbtCreatorProp, + instances: Int, + keepTempDirectory: Boolean, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, ): Unit = run( baseDir, @@ -632,6 +726,8 @@ class ScriptedRunner { instances, parallelExecution = true, keepTempDirectory, + includeFilter, + excludeFilter, ) private def run( @@ -646,6 +742,8 @@ class ScriptedRunner { instances: Int, parallelExecution: Boolean, keepTempDirectory: Boolean = false, + includeFilter: java.io.FileFilter = AllPassFilter, + excludeFilter: java.io.FileFilter = NothingFilter, ): Unit = { val addTestFile = (f: File) => { prescripted.add(f); () } val runner = new ScriptedTests(baseDir, bufferLog, javaCommand, launchOpts.toIndexedSeq) @@ -668,7 +766,9 @@ class ScriptedRunner { groupCount, prop, logger, - keepTempDirectory + keepTempDirectory, + includeFilter, + excludeFilter, ) // Fail if user provided test patterns but none matched any existing test directories if (tests.nonEmpty && scriptedRunners.isEmpty) { From d59a2be85aba748f50205197bd4b57b6b9be141a Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Mon, 27 Apr 2026 00:22:30 -0400 Subject: [PATCH 2/2] [2.x] refactor: Replace string Array with ju.List (#9138) **Problem** Scala 3 supposedly has problem with String array in structural types. **Solution** This works around the issue by using ju.List. --- build.sbt | 3 + main/src/main/scala/sbt/ScriptedPlugin.scala | 5 +- main/src/main/scala/sbt/ScriptedRun.scala | 45 +++++------ project/Scripted.scala | 9 ++- .../sbt/scriptedtest/ScriptedTests.scala | 74 ++++++++++--------- 5 files changed, 74 insertions(+), 62 deletions(-) diff --git a/build.sbt b/build.sbt index f462c4a4b..5b7edc6b6 100644 --- a/build.sbt +++ b/build.sbt @@ -524,6 +524,9 @@ lazy val scriptedSbtProj = (project in file("scripted-sbt")) libraryDependencies ++= Seq(launcherInterface % "provided"), mimaSettings, mimaBinaryIssueFilters ++= Seq( + exclude[DirectMissingMethodProblem]( + "sbt.scriptedtest.ScriptedTests.runInParallel$default$10" + ), ), ) .dependsOn(lmCore) diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 8b10dc479..7a0f44853 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -25,6 +25,7 @@ import sbt.io.syntax.* import sbt.librarymanagement.* import sbt.librarymanagement.syntax.* import sbt.nio.file.{ Glob, RecursiveGlob } +import scala.jdk.CollectionConverters.* object ScriptedPlugin extends AutoPlugin { @@ -179,10 +180,10 @@ object ScriptedPlugin extends AutoPlugin { scriptedRun.value.run( sbtTestDirectory.value, scriptedBufferLog.value, - args, + args.toList.asJava, sbtLauncher.value, Fork.javaCommand((scripted / javaHome).value, "java").getAbsolutePath, - scriptedLaunchOpts.value, + scriptedLaunchOpts.value.toList.asJava, new java.util.ArrayList[File](), scriptedParallelInstances.value, scriptedKeepTempDirectory.value, diff --git a/main/src/main/scala/sbt/ScriptedRun.scala b/main/src/main/scala/sbt/ScriptedRun.scala index 70d4e0b08..c92fe57cb 100644 --- a/main/src/main/scala/sbt/ScriptedRun.scala +++ b/main/src/main/scala/sbt/ScriptedRun.scala @@ -12,6 +12,7 @@ import java.io.{ File, FileFilter as JFileFilter } import java.lang.reflect.Method import sbt.io.{ AllPassFilter, NothingFilter } +import scala.jdk.CollectionConverters.* sealed trait ScriptedRun { final def run( @@ -64,13 +65,14 @@ sealed trait ScriptedRun { } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause } } + // v4 final def run( resourceBaseDirectory: File, bufferLog: Boolean, - tests: Seq[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Seq[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], instances: Int, keepTempDirectory: Boolean, @@ -81,10 +83,10 @@ sealed trait ScriptedRun { invoke( resourceBaseDirectory, bufferLog, - tests.toArray, + tests, launcherJar, javaCommand, - launchOpts.toArray, + launchOpts, prescripted, instances, keepTempDirectory, @@ -130,14 +132,14 @@ sealed trait ScriptedRun { keepTempDirectory: java.lang.Boolean, ): AnyRef - // Default drops filters and calls V3 invoke so V1/V2/V3 subclasses need not override. + // v4. Default drops filters and calls V3 invoke so V1/V2/V3 subclasses need not override. protected def invoke( resourceBaseDirectory: File, bufferLog: java.lang.Boolean, - tests: Array[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Array[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], instances: java.lang.Integer, keepTempDirectory: java.lang.Boolean, @@ -147,10 +149,10 @@ sealed trait ScriptedRun { invoke( resourceBaseDirectory, bufferLog, - tests, + tests.toArray(Array.empty[String]), launcherJar, javaCommand, - launchOpts, + launchOpts.toArray(Array.empty[String]), prescripted, instances, keepTempDirectory, @@ -167,6 +169,7 @@ object ScriptedRun { val asCls = classOf[Array[String]] val sCls = classOf[String] val lfCls = classOf[java.util.List[File]] + val lsCls = classOf[java.util.List[String]] val iCls = classOf[Int] val ffCls = classOf[JFileFilter] @@ -179,10 +182,10 @@ object ScriptedRun { "runInParallel", fCls, bCls, - asCls, + lsCls, fCls, sCls, - asCls, + lsCls, lfCls, iCls, bCls, @@ -223,10 +226,10 @@ object ScriptedRun { "run", fCls, bCls, - asCls, + lsCls, fCls, sCls, - asCls, + lsCls, lfCls, bCls, ffCls, @@ -420,10 +423,10 @@ object ScriptedRun { invoke( resourceBaseDirectory, bufferLog, - tests, + tests.toList.asJava, launcherJar, javaCommand, - launchOpts, + launchOpts.toList.asJava, prescripted, instances, keepTempDirectory, @@ -434,10 +437,10 @@ object ScriptedRun { override protected def invoke( resourceBaseDirectory: File, bufferLog: java.lang.Boolean, - tests: Array[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Array[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], instances: java.lang.Integer, keepTempDirectory: java.lang.Boolean, @@ -474,10 +477,10 @@ object ScriptedRun { invoke( resourceBaseDirectory, bufferLog, - tests, + tests.toList.asJava, launcherJar, javaCommand, - launchOpts, + launchOpts.toList.asJava, prescripted, instances, keepTempDirectory, @@ -488,10 +491,10 @@ object ScriptedRun { override protected def invoke( resourceBaseDirectory: File, bufferLog: java.lang.Boolean, - tests: Array[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Array[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], instances: Integer, keepTempDirectory: java.lang.Boolean, diff --git a/project/Scripted.scala b/project/Scripted.scala index 247ba2911..752837422 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -6,6 +6,7 @@ import sbt.* import sbt.internal.inc.ScalaInstance import sbt.internal.inc.classpath.{ ClasspathUtilities, FilteredLoader } import scala.annotation.nowarn +import scala.collection.JavaConverters.* object LocalScriptedPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin @@ -139,10 +140,10 @@ object Scripted { def runInParallel( resourceBaseDirectory: File, bufferLog: Boolean, - tests: Array[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Array[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], instance: Int, keepTempDirectory: Boolean, @@ -175,10 +176,10 @@ object Scripted { bridge.runInParallel( sourcePath, bufferLog, - args.toArray, + args.toList.asJava, launcherJar, "java", - launchOpts.toArray, + launchOpts.toList.asJava, callback, instances, keepTempDirectory, diff --git a/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 7c19e2934..0a5508d81 100644 --- a/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -505,10 +505,10 @@ class ScriptedRunner { def run( resourceBaseDirectory: File, bufferLog: Boolean, - tests: Array[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Array[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], keepTempDirectory: Boolean, includeFilter: java.io.FileFilter, @@ -518,10 +518,10 @@ class ScriptedRunner { run( resourceBaseDirectory, bufferLog, - tests, + tests.toArray(Array.empty[String]), logger, javaCommand, - launchOpts, + launchOpts.toArray(Array.empty[String]), prescripted, LauncherBased(launcherJar), Int.MaxValue, @@ -558,6 +558,7 @@ class ScriptedRunner { prescripted, LauncherBased(launcherJar), instance, + keepTempDirectory = false, ) } @@ -587,6 +588,7 @@ class ScriptedRunner { prescripted, LauncherBased(launcherJar), instance, + keepTempDirectory = false, ) } @@ -620,10 +622,10 @@ class ScriptedRunner { def runInParallel( resourceBaseDirectory: File, bufferLog: Boolean, - tests: Array[String], + tests: java.util.List[String], launcherJar: File, javaCommand: String, - launchOpts: Array[String], + launchOpts: java.util.List[String], prescripted: java.util.List[File], instance: Int, keepTempDirectory: Boolean, @@ -670,34 +672,8 @@ class ScriptedRunner { launchOpts, prescripted, RunFromSourceBased(scalaVersion, sbtVersion, classpath.toSeq), - instances - ) - - private[sbt] def runInParallel( - baseDir: File, - bufferLog: Boolean, - tests: Array[String], - logger: Logger, - javaCommand: String, - launchOpts: Array[String], - prescripted: java.util.List[File], - prop: RemoteSbtCreatorProp, - instances: Int, - keepTempDirectory: Boolean = false, - ): Unit = - runInParallel( - baseDir, - bufferLog, - tests, - logger, - javaCommand, - launchOpts, - prescripted, - prop, instances, - keepTempDirectory, - AllPassFilter, - NothingFilter, + keepTempDirectory = false, ) private[sbt] def runInParallel( @@ -711,8 +687,6 @@ class ScriptedRunner { prop: RemoteSbtCreatorProp, instances: Int, keepTempDirectory: Boolean, - includeFilter: java.io.FileFilter, - excludeFilter: java.io.FileFilter, ): Unit = run( baseDir, @@ -726,6 +700,36 @@ class ScriptedRunner { instances, parallelExecution = true, keepTempDirectory, + includeFilter = AllPassFilter, + excludeFilter = NothingFilter, + ) + + private[sbt] def runInParallel( + baseDir: File, + bufferLog: Boolean, + tests: java.util.List[String], + logger: Logger, + javaCommand: String, + launchOpts: java.util.List[String], + prescripted: java.util.List[File], + prop: RemoteSbtCreatorProp, + instances: Int, + keepTempDirectory: Boolean, + includeFilter: java.io.FileFilter, + excludeFilter: java.io.FileFilter, + ): Unit = + run( + baseDir, + bufferLog, + tests.toArray(Array.empty[String]), + logger, + javaCommand, + launchOpts.toArray(Array.empty[String]), + prescripted, + prop, + instances, + parallelExecution = true, + keepTempDirectory, includeFilter, excludeFilter, )