diff options
author | Jesse Luehrs <doy@tozt.net> | 2013-02-22 02:21:24 -0600 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2013-02-22 02:21:24 -0600 |
commit | d09626ef05076145ad66b657bfa3d37cabddfd53 (patch) | |
tree | 4dc6134cad285289ca25cfaa4fdfd24e96db33e8 /src | |
parent | b8012cce2cf68d2f7c94a42dba4c0f5074e55c1d (diff) | |
download | scala-test-more-d09626ef05076145ad66b657bfa3d37cabddfd53.tar.gz scala-test-more-d09626ef05076145ad66b657bfa3d37cabddfd53.zip |
tap parser
Diffstat (limited to 'src')
-rw-r--r-- | src/main/scala/org/perl8/test/tap/Consumer.scala | 142 | ||||
-rw-r--r-- | src/test/scala/org/perl8/test/tap/ConsumerTest.scala | 123 |
2 files changed, 265 insertions, 0 deletions
diff --git a/src/main/scala/org/perl8/test/tap/Consumer.scala b/src/main/scala/org/perl8/test/tap/Consumer.scala new file mode 100644 index 0000000..7a0f7aa --- /dev/null +++ b/src/main/scala/org/perl8/test/tap/Consumer.scala @@ -0,0 +1,142 @@ +package org.perl8.test.tap + +import org.perl8.test.Utils._ + +import scala.util.parsing.combinator._ +import java.io.OutputStream + +object Consumer { + def parse (input: String): TAPResult = { + import TAPParser.{parseAll,tap,Success,NoSuccess} + + parseAll(tap(), input) match { + case Success(result, _) => result + case failure: NoSuccess => throw new ParseException(failure.msg) + } + } + + def parse (input: OutputStream): TAPResult = + parse(input.toString) + + private object TAPParser extends RegexParsers { + def tap (indent: String = ""): Parser[TAPResult] = + planFirst(indent) | planLast(indent) + + def planFirst (indent: String): Parser[TAPResult] = + (line(plan, indent) ~ rep(line(result(indent), indent))) ^^ { + case plan ~ results => new TAPResult(plan, results) + } + + def planLast (indent: String): Parser[TAPResult] = + (rep(line(result(indent), indent)) ~ line(plan, indent)) ^^ { + case results ~ plan => new TAPResult(plan, results) + } + + def comment: Parser[String] = + """#[^\n]*""".r + + def plan: Parser[Plan] = + (planValue <~ ws) ~ opt(planDirective) ^^ { + case planValue ~ Some(SkipDirective(d)) => new SkipAll(d) + case planValue ~ None => new NumericPlan(planValue) + } + + def result (indent: String): Parser[TestResult] = + simpleResult | subtestResult(indent) + + def simpleResult: Parser[TestResult] = + (ok <~ ws) ~ + (testNumber <~ ws) ~ + (testDescription <~ ws) ~ + opt(testDirective) ^^ { + case ok ~ testNumber ~ testDescription ~ testDirective => + new TestResult(ok, testNumber, testDescription, testDirective, None) + } + + def subtestResult (indent: String): Parser[TestResult] = + (new Parser[TAPResult] { + def apply (in: Input) = { + val source = in.source + val offset = in.offset + val str = source.subSequence(offset, source.length) + val newIndent = ws.findPrefixMatchOf(str).getOrElse("").toString + newIndent match { + case "" => Failure("subtests must be indented", in) + case _ => { + tap(indent + newIndent)(in.drop(-indent.length)) + } + } + } + } <~ indent <~ rep(comment ~ "\n" ~ indent)) ~ simpleResult ^^ { + case tapResult ~ testResult => + new TestResult( + testResult.passed, + testResult.number, + testResult.description, + testResult.directive, + Some(tapResult) + ) + } + + def planValue: Parser[Int] = + "1.." ~> """\d+""".r ^^ { _.toInt } + + def planDirective: Parser[Directive] = + skipDirective + + def ok: Parser[Boolean] = + opt("not ") <~ "ok" ^^ { _.isEmpty } + + def testNumber: Parser[Int] = + """\d+""".r ^^ { _.toInt } + + def testDescription: Parser[String] = + """[^#\n]*""".r ^^ { _.trim } + + def testDirective: Parser[Directive] = + todoDirective | skipDirective + + def skipDirective: Parser[Directive] = + "#" ~> ws ~> """(?i:skip)""".r ~> ws ~> opt("""[^\n]*""".r) ^^ { + case desc => new SkipDirective(desc.map(s => s.trim)) + } + + def todoDirective: Parser[Directive] = + "#" ~> ws ~> """(?i:todo)""".r ~> ws ~> opt("""[^\n]*""".r) ^^ { + case desc => new TodoDirective(desc.map(s => s.trim)) + } + + def line[T] (p: => Parser[T], indent: String): Parser[T] = + rep(indent ~ comment ~ "\n") ~> + indent ~> p <~ "\n" <~ + rep(indent ~ comment ~ "\n") + + override def skipWhitespace = false + + val ws = """[ \t]*""".r + } +} + +trait Directive { + val message: Option[String] +} +case class SkipDirective ( + override val message: Option[String] +) extends Directive +case class TodoDirective ( + override val message: Option[String] +) extends Directive + +case class TestResult ( + val passed: Boolean, + val number: Int, + val description: String, + val directive: Option[Directive], + val subtest: Option[TAPResult] +) + +class TAPResult (val plan: Plan, val results: Seq[TestResult]) + +case class ParseException ( + val message: String +) extends RuntimeException(message) diff --git a/src/test/scala/org/perl8/test/tap/ConsumerTest.scala b/src/test/scala/org/perl8/test/tap/ConsumerTest.scala new file mode 100644 index 0000000..7471a8c --- /dev/null +++ b/src/test/scala/org/perl8/test/tap/ConsumerTest.scala @@ -0,0 +1,123 @@ +package org.perl8.test.tap + +import org.scalatest.FunSuite + +import org.perl8.test.Utils._ + +class ConsumerTest extends FunSuite { + test ("basic") { + val tap = + "1..1\n" + + "ok 1\n" + + val result = Consumer.parse(tap) + assert(result.plan === NumericPlan(1)) + assert(result.results.map(_.passed) === Seq(true)) + } + + test ("skip all") { + val tap = + "1..0 # SKIP nope\n" + + val result = Consumer.parse(tap) + assert(result.plan === SkipAll("nope")) + assert(result.results === Seq()) + } + + test ("more complicated") { + val tap = + "# starting...\n" + + "ok 1 - stuff\n" + + "not ok 2 - does this work?\n" + + "not ok 3 - eventually # TODO doesn't work yet\n" + + "# skipping some stuff\n" + + "ok 4 # skip don't do this yet\n" + + "# finished!\n" + + "1..4\n" + + "# Looks like you failed 1 test of 4.\n" + + val result = Consumer.parse(tap) + assert(result.plan === NumericPlan(4)) + assert(result.results.map(_.passed) === Seq(true, false, false, true)) + assert(result.results.map(_.number) === Seq(1, 2, 3, 4)) + assert( + result.results.map(_.description) === Seq( + "- stuff", + "- does this work?", + "- eventually", + "" + ) + ) + assert( + result.results.map(_.directive) === Seq( + None, + None, + Some(TodoDirective(Some("doesn't work yet"))), + Some(SkipDirective(Some("don't do this yet"))) + ) + ) + } + + test ("subtests") { + val tap = + "ok 1 - not subtest\n" + + " ok 1 - passed\n" + + " not ok 2 - failed\n" + + " ok 3 - passed again\n" + + " 1..1\n" + + " ok 1 - sub-sub-test\n" + + " ok 4 - nested subtests\n" + + " 1..4\n" + + " # Looks like you failed 1 test of 4.\n" + + "not ok 2 - subtest\n" + + "1..2\n" + + "# Looks like you failed 1 test of 2.\n" + + val result = Consumer.parse(tap) + assert(result.plan === NumericPlan(2)) + assert(result.results.map(_.passed) === Seq(true, false)) + assert(result.results.map(_.number) === Seq(1, 2)) + assert( + result.results.map(_.description) === Seq( + "- not subtest", + "- subtest" + ) + ) + assert(result.results.map(_.directive) === Seq(None, None)) + + assert(result.results(0).subtest === None) + assert(result.results(1).subtest.isDefined) + + val subtest = result.results(1).subtest.get + assert(subtest.plan === NumericPlan(4)) + assert(subtest.results.map(_.passed) === Seq(true, false, true, true)) + assert(subtest.results.map(_.number) === Seq(1, 2, 3, 4)) + assert( + subtest.results.map(_.description) === Seq( + "- passed", + "- failed", + "- passed again", + "- nested subtests" + ) + ) + assert(subtest.results.map(_.directive) === Seq(None, None, None, None)) + + assert(subtest.results(0).subtest === None) + assert(subtest.results(1).subtest === None) + assert(subtest.results(2).subtest === None) + assert(subtest.results(3).subtest.isDefined) + + val subsubtest = subtest.results(3).subtest.get + assert(subsubtest.plan === NumericPlan(1)) + assert(subsubtest.results.map(_.passed) === Seq(true)) + assert(subsubtest.results.map(_.number) === Seq(1)) + assert( + subsubtest.results.map(_.description) === Seq( + "- sub-sub-test" + ) + ) + assert(subsubtest.results.map(_.directive) === Seq(None)) + + assert(subsubtest.results(0).subtest === None) + } +} |