12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- import { WASI , File , OpenFile , ConsoleStdout , PreopenDirectory , WASIProcExit } from "@bjorn3/browser_wasi_shim" ;
15
+ import { WASI , File , OpenFile , ConsoleStdout , PreopenDirectory , WASIProcExit , Inode , Directory } from "@bjorn3/browser_wasi_shim" ;
16
16
import type { SwiftRuntime , SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime/index" ;
17
17
import { polyfill as polyfillWebAssemblyTypeReflection } from "wasm-imports-parser/polyfill" ;
18
18
import type { ImportEntry } from "wasm-imports-parser" ;
@@ -48,6 +48,7 @@ export type InstantiationOptions = {
48
48
module : WebAssembly . Module ;
49
49
args ?: string [ ] ;
50
50
env ?: Record < string , string > ;
51
+ rootFs ?: Map < string , Inode > ;
51
52
onStdout ?: ( chunk : Uint8Array ) => void ;
52
53
onStdoutLine ?: ( line : string ) => void ;
53
54
onStderr ?: ( chunk : Uint8Array ) => void ;
@@ -58,6 +59,7 @@ export type InstantiationOptions = {
58
59
59
60
export async function instantiate ( rawOptions : InstantiationOptions , extraWasmImports ?: WebAssembly . Imports ) : Promise < {
60
61
instance : WebAssembly . Instance ;
62
+ rootFs : Map < string , Inode > ;
61
63
} > {
62
64
const options : InstantiationOptions = defaultInstantiationOptions ( rawOptions ) ;
63
65
@@ -85,11 +87,12 @@ export async function instantiate(rawOptions: InstantiationOptions, extraWasmImp
85
87
} ) ;
86
88
87
89
const args = options . args || [ ] ;
90
+ const rootFs = options . rootFs || new Map < string , Inode > ( ) ;
88
91
const fds = [
89
92
new OpenFile ( new File ( [ ] ) ) , // stdin
90
93
stdout ,
91
94
stderr ,
92
- new PreopenDirectory ( "/" , new Map ( ) ) ,
95
+ new PreopenDirectory ( "/" , rootFs ) ,
93
96
] ;
94
97
95
98
// Convert env Record to array of "key=value" strings
@@ -177,7 +180,7 @@ export async function instantiate(rawOptions: InstantiationOptions, extraWasmImp
177
180
}
178
181
}
179
182
180
- return { instance } ;
183
+ return { instance, rootFs } ;
181
184
}
182
185
183
186
function defaultInstantiationOptions ( options : InstantiationOptions ) : InstantiationOptions {
@@ -206,8 +209,39 @@ function defaultInstantiationOptions(options: InstantiationOptions): Instantiati
206
209
207
210
type Instantiate = ( options : Omit < InstantiationOptions , "module" > , extraWasmImports ?: WebAssembly . Imports ) => Promise < {
208
211
instance : WebAssembly . Instance ;
212
+ rootFs : Map < string , Inode > ;
209
213
} > ;
210
214
215
+ async function extractAndSaveFile ( rootFs : Map < string , Inode > , path : string ) : Promise < boolean > {
216
+ const getFile = ( parent : Map < string , Inode > , components : string [ ] , index : number ) : Inode | undefined => {
217
+ const name = components [ index ] ;
218
+ const entry = parent . get ( name ) ;
219
+ if ( entry === undefined ) {
220
+ return undefined ;
221
+ }
222
+ if ( index === components . length - 1 ) {
223
+ return entry ;
224
+ }
225
+ if ( entry instanceof Directory ) {
226
+ return getFile ( entry . contents , components , index + 1 ) ;
227
+ }
228
+ throw new Error ( `Expected directory at ${ components . slice ( 0 , index ) . join ( "/" ) } ` ) ;
229
+ }
230
+
231
+ const components = path . split ( "/" ) ;
232
+ const file = getFile ( rootFs , components , 0 ) ;
233
+ if ( file === undefined ) {
234
+ return false ;
235
+ }
236
+ if ( file instanceof File ) {
237
+ const fs = await import ( "node:fs/promises" ) ;
238
+ console . log ( `Saved ${ path } to ${ process . cwd ( ) } ` ) ;
239
+ await fs . writeFile ( path , file . data ) ;
240
+ return true ;
241
+ }
242
+ return false ;
243
+ }
244
+
211
245
export async function testBrowser ( instantiate : Instantiate , wasmFileName : string , args : string [ ] , indexJsUrl : string , inPage : boolean ) {
212
246
if ( inPage ) {
213
247
return await testBrowserInPage ( instantiate , wasmFileName , args ) ;
@@ -221,8 +255,8 @@ export async function testBrowser(instantiate: Instantiate, wasmFileName: string
221
255
console . error ( `Playwright is not available in the current environment.
222
256
Please run the following command to install it:
223
257
224
- $ npm install playwright && npx playwright install chromium
225
- ` ) ;
258
+ $ npm install playwright && npx playwright install chromium
259
+ `) ;
226
260
process . exit ( 1 ) ;
227
261
}
228
262
} ) ( ) ;
@@ -275,9 +309,9 @@ async function testBrowserInPage(instantiate: Instantiate, wasmFileName: string,
275
309
276
310
// There are 6 cases to exit test
277
311
// 1. Successfully finished XCTest with `exit(0)` synchronously
278
- // 2. Unsuccessfully finished XCTest with `exit(non- zero)` synchronously
312
+ // 2. Unsuccessfully finished XCTest with `exit(non - zero)` synchronously
279
313
// 3. Successfully finished XCTest with `exit(0)` asynchronously
280
- // 4. Unsuccessfully finished XCTest with `exit(non- zero)` asynchronously
314
+ // 4. Unsuccessfully finished XCTest with `exit(non - zero)` asynchronously
281
315
// 5. Crash by throwing JS exception synchronously
282
316
// 6. Crash by throwing JS exception asynchronously
283
317
@@ -359,14 +393,42 @@ This usually means there are some dangling continuations, which are awaited but
359
393
}
360
394
} ) ;
361
395
362
- await instantiate ( { env, args : [ wasmFileName ] . concat ( args ) } , {
363
- "wasi_snapshot_preview1" : {
364
- // @bjorn 3/browser_wasi_shim raises an exception when
365
- // the process exits, but we just want to exit the process itself.
366
- proc_exit : ( code : number ) => {
367
- procExitCalled = true ;
368
- process . exit ( code ) ;
369
- } ,
396
+ process . on ( "unhandledRejection" , ( error ) => {
397
+ if ( error instanceof WASIProcExit && error . code == 0 ) {
398
+ return ;
399
+ }
400
+ throw error ;
401
+ } )
402
+
403
+ const rootFs = new Map < string , Inode > ( ) ;
404
+ const onExit = new Promise < number > ( async ( resolve ) => {
405
+ try {
406
+ await instantiate ( { env, args : [ wasmFileName ] . concat ( args ) , rootFs } , {
407
+ "wasi_snapshot_preview1" : {
408
+ // @bjorn 3/browser_wasi_shim raises an exception when
409
+ // the process exits, but we just want to exit the process itself.
410
+ proc_exit : ( code : number ) => {
411
+ procExitCalled = true ;
412
+ resolve ( code ) ;
413
+ throw new WASIProcExit ( code ) ;
414
+ } ,
415
+ }
416
+ } ) ;
417
+ } catch ( error ) {
418
+ if ( error instanceof WASIProcExit ) {
419
+ resolve ( error . code ) ;
420
+ } else {
421
+ throw error ;
422
+ }
370
423
}
371
424
} ) ;
425
+ let code = 1 ;
426
+ try {
427
+ code = await onExit ;
428
+ } finally {
429
+ for ( const path of [ "default.profraw" ] ) {
430
+ await extractAndSaveFile ( rootFs , path ) ;
431
+ }
432
+ process . exit ( code ) ;
433
+ }
372
434
}
0 commit comments