summaryrefslogtreecommitdiff
path: root/project
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2016-02-06 23:01:05 +1000
committerJason Zaugg <jzaugg@gmail.com>2016-02-06 23:01:05 +1000
commitfcaa3ab36f934ccd1b6cd6098ed62fdf49981dbc (patch)
treedd9abf0fd5444157b4c07313a7094746f41ced46 /project
parentb51fe3d4d01405a0bcfc586af337736cfe4e0478 (diff)
downloadscala-fcaa3ab36f934ccd1b6cd6098ed62fdf49981dbc.tar.gz
scala-fcaa3ab36f934ccd1b6cd6098ed62fdf49981dbc.tar.bz2
scala-fcaa3ab36f934ccd1b6cd6098ed62fdf49981dbc.zip
Add tab completion to the partest command
We can complete partest options (I've excluded some that aren't relevant in SBT), as well as test file names. If `--srcpath scaladoc` is included, completion of test paths will be based on `test/scaladoc` rather than the default `test/files`. Note that the `--srcpath` option is currently broken via scala partest interface, this change to scala-partest is needed to make it work: https://github.com/scala/scala-partest/pull/49 I've also hijacked the `--grep` option with logic in the SBT command itself, rather than passing this to `partest`. Just like `./bin/partest-ack`, this looks for either test file names or regex matches within the contents of test, check, or flag files. I tried for some time to make the tab completion of thousands of filenames more user friendly, but wasn't able to get something working. Ideally, it should only suggest to `test/files/{pos, neg, ...}` on the first <TAB>, and then offer files on another TAB. Files should also be offered if a full directory has been entered. Hopefully a SBT parser guru will step in and add some polish here.
Diffstat (limited to 'project')
-rw-r--r--project/PartestUtil.scala82
1 files changed, 82 insertions, 0 deletions
diff --git a/project/PartestUtil.scala b/project/PartestUtil.scala
new file mode 100644
index 0000000000..7efe6185e5
--- /dev/null
+++ b/project/PartestUtil.scala
@@ -0,0 +1,82 @@
+import sbt._
+import sbt.complete._, Parser._, Parsers._
+
+object PartestUtil {
+ private case class TestFiles(srcPath: String, globalBase: File, testBase: File) {
+ private val testCaseDir = new SimpleFileFilter(f => f.isDirectory && f.listFiles.nonEmpty && !(f.getParentFile / (f.name + ".res")).exists)
+ private def testCaseFinder = (testBase / srcPath).*(AllPassFilter).*(GlobFilter("*.scala") | GlobFilter("*.java") | GlobFilter("*.res") || testCaseDir)
+ private val basePaths = allTestCases.map(_._2.split('/').take(3).mkString("/") + "/").distinct
+
+ def allTestCases = testCaseFinder.pair(relativeTo(globalBase))
+ def basePathExamples = new FixedSetExamples(basePaths)
+ }
+ /** A parser for the custom `partest` command */
+ def partestParser(globalBase: File, testBase: File): Parser[String] = {
+ val knownUnaryOptions = List(
+ "--pos", "--neg", "--run", "--jvm", "--res", "--ant", "--scalap", "--specialized",
+ "--scalacheck", "--instrumented", "--presentation", "--failed", "--update-check",
+ "--show-diff", "--verbose", "--terse", "--debug", "--version", "--self-test", "--help")
+ val srcPathOption = "--srcpath"
+ val grepOption = "--grep"
+
+ // HACK: if we parse `--srpath scaladoc`, we overwrite this var. The parser for test file paths
+ // then lazily creates the examples based on the current value.
+ // TODO is there a cleaner way to do this with SBT's parser infrastructure?
+ var srcPath = "files"
+ var _testFiles: TestFiles = null
+ def testFiles = {
+ if (_testFiles == null || _testFiles.srcPath != srcPath) _testFiles = new TestFiles(srcPath, globalBase, testBase)
+ _testFiles
+ }
+ val TestPathParser = token(NotSpace & not('-' ~> any.*, "File name cannot start with '-'."), TokenCompletions.fixed({
+ (seen, level) =>
+ val suggestions = testFiles.allTestCases.map(_._2).filter(_.startsWith(seen)).map(_.stripPrefix(seen))
+ Completions.strict(suggestions.map(s => Completion.token(seen, s)).toSet)
+ }))
+
+ // allow `--grep "is unchecked" | --grep *t123*, in the spirit of ./bin/partest-ack
+ // superset of the --grep built into partest itself.
+ val Grep = {
+ def expandGrep(x: String): Seq[String] = {
+ val matchingFileContent = try {
+ val Pattern = ("(?i)" + x).r
+ testFiles.allTestCases.filter {
+ case (testFile, testPath) =>
+ val assocFiles = List(".check", ".flags").map(testFile.getParentFile / _)
+ val sourceFiles = if (testFile.isFile) List(testFile) else testFile.**(AllPassFilter).get.toList
+ val allFiles = testFile :: assocFiles ::: sourceFiles
+ allFiles.exists { f => f.exists && f.isFile && Pattern.findFirstIn(IO.read(f)).isDefined }
+ }
+ } catch {
+ case _: Throwable => Nil
+ }
+ val matchingFileName = try {
+ val filter = GlobFilter("*" + x + "*")
+ testFiles.allTestCases.filter(x => filter.accept(x._1.name))
+ } catch {
+ case t: Throwable => Nil
+ }
+ (matchingFileContent ++ matchingFileName).map(_._2).distinct.sorted
+ }
+
+ val completion = Completions.strict(Set("<filename glob>", "<regex> (for source, flags or checkfile contents)").map(s => Completion.displayOnly(s)))
+ val tokenCompletion = TokenCompletions.fixed((seen, level) => completion)
+
+ val globOrPattern = StringBasic.map(expandGrep).flatMap {
+ case Seq() => failure("no tests match pattern / glob")
+ case x => success(x.mkString(" "))
+ }
+ ((token(grepOption <~ Space)) ~> token(globOrPattern, tokenCompletion))
+ }
+
+ val SrcPath = ((token(srcPathOption) <~ Space) ~ token(StringBasic.examples(Set("files", "pending", "scaladoc")))) map {
+ case opt ~ path =>
+ srcPath = path
+ opt + " " + path
+ }
+ // allow the user to additional abitrary arguments, in case our parser is incomplete.
+ val WhatEver = token(NotSpace, _ => true).filter(x => !knownUnaryOptions.contains(x) && !Set(grepOption, srcPathOption).contains(x), x => x)
+ val P = oneOf(knownUnaryOptions.map(x => token(x))) | SrcPath | TestPathParser | Grep //| WhatEver
+ (Space ~> repsep(P, oneOrMore(Space))).map(_.mkString(" "))
+ }
+}