From 334517aba85eca4d9398e76db9948d65abb04288 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 14 Mar 2017 16:53:06 +0100 Subject: [PATCH] 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. --- notes/0.13.14/paginate_scripted_tests.md | 12 +++++ project/Scripted.scala | 2 + .../src/main/scala/sbt/ScriptedPlugin.scala | 47 +++++++++++++++---- 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 notes/0.13.14/paginate_scripted_tests.md diff --git a/notes/0.13.14/paginate_scripted_tests.md b/notes/0.13.14/paginate_scripted_tests.md new file mode 100644 index 000000000..8ffdae83f --- /dev/null +++ b/notes/0.13.14/paginate_scripted_tests.md @@ -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 diff --git a/project/Scripted.scala b/project/Scripted.scala index 80b1ac8c1..4f89c4cff 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -37,6 +37,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" diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala index eae62b316..896277515 100644 --- a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala @@ -62,20 +62,51 @@ object ScriptedPlugin extends AutoPlugin { 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 {