aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2013-02-22 02:21:24 -0600
committerJesse Luehrs <doy@tozt.net>2013-02-22 02:21:24 -0600
commitd09626ef05076145ad66b657bfa3d37cabddfd53 (patch)
tree4dc6134cad285289ca25cfaa4fdfd24e96db33e8 /src
parentb8012cce2cf68d2f7c94a42dba4c0f5074e55c1d (diff)
downloadscala-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.scala142
-rw-r--r--src/test/scala/org/perl8/test/tap/ConsumerTest.scala123
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)
+ }
+}