-
Notifications
You must be signed in to change notification settings - Fork 75
/
XCTestLogs.swift
143 lines (128 loc) · 3.23 KB
/
XCTestLogs.swift
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
141
142
143
import Benchmark
import Parsing
/// This benchmark demonstrates how to build process a dump of Xcode test logs to transform them
/// in an array of test failures and passes.
let xcodeLogsSuite = BenchmarkSuite(name: "Xcode Logs") { suite in
var output: [TestResult]!
suite.benchmark("Parser") {
var input = xcodeLogs[...].utf8
output = try TestResults().parse(&input)
} tearDown: {
precondition(output.count == 284)
}
}
private struct TestCaseFinishedLine: Parser {
var body: some Parser<Substring.UTF8View, Double> {
Skip {
PrefixThrough(" (".utf8)
}
Double.parser()
" seconds).\n".utf8
}
}
private struct TestCaseStartedLine: Parser {
var body: some Parser<Substring.UTF8View, Substring> {
Skip {
PrefixUpTo("Test Case '-[".utf8)
}
PrefixThrough("\n".utf8)
.map { line in line.split(separator: .init(ascii: " "))[3].dropLast(2) }
.map(Substring.init)
}
}
private struct FileName: Parser {
var body: some Parser<Substring.UTF8View, Substring> {
"/".utf8
PrefixThrough(".swift".utf8)
.compactMap { $0.split(separator: .init(ascii: "/")).last }
.map(Substring.init)
}
}
private struct _TestCaseBody: Parser {
var body: some Parser<Substring.UTF8View, (Substring, Int, Substring)> {
FileName()
":".utf8
Int.parser()
Skip {
PrefixThrough("] : ".utf8)
}
Rest().map(Substring.init)
}
}
struct TestCaseBody: Parser {
func parse(
_ input: inout Substring.UTF8View
) throws -> (file: Substring, line: Int, message: Substring) {
guard input.first == .init(ascii: "/")
else { throw ParsingError() }
var slashCount = 0
let filePathPrefix = input.prefix { codeUnit in
if codeUnit == .init(ascii: "/") {
slashCount += 1
}
return slashCount != 3
}
input.removeFirst(filePathPrefix.count)
do {
var failure = try OneOf {
PrefixUpTo(filePathPrefix)
PrefixUpTo("Test Case '-[".utf8)
}
.parse(&input)
failure.removeLast() // trailing newline
let output = try _TestCaseBody().parse(&failure)
return output
} catch {
throw error
}
}
}
enum TestResult {
case failed(
failureMessage: Substring,
file: Substring,
line: Int,
testName: Substring,
time: Double
)
case passed(testName: Substring, time: Double)
}
private struct TestFailed: Parser {
var body: some Parser<Substring.UTF8View, [TestResult]> {
Parse { testName, bodyData, time in
bodyData.map { body in
TestResult.failed(
failureMessage: body.2,
file: body.0,
line: body.1,
testName: testName,
time: time
)
}
} with: {
TestCaseStartedLine()
Many(1...) { TestCaseBody() }
TestCaseFinishedLine()
}
}
}
private struct TestPassed: Parser {
var body: some Parser<Substring.UTF8View, [TestResult]> {
Parse {
[TestResult.passed(testName: $0, time: $1)]
} with: {
TestCaseStartedLine()
TestCaseFinishedLine()
}
}
}
private struct TestResults: Parser {
var body: some Parser<Substring.UTF8View, [TestResult]> {
Many(into: [TestResult](), +=) {
OneOf {
TestFailed()
TestPassed()
}
}
}
}