diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/scala/org/perl8/test/harness/SummaryReporter.scala | 4 | ||||
-rw-r--r-- | src/main/scala/org/perl8/test/sbt/SBTReporter.scala | 2 | ||||
-rw-r--r-- | src/main/scala/org/perl8/test/tap/Consumer.scala | 383 | ||||
-rw-r--r-- | src/main/scala/org/perl8/test/tap/Parser.scala | 193 | ||||
-rw-r--r-- | src/test/scala/org/perl8/test/ExtensionTest.scala | 4 | ||||
-rw-r--r-- | src/test/scala/org/perl8/test/TestMoreTest.scala | 4 | ||||
-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)) |