Skip to content

Commit 6885060

Browse files
Merge pull request #301 from swiftwasm/katei/xctest-formatter
PackageToJS: Bring XCTest output formatter from carton
2 parents 5f1f224 + e521052 commit 6885060

File tree

13 files changed

+610
-81
lines changed

13 files changed

+610
-81
lines changed

Examples/Testing/Package.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ let package = Package(
1818
]),
1919
.testTarget(
2020
name: "CounterTests",
21-
dependencies: ["Counter"]
21+
dependencies: [
22+
"Counter",
23+
// This is needed to run the tests in the JavaScript event loop
24+
.product(name: "JavaScriptEventLoopTestSupport", package: "JavaScriptKit")
25+
]
2226
),
2327
]
2428
)

Plugins/PackageToJS/Sources/PackageToJS.swift

+96-23
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct PackageToJS {
4040
var inspect: Bool
4141
/// The extra arguments to pass to node
4242
var extraNodeArguments: [String]
43+
/// Whether to print verbose output
44+
var verbose: Bool
4345
/// The options for packaging
4446
var packageOptions: PackageOptions
4547
}
@@ -59,44 +61,54 @@ struct PackageToJS {
5961
var testJsArguments: [String] = []
6062
var testLibraryArguments: [String] = []
6163
if testOptions.listTests {
62-
testLibraryArguments += ["--list-tests"]
64+
testLibraryArguments.append("--list-tests")
6365
}
6466
if let prelude = testOptions.prelude {
6567
let preludeURL = URL(fileURLWithPath: prelude, relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath))
66-
testJsArguments += ["--prelude", preludeURL.path]
68+
testJsArguments.append("--prelude")
69+
testJsArguments.append(preludeURL.path)
6770
}
6871
if let environment = testOptions.environment {
69-
testJsArguments += ["--environment", environment]
72+
testJsArguments.append("--environment")
73+
testJsArguments.append(environment)
7074
}
7175
if testOptions.inspect {
72-
testJsArguments += ["--inspect"]
76+
testJsArguments.append("--inspect")
7377
}
7478

