@@ -16,17 +16,18 @@ import 'package:test_api/backend.dart'
1616import 'package:test_core/src/runner/application_exception.dart' ; // ignore: implementation_imports
1717import 'package:test_core/src/runner/configuration.dart' ; // ignore: implementation_imports
1818import 'package:test_core/src/runner/dart2js_compiler_pool.dart' ; // ignore: implementation_imports
19+ import 'package:test_core/src/runner/load_exception.dart' ; // ignore: implementation_imports
1920import 'package:test_core/src/runner/package_version.dart' ; // ignore: implementation_imports
2021import 'package:test_core/src/runner/platform.dart' ; // ignore: implementation_imports
2122import 'package:test_core/src/runner/plugin/customizable_platform.dart' ; // ignore: implementation_imports
2223import 'package:test_core/src/runner/plugin/environment.dart' ; // ignore: implementation_imports
2324import 'package:test_core/src/runner/plugin/platform_helpers.dart' ; // ignore: implementation_imports
2425import 'package:test_core/src/runner/runner_suite.dart' ; // ignore: implementation_imports
2526import 'package:test_core/src/runner/suite.dart' ; // ignore: implementation_imports
27+ import 'package:test_core/src/runner/wasm_compiler_pool.dart' ; // ignore: implementation_imports
2628import 'package:test_core/src/util/errors.dart' ; // ignore: implementation_imports
2729import 'package:test_core/src/util/io.dart' ; // ignore: implementation_imports
2830import 'package:test_core/src/util/package_config.dart' ; // ignore: implementation_imports
29- import 'package:test_core/src/util/pair.dart' ; // ignore: implementation_imports
3031import 'package:test_core/src/util/stack_trace_mapper.dart' ; // ignore: implementation_imports
3132import 'package:yaml/yaml.dart' ;
3233
@@ -40,7 +41,8 @@ class NodePlatform extends PlatformPlugin
4041 final Configuration _config;
4142
4243 /// The [Dart2JsCompilerPool] managing active instances of `dart2js` .
43- final _compilers = Dart2JsCompilerPool (['-Dnode=true' , '--server-mode' ]);
44+ final _jsCompilers = Dart2JsCompilerPool (['-Dnode=true' , '--server-mode' ]);
45+ final _wasmCompilers = WasmCompilerPool (['-Dnode=true' ]);
4446
4547 /// The temporary directory in which compiled JS is emitted.
4648 final _compiledDir = createTempDir ();
@@ -75,15 +77,17 @@ class NodePlatform extends PlatformPlugin
7577 @override
7678 Future <RunnerSuite > load (String path, SuitePlatform platform,
7779 SuiteConfiguration suiteConfig, Map <String , Object ?> message) async {
78- if (platform.compiler != Compiler .dart2js) {
80+ if (platform.compiler != Compiler .dart2js &&
81+ platform.compiler != Compiler .dart2wasm) {
7982 throw StateError (
8083 'Unsupported compiler for the Node platform ${platform .compiler }.' );
8184 }
82- var pair = await _loadChannel (path, platform, suiteConfig);
85+ var (channel, stackMapper) =
86+ await _loadChannel (path, platform, suiteConfig);
8387 var controller = deserializeSuite (path, platform, suiteConfig,
84- const PluginEnvironment (), pair.first , message);
88+ const PluginEnvironment (), channel , message);
8589
86- controller.channel ('test.node.mapper' ).sink.add (pair.last ? .serialize ());
90+ controller.channel ('test.node.mapper' ).sink.add (stackMapper ? .serialize ());
8791
8892 return await controller.suite;
8993 }
@@ -92,16 +96,13 @@ class NodePlatform extends PlatformPlugin
9296 ///
9397 /// Returns that channel along with a [StackTraceMapper] representing the
9498 /// source map for the compiled suite.
95- Future <Pair <StreamChannel <Object ?>, StackTraceMapper ?>> _loadChannel (
96- String path,
97- SuitePlatform platform,
98- SuiteConfiguration suiteConfig) async {
99+ Future <(StreamChannel <Object ?>, StackTraceMapper ?)> _loadChannel (String path,
100+ SuitePlatform platform, SuiteConfiguration suiteConfig) async {
99101 final servers = await _loopback ();
100102
101103 try {
102- var pair = await _spawnProcess (
103- path, platform.runtime, suiteConfig, servers.first.port);
104- var process = pair.first;
104+ var (process, stackMapper) =
105+ await _spawnProcess (path, platform, suiteConfig, servers.first.port);
105106
106107 // Forward Node's standard IO to the print handler so it's associated with
107108 // the load test.
@@ -110,7 +111,19 @@ class NodePlatform extends PlatformPlugin
110111 process.stdout.transform (lineSplitter).listen (print);
111112 process.stderr.transform (lineSplitter).listen (print);
112113
113- var socket = await StreamGroup .merge (servers).first;
114+ // Wait for the first connection (either over ipv4 or v6). If the proccess
115+ // exits before it connects, throw instead of waiting for a connection
116+ // indefinitely.
117+ var socket = await Future .any ([
118+ StreamGroup .merge (servers).first,
119+ process.exitCode.then ((_) => null ),
120+ ]);
121+
122+ if (socket == null ) {
123+ throw LoadException (
124+ path, 'Node exited before connecting to the test channel.' );
125+ }
126+
114127 var channel = StreamChannel (socket.cast <List <int >>(), socket)
115128 .transform (StreamChannelTransformer .fromCodec (utf8))
116129 .transform (_chunksToLines)
@@ -120,7 +133,7 @@ class NodePlatform extends PlatformPlugin
120133 sink.close ();
121134 }));
122135
123- return Pair (channel, pair.last );
136+ return (channel, stackMapper );
124137 } finally {
125138 unawaited (Future .wait <void >(servers.map ((s) =>
126139 s.close ().then <ServerSocket ?>((v) => v).onError ((_, __) => null ))));
@@ -131,23 +144,28 @@ class NodePlatform extends PlatformPlugin
131144 ///
132145 /// Returns that channel along with a [StackTraceMapper] representing the
133146 /// source map for the compiled suite.
134- Future <Pair <Process , StackTraceMapper ?>> _spawnProcess (String path,
135- Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
147+ Future <(Process , StackTraceMapper ?)> _spawnProcess (
148+ String path,
149+ SuitePlatform platform,
150+ SuiteConfiguration suiteConfig,
151+ int socketPort) async {
136152 if (_config.suiteDefaults.precompiledPath != null ) {
137- return _spawnPrecompiledProcess (path, runtime, suiteConfig, socketPort ,
138- _config.suiteDefaults.precompiledPath! );
153+ return _spawnPrecompiledProcess (path, platform. runtime, suiteConfig,
154+ socketPort, _config.suiteDefaults.precompiledPath! );
139155 } else {
140- return _spawnNormalProcess (path, runtime, suiteConfig, socketPort);
156+ return switch (platform.compiler) {
157+ Compiler .dart2js => _spawnNormalJsProcess (
158+ path, platform.runtime, suiteConfig, socketPort),
159+ Compiler .dart2wasm => _spawnNormalWasmProcess (
160+ path, platform.runtime, suiteConfig, socketPort),
161+ _ => throw StateError ('Unsupported compiler ${platform .compiler }' ),
162+ };
141163 }
142164 }
143165
144- /// Compiles [testPath] with dart2js, adds the node preamble, and then spawns
145- /// a Node.js process that loads that Dart test suite.
146- Future <Pair <Process , StackTraceMapper ?>> _spawnNormalProcess (String testPath,
147- Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
148- var dir = Directory (_compiledDir).createTempSync ('test_' ).path;
149- var jsPath = p.join (dir, '${p .basename (testPath )}.node_test.dart.js' );
150- await _compilers.compile ('''
166+ Future <String > _entrypointScriptForTest (
167+ String testPath, SuiteConfiguration suiteConfig) async {
168+ return '''
151169 ${suiteConfig .metadata .languageVersionComment ?? await rootPackageLanguageVersionComment }
152170 import "package:test/src/bootstrap/node.dart";
153171
@@ -156,7 +174,20 @@ class NodePlatform extends PlatformPlugin
156174 void main() {
157175 internalBootstrapNodeTest(() => test.main);
158176 }
159- ''' , jsPath, suiteConfig);
177+ ''' ;
178+ }
179+
180+ /// Compiles [testPath] with dart2js, adds the node preamble, and then spawns
181+ /// a Node.js process that loads that Dart test suite.
182+ Future <(Process , StackTraceMapper ?)> _spawnNormalJsProcess (String testPath,
183+ Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
184+ var dir = Directory (_compiledDir).createTempSync ('test_' ).path;
185+ var jsPath = p.join (dir, '${p .basename (testPath )}.node_test.dart.js' );
186+ await _jsCompilers.compile (
187+ await _entrypointScriptForTest (testPath, suiteConfig),
188+ jsPath,
189+ suiteConfig,
190+ );
160191
161192 // Add the Node.js preamble to ensure that the dart2js output is
162193 // compatible. Use the minified version so the source map remains valid.
@@ -173,12 +204,63 @@ class NodePlatform extends PlatformPlugin
173204 packageMap: (await currentPackageConfig).toPackageMap ());
174205 }
175206
176- return Pair (await _startProcess (runtime, jsPath, socketPort), mapper);
207+ return (await _startProcess (runtime, jsPath, socketPort), mapper);
208+ }
209+
210+ /// Compiles [testPath] with dart2wasm, adds a JS entrypoint and then spawns
211+ /// a Node.js process loading the compiled test suite.
212+ Future <(Process , StackTraceMapper ?)> _spawnNormalWasmProcess (String testPath,
213+ Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
214+ var dir = Directory (_compiledDir).createTempSync ('test_' ).path;
215+ // dart2wasm will emit a .wasm file and a .mjs file responsible for loading
216+ // that file.
217+ var wasmPath = p.join (dir, '${p .basename (testPath )}.node_test.dart.wasm' );
218+ var loader = '${p .basename (testPath )}.node_test.dart.wasm.mjs' ;
219+
220+ // We need to create an additional entrypoint file loading the wasm module.
221+ var jsPath = p.join (dir, '${p .basename (testPath )}.node_test.dart.js' );
222+
223+ await _wasmCompilers.compile (
224+ await _entrypointScriptForTest (testPath, suiteConfig),
225+ wasmPath,
226+ suiteConfig,
227+ );
228+
229+ await File (jsPath).writeAsString ('''
230+ const { createReadStream } = require('fs');
231+ const { once } = require('events');
232+ const { PassThrough } = require('stream');
233+
234+ const main = async () => {
235+ const { instantiate, invoke } = await import("./$loader ");
236+
237+ const wasmContents = createReadStream("$wasmPath .wasm");
238+ const stream = new PassThrough();
239+ wasmContents.pipe(stream);
240+
241+ await once(wasmContents, 'open');
242+ const response = new Response(
243+ stream,
244+ {
245+ headers: {
246+ "Content-Type": "application/wasm"
247+ }
248+ }
249+ );
250+ const instancePromise = WebAssembly.compileStreaming(response);
251+ const module = await instantiate(instancePromise, {});
252+ invoke(module);
253+ };
254+
255+ main();
256+ ''' );
257+
258+ return (await _startProcess (runtime, jsPath, socketPort), null );
177259 }
178260
179261 /// Spawns a Node.js process that loads the Dart test suite at [testPath]
180262 /// under [precompiledPath] .
181- Future <Pair < Process , StackTraceMapper ?> > _spawnPrecompiledProcess (
263+ Future <( Process , StackTraceMapper ?) > _spawnPrecompiledProcess (
182264 String testPath,
183265 Runtime runtime,
184266 SuiteConfiguration suiteConfig,
@@ -195,7 +277,7 @@ class NodePlatform extends PlatformPlugin
195277 .toPackageMap ());
196278 }
197279
198- return Pair (await _startProcess (runtime, jsPath, socketPort), mapper);
280+ return (await _startProcess (runtime, jsPath, socketPort), mapper);
199281 }
200282
201283 /// Starts the Node.js process for [runtime] with [jsPath] .
@@ -224,7 +306,8 @@ class NodePlatform extends PlatformPlugin
224306
225307 @override
226308 Future <void > close () => _closeMemo.runOnce (() async {
227- await _compilers.close ();
309+ await _jsCompilers.close ();
310+ await _wasmCompilers.close ();
228311 await Directory (_compiledDir).deleteWithRetry ();
229312 });
230313 final _closeMemo = AsyncMemoizer <void >();
0 commit comments