@@ -62,13 +62,20 @@ struct PackageToJS: CommandPlugin {
6262
6363 struct TestOptions {
6464 /// Whether to only build tests, don't run them
65- var buildOnly : Bool = false
65+ var buildOnly : Bool
66+ var listTests : Bool
67+ var testLibrary : String ?
68+ var filter : [ String ]
69+
6670 var options : Options
6771
6872 static func parse( from extractor: inout ArgumentExtractor ) -> TestOptions {
6973 let buildOnly = extractor. extractFlag ( named: " build-only " )
74+ let listTests = extractor. extractFlag ( named: " list-tests " )
75+ let testLibrary = extractor. extractOption ( named: " test-library " ) . last
76+ let filter = extractor. extractOption ( named: " filter " )
7077 let options = Options . parse ( from: & extractor)
71- return TestOptions ( buildOnly: buildOnly != 0 , options: options)
78+ return TestOptions ( buildOnly: buildOnly != 0 , listTests : listTests != 0 , testLibrary : testLibrary , filter : filter , options: options)
7279 }
7380
7481 static func help( ) -> String {
@@ -84,7 +91,7 @@ struct PackageToJS: CommandPlugin {
8491 $ swift package --swift-sdk wasm32-unknown-wasi plugin js test
8592 # Just build tests, don't run them
8693 $ swift package --swift-sdk wasm32-unknown-wasi plugin js test --build-only
87- """
94+ """
8895 }
8996 }
9097
@@ -129,7 +136,9 @@ struct PackageToJS: CommandPlugin {
129136 """
130137 } ) ,
131138 ]
132- static private func reportBuildFailure( _ build: PackageManager . BuildResult , _ arguments: [ String ] ) {
139+ static private func reportBuildFailure(
140+ _ build: PackageManager . BuildResult , _ arguments: [ String ]
141+ ) {
133142 for diagnostic in Self . friendlyBuildDiagnostics {
134143 if let message = diagnostic ( build, arguments) {
135144 printStderr ( " \n " + message)
@@ -138,11 +147,6 @@ struct PackageToJS: CommandPlugin {
138147 }
139148
140149 func performCommand( context: PluginContext , arguments: [ String ] ) throws {
141- if arguments. contains ( where: { [ " -h " , " --help " ] . contains ( $0) } ) {
142- printStderr ( BuildOptions . help ( ) )
143- return
144- }
145-
146150 if arguments. first == " test " {
147151 return try performTestCommand ( context: context, arguments: Array ( arguments. dropFirst ( ) ) )
148152 }
@@ -153,6 +157,11 @@ struct PackageToJS: CommandPlugin {
153157 static let JAVASCRIPTKIT_PACKAGE_ID : Package . ID = " javascriptkit "
154158
155159 func performBuildCommand( context: PluginContext , arguments: [ String ] ) throws {
160+ if arguments. contains ( where: { [ " -h " , " --help " ] . contains ( $0) } ) {
161+ printStderr ( BuildOptions . help ( ) )
162+ return
163+ }
164+
156165 var extractor = ArgumentExtractor ( arguments)
157166 let buildOptions = BuildOptions . parse ( from: & extractor)
158167
@@ -185,7 +194,8 @@ struct PackageToJS: CommandPlugin {
185194 }
186195 var make = MiniMake ( explain: buildOptions. options. explain)
187196 let planner = PackagingPlanner (
188- options: buildOptions. options, context: context, selfPackage: selfPackage, outputDir: outputDir)
197+ options: buildOptions. options, context: context, selfPackage: selfPackage,
198+ outputDir: outputDir)
189199 let rootTask = planner. planBuild (
190200 make: & make, wasmProductArtifact: productArtifact)
191201 cleanIfBuildGraphChanged ( root: rootTask, make: make, context: context)
@@ -195,6 +205,11 @@ struct PackageToJS: CommandPlugin {
195205 }
196206
197207 func performTestCommand( context: PluginContext , arguments: [ String ] ) throws {
208+ if arguments. contains ( where: { [ " -h " , " --help " ] . contains ( $0) } ) {
209+ printStderr ( TestOptions . help ( ) )
210+ return
211+ }
212+
198213 var extractor = ArgumentExtractor ( arguments)
199214 let testOptions = TestOptions . parse ( from: & extractor)
200215
@@ -227,13 +242,15 @@ struct PackageToJS: CommandPlugin {
227242 }
228243 }
229244 guard let productArtifact = productArtifact else {
230- throw PackageToJSError ( " Failed to find ' \( productName) .wasm' or ' \( productName) .xctest' " )
231- }
232- let outputDir = if let outputPath = testOptions. options. outputPath {
233- URL ( fileURLWithPath: outputPath)
234- } else {
235- context. pluginWorkDirectoryURL. appending ( path: " PackageTests " )
245+ throw PackageToJSError (
246+ " Failed to find ' \( productName) .wasm' or ' \( productName) .xctest' " )
236247 }
248+ let outputDir =
249+ if let outputPath = testOptions. options. outputPath {
250+ URL ( fileURLWithPath: outputPath)
251+ } else {
252+ context. pluginWorkDirectoryURL. appending ( path: " PackageTests " )
253+ }
237254 guard
238255 let selfPackage = findPackageInDependencies (
239256 package : context. package , id: Self . JAVASCRIPTKIT_PACKAGE_ID)
@@ -242,16 +259,47 @@ struct PackageToJS: CommandPlugin {
242259 }
243260 var make = MiniMake ( explain: testOptions. options. explain)
244261 let planner = PackagingPlanner (
245- options: testOptions. options, context: context, selfPackage: selfPackage, outputDir: outputDir)
246- let rootTask = planner. planTestBuild (
262+ options: testOptions. options, context: context, selfPackage: selfPackage,
263+ outputDir: outputDir)
264+ let ( rootTask, binDir) = planner. planTestBuild (
247265 make: & make, wasmProductArtifact: productArtifact)
248266 cleanIfBuildGraphChanged ( root: rootTask, make: make, context: context)
249267 print ( " Packaging tests... " )
250268 try make. build ( output: rootTask)
251269 print ( " Packaging tests finished " )
270+
271+ let testRunner = binDir. appending ( path: " test.js " )
272+ if !testOptions. buildOnly {
273+ var extraArguments : [ String ] = [ ]
274+ if testOptions. listTests {
275+ extraArguments += [ " --list-tests " ]
276+ }
277+ try runTest ( testRunner: testRunner, context: context, extraArguments: extraArguments + testOptions. filter)
278+ try runTest ( testRunner: testRunner, context: context,
279+ extraArguments: [ " --testing-library " , " swift-testing " ] + extraArguments + testOptions. filter. flatMap { [ " --filter " , $0] } )
280+ }
281+ }
282+
283+ private func runTest( testRunner: URL , context: PluginContext , extraArguments: [ String ] ) throws {
284+ let node = try which ( " node " )
285+ let arguments = [ " --experimental-wasi-unstable-preview1 " , testRunner. path] + extraArguments
286+ print ( " Running test... " )
287+ print ( " $ \( ( [ node. path] + arguments) . map { " \" \( $0) \" " } . joined ( separator: " " ) ) " )
288+
289+ let task = Process ( )
290+ task. executableURL = node
291+ task. arguments = arguments
292+ task. currentDirectoryURL = context. pluginWorkDirectoryURL
293+ try task. run ( )
294+ task. waitUntilExit ( )
295+ guard task. terminationStatus == 0 else {
296+ throw PackageToJSError ( " Test failed with status \( task. terminationStatus) " )
297+ }
252298 }
253299
254- private func buildWasm( productName: String , context: PluginContext ) throws -> PackageManager . BuildResult {
300+ private func buildWasm( productName: String , context: PluginContext ) throws
301+ -> PackageManager . BuildResult
302+ {
255303 var parameters = PackageManager . BuildParameters (
256304 configuration: . inherit,
257305 logging: . concise
@@ -357,6 +405,23 @@ private func printStderr(_ message: String) {
357405 fputs ( message + " \n " , stderr)
358406}
359407
408+ private func which( _ executable: String ) throws -> URL {
409+ let pathSeparator : Character
410+ #if os(Windows)
411+ pathSeparator = " ; "
412+ #else
413+ pathSeparator = " : "
414+ #endif
415+ let paths = ProcessInfo . processInfo. environment [ " PATH " ] !. split ( separator: pathSeparator)
416+ for path in paths {
417+ let url = URL ( fileURLWithPath: String ( path) ) . appendingPathComponent ( executable)
418+ if FileManager . default. isExecutableFile ( atPath: url. path) {
419+ return url
420+ }
421+ }
422+ throw PackageToJSError ( " Executable \( executable) not found in PATH " )
423+ }
424+
360425private struct PackageToJSError : Swift . Error , CustomStringConvertible {
361426 let description : String
362427
0 commit comments