@@ -16,17 +16,18 @@ import 'package:test_api/backend.dart'
16
16
import 'package:test_core/src/runner/application_exception.dart' ; // ignore: implementation_imports
17
17
import 'package:test_core/src/runner/configuration.dart' ; // ignore: implementation_imports
18
18
import '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
19
20
import 'package:test_core/src/runner/package_version.dart' ; // ignore: implementation_imports
20
21
import 'package:test_core/src/runner/platform.dart' ; // ignore: implementation_imports
21
22
import 'package:test_core/src/runner/plugin/customizable_platform.dart' ; // ignore: implementation_imports
22
23
import 'package:test_core/src/runner/plugin/environment.dart' ; // ignore: implementation_imports
23
24
import 'package:test_core/src/runner/plugin/platform_helpers.dart' ; // ignore: implementation_imports
24
25
import 'package:test_core/src/runner/runner_suite.dart' ; // ignore: implementation_imports
25
26
import 'package:test_core/src/runner/suite.dart' ; // ignore: implementation_imports
27
+ import 'package:test_core/src/runner/wasm_compiler_pool.dart' ; // ignore: implementation_imports
26
28
import 'package:test_core/src/util/errors.dart' ; // ignore: implementation_imports
27
29
import 'package:test_core/src/util/io.dart' ; // ignore: implementation_imports
28
30
import 'package:test_core/src/util/package_config.dart' ; // ignore: implementation_imports
29
- import 'package:test_core/src/util/pair.dart' ; // ignore: implementation_imports
30
31
import 'package:test_core/src/util/stack_trace_mapper.dart' ; // ignore: implementation_imports
31
32
import 'package:yaml/yaml.dart' ;
32
33
@@ -40,7 +41,8 @@ class NodePlatform extends PlatformPlugin
40
41
final Configuration _config;
41
42
42
43
/// 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' ]);
44
46
45
47
/// The temporary directory in which compiled JS is emitted.
46
48
final _compiledDir = createTempDir ();
@@ -75,15 +77,17 @@ class NodePlatform extends PlatformPlugin
75
77
@override
76
78
Future <RunnerSuite > load (String path, SuitePlatform platform,
77
79
SuiteConfiguration suiteConfig, Map <String , Object ?> message) async {
78
- if (platform.compiler != Compiler .dart2js) {
80
+ if (platform.compiler != Compiler .dart2js &&
81
+ platform.compiler != Compiler .dart2wasm) {
79
82
throw StateError (
80
83
'Unsupported compiler for the Node platform ${platform .compiler }.' );
81
84
}
82
- var pair = await _loadChannel (path, platform, suiteConfig);
85
+ var (channel, stackMapper) =
86
+ await _loadChannel (path, platform, suiteConfig);
83
87
var controller = deserializeSuite (path, platform, suiteConfig,
84
- const PluginEnvironment (), pair.first , message);
88
+ const PluginEnvironment (), channel , message);
85
89
86
- controller.channel ('test.node.mapper' ).sink.add (pair.last ? .serialize ());
90
+ controller.channel ('test.node.mapper' ).sink.add (stackMapper ? .serialize ());
87
91
88
92
return await controller.suite;
89
93
}
@@ -92,16 +96,13 @@ class NodePlatform extends PlatformPlugin
92
96
///
93
97
/// Returns that channel along with a [StackTraceMapper] representing the
94
98
/// 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 {
99
101
final servers = await _loopback ();
100
102
101
103
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);
105
106
106
107
// Forward Node's standard IO to the print handler so it's associated with
107
108
// the load test.
@@ -110,7 +111,19 @@ class NodePlatform extends PlatformPlugin
110
111
process.stdout.transform (lineSplitter).listen (print);
111
112
process.stderr.transform (lineSplitter).listen (print);
112
113
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
+
114
127
var channel = StreamChannel (socket.cast <List <int >>(), socket)
115
128
.transform (StreamChannelTransformer .fromCodec (utf8))
116
129
.transform (_chunksToLines)
@@ -120,7 +133,7 @@ class NodePlatform extends PlatformPlugin
120
133
sink.close ();
121
134
}));
122
135
123
- return Pair (channel, pair.last );
136
+ return (channel, stackMapper );
124
137
} finally {
125
138
unawaited (Future .wait <void >(servers.map ((s) =>
126
139
s.close ().then <ServerSocket ?>((v) => v).onError ((_, __) => null ))));
@@ -131,23 +144,28 @@ class NodePlatform extends PlatformPlugin
131
144
///
132
145
/// Returns that channel along with a [StackTraceMapper] representing the
133
146
/// 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 {
136
152
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! );
139
155
} 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
+ };
141
163
}
142
164
}
143
165
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 '''
151
169
${suiteConfig .metadata .languageVersionComment ?? await rootPackageLanguageVersionComment }
152
170
import "package:test/src/bootstrap/node.dart";
153
171
@@ -156,7 +174,20 @@ class NodePlatform extends PlatformPlugin
156
174
void main() {
157
175
internalBootstrapNodeTest(() => test.main);
158
176
}
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
+ );
160
191
161
192
// Add the Node.js preamble to ensure that the dart2js output is
162
193
// compatible. Use the minified version so the source map remains valid.
@@ -173,12 +204,63 @@ class NodePlatform extends PlatformPlugin
173
204
packageMap: (await currentPackageConfig).toPackageMap ());
174
205
}
175
206
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 );
177
259
}
178
260
179
261
/// Spawns a Node.js process that loads the Dart test suite at [testPath]
180
262
/// under [precompiledPath] .
181
- Future <Pair < Process , StackTraceMapper ?> > _spawnPrecompiledProcess (
263
+ Future <( Process , StackTraceMapper ?) > _spawnPrecompiledProcess (
182
264
String testPath,
183
265
Runtime runtime,
184
266
SuiteConfiguration suiteConfig,
@@ -195,7 +277,7 @@ class NodePlatform extends PlatformPlugin
195
277
.toPackageMap ());
196
278
}
197
279
198
- return Pair (await _startProcess (runtime, jsPath, socketPort), mapper);
280
+ return (await _startProcess (runtime, jsPath, socketPort), mapper);
199
281
}
200
282
201
283
/// Starts the Node.js process for [runtime] with [jsPath] .
@@ -224,7 +306,8 @@ class NodePlatform extends PlatformPlugin
224
306
225
307
@override
226
308
Future <void > close () => _closeMemo.runOnce (() async {
227
- await _compilers.close ();
309
+ await _jsCompilers.close ();
310
+ await _wasmCompilers.close ();
228
311
await Directory (_compiledDir).deleteWithRetry ();
229
312
});
230
313
final _closeMemo = AsyncMemoizer <void >();
0 commit comments