|
4 | 4 |
|
5 | 5 | // @dart = 2.6 |
6 | 6 | import 'dart:async'; |
| 7 | +import 'dart:isolate'; |
7 | 8 | import 'dart:io' as io; |
8 | 9 |
|
9 | 10 | import 'package:args/command_runner.dart'; |
10 | 11 | import 'package:meta/meta.dart'; |
11 | 12 | import 'package:path/path.dart' as path; |
| 13 | +import 'package:quiver/iterables.dart'; |
12 | 14 | import 'package:test_core/src/runner/hack_register_platform.dart' |
13 | 15 | as hack; // ignore: implementation_imports |
14 | 16 | import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports |
@@ -38,6 +40,9 @@ enum TestTypesRequested { |
38 | 40 | all, |
39 | 41 | } |
40 | 42 |
|
| 43 | +/// How many isolates does the test build is distributed. |
| 44 | +const int numberOfIsolates = 8; |
| 45 | + |
41 | 46 | class TestCommand extends Command<bool> with ArgUtils { |
42 | 47 | TestCommand() { |
43 | 48 | argParser |
@@ -241,11 +246,12 @@ class TestCommand extends Command<bool> with ArgUtils { |
241 | 246 | } |
242 | 247 |
|
243 | 248 | if (htmlTargets.isNotEmpty) { |
244 | | - await _buildTests(targets: htmlTargets, forCanvasKit: false); |
| 249 | + await _buildTestsInParallel(targets: htmlTargets, forCanvasKit: false); |
245 | 250 | } |
246 | 251 |
|
247 | 252 | if (canvasKitTargets.isNotEmpty) { |
248 | | - await _buildTests(targets: canvasKitTargets, forCanvasKit: true); |
| 253 | + await _buildTestsInParallel( |
| 254 | + targets: canvasKitTargets, forCanvasKit: true); |
249 | 255 | } |
250 | 256 | stopwatch.stop(); |
251 | 257 | print('The build took ${stopwatch.elapsedMilliseconds ~/ 1000} seconds.'); |
@@ -448,56 +454,101 @@ class TestCommand extends Command<bool> with ArgUtils { |
448 | 454 | timestampFile.writeAsStringSync(timestamp); |
449 | 455 | } |
450 | 456 |
|
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( |
460 | 458 | {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 | + } |
468 | 475 |
|
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); |
471 | 480 |
|
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); |
474 | 486 | } |
| 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 | + } |
475 | 498 |
|
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 | + } |
495 | 524 |
|
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 | + } |
499 | 549 | } |
500 | | - } |
| 550 | + sendPort.send('pass'); |
| 551 | + }); |
501 | 552 | } |
502 | 553 |
|
503 | 554 | /// Runs a batch of tests. |
@@ -568,3 +619,16 @@ void _copyTestFontsIntoWebUi() { |
568 | 619 | sourceTtf.copySync(destinationTtfPath); |
569 | 620 | } |
570 | 621 | } |
| 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