2018-07-31 08:28:42 +08:00
|
|
|
package local
|
|
|
|
|
|
2018-01-24 21:42:18 +08:00
|
|
|
import java.lang.reflect.InvocationTargetException
|
|
|
|
|
|
2025-01-02 10:16:32 +08:00
|
|
|
import sbt.*
|
2017-05-03 22:52:36 +08:00
|
|
|
import sbt.internal.inc.ScalaInstance
|
2018-01-24 21:42:18 +08:00
|
|
|
import sbt.internal.inc.classpath.{ ClasspathUtilities, FilteredLoader }
|
2024-09-29 06:01:48 +08:00
|
|
|
import scala.annotation.nowarn
|
2015-07-10 17:53:48 +08:00
|
|
|
|
2018-07-31 08:28:42 +08:00
|
|
|
object LocalScriptedPlugin extends AutoPlugin {
|
2016-03-31 11:48:20 +08:00
|
|
|
override def requires = plugins.JvmPlugin
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2018-07-10 12:58:45 +08:00
|
|
|
object autoImport extends ScriptedKeys
|
2016-03-31 11:48:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait ScriptedKeys {
|
2018-01-17 23:19:02 +08:00
|
|
|
val publishLocalBinAll = taskKey[Unit]("")
|
2019-01-31 09:25:00 +08:00
|
|
|
val scriptedUnpublished = inputKey[Unit](
|
|
|
|
|
"Execute scripted without publishing sbt first. " +
|
|
|
|
|
"Saves you some time when only your test has changed"
|
|
|
|
|
)
|
2018-01-17 23:19:02 +08:00
|
|
|
val scriptedSource = settingKey[File]("")
|
|
|
|
|
val scriptedPrescripted = taskKey[File => Unit]("")
|
2026-01-24 10:22:50 +08:00
|
|
|
val scriptedKeepTempDirectory = settingKey[Boolean](
|
|
|
|
|
"If true, keeps the temporary directory after scripted tests complete for debugging."
|
|
|
|
|
)
|
2016-03-31 11:48:20 +08:00
|
|
|
}
|
2015-01-13 11:01:16 +08:00
|
|
|
|
2016-03-31 11:48:20 +08:00
|
|
|
object Scripted {
|
2017-12-21 13:08:56 +08:00
|
|
|
// This is to workaround https://github.com/sbt/io/issues/110
|
2026-01-09 05:47:42 +08:00
|
|
|
if (!sys.props.contains("jna.nosys")) sys.props.put("jna.nosys", "true")
|
2017-12-21 13:08:56 +08:00
|
|
|
|
2018-01-17 23:19:21 +08:00
|
|
|
val RepoOverrideTest = config("repoOverrideTest") extend Compile
|
2014-12-18 12:38:10 +08:00
|
|
|
|
2025-01-02 10:16:32 +08:00
|
|
|
import sbt.complete.*
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2014-12-18 12:38:10 +08:00
|
|
|
// Paging, 1-index based.
|
2018-01-24 21:42:18 +08:00
|
|
|
final case class ScriptedTestPage(page: Int, total: Int)
|
|
|
|
|
|
2017-03-14 23:53:06 +08:00
|
|
|
// FIXME: Duplicated with ScriptedPlugin.scriptedParser, this can be
|
|
|
|
|
// avoided once we upgrade build.properties to 0.13.14
|
2017-04-21 15:14:31 +08:00
|
|
|
def scriptedParser(scriptedBase: File): Parser[Seq[String]] = {
|
2025-01-02 10:16:32 +08:00
|
|
|
import DefaultParsers.*
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2017-04-21 15:14:31 +08:00
|
|
|
val scriptedFiles: NameFilter = ("test": NameFilter) | "pending"
|
2026-01-13 04:19:40 +08:00
|
|
|
val pairs = (scriptedBase * AllPassFilter * AllPassFilter * scriptedFiles).get() map {
|
2017-04-21 15:14:31 +08:00
|
|
|
(f: File) =>
|
2014-12-18 12:38:10 +08:00
|
|
|
val p = f.getParentFile
|
|
|
|
|
(p.getParentFile.getName, p.getName)
|
2017-04-21 15:14:31 +08:00
|
|
|
}
|
2018-01-24 21:42:18 +08:00
|
|
|
val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet)
|
2014-12-18 12:38:10 +08:00
|
|
|
|
2017-04-21 15:14:31 +08:00
|
|
|
val id = charClass(c => !c.isWhitespace && c != '/').+.string
|
2018-01-24 21:42:18 +08:00
|
|
|
val groupP = token(id.examples(pairMap.keySet)) <~ token('/')
|
2014-12-18 12:38:10 +08:00
|
|
|
|
2017-04-21 15:14:31 +08:00
|
|
|
// A parser for page definitions
|
2019-10-07 05:05:56 +08:00
|
|
|
val pageNumber = (NatBasic & not('0', "zero page number")).flatMap { i =>
|
|
|
|
|
if (i <= pairs.size) Parser.success(i)
|
|
|
|
|
else Parser.failure(s"$i exceeds the number of tests (${pairs.size})")
|
|
|
|
|
}
|
2019-09-01 07:37:41 +08:00
|
|
|
val pageP: Parser[ScriptedTestPage] = ("*" ~> pageNumber ~ ("of" ~> pageNumber)) flatMap {
|
|
|
|
|
case (page, total) if page <= total => success(ScriptedTestPage(page, total))
|
|
|
|
|
case (page, total) => failure(s"Page $page was greater than $total")
|
2017-04-21 15:14:31 +08:00
|
|
|
}
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2017-04-21 15:14:31 +08:00
|
|
|
// Grabs the filenames from a given test group in the current page definition.
|
|
|
|
|
def pagedFilenames(group: String, page: ScriptedTestPage): Seq[String] = {
|
2019-10-07 05:01:56 +08:00
|
|
|
val files = pairMap.get(group).toSeq.flatten.sortBy(_.toLowerCase)
|
2019-08-09 01:18:43 +08:00
|
|
|
val pageSize = if (page.total == 0) files.size else files.size / page.total
|
2017-04-21 15:14:31 +08:00
|
|
|
// The last page may loose some values, so we explicitly keep them
|
|
|
|
|
val dropped = files.drop(pageSize * (page.page - 1))
|
|
|
|
|
if (page.page == page.total) dropped
|
|
|
|
|
else dropped.take(pageSize)
|
|
|
|
|
}
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2017-04-21 15:14:31 +08:00
|
|
|
def nameP(group: String) = {
|
2017-05-02 05:31:47 +08:00
|
|
|
token("*".id | id.examples(pairMap.getOrElse(group, Set.empty[String])))
|
2014-12-18 12:38:10 +08:00
|
|
|
}
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2017-04-21 15:14:31 +08:00
|
|
|
val PagedIds: Parser[Seq[String]] =
|
|
|
|
|
for {
|
|
|
|
|
group <- groupP
|
|
|
|
|
page <- pageP
|
|
|
|
|
files = pagedFilenames(group, page)
|
|
|
|
|
// TODO - Fail the parser if we don't have enough files for the given page size
|
2022-01-31 04:07:23 +08:00
|
|
|
// if !files.isEmpty
|
2018-01-24 21:42:18 +08:00
|
|
|
} yield files map (f => s"$group/$f")
|
2017-04-21 15:14:31 +08:00
|
|
|
|
|
|
|
|
val testID = (for (group <- groupP; name <- nameP(group)) yield (group, name))
|
|
|
|
|
val testIdAsGroup = matched(testID) map (test => Seq(test))
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2022-01-31 04:07:23 +08:00
|
|
|
// (token(Space) ~> matched(testID)).*
|
2017-04-21 15:14:31 +08:00
|
|
|
(token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten)
|
|
|
|
|
}
|
2014-12-18 12:38:10 +08:00
|
|
|
|
2024-09-29 06:01:48 +08:00
|
|
|
@nowarn
|
2018-01-24 21:42:18 +08:00
|
|
|
def doScripted(
|
|
|
|
|
scriptedSbtInstance: ScalaInstance,
|
|
|
|
|
sourcePath: File,
|
|
|
|
|
bufferLog: Boolean,
|
|
|
|
|
args: Seq[String],
|
|
|
|
|
prescripted: File => Unit,
|
|
|
|
|
launchOpts: Seq[String],
|
2020-01-12 11:52:36 +08:00
|
|
|
scalaVersion: String,
|
|
|
|
|
sbtVersion: String,
|
|
|
|
|
classpath: Seq[File],
|
2022-10-02 13:58:37 +08:00
|
|
|
launcherJar: File,
|
2026-01-24 10:22:50 +08:00
|
|
|
logger: Logger,
|
|
|
|
|
keepTempDirectory: Boolean
|
2018-01-24 21:42:18 +08:00
|
|
|
): Unit = {
|
2026-01-10 03:57:58 +08:00
|
|
|
logger.info(s"Tests selected: ${args.mkString("\n * ", "\n * ", "\n")}")
|
2019-11-18 04:34:19 +08:00
|
|
|
logger.info("")
|
2018-01-24 21:42:18 +08:00
|
|
|
|
2017-11-16 22:09:25 +08:00
|
|
|
// 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"
|
2018-01-24 21:42:18 +08:00
|
|
|
|
|
|
|
|
val noJLine = new FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil)
|
2020-01-12 11:52:36 +08:00
|
|
|
val loader = ClasspathUtilities.toLoader(classpath, noJLine)
|
2018-01-14 06:08:48 +08:00
|
|
|
val bridgeClass = Class.forName("sbt.scriptedtest.ScriptedRunner", true, loader)
|
2018-01-24 21:42:18 +08:00
|
|
|
|
|
|
|
|
// Interface to cross class loader
|
|
|
|
|
type SbtScriptedRunner = {
|
2022-10-02 13:58:37 +08:00
|
|
|
// 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
|
|
|
|
|
|
2018-01-24 21:42:18 +08:00
|
|
|
def runInParallel(
|
2019-01-31 09:25:00 +08:00
|
|
|
resourceBaseDirectory: File,
|
|
|
|
|
bufferLog: Boolean,
|
|
|
|
|
tests: Array[String],
|
2022-10-02 13:58:37 +08:00
|
|
|
launcherJar: File,
|
|
|
|
|
javaCommand: String,
|
2019-01-31 09:25:00 +08:00
|
|
|
launchOpts: Array[String],
|
|
|
|
|
prescripted: java.util.List[File],
|
2022-10-02 13:58:37 +08:00
|
|
|
instance: Int,
|
2026-01-24 10:22:50 +08:00
|
|
|
keepTempDirectory: Boolean
|
2018-01-24 21:42:18 +08:00
|
|
|
): Unit
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 09:25:00 +08:00
|
|
|
val initLoader = Thread.currentThread.getContextClassLoader
|
2015-06-20 01:40:10 +08:00
|
|
|
try {
|
2019-01-31 09:25:00 +08:00
|
|
|
Thread.currentThread.setContextClassLoader(loader)
|
|
|
|
|
val bridge =
|
|
|
|
|
bridgeClass.getDeclaredConstructor().newInstance().asInstanceOf[SbtScriptedRunner]
|
|
|
|
|
try {
|
|
|
|
|
// Using java.util.List to encode File => Unit.
|
|
|
|
|
val callback = new java.util.AbstractList[File] {
|
|
|
|
|
override def add(x: File): Boolean = { prescripted(x); false }
|
|
|
|
|
def get(x: Int): sbt.File = ???
|
|
|
|
|
def size(): Int = 0
|
|
|
|
|
}
|
2019-01-31 10:27:39 +08:00
|
|
|
val instances: Int = (System.getProperty("sbt.scripted.parallel.instances") match {
|
|
|
|
|
case null => 1
|
|
|
|
|
case i => scala.util.Try(i.toInt).getOrElse(1)
|
|
|
|
|
}) match {
|
|
|
|
|
case i if i > 0 => i
|
|
|
|
|
case _ => 1
|
|
|
|
|
}
|
2019-01-31 09:25:00 +08:00
|
|
|
import scala.language.reflectiveCalls
|
2022-10-02 13:58:37 +08:00
|
|
|
|
|
|
|
|
// bridge.runInParallel(
|
|
|
|
|
// sourcePath,
|
|
|
|
|
// bufferLog,
|
|
|
|
|
// args.toArray,
|
|
|
|
|
// launchOpts.toArray,
|
|
|
|
|
// callback,
|
|
|
|
|
// scalaVersion,
|
|
|
|
|
// sbtVersion,
|
|
|
|
|
// classpath.toArray,
|
|
|
|
|
// instances
|
|
|
|
|
// )
|
2019-01-31 09:25:00 +08:00
|
|
|
bridge.runInParallel(
|
|
|
|
|
sourcePath,
|
|
|
|
|
bufferLog,
|
|
|
|
|
args.toArray,
|
2022-10-02 13:58:37 +08:00
|
|
|
launcherJar,
|
|
|
|
|
"java",
|
2019-01-31 09:25:00 +08:00
|
|
|
launchOpts.toArray,
|
|
|
|
|
callback,
|
2026-01-24 10:22:50 +08:00
|
|
|
instances,
|
|
|
|
|
keepTempDirectory
|
2019-01-31 09:25:00 +08:00
|
|
|
)
|
|
|
|
|
} catch { case ite: InvocationTargetException => throw ite.getCause }
|
|
|
|
|
} finally {
|
|
|
|
|
Thread.currentThread.setContextClassLoader(initLoader)
|
|
|
|
|
}
|
2014-12-18 12:38:10 +08:00
|
|
|
}
|
2015-06-20 01:40:10 +08:00
|
|
|
}
|