aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2013-02-26 20:36:13 -0600
committerJesse Luehrs <doy@tozt.net>2013-02-26 20:36:13 -0600
commit2f6007fbe3131b24c22f139dbd4d317b668aef9b (patch)
tree5ea7ad5a1f88dcbd9c7b8a7f0e582dc7b9762563
parent040c5b9d27423c9831fc9e15e64a3659a2a51b35 (diff)
downloadscala-test-more-2f6007fbe3131b24c22f139dbd4d317b668aef9b.tar.gz
scala-test-more-2f6007fbe3131b24c22f139dbd4d317b668aef9b.zip
split out the tap stream parser from the individual line parsers
-rw-r--r--src/main/scala/org/perl8/test/harness/SummaryReporter.scala4
-rw-r--r--src/main/scala/org/perl8/test/sbt/SBTReporter.scala2
-rw-r--r--src/main/scala/org/perl8/test/tap/Consumer.scala383
-rw-r--r--src/main/scala/org/perl8/test/tap/Parser.scala193
-rw-r--r--src/test/scala/org/perl8/test/ExtensionTest.scala4
-rw-r--r--src/test/scala/org/perl8/test/TestMoreTest.scala4
-rw-r--r--src/test/scala/org/perl8/test/tap/ParserTest.scala (renamed from src/test/scala/org/perl8/test/tap/ConsumerTest.scala)11
7 files changed, 293 insertions, 308 deletions
diff --git a/src/main/scala/org/perl8/test/harness/SummaryReporter.scala b/src/main/scala/org/perl8/test/harness/SummaryReporter.scala
index 7c343c3..ddeeb43 100644
--- a/src/main/scala/org/perl8/test/harness/SummaryReporter.scala
+++ b/src/main/scala/org/perl8/test/harness/SummaryReporter.scala
@@ -3,7 +3,7 @@ package org.perl8.test.harness
import java.io.ByteArrayOutputStream
import org.perl8.test.tap
-import org.perl8.test.tap.{TAPResult,TodoDirective}
+import org.perl8.test.tap.Consumer.{TAPResult,TodoDirective}
import org.perl8.test.Test
class SummaryReporter extends MultiTestReporter {
@@ -25,7 +25,7 @@ class SummaryReporter extends MultiTestReporter {
Console.withOut(out) {
test.run
}
- val result = tap.Consumer.parse(out)
+ val result = (new tap.Parser).parse(out)
if (result.success) {
println("ok")
diff --git a/src/main/scala/org/perl8/test/sbt/SBTReporter.scala b/src/main/scala/org/perl8/test/sbt/SBTReporter.scala
index 7b9c65a..5da3a82 100644
--- a/src/main/scala/org/perl8/test/sbt/SBTReporter.scala
+++ b/src/main/scala/org/perl8/test/sbt/SBTReporter.scala
@@ -20,7 +20,7 @@ class SBTReporter (
test.run
}
- val result = tap.Consumer.parse(out)
+ val result = (new tap.Parser).parse(out)
result.results.foreach { r =>
val event = new Event {
diff --git a/src/main/scala/org/perl8/test/tap/Consumer.scala b/src/main/scala/org/perl8/test/tap/Consumer.scala
index db4dbf9..49f642d 100644
--- a/src/main/scala/org/perl8/test/tap/Consumer.scala
+++ b/src/main/scala/org/perl8/test/tap/Consumer.scala
@@ -1,223 +1,70 @@
package org.perl8.test.tap
-import java.io.{ByteArrayInputStream,InputStream,OutputStream}
-import scala.annotation.tailrec
-import scala.io.Source
-import scala.util.parsing.combinator._
-import scala.util.parsing.input.{Position,Reader}
-
import org.perl8.test.{Plan,NumericPlan,SkipAll}
object Consumer {
- def parse (input: InputStream, cb: TAPEvent => Unit): TAPResult =
- consumer(cb).parse(input)
-
- def parse (input: InputStream): TAPResult =
- consumer().parse(input)
-
- def parse (input: String, cb: TAPEvent => Unit): TAPResult =
- consumer(cb).parse(new ByteArrayInputStream(input.getBytes))
-
- def parse (input: String): TAPResult =
- consumer().parse(new ByteArrayInputStream(input.getBytes))
-
- // XXX should be able to make a streaming input stream out of this
- def parse (input: OutputStream, cb: TAPEvent => Unit): TAPResult =
- consumer(cb).parse(new ByteArrayInputStream(input.toString.getBytes))
-
- def parse (input: OutputStream): TAPResult =
- consumer().parse(new ByteArrayInputStream(input.toString.getBytes))
-
- private def consumer (cb: TAPEvent => Unit = e => ()) =
- new Consumer(cb)
-}
-
-class Consumer (cb: TAPEvent => Unit) {
- def parse (input: InputStream): TAPResult = {
- import TAPParser.{tap,Success,NoSuccess}
-
- tap(new LineReader(input)) match {
- case Success(result, _) => result
- case failure: NoSuccess => throw new ParseException(failure.msg)
- }
- }
-
- private object TAPParser extends Parsers {
- type Elem = Line
-
- def tap: Parser[TAPResult] =
- planFirst | planLast
-
- private def planFirst: Parser[TAPResult] =
- plan ~ rep(result) ^^ { case plan ~ results =>
- new TAPResult(plan, results)
+ def parseLine (line: String): Line = {
+ commentRx.findFirstMatchIn(line).map { m =>
+ m.subgroups match {
+ case Seq(indent, text) => new CommentLine(text, indent)
}
-
- private def planLast: Parser[TAPResult] =
- rep(result) ~ plan ^^ { case results ~ plan =>
- new TAPResult(plan, results)
- }
-
- private def plan: Parser[Plan] =
- planLine ^^ { _.plan }
-
- private def result: Parser[TestResult] =
- simpleResult | subtestResult
-
- private def simpleResult: Parser[TestResult] =
- resultLine ^^ { _.result }
-
- private def subtestResult: Parser[TestResult] =
- subtest ~ simpleResult ^^ { case subtest ~ simpleResult =>
- new TestResult(
- simpleResult.passed,
- simpleResult.number,
- simpleResult.description,
- simpleResult.directive,
- Some(subtest)
- )
- }
-
- private def subtest: Parser[TAPResult] = LineParser("subtest") { in =>
- val oldIndent = expectedIndent
- val newIndent = in.first.indent
-
- try {
- expectedIndent = newIndent
- tap(in)
- }
- finally {
- expectedIndent = oldIndent
- }
- }
-
- private def planLine: Parser[PlanLine] = LineParser("plan") { in =>
- val line = in.first
- if (line.indent == expectedIndent) {
- line match {
- case p: PlanLine =>
- Success(p, in.rest)
- case _ =>
- Failure("Plan line expected, but '" + line + "' found", in)
- }
- }
- else {
- Failure(
- "Plan line expected, but " +
- "'" + line + "' has incorrect indentation",
- in
- )
- }
- }
-
- private def resultLine: Parser[ResultLine] = LineParser("result") { in =>
- val line = in.first
- if (line.indent == expectedIndent) {
- line match {
- case p: ResultLine =>
- Success(p, in.rest)
- case _ =>
- Failure("Result line expected, but '" + line + "' found", in)
+ }.getOrElse {
+ planRx.findFirstMatchIn(line).map { m =>
+ m.subgroups match {
+ case Seq(indent, p, null) =>
+ new PlanLine(NumericPlan(p.toInt), indent)
+ case Seq(indent, _, skip) =>
+ new PlanLine(SkipAll(skip), indent)
}
- }
- else {
- Failure(
- "Result line expected, but " +
- "'" + line + "' has incorrect indentation",
- in
- )
- }
- }
-
- private def LineParser[T] (
- lineType: String
- )(
- body: Input => ParseResult[T]
- ): Parser[T] = {
- new Parser[T] {
- def apply (in: Input): ParseResult[T] = {
- if (in.atEnd) {
- Failure(lineType + " line expected, but end of input found", in)
+ }.getOrElse {
+ resultRx.findFirstMatchIn(line).map { m =>
+ val indent = m.group(1)
+ val passed = m.group(2) == null
+ val number = m.group(3).toInt
+ val description = m.group(4) match {
+ case null => ""
+ case s => s.trim
}
- else {
- body(in)
+ val directive = (m.group(5), m.group(6)) match {
+ case (null, null) => None
+ case (d, r) => {
+ val reason = if (r == null) "" else r
+ """(?i:skip)""".r.findFirstIn(d) match {
+ case Some(_) => Some(new SkipDirective(Some(reason)))
+ case None => Some(new TodoDirective(Some(reason)))
+ }
+ }
}
+ val result = new TestResult(
+ passed,
+ number,
+ description,
+ directive,
+ None
+ )
+ new ResultLine(result, indent)
+ }.getOrElse {
+ throw ParseException("Couldn't parse line: " + line)
}
}
}
-
- private var expectedIndent = ""
}
- private sealed trait Line {
+ sealed trait Line {
def contents: String
def indent: String
override def toString: String =
indent + contents
}
- private object Line {
- def apply (line: String): Line = {
- commentRx.findFirstMatchIn(line).map { m =>
- m.subgroups match {
- case Seq(indent, text) => new CommentLine(text, indent)
- }
- }.getOrElse {
- planRx.findFirstMatchIn(line).map { m =>
- m.subgroups match {
- case Seq(indent, p, null) =>
- new PlanLine(NumericPlan(p.toInt), indent)
- case Seq(indent, _, skip) =>
- new PlanLine(SkipAll(skip), indent)
- }
- }.getOrElse {
- resultRx.findFirstMatchIn(line).map { m =>
- val indent = m.group(1)
- val passed = m.group(2) == null
- val number = m.group(3).toInt
- val description = m.group(4) match {
- case null => ""
- case s => s.trim
- }
- val directive = (m.group(5), m.group(6)) match {
- case (null, null) => None
- case (d, r) => {
- val reason = if (r == null) "" else r
- """(?i:skip)""".r.findFirstIn(d) match {
- case Some(_) => Some(new SkipDirective(Some(reason)))
- case None => Some(new TodoDirective(Some(reason)))
- }
- }
- }
- val result = new TestResult(
- passed,
- number,
- description,
- directive,
- None
- )
- new ResultLine(result, indent)
- }.getOrElse {
- throw ParseException("Couldn't parse line: " + line)
- }
- }
- }
- }
-
- private val commentRx = """^(\s*)#\s*(.*)""".r
- private val planRx = """^(\s*)1..(\d+)\s*(?:# SKIP (.*))?""".r
- private val resultRx =
- """^(\s*)(not )?ok (\d+)\s*([^#]+)?(?:#\s*(?i:(skip|todo))\s+(.*))?""".r
- }
-
- private case class CommentLine (
+ case class CommentLine (
val text: String,
override val indent: String
) extends Line {
def contents = "# " + text
}
- private case class PlanLine (
+ case class PlanLine (
val plan: Plan,
override val indent: String
) extends Line {
@@ -231,7 +78,7 @@ class Consumer (cb: TAPEvent => Unit) {
}
}
- private case class ResultLine(
+ case class ResultLine(
val result: TestResult,
override val indent: String
) extends Line {
@@ -252,113 +99,57 @@ class Consumer (cb: TAPEvent => Unit) {
}
}
- private class LineReader (
- in: Stream[Char],
- lineNum: Int
- ) extends Reader[Line] {
- def this (in: InputStream) =
- this(Source.fromInputStream(in).toStream, 1)
-
- def atEnd: Boolean =
- nextLine.isEmpty
-
- def first: Line =
- nextLine.getOrElse(throw new RuntimeException("read from empty input"))
-
- lazy val pos =
- new LinePosition(lineNum, nextLine.map(_.toString).getOrElse(""))
+ private val commentRx = """^(\s*)#\s*(.*)""".r
+ private val planRx = """^(\s*)1..(\d+)\s*(?:# SKIP (.*))?""".r
+ private val resultRx =
+ """^(\s*)(not )?ok (\d+)\s*([^#]+)?(?:#\s*(?i:(skip|todo))\s+(.*))?""".r
- lazy val rest: Reader[Line] =
- new LineReader(remainingStream, lineNum + 1)
-
- private def nextLine: Option[Line] =
- state._1
-
- private def remainingStream: Stream[Char] =
- state._2
-
- private lazy val state: (Option[Line], Stream[Char]) =
- readNextLine(in)
-
- @tailrec
- private def readNextLine (
- stream: Stream[Char]
- ): (Option[Line], Stream[Char]) = {
- stream match {
- case Stream.Empty => (None, in)
- case s => {
- val (line, rest) = s.span(_ != '\n') match {
- case (l, r) => (Line(l.mkString), r.tail)
- }
- line match {
- case _: CommentLine => readNextLine(rest)
- case other => (Some(other), rest)
- }
- }
- }
- }
- }
-
- private case class LinePosition (
- override val line: Int,
- override val lineContents: String
- ) extends Position {
- def column: Int = 1
+ sealed 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]) {
+ val correctPlan = plan match {
+ case NumericPlan(n) => results.length == n
+ case SkipAll(_) => results.length == 0
+ }
-sealed 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
+ val fails = results.count { r =>
+ !r.passed && !r.directive.isDefined
+ }
-case class TestResult (
- val passed: Boolean,
- val number: Int,
- val description: String,
- val directive: Option[Directive],
- val subtest: Option[TAPResult]
-)
+ val testsPassed = fails == 0
-class TAPResult (val plan: Plan, val results: Seq[TestResult]) {
- val correctPlan = plan match {
- case NumericPlan(n) => results.length == n
- case SkipAll(_) => results.length == 0
- }
+ val success =
+ correctPlan && testsPassed
- val fails = results.count { r =>
- !r.passed && !r.directive.isDefined
+ val exitCode =
+ if (success) {
+ 0
+ }
+ else if (!correctPlan) {
+ 255
+ }
+ else {
+ fails
+ }
}
- val testsPassed = fails == 0
-
- val success =
- correctPlan && testsPassed
-
- val exitCode =
- if (success) {
- 0
- }
- else if (!correctPlan) {
- 255
- }
- else {
- fails
- }
+ case class ParseException (
+ val message: String
+ ) extends RuntimeException(message)
}
-
-sealed trait TAPEvent
-case class ResultEvent (result: TestResult) extends TAPEvent
-case class PlanEvent (plan: Plan) extends TAPEvent
-case object SubtestStartEvent extends TAPEvent
-case class SubtestEndEvent (result: TestResult) extends TAPEvent
-case class CommentEvent (text: String) extends TAPEvent
-
-case class ParseException (
- val message: String
-) extends RuntimeException(message)
diff --git a/src/main/scala/org/perl8/test/tap/Parser.scala b/src/main/scala/org/perl8/test/tap/Parser.scala
new file mode 100644
index 0000000..8674c18
--- /dev/null
+++ b/src/main/scala/org/perl8/test/tap/Parser.scala
@@ -0,0 +1,193 @@
+package org.perl8.test.tap
+
+import java.io.{ByteArrayInputStream,InputStream,OutputStream}
+import scala.annotation.tailrec
+import scala.io.Source
+import scala.util.parsing.combinator._
+import scala.util.parsing.input.{Position,Reader}
+
+import org.perl8.test.Plan
+import org.perl8.test.tap.Consumer._
+
+class Parser (cb: TAPEvent => Unit) extends Parsers {
+ type Elem = Line
+
+ def this () =
+ this(e => ())
+
+ def parse (input: InputStream): TAPResult =
+ tap(new LineReader(input)) match {
+ case Success(result, _) => result
+ case failure: NoSuccess => throw new ParseException(failure.msg)
+ }
+
+ def parse (input: String): TAPResult =
+ parse(new ByteArrayInputStream(input.getBytes))
+
+ def parse (input: OutputStream): TAPResult =
+ parse(input.toString)
+
+ def tap: Parser[TAPResult] =
+ planFirst | planLast
+
+ private def planFirst: Parser[TAPResult] =
+ plan ~ rep(result) ^^ { case plan ~ results =>
+ new TAPResult(plan, results)
+ }
+
+ private def planLast: Parser[TAPResult] =
+ rep(result) ~ plan ^^ { case results ~ plan =>
+ new TAPResult(plan, results)
+ }
+
+ private def plan: Parser[Plan] =
+ planLine ^^ { _.plan }
+
+ private def result: Parser[TestResult] =
+ simpleResult | subtestResult
+
+ private def simpleResult: Parser[TestResult] =
+ resultLine ^^ { _.result }
+
+ private def subtestResult: Parser[TestResult] =
+ subtest ~ simpleResult ^^ { case subtest ~ simpleResult =>
+ new TestResult(
+ simpleResult.passed,
+ simpleResult.number,
+ simpleResult.description,
+ simpleResult.directive,
+ Some(subtest)
+ )
+ }
+
+ private def subtest: Parser[TAPResult] = LineParser("subtest") { in =>
+ val oldIndent = expectedIndent
+ val newIndent = in.first.indent
+
+ try {
+ expectedIndent = newIndent
+ tap(in)
+ }
+ finally {
+ expectedIndent = oldIndent
+ }
+ }
+
+ private def planLine: Parser[PlanLine] = LineParser("plan") { in =>
+ val line = in.first
+ if (line.indent == expectedIndent) {
+ line match {
+ case p: PlanLine =>
+ Success(p, in.rest)
+ case _ =>
+ Failure("Plan line expected, but '" + line + "' found", in)
+ }
+ }
+ else {
+ Failure(
+ "Plan line expected, but " +
+ "'" + line + "' has incorrect indentation",
+ in
+ )
+ }
+ }
+
+ private def resultLine: Parser[ResultLine] = LineParser("result") { in =>
+ val line = in.first
+ if (line.indent == expectedIndent) {
+ line match {
+ case p: ResultLine =>
+ Success(p, in.rest)
+ case _ =>
+ Failure("Result line expected, but '" + line + "' found", in)
+ }
+ }
+ else {
+ Failure(
+ "Result line expected, but " +
+ "'" + line + "' has incorrect indentation",
+ in
+ )
+ }
+ }
+
+ private def LineParser[T] (
+ lineType: String
+ )(
+ body: Input => ParseResult[T]
+ ): Parser[T] = {
+ new Parser[T] {
+ def apply (in: Input): ParseResult[T] = {
+ if (in.atEnd) {
+ Failure(lineType + " line expected, but end of input found", in)
+ }
+ else {
+ body(in)
+ }
+ }
+ }
+ }
+
+ private class LineReader (
+ in: Stream[Char],
+ lineNum: Int
+ ) extends Reader[Line] {
+ def this (in: InputStream) =
+ this(Source.fromInputStream(in).toStream, 1)
+
+ def atEnd: Boolean =
+ nextLine.isEmpty
+
+ def first: Line =
+ nextLine.getOrElse(throw new RuntimeException("read from empty input"))
+
+ lazy val pos =
+ new LinePosition(lineNum, nextLine.map(_.toString).getOrElse(""))
+
+ lazy val rest: Reader[Line] =
+ new LineReader(remainingStream, lineNum + 1)
+
+ private def nextLine: Option[Line] =
+ state._1
+
+ private def remainingStream: Stream[Char] =
+ state._2
+
+ private lazy val state: (Option[Line], Stream[Char]) =
+ readNextLine(in)
+
+ @tailrec
+ private def readNextLine (
+ stream: Stream[Char]
+ ): (Option[Line], Stream[Char]) = {
+ stream match {
+ case Stream.Empty => (None, in)
+ case s => {
+ val (line, rest) = s.span(_ != '\n') match {
+ case (l, r) => (parseLine(l.mkString), r.tail)
+ }
+ line match {
+ case _: CommentLine => readNextLine(rest)
+ case other => (Some(other), rest)
+ }
+ }
+ }
+ }
+ }
+
+ private case class LinePosition (
+ override val line: Int,
+ override val lineContents: String
+ ) extends Position {
+ def column: Int = 1
+ }
+
+ private var expectedIndent = ""
+}
+
+sealed trait TAPEvent
+case class ResultEvent (result: TestResult) extends TAPEvent
+case class PlanEvent (plan: Plan) extends TAPEvent
+case object SubtestStartEvent extends TAPEvent
+case class SubtestEndEvent (result: TestResult) extends TAPEvent
+case class CommentEvent (text: String) extends TAPEvent
diff --git a/src/test/scala/org/perl8/test/ExtensionTest.scala b/src/test/scala/org/perl8/test/ExtensionTest.scala
index 5a9c1ac..f5635c4 100644
--- a/src/test/scala/org/perl8/test/ExtensionTest.scala
+++ b/src/test/scala/org/perl8/test/ExtensionTest.scala
@@ -2,7 +2,7 @@ package org.perl8.test
import java.io.ByteArrayOutputStream
-import org.perl8.test.tap.Consumer
+import org.perl8.test.tap.Parser
trait NumberZero { this: TestMore =>
def is_zero (i: Int, desc: String): Boolean = hideTestMethod {
@@ -34,7 +34,7 @@ class ExtensionTest extends TestMore {
}
}
- is(Consumer.parse(out).exitCode, 2)
+ is((new Parser).parse(out).exitCode, 2)
val tap =
"ok 1 - it's zero\n" +
diff --git a/src/test/scala/org/perl8/test/TestMoreTest.scala b/src/test/scala/org/perl8/test/TestMoreTest.scala
index bee1e64..b1af5da 100644
--- a/src/test/scala/org/perl8/test/TestMoreTest.scala
+++ b/src/test/scala/org/perl8/test/TestMoreTest.scala
@@ -2,7 +2,7 @@ package org.perl8.test
import java.io.ByteArrayOutputStream
-import org.perl8.test.tap.Consumer
+import org.perl8.test.tap.Parser
class TestMoreTest extends TestMore {
val lineZero = Thread.currentThread.getStackTrace()(1).getLineNumber + 3
@@ -64,7 +64,7 @@ class TestMoreTest extends TestMore {
}
}
- is(Consumer.parse(out).exitCode, 9, "got the right plan")
+ is((new Parser).parse(out).exitCode, 9, "got the right plan")
val expected =
"# ok\n" +
diff --git a/src/test/scala/org/perl8/test/tap/ConsumerTest.scala b/src/test/scala/org/perl8/test/tap/ParserTest.scala
index e07e73d..0781399 100644
--- a/src/test/scala/org/perl8/test/tap/ConsumerTest.scala
+++ b/src/test/scala/org/perl8/test/tap/ParserTest.scala
@@ -1,14 +1,15 @@
package org.perl8.test.tap
import org.perl8.test.{TestMore,SkipAll,NumericPlan}
+import org.perl8.test.tap.Consumer.{SkipDirective,TodoDirective}
-class ConsumerTest extends TestMore {
+class ParserTest extends TestMore {
subtest ("basic") {
val tap =
"1..1\n" +
"ok 1\n"
- val result = Consumer.parse(tap)
+ val result = (new Parser).parse(tap)
is(result.plan, NumericPlan(1), "got the right plan")
is(result.results.map(_.passed), Seq(true), "got the right results")
}
@@ -17,7 +18,7 @@ class ConsumerTest extends TestMore {
val tap =
"1..0 # SKIP nope\n"
- val result = Consumer.parse(tap)
+ val result = (new Parser).parse(tap)
is(result.plan, SkipAll("nope"), "got the right plan")
is(result.results, Nil, "got the right results")
}
@@ -34,7 +35,7 @@ class ConsumerTest extends TestMore {
"1..4\n" +
"# Looks like you failed 1 test of 4.\n"
- val result = Consumer.parse(tap)
+ val result = (new Parser).parse(tap)
is(result.plan, NumericPlan(4))
is(result.results.map(_.passed), Seq(true, false, false, true))
is(result.results.map(_.number), Seq(1, 2, 3, 4))
@@ -74,7 +75,7 @@ class ConsumerTest extends TestMore {
"1..2\n" +
"# Looks like you failed 1 test of 2.\n"
- val result = Consumer.parse(tap)
+ val result = (new Parser).parse(tap)
is(result.plan, NumericPlan(2))
is(result.results.map(_.passed), Seq(true, false))
is(result.results.map(_.number), Seq(1, 2))