7579
let xctestCoverageFile = outputDir.appending(path: "XCTest.profraw")
7680
do {
7781
var extraArguments = testJsArguments
7882
if testOptions.packageOptions.enableCodeCoverage {
79-
extraArguments += ["--coverage-file", xctestCoverageFile.path]
83+
extraArguments.append("--coverage-file")
84+
extraArguments.append(xctestCoverageFile.path)
8085
}
81-
extraArguments += ["--"]
82-
extraArguments += testLibraryArguments
83-
extraArguments += testOptions.filter
86+
extraArguments.append("--")
87+
extraArguments.append(contentsOf: testLibraryArguments)
88+
extraArguments.append(contentsOf: testOptions.filter)
8489

8590
try PackageToJS.runSingleTestingLibrary(
8691
testRunner: testRunner, currentDirectoryURL: currentDirectoryURL,
8792
extraArguments: extraArguments,
93+
testParser: testOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }),
8894
testOptions: testOptions
8995
)
9096
}
9197
let swiftTestingCoverageFile = outputDir.appending(path: "SwiftTesting.profraw")
9298
do {
9399
var extraArguments = testJsArguments
94100
if testOptions.packageOptions.enableCodeCoverage {
95-
extraArguments += ["--coverage-file", swiftTestingCoverageFile.path]
101+
extraArguments.append("--coverage-file")
102+
extraArguments.append(swiftTestingCoverageFile.path)
103+
}
104+
extraArguments.append("--")
105+
extraArguments.append("--testing-library")
106+
extraArguments.append("swift-testing")
107+
extraArguments.append(contentsOf: testLibraryArguments)
108+
for filter in testOptions.filter {
109+
extraArguments.append("--filter")
110+
extraArguments.append(filter)
96111
}
97-
extraArguments += ["--", "--testing-library", "swift-testing"]
98-
extraArguments += testLibraryArguments
99-
extraArguments += testOptions.filter.flatMap { ["--filter", $0] }
100112

101113
try PackageToJS.runSingleTestingLibrary(
102114
testRunner: testRunner, currentDirectoryURL: currentDirectoryURL,
@@ -106,7 +118,7 @@ struct PackageToJS {
106118
}
107119

108120
if testOptions.packageOptions.enableCodeCoverage {
109-
let profrawFiles = [xctestCoverageFile, swiftTestingCoverageFile].filter { FileManager.default.fileExists(atPath: $0.path) }
121+
let profrawFiles = [xctestCoverageFile.path, swiftTestingCoverageFile.path].filter { FileManager.default.fileExists(atPath: $0) }
110122
do {
111123
try PackageToJS.postProcessCoverageFiles(outputDir: outputDir, profrawFiles: profrawFiles)
112124
} catch {
@@ -119,38 +131,97 @@ struct PackageToJS {
119131
testRunner: URL,
120132
currentDirectoryURL: URL,
121133
extraArguments: [String],
134+
testParser: FancyTestsParser? = nil,
122135
testOptions: TestOptions
123136
) throws {
124137
let node = try which("node")
125-
let arguments = ["--experimental-wasi-unstable-preview1"] + testOptions.extraNodeArguments + [testRunner.path] + extraArguments
138+
var arguments = ["--experimental-wasi-unstable-preview1"]
139+
arguments.append(contentsOf: testOptions.extraNodeArguments)
140+
arguments.append(testRunner.path)
141+
arguments.append(contentsOf: extraArguments)
142+
126143
print("Running test...")
127144
logCommandExecution(node.path, arguments)
128145

129146
let task = Process()
130147
task.executableURL = node
131148
task.arguments = arguments
149+
150+
var finalize: () -> Void = {}
151+
if let testParser = testParser {
152+
let stdoutBuffer = LineBuffer { line in
153+
testParser.onLine(line)
154+
}
155+
let stdoutPipe = Pipe()
156+
stdoutPipe.fileHandleForReading.readabilityHandler = { handle in
157+
stdoutBuffer.append(handle.availableData)
158+
}
159+
task.standardOutput = stdoutPipe
160+
finalize = {
161+
if let data = try? stdoutPipe.fileHandleForReading.readToEnd() {
162+
stdoutBuffer.append(data)
163+
}
164+
stdoutBuffer.flush()
165+
testParser.finalize()
166+
}
167+
}
168+
132169
task.currentDirectoryURL = currentDirectoryURL
133170
try task.forwardTerminationSignals {
134171
try task.run()
135172
task.waitUntilExit()
136173
}
174+
finalize()
137175
// swift-testing returns EX_UNAVAILABLE (which is 69 in wasi-libc) for "no tests found"
138-
guard task.terminationStatus == 0 || task.terminationStatus == 69 else {
176+
guard [0, 69].contains(task.terminationStatus) else {
139177
throw PackageToJSError("Test failed with status \(task.terminationStatus)")
140178
}
141179
}
142180

143-
static func postProcessCoverageFiles(outputDir: URL, profrawFiles: [URL]) throws {
181+
static func postProcessCoverageFiles(outputDir: URL, profrawFiles: [String]) throws {
144182
let mergedCoverageFile = outputDir.appending(path: "default.profdata")
145183
do {
146184
// Merge the coverage files by llvm-profdata
147-
let arguments = ["merge", "-sparse", "-output", mergedCoverageFile.path] + profrawFiles.map { $0.path }
185+
let arguments = ["merge", "-sparse", "-output", mergedCoverageFile.path] + profrawFiles
148186
let llvmProfdata = try which("llvm-profdata")
149187
logCommandExecution(llvmProfdata.path, arguments)
150188
try runCommand(llvmProfdata, arguments)
151189
print("Saved profile data to \(mergedCoverageFile.path)")
152190
}
153191
}
192+
193+
class LineBuffer: @unchecked Sendable {
194+
let lock = NSLock()
195+
var buffer = ""
196+
let handler: (String) -> Void
197+
198+
init(handler: @escaping (String) -> Void) {
199+
self.handler = handler
200+
}
201+
202+
func append(_ data: Data) {
203+
let string = String(data: data, encoding: .utf8) ?? ""
204+
append(string)
205+
}
206+
207+
func append(_ data: String) {
208+
lock.lock()
209+
defer { lock.unlock() }
210+
buffer.append(data)
211+
let lines = buffer.split(separator: "\n", omittingEmptySubsequences: false)
212+
for line in lines.dropLast() {
213+
handler(String(line))
214+
}
215+
buffer = String(lines.last ?? "")
216+
}
217+
218+
func flush() {
219+
lock.lock()
220+
defer { lock.unlock() }
221+
handler(buffer)
222+
buffer = ""
223+
}
224+
}
154225
}
155226

156227
struct PackageToJSError: Swift.Error, CustomStringConvertible {
@@ -509,12 +580,12 @@ struct PackagingPlanner {
509580
}
510581

511582
let inputPath = selfPackageDir.appending(path: file)
512-
let conditions = [
583+
let conditions: [String: Bool] = [
513584
"USE_SHARED_MEMORY": triple == "wasm32-unknown-wasip1-threads",
514585
"IS_WASI": triple.hasPrefix("wasm32-unknown-wasi"),
515586
"USE_WASI_CDN": options.useCDN,
516587
]
517-
let constantSubstitutions = [
588+
let constantSubstitutions: [String: String] = [
518589
"PACKAGE_TO_JS_MODULE_PATH": wasmFilename,
519590
"PACKAGE_TO_JS_PACKAGE_NAME": options.packageName ?? packageId.lowercased(),
520591
]
@@ -529,11 +600,13 @@ struct PackagingPlanner {
529600
if let wasmImportsPath = wasmImportsPath {
530601
let wasmImportsPath = $1.resolve(path: wasmImportsPath)
531602
let importEntries = try JSONDecoder().decode([ImportEntry].self, from: Data(contentsOf: wasmImportsPath))
532-
let memoryImport = importEntries.first { $0.module == "env" && $0.name == "memory" }
603+
let memoryImport = importEntries.first {
604+
$0.module == "env" && $0.name == "memory"
605+
}
533606
if case .memory(let type) = memoryImport?.kind {
534-
substitutions["PACKAGE_TO_JS_MEMORY_INITIAL"] = "\(type.minimum)"
535-
substitutions["PACKAGE_TO_JS_MEMORY_MAXIMUM"] = "\(type.maximum ?? type.minimum)"
536-
substitutions["PACKAGE_TO_JS_MEMORY_SHARED"] = "\(type.shared)"
607+
substitutions["PACKAGE_TO_JS_MEMORY_INITIAL"] = type.minimum.description
608+
substitutions["PACKAGE_TO_JS_MEMORY_MAXIMUM"] = (type.maximum ?? type.minimum).description
609+
substitutions["PACKAGE_TO_JS_MEMORY_SHARED"] = type.shared.description
537610
}
538611
}
539612

Plugins/PackageToJS/Sources/PackageToJSPlugin.swift

+4
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,14 @@ extension PackageToJS.TestOptions {
340340
let prelude = extractor.extractOption(named: "prelude").last
341341
let environment = extractor.extractOption(named: "environment").last
342342
let inspect = extractor.extractFlag(named: "inspect")
343+
let verbose = extractor.extractFlag(named: "verbose")
343344
let extraNodeArguments = extractor.extractSingleDashOption(named: "Xnode")
344345
let packageOptions = PackageToJS.PackageOptions.parse(from: &extractor)
345346
var options = PackageToJS.TestOptions(
346347
buildOnly: buildOnly != 0, listTests: listTests != 0,
347348
filter: filter, prelude: prelude, environment: environment, inspect: inspect != 0,
348349
extraNodeArguments: extraNodeArguments,
350+
verbose: verbose != 0,
349351
packageOptions: packageOptions
350352
)
351353

@@ -369,6 +371,8 @@ extension PackageToJS.TestOptions {
369371
--inspect Whether to run tests in the browser with inspector enabled
370372
--use-cdn Whether to use CDN for dependency packages (default: false)
371373
--enable-code-coverage Whether to enable code coverage collection (default: false)
374+
--verbose Whether to print verbose output (default: false)
375+
-Xnode <args> Extra arguments to pass to Node.js
372376
373377
EXAMPLES:
374378
$ swift package --swift-sdk wasm32-unknown-wasi plugin js test

0 commit comments

Comments
 (0)