Skip to content

Commit dfb5888

Browse files
derekxu16dnfield
andauthored
Support using lightweight Flutter Engines to run tests (#141726)
This PR implements the functionality described above and hides it behind the `--experimental-faster-testing` flag of `flutter test`. ### The following are some performance measurements from test runs conducted on GitHub Actions run 1 logs: https://github.com/derekxu16/flutter_test_ci/actions/runs/8008029772/attempts/1 run 2 logs: https://github.com/derekxu16/flutter_test_ci/actions/runs/8008029772/attempts/2 run 3 logs: https://github.com/derekxu16/flutter_test_ci/actions/runs/8008029772/attempts/3 **length of `flutter test --reporter=expanded test/animation test/foundation` step** run 1: 54s run 2: 52s run 3: 56s average: 54s **length of `flutter test --experimental-faster-testing --reporter=expanded test/animation test/foundation` step** run 1: 27s run 2: 27s run 3: 29s average: 27.67s (~48.77% shorter than 54s) **length of `flutter test --reporter=expanded test/animation test/foundation test/gestures test/painting test/physics test/rendering test/scheduler test/semantics test/services` step** run 1: 260s run 2: 270s run 3: 305s average: 278.33s **length of `flutter test --experimental-faster-testing --reporter=expanded test/animation test/foundation test/gestures test/painting test/physics test/rendering test/scheduler test/semantics test/services` step** from a clean build (right after deleting the build folder): run 1: 215s run 2: 227s run 3: 245s average: 229s (~17.72% shorter than 278.33s) Note that in reality, `test/material` was not passed to `flutter test` in the trials below. All of the test files under `test/material` except for `test/material/icons_test.dart` were listed out individually **length of `flutter test --reporter=expanded test/material` step** run 1: 408s run 2: 421s run 3: 451s average: 426.67s **length of `flutter test --experimental-faster-testing --reporter=expanded test/material` step** run 1: 382s run 2: 373s run 3: 400s average: 385s (~9.77% shorter than 426.67s) --------- Co-authored-by: Dan Field <[email protected]>
1 parent 44bcc9c commit dfb5888

File tree

6 files changed

+1118
-30
lines changed

6 files changed

+1118
-30
lines changed

packages/flutter_test/lib/src/binding.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
195195
TestWidgetsFlutterBinding() : platformDispatcher = TestPlatformDispatcher(
196196
platformDispatcher: PlatformDispatcher.instance,
197197
) {
198+
platformDispatcher.defaultRouteNameTestValue = '/';
198199
debugPrint = debugPrintOverride;
199200
debugDisableShadows = disableShadows;
200201
}
@@ -246,6 +247,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
246247
void reset() {
247248
_restorationManager?.dispose();
248249
_restorationManager = null;
250+
platformDispatcher.defaultRouteNameTestValue = '/';
249251
resetGestureBinding();
250252
testTextInput.reset();
251253
if (registerTestTextInput) {

packages/flutter_tools/lib/src/commands/test.dart

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
8080
addEnableImpellerFlag(verboseHelp: verboseHelp);
8181

8282
argParser
83+
..addFlag('experimental-faster-testing',
84+
negatable: false,
85+
hide: !verboseHelp,
86+
help: 'Run each test in a separate lightweight Flutter Engine to speed up testing.'
87+
)
8388
..addMultiOption('name',
8489
help: 'A regular expression matching substrings of the names of tests to run.',
8590
valueHelp: 'regexp',
@@ -350,6 +355,23 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
350355
);
351356
}
352357

358+
bool experimentalFasterTesting = boolArg('experimental-faster-testing');
359+
if (experimentalFasterTesting) {
360+
if (_isIntegrationTest || isWeb) {
361+
experimentalFasterTesting = false;
362+
globals.printStatus(
363+
'--experimental-faster-testing was parsed but will be ignored. This '
364+
'option is not supported when running integration tests or web tests.',
365+
);
366+
} else if (_testFileUris.length == 1) {
367+
experimentalFasterTesting = false;
368+
globals.printStatus(
369+
'--experimental-faster-testing was parsed but will be ignored. This '
370+
'option should not be used when running a single test file.',
371+
);
372+
}
373+
}
374+
353375
final bool startPaused = boolArg('start-paused');
354376
if (startPaused && _testFileUris.length != 1) {
355377
throwToolExit(
@@ -402,6 +424,13 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
402424
// Running with concurrency will result in deploying multiple test apps
403425
// on the connected device concurrently, which is not supported.
404426
jobs = 1;
427+
} else if (experimentalFasterTesting) {
428+
if (argResults!.wasParsed('concurrency')) {
429+
globals.printStatus(
430+
'-j/--concurrency was parsed but will be ignored. This option is not '
431+
'compatible with --experimental-faster-testing.',
432+
);
433+
}
405434
}
406435

407436
final int? shardIndex = int.tryParse(stringArg('shard-index') ?? '');
@@ -425,6 +454,25 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
425454
'If you set --shard-index you need to also set --total-shards.');
426455
}
427456

457+
final bool enableVmService = boolArg('enable-vmservice');
458+
if (experimentalFasterTesting && enableVmService) {
459+
globals.printStatus(
460+
'--enable-vmservice was parsed but will be ignored. This option is not '
461+
'compatible with --experimental-faster-testing.',
462+
);
463+
}
464+
465+
final bool ipv6 = boolArg('ipv6');
466+
if (experimentalFasterTesting && ipv6) {
467+
// [ipv6] is set when the user desires for the test harness server to use
468+
// IPv6, but a test harness server will not be started at all when
469+
// [experimentalFasterTesting] is set.
470+
globals.printStatus(
471+
'--ipv6 was parsed but will be ignored. This option is not compatible '
472+
'with --experimental-faster-testing.',
473+
);
474+
}
475+
428476
final bool machine = boolArg('machine');
429477
CoverageCollector? collector;
430478
if (boolArg('coverage') || boolArg('merge-coverage') ||
@@ -487,35 +535,61 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
487535
}
488536

489537
final Stopwatch? testRunnerTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.TestRunner);
490-
final int result = await testRunner.runTests(
491-
testWrapper,
492-
_testFileUris.toList(),
493-
debuggingOptions: debuggingOptions,
494-
names: names,
495-
plainNames: plainNames,
496-
tags: tags,
497-
excludeTags: excludeTags,
498-
watcher: watcher,
499-
enableVmService: collector != null || startPaused || boolArg('enable-vmservice'),
500-
ipv6: boolArg('ipv6'),
501-
machine: machine,
502-
updateGoldens: boolArg('update-goldens'),
503-
concurrency: jobs,
504-
testAssetDirectory: testAssetDirectory,
505-
flutterProject: flutterProject,
506-
web: isWeb,
507-
randomSeed: stringArg('test-randomize-ordering-seed'),
508-
reporter: stringArg('reporter'),
509-
fileReporter: stringArg('file-reporter'),
510-
timeout: stringArg('timeout'),
511-
runSkipped: boolArg('run-skipped'),
512-
shardIndex: shardIndex,
513-
totalShards: totalShards,
514-
integrationTestDevice: integrationTestDevice,
515-
integrationTestUserIdentifier: stringArg(FlutterOptions.kDeviceUser),
516-
testTimeRecorder: testTimeRecorder,
517-
nativeAssetsBuilder: nativeAssetsBuilder,
518-
);
538+
final int result;
539+
if (experimentalFasterTesting) {
540+
assert(!isWeb && !_isIntegrationTest && _testFileUris.length > 1);
541+
result = await testRunner.runTestsBySpawningLightweightEngines(
542+
_testFileUris.toList(),
543+
debuggingOptions: debuggingOptions,
544+
names: names,
545+
plainNames: plainNames,
546+
tags: tags,
547+
excludeTags: excludeTags,
548+
machine: machine,
549+
updateGoldens: boolArg('update-goldens'),
550+
concurrency: jobs,
551+
testAssetDirectory: testAssetDirectory,
552+
flutterProject: flutterProject,
553+
randomSeed: stringArg('test-randomize-ordering-seed'),
554+
reporter: stringArg('reporter'),
555+
fileReporter: stringArg('file-reporter'),
556+
timeout: stringArg('timeout'),
557+
runSkipped: boolArg('run-skipped'),
558+
shardIndex: shardIndex,
559+
totalShards: totalShards,
560+
testTimeRecorder: testTimeRecorder,
561+
);
562+
} else {
563+
result = await testRunner.runTests(
564+
testWrapper,
565+
_testFileUris.toList(),
566+
debuggingOptions: debuggingOptions,
567+
names: names,
568+
plainNames: plainNames,
569+
tags: tags,
570+
excludeTags: excludeTags,
571+
watcher: watcher,
572+
enableVmService: collector != null || startPaused || enableVmService,
573+
ipv6: ipv6,
574+
machine: machine,
575+
updateGoldens: boolArg('update-goldens'),
576+
concurrency: jobs,
577+
testAssetDirectory: testAssetDirectory,
578+
flutterProject: flutterProject,
579+
web: isWeb,
580+
randomSeed: stringArg('test-randomize-ordering-seed'),
581+
reporter: stringArg('reporter'),
582+
fileReporter: stringArg('file-reporter'),
583+
timeout: stringArg('timeout'),
584+
runSkipped: boolArg('run-skipped'),
585+
shardIndex: shardIndex,
586+
totalShards: totalShards,
587+
integrationTestDevice: integrationTestDevice,
588+
integrationTestUserIdentifier: stringArg(FlutterOptions.kDeviceUser),
589+
testTimeRecorder: testTimeRecorder,
590+
nativeAssetsBuilder: nativeAssetsBuilder,
591+
);
592+
}
519593
testTimeRecorder?.stop(TestTimePhases.TestRunner, testRunnerTimeRecorderStopwatch!);
520594

521595
if (collector != null) {

0 commit comments

Comments
 (0)