1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
package org.perl8.test.tap
import org.perl8.test.{Plan,NumericPlan,SkipAll}
object Consumer {
def parseLine (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)
}
}
}
}
sealed trait Line {
def contents: String
def indent: String
override def toString: String =
indent + contents
}
case class CommentLine (text: String, indent: String) extends Line {
def contents = "# " + text
}
case class PlanLine (plan: Plan, indent: String) extends Line {
def contents = {
val count = plan.plan
val comment = plan match {
case SkipAll(m) => " # SKIP " + m
case _ => ""
}
indent + "1.." + count + comment
}
}
case class ResultLine (result: TestResult, indent: String) extends Line {
def contents = {
val success = (if (result.passed) "ok" else "not ok") + " "
val number = result.number + " "
val description = result.description match {
case "" => ""
case s => s + " "
}
val directive = result.directive.map { d =>
d match {
case TodoDirective(m) => "# TODO " + m
case SkipDirective(m) => "# skip " + m
}
}.getOrElse("")
indent + success + number + description + directive
}
}
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
sealed trait Directive {
val message: Option[String]
}
case class SkipDirective (message: Option[String]) extends Directive
case class TodoDirective (message: Option[String]) extends Directive
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 _ => results.length == 0
}
val fails = results.count { r =>
!r.passed && !r.directive.isDefined
}
val testsPassed = fails == 0
val success =
correctPlan && testsPassed
val exitCode =
if (success) {
0
}
else if (!correctPlan) {
255
}
else {
fails
}
}
case class ParseException (message: String) extends RuntimeException(message)
}
|