Unverified Commit 2a113165 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Support --fast-start for Android applications (as an opt-in) (#45431)

parent 16570759
......@@ -273,6 +273,13 @@ Future<void> _runToolTests() async {
await selectSubshard(subshards);
}
// Example apps that should not be built by _runBuildTests`
const List<String> _excludedExampleApplications = <String>[
// This application contains no platform code and cannot be built, except for
// as a part of a '--fast-start' Android application.
'splash',
];
/// Verifies that AOT, APK, and IPA (if on macOS) builds the examples apps
/// without crashing. It does not actually launch the apps. That happens later
/// in the devicelab. This is just a smoke-test. In particular, this will verify
......@@ -284,6 +291,9 @@ Future<void> _runBuildTests() async {
if (fileEntity is! Directory) {
continue;
}
if (_excludedExampleApplications.any(fileEntity.path.endsWith)) {
continue;
}
final String examplePath = fileEntity.path;
await _flutterBuildAot(examplePath);
await _flutterBuildApk(examplePath);
......@@ -756,6 +766,7 @@ Future<void> _runHostOnlyDeviceLabTests() async {
if (Platform.isMacOS) () => _runDevicelabTest('flutter_create_offline_test_mac'),
if (Platform.isLinux) () => _runDevicelabTest('flutter_create_offline_test_linux'),
if (Platform.isWindows) () => _runDevicelabTest('flutter_create_offline_test_windows'),
() => _runDevicelabTest('gradle_fast_start_test', environment: gradleEnvironment),
// TODO(ianh): Fails on macOS looking for "dexdump", https://github.com/flutter/flutter/issues/42494
if (!Platform.isMacOS) () => _runDevicelabTest('gradle_jetifier_test', environment: gradleEnvironment),
() => _runDevicelabTest('gradle_non_android_plugin_test', environment: gradleEnvironment),
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
Future<void> main() async {
await task(() async {
try {
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleDebug with --fast-start');
await pluginProject.runGradleTask('assembleDebug',
options: <String>['-Pfast-start=true']);
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
checkCollectionContains<String>(<String>[
...debugAssets,
...baseApkFiles,
'lib/x86/libflutter.so',
'lib/x86_64/libflutter.so',
'lib/armeabi-v7a/libflutter.so',
'lib/arm64-v8a/libflutter.so',
], apkFiles);
checkCollectionDoesNotContain<String>(<String>[
...flutterAssets,
], apkFiles);
});
return TaskResult.success(null);
} on TaskResult catch (taskResult) {
return taskResult;
} catch (e) {
return TaskResult.failure(e.toString());
}
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
void main() {
runApp(
const DecoratedBox(
decoration: BoxDecoration(color: Colors.white),
child: Center(
child: FlutterLogo(size: 48),
),
),
);
}
name: splash
environment:
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies:
flutter_test:
sdk: flutter
archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
# PUBSPEC CHECKSUM: f789
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:splash/main.dart' as entrypoint;
void main() {
testWidgets('Displays flutter logo', (WidgetTester tester) async {
entrypoint.main();
expect(find.byType(FlutterLogo), findsOneWidget);
});
}
......@@ -472,6 +472,15 @@ class FlutterPlugin implements Plugin<Project> {
return false
}
/// Whether to build the debug app in "fast-start" mode.
private Boolean isFastStart() {
if (project.hasProperty("fast-start")) {
return project.property("fast-start").toBoolean()
}
return false
}
private static Boolean shouldShrinkResources(Project project) {
if (project.hasProperty("shrink")) {
return project.property("shrink").toBoolean()
......@@ -610,6 +619,7 @@ class FlutterPlugin implements Plugin<Project> {
localEngineSrcPath this.localEngineSrcPath
targetPath target
verbose isVerbose()
fastStart isFastStart()
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
......@@ -729,6 +739,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String localEngine
String localEngineSrcPath
@Input
Boolean fastStart
@Input
String targetPath
@Optional
Boolean verbose
......@@ -769,9 +781,13 @@ abstract class BaseFlutterTask extends DefaultTask {
// cache.
String[] ruleNames;
if (buildMode == "debug") {
ruleNames = ["debug_android_application"]
if (fastStart) {
ruleNames = ["faststart_android_application"]
} else {
ruleNames = ["debug_android_application"]
}
} else {
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
}
project.exec {
executable flutterExecutable.absolutePath
......@@ -788,7 +804,11 @@ abstract class BaseFlutterTask extends DefaultTask {
args "assemble"
args "--depfile", "${intermediateDir}/flutter_build.d"
args "--output", "${intermediateDir}"
args "-dTargetFile=${targetPath}"
if (!fastStart || buildMode != "debug") {
args "-dTargetFile=${targetPath}"
} else {
args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}"
}
args "-dTargetPlatform=android"
args "-dBuildMode=${buildMode}"
if (extraFrontEndOptions != null) {
......
......@@ -527,6 +527,7 @@ class AndroidDevice extends Device {
androidBuildInfo: AndroidBuildInfo(
debuggingOptions.buildInfo,
targetArchs: <AndroidArch>[androidArch],
fastStart: debuggingOptions.fastStart
),
);
// Package has been built, so we can get the updated application ID and
......@@ -643,6 +644,9 @@ class AndroidDevice extends Device {
@override
bool get supportsHotRestart => true;
@override
bool get supportsFastStart => true;
@override
Future<bool> stopApp(AndroidApk app) {
final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]);
......
......@@ -331,6 +331,9 @@ Future<void> buildGradleApp({
// Don't use settings.gradle from the current project since it includes the plugins as subprojects.
command.add('--settings-file=settings_aar.gradle');
}
if (androidBuildInfo.fastStart) {
command.add('-Pfast-start=true');
}
command.add(assembleTask);
GradleHandledError detectedGradleError;
......
......@@ -103,6 +103,7 @@ class AndroidBuildInfo {
],
this.splitPerAbi = false,
this.shrink = false,
this.fastStart = false,
});
// The build info containing the mode and flavor.
......@@ -120,6 +121,9 @@ class AndroidBuildInfo {
/// The target platforms for the build.
final Iterable<AndroidArch> targetArchs;
/// Whether to bootstrap an empty application.
final bool fastStart;
}
/// A summary of the compilation strategy used for Dart.
......
......@@ -423,7 +423,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner {
@override
Future<OperationResult> restart({
bool fullRestart = false,
bool pauseAfterRestart = false,
bool pause = false,
String reason,
bool benchmarkMode = false,
}) async {
......@@ -692,7 +692,7 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
@override
Future<OperationResult> restart({
bool fullRestart = false,
bool pauseAfterRestart = false,
bool pause = false,
String reason,
bool benchmarkMode = false,
}) async {
......
......@@ -31,10 +31,13 @@ abstract class AndroidAssetBundle extends Target {
List<Source> get outputs => const <Source>[];
@override
List<String> get depfiles => const <String>[
'flutter_assets.d',
List<String> get depfiles => <String>[
if (_copyAssets)
'flutter_assets.d',
];
bool get _copyAssets => true;
@override
Future<void> build(Environment environment) async {
if (environment.defines[kBuildMode] == null) {
......@@ -56,8 +59,10 @@ abstract class AndroidAssetBundle extends Target {
fs.file(isolateSnapshotData)
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
}
final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
if (_copyAssets) {
final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
}
}
@override
......@@ -90,6 +95,17 @@ class DebugAndroidApplication extends AndroidAssetBundle {
];
}
/// A minimal android application that does not include assets.
class FastStartAndroidApplication extends DebugAndroidApplication {
const FastStartAndroidApplication();
@override
String get name => 'faststart_android_application';
@override
bool get _copyAssets => false;
}
/// An implementation of [AndroidAssetBundle] that only includes assets.
class AotAndroidAssetBundle extends AndroidAssetBundle {
const AotAndroidAssetBundle();
......
......@@ -37,6 +37,7 @@ const List<Target> _kDefaultTargets = <Target>[
DebugBundleLinuxAssets(),
WebReleaseBundle(),
DebugAndroidApplication(),
FastStartAndroidApplication(),
ProfileAndroidApplication(),
ReleaseAndroidApplication(),
// These are one-off rules for bundle and aot compat
......
......@@ -577,7 +577,7 @@ class AppDomain extends Domain {
}
_inProgressHotReload = app._runInZone<OperationResult>(this, () {
return app.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: restartReason);
return app.restart(fullRestart: fullRestart, pause: pauseAfterRestart, reason: restartReason);
});
return _inProgressHotReload.whenComplete(() {
_inProgressHotReload = null;
......@@ -922,8 +922,8 @@ class AppInstance {
_AppRunLogger _logger;
Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
return runner.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: reason);
Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
return runner.restart(fullRestart: fullRestart, pause: pause, reason: reason);
}
Future<void> stop() => runner.exit();
......
......@@ -187,6 +187,14 @@ class RunCommand extends RunCommandBase {
hide: true,
help: 'Whether to automatically invoke webOnlyInitializePlatform.',
)
..addFlag('fast-start',
negatable: true,
defaultsTo: false,
hide: true,
help: 'Whether to quickly bootstrap applications with a minimal app. '
'Currently this is only supported on Android devices. This option '
'cannot be paired with --use-application-binary.'
)
..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
..addMultiOption(FlutterOptions.kEnableExperiment,
......@@ -284,6 +292,9 @@ class RunCommand extends RunCommandBase {
if (!runningWithPrebuiltApplication) {
await super.validateCommand();
}
if (boolArg('fast-start') && runningWithPrebuiltApplication) {
throwToolExit('--fast-start is not supported with --use-application-binary');
}
if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
throwToolExit('Using -d all with --use-application-binary is not supported');
}
......@@ -318,6 +329,9 @@ class RunCommand extends RunCommandBase {
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
vmserviceOutFile: stringArg('vmservice-out-file'),
// Allow forcing fast-start to off to prevent doing more work on devices that
// don't support it.
fastStart: boolArg('fast-start') && devices.every((Device device) => device.supportsFastStart),
);
}
}
......@@ -385,6 +399,12 @@ class RunCommand extends RunCommandBase {
}
for (Device device in devices) {
if (!device.supportsFastStart && boolArg('fast-start')) {
printStatus(
'Using --fast-start option with device ${device.name}, but this device '
'does not support it. Overriding the setting to false.'
);
}
if (await device.isLocalEmulator) {
if (await device.supportsHardwareRendering) {
final bool enableSoftwareRendering = boolArg('enable-software-rendering') == true;
......
......@@ -420,6 +420,9 @@ abstract class Device {
/// application.
bool get supportsScreenshot => false;
/// Whether the device supports the '--fast-start' development mode.
bool get supportsFastStart => false;
/// Stop an app package on the current device.
Future<bool> stopApp(covariant ApplicationPackage app);
......@@ -534,6 +537,7 @@ class DebuggingOptions {
this.hostname,
this.port,
this.vmserviceOutFile,
this.fastStart = false,
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo, { this.initializePlatform = true, this.port, this.hostname, this.cacheSkSL = false, })
......@@ -550,7 +554,8 @@ class DebuggingOptions {
verboseSystemLogs = false,
hostVmServicePort = null,
deviceVmServicePort = null,
vmserviceOutFile = null;
vmserviceOutFile = null,
fastStart = false;
final bool debuggingEnabled;
......@@ -574,6 +579,7 @@ class DebuggingOptions {
final String hostname;
/// A file where the vmservice URL should be written after the application is started.
final String vmserviceOutFile;
final bool fastStart;
bool get hasObservatoryPort => hostVmServicePort != null;
}
......
......@@ -712,7 +712,7 @@ abstract class ResidentRunner {
bool get supportsRestart => false;
Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
final String mode = isRunningProfile ? 'profile' :
isRunningRelease ? 'release' : 'this';
throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
......
......@@ -103,7 +103,7 @@ class HotRunner extends ResidentRunner {
bool pause = false,
}) async {
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
final OperationResult result = await restart(pauseAfterRestart: pause);
final OperationResult result = await restart(pause: pause);
if (!result.isOk) {
throw rpc.RpcException(
rpc_error_code.INTERNAL_ERROR,
......@@ -114,7 +114,7 @@ class HotRunner extends ResidentRunner {
Future<void> _restartService({ bool pause = false }) async {
final OperationResult result =
await restart(fullRestart: true, pauseAfterRestart: pause);
await restart(fullRestart: true, pause: pause);
if (!result.isOk) {
throw rpc.RpcException(
rpc_error_code.INTERNAL_ERROR,
......@@ -211,6 +211,15 @@ class HotRunner extends ResidentRunner {
}
}
if (debuggingOptions.fastStart) {
await restart(
fullRestart: true,
benchmarkMode: !debuggingOptions.startPaused,
reason: 'restart',
silent: true,
);
}
appStartedCompleter?.complete();
if (benchmarkMode) {
......@@ -356,12 +365,10 @@ class HotRunner extends ResidentRunner {
Uri packagesUri,
Uri assetsDirectoryUri,
) {
final List<Future<void>> futures = <Future<void>>[
for (FlutterView view in device.views) view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
];
final Completer<void> completer = Completer<void>();
Future.wait(futures).whenComplete(() { completer.complete(null); });
return completer.future;
return Future.wait(<Future<void>>[
for (FlutterView view in device.views)
view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
]);
}
Future<void> _launchFromDevFS(String mainScript) async {
......@@ -458,9 +465,11 @@ class HotRunner extends ResidentRunner {
for (FlutterDevice device in flutterDevices) {
for (FlutterView view in device.views) {
isolateNotifications.add(
view.owner.vm.vmService.onIsolateEvent.then((Stream<ServiceEvent> serviceEvents) async {
view.owner.vm.vmService.onIsolateEvent
.then((Stream<ServiceEvent> serviceEvents) async {
await for (ServiceEvent serviceEvent in serviceEvents) {
if (serviceEvent.owner.name.contains('_spawn') && serviceEvent.kind == ServiceEvent.kIsolateExit) {
if (serviceEvent.owner.name.contains('_spawn')
&& serviceEvent.kind == ServiceEvent.kIsolateExit) {
return;
}
}
......@@ -521,9 +530,10 @@ class HotRunner extends ResidentRunner {
@override
Future<OperationResult> restart({
bool fullRestart = false,
bool pauseAfterRestart = false,
String reason,
bool benchmarkMode = false,
bool silent = false,
bool pause = false,
}) async {
String targetPlatform;
String sdkName;
......@@ -550,8 +560,11 @@ class HotRunner extends ResidentRunner {
emulator: emulator,
reason: reason,
benchmarkMode: benchmarkMode,
silent: silent,
);
printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
if (!silent) {
printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
}
return result;
}
final OperationResult result = await _hotReloadHelper(
......@@ -559,11 +572,13 @@ class HotRunner extends ResidentRunner {
sdkName: sdkName,
emulator: emulator,
reason: reason,
pauseAfterRestart: pauseAfterRestart,
pause: pause,
);
if (result.isOk) {
final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
printStatus('${result.message} in $elapsed.');
if (!silent) {
printStatus('${result.message} in $elapsed.');
}
}
return result;
}
......@@ -574,15 +589,19 @@ class HotRunner extends ResidentRunner {
bool emulator,
String reason,
bool benchmarkMode,
bool silent,
}) async {
if (!canHotRestart) {
return OperationResult(1, 'hotRestart not supported');
}
final Status status = logger.startProgress(
'Performing hot restart...',
timeout: timeoutConfiguration.fastOperation,
progressId: 'hot.restart',
);
Status status;
if (!silent) {
status = logger.startProgress(
'Performing hot restart...',
timeout: timeoutConfiguration.fastOperation,
progressId: 'hot.restart',
);
}
OperationResult result;
String restartEvent = 'restart';
try {
......@@ -610,7 +629,7 @@ class HotRunner extends ResidentRunner {
emulator: emulator,
fullRestart: true,
reason: reason).send();
status.cancel();
status?.cancel();
}
return result;
}
......@@ -620,7 +639,7 @@ class HotRunner extends ResidentRunner {
String sdkName,
bool emulator,
String reason,
bool pauseAfterRestart = false,
bool pause,
}) async {
final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
......@@ -635,8 +654,8 @@ class HotRunner extends ResidentRunner {
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
pause: pauseAfterRestart,
reason: reason,
pause: pause,
onSlow: (String message) {
status?.cancel();
status = logger.startProgress(
......
......@@ -10,11 +10,14 @@ import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
......@@ -53,6 +56,71 @@ void main() {
}
});
testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
final RunCommand command = RunCommand();
applyMocksToCommand(command);
try {
await createTestCommandRunner(command).run(<String>[
'run',
'--use-application-binary=app/bar/faz',
'--fast-start',
'--no-pub',
'--show-test-device',
]);
fail('Expect exception');
} catch (e) {
expect(e.toString(), contains('--fast-start is not supported with --use-application-binary'));
}
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Forces fast start off for devices that do not support it', () async {
final MockDevice mockDevice = MockDevice(TargetPlatform.android_arm);
when(mockDevice.name).thenReturn('mockdevice');
when(mockDevice.supportsFastStart).thenReturn(false);
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async => false);
when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) {
return Future<List<Device>>.value(<Device>[mockDevice]);
});
when(deviceManager.getDevices()).thenAnswer((Invocation invocation) {
return Stream<Device>.value(mockDevice);
});
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
final RunCommand command = RunCommand();
applyMocksToCommand(command);
try {
await createTestCommandRunner(command).run(<String>[
'run',
'--fast-start',
'--no-pub',
]);
fail('Expect exception');
} catch (e) {
expect(e, isInstanceOf<ToolExit>());
}
final BufferLogger bufferLogger = logger as BufferLogger;
expect(bufferLogger.statusText, contains(
'Using --fast-start option with device mockdevice, but this device '
'does not support it. Overriding the setting to false.'
));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
DeviceManager: () => MockDeviceManager(),
});
group('cache', () {
MemoryFileSystem fs;
MockCache mockCache;
......
......@@ -154,6 +154,41 @@ void main() {
expect(onAppStart.isCompleted, true);
}));
test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example';
});
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
return TargetPlatform.android_arm;
});
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
return false;
});
residentRunner = HotRunner(
<FlutterDevice>[
mockFlutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true),
);
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> onAppStart = Completer<void>.sync();
final Future<int> result = residentRunner.attach(
appStartedCompleter: onAppStart,
connectionInfoCompleter: onConnectionInfo,
);
final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;
expect(await result, 0);
verify(mockFlutterDevice.initLogReader()).called(1);
expect(onConnectionInfo.isCompleted, true);
expect((await connectionInfo).baseUri, 'foo://bar');
expect(onAppStart.isCompleted, true);
}));
test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example';
......
......@@ -497,7 +497,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
// fast.
unawaited(_process.exitCode.then((_) {
if (!prematureExitGuard.isCompleted) {
prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}');
prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}: $_errorBuffer');
}
}));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment