Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4781ac0

Browse files
author
nturgut
committed
make tests build in paralel. Total time dropped from 586 to 177 seconds for 8 core MacBook
1 parent 6c0c068 commit 4781ac0

File tree

1 file changed

+109
-45
lines changed

1 file changed

+109
-45
lines changed

lib/web_ui/dev/test_runner.dart

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
// @dart = 2.6
66
import 'dart:async';
7+
import 'dart:isolate';
78
import 'dart:io' as io;
89

910
import 'package:args/command_runner.dart';
1011
import 'package:meta/meta.dart';
1112
import 'package:path/path.dart' as path;
13+
import 'package:quiver/iterables.dart';
1214
import 'package:test_core/src/runner/hack_register_platform.dart'
1315
as hack; // ignore: implementation_imports
1416
import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
@@ -38,6 +40,9 @@ enum TestTypesRequested {
3840
all,
3941
}
4042

43+
/// How many isolates does the test build is distributed.
44+
const int numberOfIsolates = 8;
45+
4146
class TestCommand extends Command<bool> with ArgUtils {
4247
TestCommand() {
4348
argParser
@@ -241,11 +246,12 @@ class TestCommand extends Command<bool> with ArgUtils {
241246
}
242247

243248
if (htmlTargets.isNotEmpty) {
244-
await _buildTests(targets: htmlTargets, forCanvasKit: false);
249+
await _buildTestsInParallel(targets: htmlTargets, forCanvasKit: false);
245250
}
246251

247252
if (canvasKitTargets.isNotEmpty) {
248-
await _buildTests(targets: canvasKitTargets, forCanvasKit: true);
253+
await _buildTestsInParallel(
254+
targets: canvasKitTargets, forCanvasKit: true);
249255
}
250256
stopwatch.stop();
251257
print('The build took ${stopwatch.elapsedMilliseconds ~/ 1000} seconds.');
@@ -448,56 +454,101 @@ class TestCommand extends Command<bool> with ArgUtils {
448454
timestampFile.writeAsStringSync(timestamp);
449455
}
450456

451-
/// Builds the specific test [targets].
452-
///
453-
/// [targets] must not be null.
454-
///
455-
/// Uses `dart2js` for building the tests.
456-
///
457-
/// When building for CanvasKit we have to use extra argument
458-
/// `DFLUTTER_WEB_USE_SKIA=true`.
459-
Future<void> _buildTests(
457+
Future<void> _buildTestsInParallel(
460458
{List<FilePath> targets, bool forCanvasKit = false}) async {
461-
print('Building ${targets.length} targets for HTML');
462-
463-
for (FilePath file in targets) {
464-
final targetFileName = file.relativeToWebUi
465-
.replaceFirst('.dart', '.dart.browser_test.dart.js');
466-
final String targetPath = path.join('build', targetFileName);
467-
print('target path: $targetPath current file: ${file.relativeToWebUi}');
459+
final double numberOfTargetsPerIsolate = targets.length / numberOfIsolates;
460+
Iterable<List<FilePath>> targetsPerIsolate =
461+
partition(targets, numberOfTargetsPerIsolate.ceil());
462+
final List<Completer<void>> completers = List.empty(growable: true);
463+
int i = 1;
464+
for (final List<FilePath> t in targetsPerIsolate) {
465+
final Completer<void> completer = new Completer();
466+
print('INFO: Isolate no $i will start');
467+
_buildTestsInIsolates(completer,
468+
input: TestBuildIsolateInput(t, forCanvasKit: forCanvasKit),
469+
forCanvasKit: forCanvasKit);
470+
completers.add(completer);
471+
i++;
472+
}
473+
await Future.wait(completers.map((e) => e.future));
474+
}
468475

469-
final io.Directory directoryToTarget = io.Directory(path.join(
470-
environment.webUiBuildDir.path, path.dirname(file.relativeToWebUi)));
476+
void _buildTestsInIsolates(Completer completer,
477+
{TestBuildIsolateInput input, bool forCanvasKit = false}) async {
478+
final ReceivePort receivePort = new ReceivePort();
479+
final Isolate isolate = await Isolate.spawn(continuesBuilding, receivePort.sendPort);
471480

472-
if (!directoryToTarget.existsSync()) {
473-
directoryToTarget.createSync(recursive: true);
481+
receivePort.listen((dynamic message) async {
482+
if (message is SendPort) {
483+
// Record isolate send port.
484+
final SendPort sendPort = message;
485+
sendPort.send(input);
474486
}
487+
if (message is String) {
488+
if (message != 'pass') {
489+
throw ToolException('Failed to compile tests with error $message');
490+
}
491+
receivePort.close();
492+
}
493+
}, onDone: () {
494+
completer.complete();
495+
isolate.kill(priority: Isolate.immediate);
496+
});
497+
}
475498

476-
List<String> arguments = <String>[
477-
'--no-minify',
478-
'--disable-inlining',
479-
'--enable-asserts',
480-
'--enable-experiment=non-nullable',
481-
'--no-sound-null-safety',
482-
if (forCanvasKit) '-DFLUTTER_WEB_USE_SKIA=true',
483-
'-O2',
484-
'-o',
485-
'${targetPath}', // target path
486-
'${file.relativeToWebUi}', // current path
487-
];
488-
489-
final int exitCode = await runProcess(
490-
environment.dart2jsExecutable,
491-
arguments,
492-
workingDirectory: environment.webUiRootDir.path,
493-
// environment.
494-
);
499+
/// The main method for building the test files.
500+
///
501+
/// This method runs inside the isolates.
502+
///
503+
/// There are [numberOfIsolates] running in parallel.
504+
static void continuesBuilding(SendPort sendPort) async {
505+
final ReceivePort receivePort = new ReceivePort();
506+
sendPort.send(receivePort.sendPort);
507+
await receivePort.listen((dynamic message) async {
508+
final TestBuildIsolateInput isolateInput =
509+
message as TestBuildIsolateInput;
510+
final List<FilePath> targets = isolateInput.targets;
511+
512+
for (FilePath file in targets) {
513+
final targetFileName = file.relativeToWebUi
514+
.replaceFirst('.dart', '.dart.browser_test.dart.js');
515+
final String targetPath = path.join('build', targetFileName);
516+
517+
final io.Directory directoryToTarget = io.Directory(path.join(
518+
environment.webUiBuildDir.path,
519+
path.dirname(file.relativeToWebUi)));
520+
521+
if (!directoryToTarget.existsSync()) {
522+
directoryToTarget.createSync(recursive: true);
523+
}
495524

496-
if (exitCode != 0) {
497-
throw ToolException(
498-
'Failed to compile tests. Dart2js exited with exit code $exitCode');
525+
List<String> arguments = <String>[
526+
'--no-minify',
527+
'--disable-inlining',
528+
'--enable-asserts',
529+
'--enable-experiment=non-nullable',
530+
'--no-sound-null-safety',
531+
if (isolateInput.forCanvasKit) '-DFLUTTER_WEB_USE_SKIA=true',
532+
'-O2',
533+
'-o',
534+
'${targetPath}',
535+
'${file}',
536+
];
537+
538+
final int exitCode = await runProcess(
539+
environment.dart2jsExecutable,
540+
arguments,
541+
workingDirectory: environment.webUiRootDir.path,
542+
);
543+
544+
if (exitCode != 0) {
545+
print('>>> Exception finish of isolate $exitCode.'
546+
'target failed: ${file.relativeToWebUi}');
547+
sendPort.send('failure');
548+
}
499549
}
500-
}
550+
sendPort.send('pass');
551+
});
501552
}
502553

503554
/// Runs a batch of tests.
@@ -568,3 +619,16 @@ void _copyTestFontsIntoWebUi() {
568619
sourceTtf.copySync(destinationTtfPath);
569620
}
570621
}
622+
623+
/// This objest is used as an input message to the isolates that builds the
624+
/// test files.
625+
class TestBuildIsolateInput {
626+
/// List of targets to build.
627+
final List<FilePath> targets;
628+
/// Whether these tests should be build for CanvasKit.
629+
///
630+
/// `-DFLUTTER_WEB_USE_SKIA=true` is passed to dart2js for CanvasKit.
631+
final bool forCanvasKit;
632+
633+
TestBuildIsolateInput(this.targets, {this.forCanvasKit = false});
634+
}

0 commit comments

Comments
 (0)