Fix #3013: ScriptedPlugin: Add support for paginated tests

This was already supported in the internal Scripted used by sbt but not
in the ScriptedPlugin. This is fixed by just copy-pasting the modified
parser. We will have to wait for sbt itself to be built using an sbt
with the upgraded ScriptedPlugin to be able to avoid the code duplication.
This commit is contained in:
Guillaume Martres 2017-03-14 16:53:06 +01:00
parent a7413f6415
commit 052face8ee
3 changed files with 53 additions and 8 deletions

View File

@ -0,0 +1,12 @@
### Improvements
- ScriptedPlugin: Add the ability to paginate scripted tests.
It is now possible to run a subset of scripted tests in a directory at once,
for example:
```
scripted source-dependencies/*1of3
```
Will create three pages and run page 1. This is especially useful when running
scripted tests on a CI, to benefit from the available parallelism.
[3013]: https://github.com/sbt/sbt/pull/3013
[@smarter]: https://github.com/smarter

View File

@ -19,6 +19,8 @@ object Scripted {
import DefaultParsers._
// Paging, 1-index based.
case class ScriptedTestPage(page: Int, total: Int)
// FIXME: Duplicated with ScriptedPlugin.scriptedParser, this can be
// avoided once we upgrade build.properties to 0.13.14
def scriptedParser(scriptedBase: File): Parser[Seq[String]] =
{
val scriptedFiles: NameFilter = ("test": NameFilter) | "pending"

View File

@ -38,20 +38,51 @@ object ScriptedPlugin extends Plugin {
m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[Array[String]], classOf[File], classOf[Array[String]])
}
private def scriptedParser(scriptedBase: File): Parser[Seq[String]] =
import DefaultParsers._
case class ScriptedTestPage(page: Int, total: Int)
private[sbt] def scriptedParser(scriptedBase: File): Parser[Seq[String]] =
{
import DefaultParsers._
val pairs = (scriptedBase * AllPassFilter * AllPassFilter * "test").get map { (f: File) =>
val scriptedFiles: NameFilter = ("test": NameFilter) | "pending"
val pairs = (scriptedBase * AllPassFilter * AllPassFilter * scriptedFiles).get map { (f: File) =>
val p = f.getParentFile
(p.getParentFile.getName, p.getName)
}
val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet)
val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet);
val id = charClass(c => !c.isWhitespace && c != '/').+.string
val groupP = token(id.examples(pairMap.keySet)) <~ token('/')
def nameP(group: String) = token("*".id | id.examples(pairMap(group)))
val testID = for (group <- groupP; name <- nameP(group)) yield (group, name)
(token(Space) ~> matched(testID)).*
val groupP = token(id.examples(pairMap.keySet.toSet)) <~ token('/')
// A parser for page definitions
val pageP: Parser[ScriptedTestPage] = ("*" ~ NatBasic ~ "of" ~ NatBasic) map {
case _ ~ page ~ _ ~ total => ScriptedTestPage(page, total)
}
// Grabs the filenames from a given test group in the current page definition.
def pagedFilenames(group: String, page: ScriptedTestPage): Seq[String] = {
val files = pairMap(group).toSeq.sortBy(_.toLowerCase)
val pageSize = files.size / page.total
// 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)
}
def nameP(group: String) = {
token("*".id | id.examples(pairMap(group)))
}
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
//if !files.isEmpty
} yield files map (f => group + '/' + f)
val testID = (for (group <- groupP; name <- nameP(group)) yield (group, name))
val testIdAsGroup = matched(testID) map (test => Seq(test))
//(token(Space) ~> matched(testID)).*
(token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten)
}
def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask {