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._ import DefaultParsers._
// Paging, 1-index based. // Paging, 1-index based.
case class ScriptedTestPage(page: Int, total: Int) 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]] = def scriptedParser(scriptedBase: File): Parser[Seq[String]] =
{ {
val scriptedFiles: NameFilter = ("test": NameFilter) | "pending" 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]]) 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 val p = f.getParentFile
(p.getParentFile.getName, p.getName) (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 id = charClass(c => !c.isWhitespace && c != '/').+.string
val groupP = token(id.examples(pairMap.keySet)) <~ token('/') val groupP = token(id.examples(pairMap.keySet.toSet)) <~ token('/')
def nameP(group: String) = token("*".id | id.examples(pairMap(group)))
val testID = for (group <- groupP; name <- nameP(group)) yield (group, name) // A parser for page definitions
(token(Space) ~> matched(testID)).* 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 { def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask {