Skip to content

Commit 6bf418e

Browse files
Optimize compile-time
1 parent faa9359 commit 6bf418e

File tree

8 files changed

+94
-113
lines changed

8 files changed

+94
-113
lines changed

Plugins/PackageToJS/Sources/PackageToJS.swift

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ struct PackageToJS {
8787
try PackageToJS.runSingleTestingLibrary(
8888
testRunner: testRunner, currentDirectoryURL: currentDirectoryURL,
8989
extraArguments: extraArguments,
90-
testParser: testOptions.verbose ? nil : FancyTestsParser(),
90+
testParser: testOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }),
9191
testOptions: testOptions
9292
)
9393
}
@@ -122,7 +122,7 @@ struct PackageToJS {
122122
testRunner: URL,
123123
currentDirectoryURL: URL,
124124
extraArguments: [String],
125-
testParser: (any TestsParser)? = nil,
125+
testParser: FancyTestsParser? = nil,
126126
testOptions: TestOptions
127127
) throws {
128128
let node = try which("node")
@@ -136,15 +136,8 @@ struct PackageToJS {
136136

137137
var finalize: () -> Void = {}
138138
if let testParser = testParser {
139-
class Writer: InteractiveWriter {
140-
func write(_ string: String) {
141-
print(string, terminator: "")
142-
}
143-
}
144-
145-
let writer = Writer()
146139
let stdoutBuffer = LineBuffer { line in
147-
testParser.onLine(line, writer)
140+
testParser.onLine(line)
148141
}
149142
let stdoutPipe = Pipe()
150143
stdoutPipe.fileHandleForReading.readabilityHandler = { handle in
@@ -156,7 +149,7 @@ struct PackageToJS {
156149
stdoutBuffer.append(data)
157150
}
158151
stdoutBuffer.flush()
159-
testParser.finalize(writer)
152+
testParser.finalize()
160153
}
161154
}
162155

Plugins/PackageToJS/Sources/TestsParser.swift

Lines changed: 74 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,21 @@
44
import Foundation
55
import RegexBuilder
66

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-
177
extension String.StringInterpolation {
188
/// Display `value` with the specified ANSI-escaped `color` values, then apply the reset.
199
fileprivate mutating func appendInterpolation<T>(_ value: T, color: String...) {
2010
appendInterpolation("\(color.map { "\u{001B}\($0)" }.joined())\(value)\u{001B}[0m")
2111
}
2212
}
2313

24-
class FancyTestsParser: TestsParser {
25-
init() {}
14+
class FancyTestsParser {
15+
let write: (String) -> Void
2616

27-
enum Status: Equatable {
17+
init(write: @escaping (String) -> Void) {
18+
self.write = write
19+
}
20+
21+
private enum Status: Equatable {
2822
case passed, failed, skipped
2923
case unknown(String.SubSequence?)
3024

@@ -45,7 +39,7 @@ class FancyTestsParser: TestsParser {
4539
}
4640
}
4741

48-
struct Suite {
42+
private struct Suite {
4943
let name: String.SubSequence
5044
var status: Status = .unknown(nil)
5145

@@ -76,19 +70,19 @@ class FancyTestsParser: TestsParser {
7670
}
7771
}
7872

79-
var suites = [Suite]()
73+
private var suites = [Suite]()
8074

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 {
8478
"Test Suite '"
8579
Capture {
8680
OneOrMore(CharacterClass.anyOf("'").inverted)
8781
}
8882
"' started at "
8983
Capture { self.timestamp }
9084
}
91-
lazy var suiteStatus = Regex {
85+
private lazy var suiteStatus = Regex {
9286
"Test Suite '"
9387
Capture { OneOrMore(CharacterClass.anyOf("'").inverted) }
9488
"' "
@@ -101,14 +95,14 @@ class FancyTestsParser: TestsParser {
10195
" at "
10296
Capture { self.timestamp }
10397
}
104-
lazy var testCaseStarted = Regex {
98+
private lazy var testCaseStarted = Regex {
10599
"Test Case '"
106100
Capture { self.swiftIdentifier }
107101
"."
108102
Capture { self.swiftIdentifier }
109103
"' started"
110104
}
111-
lazy var testCaseStatus = Regex {
105+
private lazy var testCaseStatus = Regex {
112106
"Test Case '"
113107
Capture { self.swiftIdentifier }
114108
"."
@@ -130,10 +124,10 @@ class FancyTestsParser: TestsParser {
130124
" seconds)"
131125
}
132126

133-
let testSummary =
127+
private let testSummary =
134128
#/Executed \d+ (test|tests), with (?:\d+ (?:test|tests) skipped and )?\d+ (failure|failures) \((?<unexpected>\d+) unexpected\) in (?<duration>\d+\.\d+) \(\d+\.\d+\) seconds/#
135129

136-
func onLine(_ line: String, _ terminal: InteractiveWriter) {
130+
func onLine(_ line: String) {
137131
if let match = line.firstMatch(
138132
of: suiteStarted
139133
) {
@@ -145,7 +139,7 @@ class FancyTestsParser: TestsParser {
145139
let (_, suite, status, _) = match.output
146140
if let suiteIdx = suites.firstIndex(where: { $0.name == suite }) {
147141
suites[suiteIdx].status = Status(rawValue: status)
148-
flushSingleSuite(suites[suiteIdx], terminal)
142+
flushSingleSuite(suites[suiteIdx])
149143
}
150144
} else if let match = line.firstMatch(
151145
of: testCaseStarted
@@ -172,86 +166,87 @@ class FancyTestsParser: TestsParser {
172166
// do nothing
173167
} else {
174168
if !line.isEmpty {
175-
terminal.write(line + "\n")
169+
write(line + "\n")
176170
}
177171
}
178172
}
179173

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")
188177
for testCase in suite.cases {
189-
terminal.write(" \(testCase.statusMark) ")
178+
write(" \(testCase.statusMark) ")
190179
if let duration = testCase.duration {
191-
terminal
192-
.write(
180+
write(
193181
"\(testCase.name) \("(\(Int(Double(duration)! * 1000))ms)", color: "[90m")\n"
194182
) // gray
195183
}
196184
}
197185
}
198186

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
218220
}
219-
terminal.write("\(suitesWithCases.count) total\n")
220221

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 }
238230
}
239-
terminal.write("\(allTests.count) total\n")
231+
write(
232+
formatCategory(
233+
label: "Tests: ", statuses: allCaseStatuses
234+
)
235+
)
240236

241237
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
243239
}
244240

245241
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")
248243
for suite in suites.filter({ $0.status.isNegative }) {
249244
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")
251246
}
252247
}
253248

254-
terminal.write(
249+
write(
255250
"\n\("Some tests failed. Use --verbose for raw test output.", color: "[33m")\n"
256251
)
257252
}

Plugins/PackageToJS/Tests/TestParserTests.swift renamed to Plugins/PackageToJS/Tests/TestsParserTests.swift

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,23 @@ import Testing
33

44
@testable import PackageToJS
55

6-
@Suite struct TestParserTests {
6+
@Suite struct TestsParserTests {
77
func assertFancyFormatSnapshot(
88
_ input: String,
99
filePath: String = #filePath, function: String = #function,
1010
sourceLocation: SourceLocation = #_sourceLocation
1111
) throws {
12-
let parser = FancyTestsParser()
12+
var output = ""
13+
let parser = FancyTestsParser(write: { output += $0 })
1314
let lines = input.split(separator: "\n", omittingEmptySubsequences: false)
1415

15-
class Writer: InteractiveWriter {
16-
var output = ""
17-
func write(_ string: String) {
18-
output += string
19-
}
20-
}
21-
22-
let writer = Writer()
2316
for line in lines {
24-
parser.onLine(String(line), writer)
17+
parser.onLine(String(line))
2518
}
26-
parser.finalize(writer)
19+
parser.finalize()
2720
try assertSnapshot(
2821
filePath: filePath, function: function, sourceLocation: sourceLocation,
29-
input: Data(writer.output.utf8), fileExtension: "txt",
22+
input: Data(output.utf8), fileExtension: "txt",
3023
)
3124
}
3225

Plugins/PackageToJS/Tests/__Snapshots__/TestParserTests/testAllPassed.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
 PASSED  /.xctest
55
 PASSED  All tests
66

7-
Test Suites: 1 passed, 1 total
8-
Tests: 2 passed, 2 total
7+
Test Suites: 1 passed, 1 total
8+
Tests: 2 passed, 2 total
99
Ran all test suites.

Plugins/PackageToJS/Tests/__Snapshots__/TestParserTests/testAssertFailure.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
 FAILED  /.xctest
55
 FAILED  All tests
66

7-
Test Suites: 1 failed, 1 total
8-
Tests: 1 failed, 1 total
7+
Test Suites: 1 failed, 1 total
8+
Tests: 1 failed, 1 total
99
Ran all test suites.
1010

1111
Failed test cases:

Plugins/PackageToJS/Tests/__Snapshots__/TestParserTests/testCrash.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ RuntimeError: unreachable
1212
at CounterPackageTests.xctest.$s19JavaScriptEventLoopAAC6create33_F9DB15AFB1FFBEDBFE9D13500E01F3F2LLAByFZyyyccfU0_0aB3Kit20ConvertibleToJSValue_pAE0Q0OcfU_ (wasm://wasm/CounterPackageTests.xctest-0ef3150a:wasm-function[1541]:0x9de13)
1313
at CounterPackageTests.xctest.$s19JavaScriptEventLoopAAC6create33_F9DB15AFB1FFBEDBFE9D13500E01F3F2LLAByFZyyyccfU0_0aB3Kit20ConvertibleToJSValue_pAE0Q0OcfU_TA (wasm://wasm/CounterPackageTests.xctest-0ef3150a:wasm-function[1540]:0x9dd8d)
1414

15-
Test Suites: 1 unknown, 1 total
16-
Tests: 1 unknown, 1 total
15+
Test Suites: 1 unknown, 1 total
16+
Tests: 1 unknown, 1 total
1717
Ran all test suites.
1818

1919
Failed test cases:

Plugins/PackageToJS/Tests/__Snapshots__/TestParserTests/testSkipped.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
 PASSED  /.xctest
66
 PASSED  All tests
77

8-
Test Suites: 1 passed, 1 total
9-
Tests: 1 passed, 1 skipped, 2 total
8+
Test Suites: 1 passed, 1 total
9+
Tests: 1 passed, 1 skipped, 2 total
1010
Ran all test suites.

Plugins/PackageToJS/Tests/__Snapshots__/TestParserTests/testThrowFailure.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
 FAILED  /.xctest
55
 FAILED  All tests
66

7-
Test Suites: 1 failed, 1 total
8-
Tests: 1 failed, 1 total
7+
Test Suites: 1 failed, 1 total
8+
Tests: 1 failed, 1 total
99
Ran all test suites.
1010

1111
Failed test cases:

0 commit comments

Comments
 (0)