4
4
import Foundation
5
5
import RegexBuilder
6
6
7
- protocol InteractiveWriter {
8
- func write( _ string: String )
9
- }
10
-
11
- protocol TestsParser {
12
- /// Parse the output of a test process, format it, then output in the `InteractiveWriter`.
13
- func onLine( _ line: String , _ terminal: InteractiveWriter )
14
- func finalize( _ terminal: InteractiveWriter )
15
- }
16
-
17
7
extension String . StringInterpolation {
18
8
/// Display `value` with the specified ANSI-escaped `color` values, then apply the reset.
19
9
fileprivate mutating func appendInterpolation< T> ( _ value: T , color: String ... ) {
20
10
appendInterpolation ( " \( color. map { " \u{001B} \( $0) " } . joined ( ) ) \( value) \u{001B} [0m " )
21
11
}
22
12
}
23
13
24
- class FancyTestsParser : TestsParser {
25
- init ( ) { }
14
+ class FancyTestsParser {
15
+ let write : ( String ) -> Void
26
16
27
- enum Status : Equatable {
17
+ init ( write: @escaping ( String ) -> Void ) {
18
+ self . write = write
19
+ }
20
+
21
+ private enum Status : Equatable {
28
22
case passed, failed, skipped
29
23
case unknown( String . SubSequence ? )
30
24
@@ -45,7 +39,7 @@ class FancyTestsParser: TestsParser {
45
39
}
46
40
}
47
41
48
- struct Suite {
42
+ private struct Suite {
49
43
let name : String . SubSequence
50
44
var status : Status = . unknown( nil )
51
45
@@ -76,19 +70,19 @@ class FancyTestsParser: TestsParser {
76
70
}
77
71
}
78
72
79
- var suites = [ Suite] ( )
73
+ private var suites = [ Suite] ( )
80
74
81
- let swiftIdentifier = #/[_\p{L}\p{Nl}][_\p{L}\p{Nl}\p{Mn}\p{Nd}\p{Pc}]*/#
82
- let timestamp = #/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}/#
83
- lazy var suiteStarted = Regex {
75
+ private let swiftIdentifier = #/[_\p{L}\p{Nl}][_\p{L}\p{Nl}\p{Mn}\p{Nd}\p{Pc}]*/#
76
+ private let timestamp = #/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}/#
77
+ private lazy var suiteStarted = Regex {
84
78
" Test Suite ' "
85
79
Capture {
86
80
OneOrMore ( CharacterClass . anyOf ( " ' " ) . inverted)
87
81
}
88
82
" ' started at "
89
83
Capture { self . timestamp }
90
84
}
91
- lazy var suiteStatus = Regex {
85
+ private lazy var suiteStatus = Regex {
92
86
" Test Suite ' "
93
87
Capture { OneOrMore ( CharacterClass . anyOf ( " ' " ) . inverted) }
94
88
" ' "
@@ -101,14 +95,14 @@ class FancyTestsParser: TestsParser {
101
95
" at "
102
96
Capture { self . timestamp }
103
97
}
104
- lazy var testCaseStarted = Regex {
98
+ private lazy var testCaseStarted = Regex {
105
99
" Test Case ' "
106
100
Capture { self . swiftIdentifier }
107
101
" . "
108
102
Capture { self . swiftIdentifier }
109
103
" ' started "
110
104
}
111
- lazy var testCaseStatus = Regex {
105
+ private lazy var testCaseStatus = Regex {
112
106
" Test Case ' "
113
107
Capture { self . swiftIdentifier }
114
108
" . "
@@ -130,10 +124,10 @@ class FancyTestsParser: TestsParser {
130
124
" seconds) "
131
125
}
132
126
133
- let testSummary =
127
+ private let testSummary =
134
128
#/Executed \d+ (test|tests), with (?:\d+ (?:test|tests) skipped and )?\d+ (failure|failures) \((?<unexpected>\d+) unexpected\) in (?<duration>\d+\.\d+) \(\d+\.\d+\) seconds/#
135
129
136
- func onLine( _ line: String , _ terminal : InteractiveWriter ) {
130
+ func onLine( _ line: String ) {
137
131
if let match = line. firstMatch (
138
132
of: suiteStarted
139
133
) {
@@ -145,7 +139,7 @@ class FancyTestsParser: TestsParser {
145
139
let ( _, suite, status, _) = match. output
146
140
if let suiteIdx = suites. firstIndex ( where: { $0. name == suite } ) {
147
141
suites [ suiteIdx] . status = Status ( rawValue: status)
148
- flushSingleSuite ( suites [ suiteIdx] , terminal )
142
+ flushSingleSuite ( suites [ suiteIdx] )
149
143
}
150
144
} else if let match = line. firstMatch (
151
145
of: testCaseStarted
@@ -172,86 +166,87 @@ class FancyTestsParser: TestsParser {
172
166
// do nothing
173
167
} else {
174
168
if !line. isEmpty {
175
- terminal . write ( line + " \n " )
169
+ write ( line + " \n " )
176
170
}
177
171
}
178
172
}
179
173
180
- func finalize( _ terminal: InteractiveWriter ) {
181
- terminal. write ( " \n " )
182
- flushSummary ( of: suites, terminal)
183
- }
184
-
185
- private func flushSingleSuite( _ suite: Suite , _ terminal: InteractiveWriter ) {
186
- terminal. write ( suite. statusLabel)
187
- terminal. write ( " \( suite. name) \n " )
174
+ private func flushSingleSuite( _ suite: Suite ) {
175
+ write ( suite. statusLabel)
176
+ write ( " \( suite. name) \n " )
188
177
for testCase in suite. cases {
189
- terminal . write ( " \( testCase. statusMark) " )
178
+ write ( " \( testCase. statusMark) " )
190
179
if let duration = testCase. duration {
191
- terminal
192
- . write (
180
+ write (
193
181
" \( testCase. name) \( " ( \( Int ( Double ( duration) ! * 1000 ) ) ms) " , color: " [90m " ) \n "
194
182
) // gray
195
183
}
196
184
}
197
185
}
198
186
199
- private func flushSummary( of suites: [ Suite ] , _ terminal: InteractiveWriter ) {
200
- let suitesWithCases = suites. filter { $0. cases. count > 0 }
201
-
202
- terminal. write ( " Test Suites: " )
203
- let suitesPassed = suitesWithCases. filter { $0. status == . passed } . count
204
- if suitesPassed > 0 {
205
- terminal. write ( " \( " \( suitesPassed) passed " , color: " [32m " ) , " )
206
- }
207
- let suitesSkipped = suitesWithCases. filter { $0. status == . skipped } . count
208
- if suitesSkipped > 0 {
209
- terminal. write ( " \( " \( suitesSkipped) skipped " , color: " [97m " ) , " )
210
- }
211
- let suitesFailed = suitesWithCases. filter { $0. status == . failed } . count
212
- if suitesFailed > 0 {
213
- terminal. write ( " \( " \( suitesFailed) failed " , color: " [31m " ) , " )
214
- }
215
- let suitesUnknown = suitesWithCases. filter { $0. status == . unknown( nil ) } . count
216
- if suitesUnknown > 0 {
217
- terminal. write ( " \( " \( suitesUnknown) unknown " , color: " [31m " ) , " )
187
+ func finalize( ) {
188
+ write ( " \n " )
189
+
190
+ func formatCategory(
191
+ label: String , statuses: [ Status ]
192
+ ) -> String {
193
+ var passed = 0
194
+ var skipped = 0
195
+ var failed = 0
196
+ var unknown = 0
197
+ for status in statuses {
198
+ switch status {
199
+ case . passed: passed += 1
200
+ case . skipped: skipped += 1
201
+ case . failed: failed += 1
202
+ case . unknown: unknown += 1
203
+ }
204
+ }
205
+ var result = " \( label) "
206
+ if passed > 0 {
207
+ result += " \u{001B} [32m \( passed) passed \u{001B} [0m, "
208
+ }
209
+ if skipped > 0 {
210
+ result += " \u{001B} [97m \( skipped) skipped \u{001B} [0m, "
211
+ }
212
+ if failed > 0 {
213
+ result += " \u{001B} [31m \( failed) failed \u{001B} [0m, "
214
+ }
215
+ if unknown > 0 {
216
+ result += " \u{001B} [31m \( unknown) unknown \u{001B} [0m, "
217
+ }
218
+ result += " \u{001B} [0m \( statuses. count) total \n "
219
+ return result
218
220
}
219
- terminal. write ( " \( suitesWithCases. count) total \n " )
220
221
221
- terminal. write ( " Tests: " )
222
- let allTests = suitesWithCases. map ( \. cases) . reduce ( [ ] , + )
223
- let testsPassed = allTests. filter { $0. status == . passed } . count
224
- if testsPassed > 0 {
225
- terminal. write ( " \( " \( testsPassed) passed " , color: " [32m " ) , " )
226
- }
227
- let testsSkipped = allTests. filter { $0. status == . skipped } . count
228
- if testsSkipped > 0 {
229
- terminal. write ( " \( " \( testsSkipped) skipped " , color: " [97m " ) , " )
230
- }
231
- let testsFailed = allTests. filter { $0. status == . failed } . count
232
- if testsFailed > 0 {
233
- terminal. write ( " \( " \( testsFailed) failed " , color: " [31m " ) , " )
234
- }
235
- let testsUnknown = allTests. filter { $0. status == . unknown( nil ) } . count
236
- if testsUnknown > 0 {
237
- terminal. write ( " \( " \( testsUnknown) unknown " , color: " [31m " ) , " )
222
+ let suitesWithCases = suites. filter { $0. cases. count > 0 }
223
+ write (
224
+ formatCategory (
225
+ label: " Test Suites: " , statuses: suitesWithCases. map ( \. status)
226
+ )
227
+ )
228
+ let allCaseStatuses = suitesWithCases. flatMap {
229
+ $0. cases. map { $0. status }
238
230
}
239
- terminal. write ( " \( allTests. count) total \n " )
231
+ write (
232
+ formatCategory (
233
+ label: " Tests: " , statuses: allCaseStatuses
234
+ )
235
+ )
240
236
241
237
if suites. contains ( where: { $0. name == " All tests " } ) {
242
- terminal . write ( " \( " Ran all test suites. " , color: " [90m " ) \n " ) // gray
238
+ write ( " \( " Ran all test suites. " , color: " [90m " ) \n " ) // gray
243
239
}
244
240
245
241
if suites. contains ( where: { $0. status. isNegative } ) {
246
- print ( suites. filter ( { $0. status. isNegative } ) )
247
- terminal. write ( " \n \( " Failed test cases: " , color: " [31m " ) \n " )
242
+ write ( " \n \( " Failed test cases: " , color: " [31m " ) \n " )
248
243
for suite in suites. filter ( { $0. status. isNegative } ) {
249
244
for testCase in suite. cases. filter ( { $0. status. isNegative } ) {
250
- terminal . write ( " \( testCase. statusMark) \( suite. name) . \( testCase. name) \n " )
245
+ write ( " \( testCase. statusMark) \( suite. name) . \( testCase. name) \n " )
251
246
}
252
247
}
253
248
254
- terminal . write (
249
+ write (
255
250
" \n \( " Some tests failed. Use --verbose for raw test output. " , color: " [33m " ) \n "
256
251
)
257
252
}
0 commit comments