Unverified Commit 88631339 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Support work profiles and multiple Android users for run, install, attach, drive (#58815)

parent f0174b17
...@@ -389,10 +389,21 @@ class AndroidDevice extends Device { ...@@ -389,10 +389,21 @@ class AndroidDevice extends Device {
String get name => modelID; String get name => modelID;
@override @override
Future<bool> isAppInstalled(AndroidApk app) async { Future<bool> isAppInstalled(
AndroidApk app, {
String userIdentifier,
}) async {
// This call takes 400ms - 600ms. // This call takes 400ms - 600ms.
try { try {
final RunResult listOut = await runAdbCheckedAsync(<String>['shell', 'pm', 'list', 'packages', app.id]); final RunResult listOut = await runAdbCheckedAsync(<String>[
'shell',
'pm',
'list',
'packages',
if (userIdentifier != null)
...<String>['--user', userIdentifier],
app.id
]);
return LineSplitter.split(listOut.stdout).contains('package:${app.id}'); return LineSplitter.split(listOut.stdout).contains('package:${app.id}');
} on Exception catch (error) { } on Exception catch (error) {
_logger.printTrace('$error'); _logger.printTrace('$error');
...@@ -407,7 +418,10 @@ class AndroidDevice extends Device { ...@@ -407,7 +418,10 @@ class AndroidDevice extends Device {
} }
@override @override
Future<bool> installApp(AndroidApk app) async { Future<bool> installApp(
AndroidApk app, {
String userIdentifier,
}) async {
if (!app.file.existsSync()) { if (!app.file.existsSync()) {
_logger.printError('"${_fileSystem.path.relative(app.file.path)}" does not exist.'); _logger.printError('"${_fileSystem.path.relative(app.file.path)}" does not exist.');
return false; return false;
...@@ -423,7 +437,14 @@ class AndroidDevice extends Device { ...@@ -423,7 +437,14 @@ class AndroidDevice extends Device {
timeout: _timeoutConfiguration.slowOperation, timeout: _timeoutConfiguration.slowOperation,
); );
final RunResult installResult = await _processUtils.run( final RunResult installResult = await _processUtils.run(
adbCommandForDevice(<String>['install', '-t', '-r', app.file.path])); adbCommandForDevice(<String>[
'install',
'-t',
'-r',
if (userIdentifier != null)
...<String>['--user', userIdentifier],
app.file.path
]));
status.stop(); status.stop();
// Some versions of adb exit with exit code 0 even on failure :( // Some versions of adb exit with exit code 0 even on failure :(
// Parsing the output to check for failures. // Parsing the output to check for failures.
...@@ -434,8 +455,12 @@ class AndroidDevice extends Device { ...@@ -434,8 +455,12 @@ class AndroidDevice extends Device {
return false; return false;
} }
if (installResult.exitCode != 0) { if (installResult.exitCode != 0) {
_logger.printError('Error: ADB exited with exit code ${installResult.exitCode}'); if (installResult.stderr.contains('Bad user number')) {
_logger.printError('$installResult'); _logger.printError('Error: User "$userIdentifier" not found. Run "adb shell pm list users" to see list of available identifiers.');
} else {
_logger.printError('Error: ADB exited with exit code ${installResult.exitCode}');
_logger.printError('$installResult');
}
return false; return false;
} }
try { try {
...@@ -450,7 +475,10 @@ class AndroidDevice extends Device { ...@@ -450,7 +475,10 @@ class AndroidDevice extends Device {
} }
@override @override
Future<bool> uninstallApp(AndroidApk app) async { Future<bool> uninstallApp(
AndroidApk app, {
String userIdentifier,
}) async {
if (!await _checkForSupportedAdbVersion() || if (!await _checkForSupportedAdbVersion() ||
!await _checkForSupportedAndroidVersion()) { !await _checkForSupportedAndroidVersion()) {
return false; return false;
...@@ -459,7 +487,11 @@ class AndroidDevice extends Device { ...@@ -459,7 +487,11 @@ class AndroidDevice extends Device {
String uninstallOut; String uninstallOut;
try { try {
final RunResult uninstallResult = await _processUtils.run( final RunResult uninstallResult = await _processUtils.run(
adbCommandForDevice(<String>['uninstall', app.id]), adbCommandForDevice(<String>[
'uninstall',
if (userIdentifier != null)
...<String>['--user', userIdentifier],
app.id]),
throwOnError: true, throwOnError: true,
); );
uninstallOut = uninstallResult.stdout; uninstallOut = uninstallResult.stdout;
...@@ -477,8 +509,8 @@ class AndroidDevice extends Device { ...@@ -477,8 +509,8 @@ class AndroidDevice extends Device {
return true; return true;
} }
Future<bool> _installLatestApp(AndroidApk package) async { Future<bool> _installLatestApp(AndroidApk package, String userIdentifier) async {
final bool wasInstalled = await isAppInstalled(package); final bool wasInstalled = await isAppInstalled(package, userIdentifier: userIdentifier);
if (wasInstalled) { if (wasInstalled) {
if (await isLatestBuildInstalled(package)) { if (await isLatestBuildInstalled(package)) {
_logger.printTrace('Latest build already installed.'); _logger.printTrace('Latest build already installed.');
...@@ -486,15 +518,15 @@ class AndroidDevice extends Device { ...@@ -486,15 +518,15 @@ class AndroidDevice extends Device {
} }
} }
_logger.printTrace('Installing APK.'); _logger.printTrace('Installing APK.');
if (!await installApp(package)) { if (!await installApp(package, userIdentifier: userIdentifier)) {
_logger.printTrace('Warning: Failed to install APK.'); _logger.printTrace('Warning: Failed to install APK.');
if (wasInstalled) { if (wasInstalled) {
_logger.printStatus('Uninstalling old version...'); _logger.printStatus('Uninstalling old version...');
if (!await uninstallApp(package)) { if (!await uninstallApp(package, userIdentifier: userIdentifier)) {
_logger.printError('Error: Uninstalling old version failed.'); _logger.printError('Error: Uninstalling old version failed.');
return false; return false;
} }
if (!await installApp(package)) { if (!await installApp(package, userIdentifier: userIdentifier)) {
_logger.printError('Error: Failed to install APK again.'); _logger.printError('Error: Failed to install APK again.');
return false; return false;
} }
...@@ -516,6 +548,7 @@ class AndroidDevice extends Device { ...@@ -516,6 +548,7 @@ class AndroidDevice extends Device {
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
if (!await _checkForSupportedAdbVersion() || if (!await _checkForSupportedAdbVersion() ||
!await _checkForSupportedAndroidVersion()) { !await _checkForSupportedAndroidVersion()) {
...@@ -570,9 +603,9 @@ class AndroidDevice extends Device { ...@@ -570,9 +603,9 @@ class AndroidDevice extends Device {
} }
_logger.printTrace("Stopping app '${package.name}' on $name."); _logger.printTrace("Stopping app '${package.name}' on $name.");
await stopApp(package); await stopApp(package, userIdentifier: userIdentifier);
if (!await _installLatestApp(package)) { if (!await _installLatestApp(package, userIdentifier)) {
return LaunchResult.failed(); return LaunchResult.failed();
} }
...@@ -636,6 +669,8 @@ class AndroidDevice extends Device { ...@@ -636,6 +669,8 @@ class AndroidDevice extends Device {
...<String>['--ez', 'use-test-fonts', 'true'], ...<String>['--ez', 'use-test-fonts', 'true'],
if (debuggingOptions.verboseSystemLogs) if (debuggingOptions.verboseSystemLogs)
...<String>['--ez', 'verbose-logging', 'true'], ...<String>['--ez', 'verbose-logging', 'true'],
if (userIdentifier != null)
...<String>['--user', userIdentifier],
], ],
package.launchActivity, package.launchActivity,
]; ];
...@@ -687,11 +722,21 @@ class AndroidDevice extends Device { ...@@ -687,11 +722,21 @@ class AndroidDevice extends Device {
bool get supportsFastStart => true; bool get supportsFastStart => true;
@override @override
Future<bool> stopApp(AndroidApk app) { Future<bool> stopApp(
AndroidApk app, {
String userIdentifier,
}) {
if (app == null) { if (app == null) {
return Future<bool>.value(false); return Future<bool>.value(false);
} }
final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]); final List<String> command = adbCommandForDevice(<String>[
'shell',
'am',
'force-stop',
if (userIdentifier != null)
...<String>['--user', userIdentifier],
app.id,
]);
return _processUtils.stream(command).then<bool>( return _processUtils.stream(command).then<bool>(
(int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode, _platform)); (int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode, _platform));
} }
......
...@@ -63,6 +63,7 @@ class AttachCommand extends FlutterCommand { ...@@ -63,6 +63,7 @@ class AttachCommand extends FlutterCommand {
usesFilesystemOptions(hide: !verboseHelp); usesFilesystemOptions(hide: !verboseHelp);
usesFuchsiaOptions(hide: !verboseHelp); usesFuchsiaOptions(hide: !verboseHelp);
usesDartDefineOption(); usesDartDefineOption();
usesDeviceUserOption();
argParser argParser
..addOption( ..addOption(
'debug-port', 'debug-port',
...@@ -136,6 +137,8 @@ class AttachCommand extends FlutterCommand { ...@@ -136,6 +137,8 @@ class AttachCommand extends FlutterCommand {
return stringArg('app-id'); return stringArg('app-id');
} }
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
@override @override
Future<void> validateCommand() async { Future<void> validateCommand() async {
await super.validateCommand(); await super.validateCommand();
...@@ -159,6 +162,13 @@ class AttachCommand extends FlutterCommand { ...@@ -159,6 +162,13 @@ class AttachCommand extends FlutterCommand {
throwToolExit( throwToolExit(
'Either --debugPort or --debugUri can be provided, not both.'); 'Either --debugPort or --debugUri can be provided, not both.');
} }
if (userIdentifier != null) {
final Device device = await findTargetDevice();
if (device is! AndroidDevice) {
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
}
}
} }
@override @override
...@@ -356,6 +366,7 @@ class AttachCommand extends FlutterCommand { ...@@ -356,6 +366,7 @@ class AttachCommand extends FlutterCommand {
target: stringArg('target'), target: stringArg('target'),
targetModel: TargetModel(stringArg('target-model')), targetModel: TargetModel(stringArg('target-model')),
buildInfo: getBuildInfo(), buildInfo: getBuildInfo(),
userIdentifier: userIdentifier,
); );
flutterDevice.observatoryUris = observatoryUris; flutterDevice.observatoryUris = observatoryUris;
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice]; final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
......
...@@ -10,6 +10,7 @@ import 'package:vm_service/vm_service.dart' as vm_service; ...@@ -10,6 +10,7 @@ import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:webdriver/async_io.dart' as async_io; import 'package:webdriver/async_io.dart' as async_io;
import '../android/android_device.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
...@@ -22,7 +23,7 @@ import '../device.dart'; ...@@ -22,7 +23,7 @@ import '../device.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult; import '../runner/flutter_command.dart' show FlutterCommandResult, FlutterOptions;
import '../vmservice.dart'; import '../vmservice.dart';
import '../web/web_runner.dart'; import '../web/web_runner.dart';
import 'run.dart'; import 'run.dart';
...@@ -136,11 +137,23 @@ class DriveCommand extends RunCommandBase { ...@@ -136,11 +137,23 @@ class DriveCommand extends RunCommandBase {
bool get shouldBuild => boolArg('build'); bool get shouldBuild => boolArg('build');
bool get verboseSystemLogs => boolArg('verbose-system-logs'); bool get verboseSystemLogs => boolArg('verbose-system-logs');
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
/// Subscription to log messages printed on the device or simulator. /// Subscription to log messages printed on the device or simulator.
// ignore: cancel_subscriptions // ignore: cancel_subscriptions
StreamSubscription<String> _deviceLogSubscription; StreamSubscription<String> _deviceLogSubscription;
@override
Future<void> validateCommand() async {
if (userIdentifier != null) {
final Device device = await findTargetDevice();
if (device is! AndroidDevice) {
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
}
}
return super.validateCommand();
}
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
final String testFile = _getTestFile(); final String testFile = _getTestFile();
...@@ -195,7 +208,7 @@ class DriveCommand extends RunCommandBase { ...@@ -195,7 +208,7 @@ class DriveCommand extends RunCommandBase {
device, device,
flutterProject: flutterProject, flutterProject: flutterProject,
target: targetFile, target: targetFile,
buildInfo: buildInfo buildInfo: buildInfo,
); );
residentRunner = webRunnerFactory.createWebRunner( residentRunner = webRunnerFactory.createWebRunner(
flutterDevice, flutterDevice,
...@@ -412,7 +425,11 @@ void restoreAppStarter() { ...@@ -412,7 +425,11 @@ void restoreAppStarter() {
appStarter = _startApp; appStarter = _startApp;
} }
Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async { Future<LaunchResult> _startApp(
DriveCommand command,
Uri webUri, {
String userIdentifier,
}) async {
final String mainPath = findMainDartFile(command.targetFile); final String mainPath = findMainDartFile(command.targetFile);
if (await globals.fs.type(mainPath) != FileSystemEntityType.file) { if (await globals.fs.type(mainPath) != FileSystemEntityType.file) {
globals.printError('Tried to run $mainPath, but that file does not exist.'); globals.printError('Tried to run $mainPath, but that file does not exist.');
...@@ -427,10 +444,10 @@ Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async { ...@@ -427,10 +444,10 @@ Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async {
if (command.shouldBuild) { if (command.shouldBuild) {
globals.printTrace('Installing application package.'); globals.printTrace('Installing application package.');
if (await command.device.isAppInstalled(package)) { if (await command.device.isAppInstalled(package, userIdentifier: userIdentifier)) {
await command.device.uninstallApp(package); await command.device.uninstallApp(package, userIdentifier: userIdentifier);
} }
await command.device.installApp(package); await command.device.installApp(package, userIdentifier: userIdentifier);
} }
final Map<String, dynamic> platformArgs = <String, dynamic>{}; final Map<String, dynamic> platformArgs = <String, dynamic>{};
...@@ -469,6 +486,7 @@ Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async { ...@@ -469,6 +486,7 @@ Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async {
), ),
platformArgs: platformArgs, platformArgs: platformArgs,
prebuiltApplication: !command.shouldBuild, prebuiltApplication: !command.shouldBuild,
userIdentifier: userIdentifier,
); );
if (!result.started) { if (!result.started) {
...@@ -520,7 +538,7 @@ Future<bool> _stopApp(DriveCommand command) async { ...@@ -520,7 +538,7 @@ Future<bool> _stopApp(DriveCommand command) async {
await command.device.targetPlatform, await command.device.targetPlatform,
command.getBuildInfo(), command.getBuildInfo(),
); );
final bool stopped = await command.device.stopApp(package); final bool stopped = await command.device.stopApp(package, userIdentifier: command.userIdentifier);
await command._deviceLogSubscription?.cancel(); await command._deviceLogSubscription?.cancel();
return stopped; return stopped;
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import '../android/android_device.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -15,6 +16,7 @@ import '../runner/flutter_command.dart'; ...@@ -15,6 +16,7 @@ import '../runner/flutter_command.dart';
class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
InstallCommand() { InstallCommand() {
requiresPubspecYaml(); requiresPubspecYaml();
usesDeviceUserOption();
argParser.addFlag('uninstall-only', argParser.addFlag('uninstall-only',
negatable: true, negatable: true,
defaultsTo: false, defaultsTo: false,
...@@ -31,6 +33,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -31,6 +33,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
Device device; Device device;
bool get uninstallOnly => boolArg('uninstall-only'); bool get uninstallOnly => boolArg('uninstall-only');
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
@override @override
Future<void> validateCommand() async { Future<void> validateCommand() async {
...@@ -39,6 +42,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -39,6 +42,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
if (device == null) { if (device == null) {
throwToolExit('No target device found'); throwToolExit('No target device found');
} }
if (userIdentifier != null && device is! AndroidDevice) {
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
}
} }
@override @override
...@@ -59,9 +65,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -59,9 +65,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
} }
Future<void> _uninstallApp(ApplicationPackage package) async { Future<void> _uninstallApp(ApplicationPackage package) async {
if (await device.isAppInstalled(package)) { if (await device.isAppInstalled(package, userIdentifier: userIdentifier)) {
globals.printStatus('Uninstalling $package from $device...'); globals.printStatus('Uninstalling $package from $device...');
if (!await device.uninstallApp(package)) { if (!await device.uninstallApp(package, userIdentifier: userIdentifier)) {
globals.printError('Uninstalling old version failed'); globals.printError('Uninstalling old version failed');
} }
} else { } else {
...@@ -72,21 +78,26 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -72,21 +78,26 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
Future<void> _installApp(ApplicationPackage package) async { Future<void> _installApp(ApplicationPackage package) async {
globals.printStatus('Installing $package to $device...'); globals.printStatus('Installing $package to $device...');
if (!await installApp(device, package)) { if (!await installApp(device, package, userIdentifier: userIdentifier)) {
throwToolExit('Install failed'); throwToolExit('Install failed');
} }
} }
} }
Future<bool> installApp(Device device, ApplicationPackage package, { bool uninstall = true }) async { Future<bool> installApp(
Device device,
ApplicationPackage package, {
String userIdentifier,
bool uninstall = true
}) async {
if (package == null) { if (package == null) {
return false; return false;
} }
try { try {
if (uninstall && await device.isAppInstalled(package)) { if (uninstall && await device.isAppInstalled(package, userIdentifier: userIdentifier)) {
globals.printStatus('Uninstalling old version...'); globals.printStatus('Uninstalling old version...');
if (!await device.uninstallApp(package)) { if (!await device.uninstallApp(package, userIdentifier: userIdentifier)) {
globals.printError('Warning: uninstalling old version failed'); globals.printError('Warning: uninstalling old version failed');
} }
} }
...@@ -94,5 +105,5 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst ...@@ -94,5 +105,5 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst
globals.printError('Error accessing device ${device.id}:\n${e.message}'); globals.printError('Error accessing device ${device.id}:\n${e.message}');
} }
return device.installApp(package); return device.installApp(package, userIdentifier: userIdentifier);
} }
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import '../android/android_device.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -67,6 +68,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -67,6 +68,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
usesTrackWidgetCreation(verboseHelp: verboseHelp); usesTrackWidgetCreation(verboseHelp: verboseHelp);
usesIsolateFilterOption(hide: !verboseHelp); usesIsolateFilterOption(hide: !verboseHelp);
addNullSafetyModeOptions(); addNullSafetyModeOptions();
usesDeviceUserOption();
} }
bool get traceStartup => boolArg('trace-startup'); bool get traceStartup => boolArg('trace-startup');
...@@ -221,6 +223,8 @@ class RunCommand extends RunCommandBase { ...@@ -221,6 +223,8 @@ class RunCommand extends RunCommandBase {
List<Device> devices; List<Device> devices;
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
@override @override
Future<String> get usagePath async { Future<String> get usagePath async {
final String command = await super.usagePath; final String command = await super.usagePath;
...@@ -336,6 +340,13 @@ class RunCommand extends RunCommandBase { ...@@ -336,6 +340,13 @@ class RunCommand extends RunCommandBase {
if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) { if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
throwToolExit('Using -d all with --use-application-binary is not supported'); throwToolExit('Using -d all with --use-application-binary is not supported');
} }
if (userIdentifier != null
&& devices.every((Device device) => device is! AndroidDevice)) {
throwToolExit(
'--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.'
);
}
} }
DebuggingOptions _createDebuggingOptions() { DebuggingOptions _createDebuggingOptions() {
...@@ -491,6 +502,7 @@ class RunCommand extends RunCommandBase { ...@@ -491,6 +502,7 @@ class RunCommand extends RunCommandBase {
experimentalFlags: expFlags, experimentalFlags: expFlags,
target: stringArg('target'), target: stringArg('target'),
buildInfo: getBuildInfo(), buildInfo: getBuildInfo(),
userIdentifier: userIdentifier,
), ),
]; ];
// Only support "web mode" with a single web device due to resident runner // Only support "web mode" with a single web device due to resident runner
......
...@@ -33,7 +33,10 @@ abstract class DesktopDevice extends Device { ...@@ -33,7 +33,10 @@ abstract class DesktopDevice extends Device {
// Since the host and target devices are the same, no work needs to be done // Since the host and target devices are the same, no work needs to be done
// to install the application. // to install the application.
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) async => true; Future<bool> isAppInstalled(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
// Since the host and target devices are the same, no work needs to be done // Since the host and target devices are the same, no work needs to be done
// to install the application. // to install the application.
...@@ -43,12 +46,18 @@ abstract class DesktopDevice extends Device { ...@@ -43,12 +46,18 @@ abstract class DesktopDevice extends Device {
// Since the host and target devices are the same, no work needs to be done // Since the host and target devices are the same, no work needs to be done
// to install the application. // to install the application.
@override @override
Future<bool> installApp(ApplicationPackage app) async => true; Future<bool> installApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
// Since the host and target devices are the same, no work needs to be done // Since the host and target devices are the same, no work needs to be done
// to uninstall the application. // to uninstall the application.
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async => true; Future<bool> uninstallApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
Future<bool> get isLocalEmulator async => false; Future<bool> get isLocalEmulator async => false;
...@@ -86,6 +95,7 @@ abstract class DesktopDevice extends Device { ...@@ -86,6 +95,7 @@ abstract class DesktopDevice extends Device {
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
if (!prebuiltApplication) { if (!prebuiltApplication) {
Cache.releaseLockEarly(); Cache.releaseLockEarly();
...@@ -138,7 +148,10 @@ abstract class DesktopDevice extends Device { ...@@ -138,7 +148,10 @@ abstract class DesktopDevice extends Device {
} }
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
bool succeeded = true; bool succeeded = true;
// Walk a copy of _runningProcesses, since the exit handler removes from the // Walk a copy of _runningProcesses, since the exit handler removes from the
// set. // set.
......
...@@ -413,17 +413,33 @@ abstract class Device { ...@@ -413,17 +413,33 @@ abstract class Device {
/// Whether the device is supported for the current project directory. /// Whether the device is supported for the current project directory.
bool isSupportedForProject(FlutterProject flutterProject); bool isSupportedForProject(FlutterProject flutterProject);
/// Check if a version of the given app is already installed /// Check if a version of the given app is already installed.
Future<bool> isAppInstalled(covariant ApplicationPackage app); ///
/// Specify [userIdentifier] to check if installed for a particular user (Android only).
Future<bool> isAppInstalled(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Check if the latest build of the [app] is already installed. /// Check if the latest build of the [app] is already installed.
Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app); Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app);
/// Install an app package on the current device /// Install an app package on the current device.
Future<bool> installApp(covariant ApplicationPackage app); ///
/// Specify [userIdentifier] to install for a particular user (Android only).
Future<bool> installApp(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Uninstall an app package from the current device /// Uninstall an app package from the current device.
Future<bool> uninstallApp(covariant ApplicationPackage app); ///
/// Specify [userIdentifier] to uninstall for a particular user,
/// defaults to all users (Android only).
Future<bool> uninstallApp(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Check if the device is supported by Flutter /// Check if the device is supported by Flutter
bool isSupported(); bool isSupported();
...@@ -471,6 +487,7 @@ abstract class Device { ...@@ -471,6 +487,7 @@ abstract class Device {
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}); });
/// Whether this device implements support for hot reload. /// Whether this device implements support for hot reload.
...@@ -491,7 +508,12 @@ abstract class Device { ...@@ -491,7 +508,12 @@ abstract class Device {
bool get supportsFastStart => false; bool get supportsFastStart => false;
/// Stop an app package on the current device. /// Stop an app package on the current device.
Future<bool> stopApp(covariant ApplicationPackage app); ///
/// Specify [userIdentifier] to stop app installed to a profile (Android only).
Future<bool> stopApp(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Query the current application memory usage.. /// Query the current application memory usage..
/// ///
......
...@@ -237,16 +237,25 @@ class FuchsiaDevice extends Device { ...@@ -237,16 +237,25 @@ class FuchsiaDevice extends Device {
bool get supportsStartPaused => false; bool get supportsStartPaused => false;
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) async => false; Future<bool> isAppInstalled(
ApplicationPackage app, {
String userIdentifier,
}) async => false;
@override @override
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false; Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
@override @override
Future<bool> installApp(ApplicationPackage app) => Future<bool>.value(false); Future<bool> installApp(
ApplicationPackage app, {
String userIdentifier,
}) => Future<bool>.value(false);
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async => false; Future<bool> uninstallApp(
ApplicationPackage app, {
String userIdentifier,
}) async => false;
@override @override
bool isSupported() => true; bool isSupported() => true;
...@@ -263,6 +272,7 @@ class FuchsiaDevice extends Device { ...@@ -263,6 +272,7 @@ class FuchsiaDevice extends Device {
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
if (!prebuiltApplication) { if (!prebuiltApplication) {
await buildFuchsia(fuchsiaProject: FlutterProject.current().fuchsia, await buildFuchsia(fuchsiaProject: FlutterProject.current().fuchsia,
...@@ -434,7 +444,10 @@ class FuchsiaDevice extends Device { ...@@ -434,7 +444,10 @@ class FuchsiaDevice extends Device {
} }
@override @override
Future<bool> stopApp(covariant FuchsiaApp app) async { Future<bool> stopApp(
covariant FuchsiaApp app, {
String userIdentifier,
}) async {
final int appKey = await FuchsiaTilesCtl.findAppKey(this, app.id); final int appKey = await FuchsiaTilesCtl.findAppKey(this, app.id);
if (appKey != -1) { if (appKey != -1) {
if (!await fuchsiaDeviceTools.tilesCtl.remove(this, appKey)) { if (!await fuchsiaDeviceTools.tilesCtl.remove(this, appKey)) {
......
...@@ -225,7 +225,10 @@ class IOSDevice extends Device { ...@@ -225,7 +225,10 @@ class IOSDevice extends Device {
bool get supportsStartPaused => false; bool get supportsStartPaused => false;
@override @override
Future<bool> isAppInstalled(IOSApp app) async { Future<bool> isAppInstalled(
IOSApp app, {
String userIdentifier,
}) async {
bool result; bool result;
try { try {
result = await _iosDeploy.isAppInstalled( result = await _iosDeploy.isAppInstalled(
...@@ -243,7 +246,10 @@ class IOSDevice extends Device { ...@@ -243,7 +246,10 @@ class IOSDevice extends Device {
Future<bool> isLatestBuildInstalled(IOSApp app) async => false; Future<bool> isLatestBuildInstalled(IOSApp app) async => false;
@override @override
Future<bool> installApp(IOSApp app) async { Future<bool> installApp(
IOSApp app, {
String userIdentifier,
}) async {
final Directory bundle = _fileSystem.directory(app.deviceBundlePath); final Directory bundle = _fileSystem.directory(app.deviceBundlePath);
if (!bundle.existsSync()) { if (!bundle.existsSync()) {
_logger.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?'); _logger.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?');
...@@ -273,7 +279,10 @@ class IOSDevice extends Device { ...@@ -273,7 +279,10 @@ class IOSDevice extends Device {
} }
@override @override
Future<bool> uninstallApp(IOSApp app) async { Future<bool> uninstallApp(
IOSApp app, {
String userIdentifier,
}) async {
int uninstallationResult; int uninstallationResult;
try { try {
uninstallationResult = await _iosDeploy.uninstallApp( uninstallationResult = await _iosDeploy.uninstallApp(
...@@ -304,6 +313,7 @@ class IOSDevice extends Device { ...@@ -304,6 +313,7 @@ class IOSDevice extends Device {
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
@visibleForTesting Duration fallbackPollingDelay, @visibleForTesting Duration fallbackPollingDelay,
String userIdentifier,
}) async { }) async {
String packageId; String packageId;
...@@ -443,7 +453,10 @@ class IOSDevice extends Device { ...@@ -443,7 +453,10 @@ class IOSDevice extends Device {
} }
@override @override
Future<bool> stopApp(IOSApp app) async { Future<bool> stopApp(
IOSApp app, {
String userIdentifier,
}) async {
// Currently we don't have a way to stop an app running on iOS. // Currently we don't have a way to stop an app running on iOS.
return false; return false;
} }
......
...@@ -319,7 +319,10 @@ class IOSSimulator extends Device { ...@@ -319,7 +319,10 @@ class IOSSimulator extends Device {
String get xcrunPath => globals.fs.path.join('/usr', 'bin', 'xcrun'); String get xcrunPath => globals.fs.path.join('/usr', 'bin', 'xcrun');
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) { Future<bool> isAppInstalled(
ApplicationPackage app, {
String userIdentifier,
}) {
return _simControl.isInstalled(id, app.id); return _simControl.isInstalled(id, app.id);
} }
...@@ -327,7 +330,10 @@ class IOSSimulator extends Device { ...@@ -327,7 +330,10 @@ class IOSSimulator extends Device {
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false; Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
@override @override
Future<bool> installApp(covariant IOSApp app) async { Future<bool> installApp(
covariant IOSApp app, {
String userIdentifier,
}) async {
try { try {
final IOSApp iosApp = app; final IOSApp iosApp = app;
await _simControl.install(id, iosApp.simulatorBundlePath); await _simControl.install(id, iosApp.simulatorBundlePath);
...@@ -338,7 +344,10 @@ class IOSSimulator extends Device { ...@@ -338,7 +344,10 @@ class IOSSimulator extends Device {
} }
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async { Future<bool> uninstallApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
try { try {
await _simControl.uninstall(id, app.id); await _simControl.uninstall(id, app.id);
return true; return true;
...@@ -384,6 +393,7 @@ class IOSSimulator extends Device { ...@@ -384,6 +393,7 @@ class IOSSimulator extends Device {
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
if (!prebuiltApplication && package is BuildableIOSApp) { if (!prebuiltApplication && package is BuildableIOSApp) {
globals.printTrace('Building ${package.name} for $id.'); globals.printTrace('Building ${package.name} for $id.');
...@@ -495,7 +505,10 @@ class IOSSimulator extends Device { ...@@ -495,7 +505,10 @@ class IOSSimulator extends Device {
} }
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
// Currently we don't have a way to stop an app running on iOS. // Currently we don't have a way to stop an app running on iOS.
return false; return false;
} }
......
...@@ -47,6 +47,7 @@ class FlutterDevice { ...@@ -47,6 +47,7 @@ class FlutterDevice {
TargetModel targetModel = TargetModel.flutter, TargetModel targetModel = TargetModel.flutter,
TargetPlatform targetPlatform, TargetPlatform targetPlatform,
ResidentCompiler generator, ResidentCompiler generator,
this.userIdentifier,
}) : assert(buildInfo.trackWidgetCreation != null), }) : assert(buildInfo.trackWidgetCreation != null),
generator = generator ?? ResidentCompiler( generator = generator ?? ResidentCompiler(
globals.artifacts.getArtifactPath( globals.artifacts.getArtifactPath(
...@@ -76,6 +77,7 @@ class FlutterDevice { ...@@ -76,6 +77,7 @@ class FlutterDevice {
TargetModel targetModel = TargetModel.flutter, TargetModel targetModel = TargetModel.flutter,
List<String> experimentalFlags, List<String> experimentalFlags,
ResidentCompiler generator, ResidentCompiler generator,
String userIdentifier,
}) async { }) async {
ResidentCompiler generator; ResidentCompiler generator;
final TargetPlatform targetPlatform = await device.targetPlatform; final TargetPlatform targetPlatform = await device.targetPlatform;
...@@ -150,12 +152,14 @@ class FlutterDevice { ...@@ -150,12 +152,14 @@ class FlutterDevice {
targetPlatform: targetPlatform, targetPlatform: targetPlatform,
generator: generator, generator: generator,
buildInfo: buildInfo, buildInfo: buildInfo,
userIdentifier: userIdentifier,
); );
} }
final Device device; final Device device;
final ResidentCompiler generator; final ResidentCompiler generator;
final BuildInfo buildInfo; final BuildInfo buildInfo;
final String userIdentifier;
Stream<Uri> observatoryUris; Stream<Uri> observatoryUris;
vm_service.VmService vmService; vm_service.VmService vmService;
DevFS devFS; DevFS devFS;
...@@ -239,11 +243,11 @@ class FlutterDevice { ...@@ -239,11 +243,11 @@ class FlutterDevice {
@visibleForTesting Duration timeoutDelay = const Duration(seconds: 10), @visibleForTesting Duration timeoutDelay = const Duration(seconds: 10),
}) async { }) async {
if (!device.supportsFlutterExit || vmService == null) { if (!device.supportsFlutterExit || vmService == null) {
return device.stopApp(package); return device.stopApp(package, userIdentifier: userIdentifier);
} }
final List<FlutterView> views = await vmService.getFlutterViews(); final List<FlutterView> views = await vmService.getFlutterViews();
if (views == null || views.isEmpty) { if (views == null || views.isEmpty) {
return device.stopApp(package); return device.stopApp(package, userIdentifier: userIdentifier);
} }
// If any of the flutter views are paused, we might not be able to // If any of the flutter views are paused, we might not be able to
// cleanly exit since the service extension may not have been registered. // cleanly exit since the service extension may not have been registered.
...@@ -254,7 +258,7 @@ class FlutterDevice { ...@@ -254,7 +258,7 @@ class FlutterDevice {
continue; continue;
} }
if (isPauseEvent(isolate.pauseEvent.kind)) { if (isPauseEvent(isolate.pauseEvent.kind)) {
return device.stopApp(package); return device.stopApp(package, userIdentifier: userIdentifier);
} }
} }
for (final FlutterView view in views) { for (final FlutterView view in views) {
...@@ -277,7 +281,7 @@ class FlutterDevice { ...@@ -277,7 +281,7 @@ class FlutterDevice {
// flutter_attach_android_test. This log should help verify this // flutter_attach_android_test. This log should help verify this
// is where the tool is getting stuck. // is where the tool is getting stuck.
globals.logger.printTrace('error: vm service shutdown failed'); globals.logger.printTrace('error: vm service shutdown failed');
return device.stopApp(package); return device.stopApp(package, userIdentifier: userIdentifier);
}); });
} }
...@@ -502,6 +506,7 @@ class FlutterDevice { ...@@ -502,6 +506,7 @@ class FlutterDevice {
route: route, route: route,
prebuiltApplication: prebuiltMode, prebuiltApplication: prebuiltMode,
ipv6: hotRunner.ipv6, ipv6: hotRunner.ipv6,
userIdentifier: userIdentifier,
); );
final LaunchResult result = await futureResult; final LaunchResult result = await futureResult;
...@@ -575,6 +580,7 @@ class FlutterDevice { ...@@ -575,6 +580,7 @@ class FlutterDevice {
route: route, route: route,
prebuiltApplication: prebuiltMode, prebuiltApplication: prebuiltMode,
ipv6: coldRunner.ipv6, ipv6: coldRunner.ipv6,
userIdentifier: userIdentifier,
); );
if (!result.started) { if (!result.started) {
......
...@@ -202,7 +202,7 @@ class ColdRunner extends ResidentRunner { ...@@ -202,7 +202,7 @@ class ColdRunner extends ResidentRunner {
for (final FlutterDevice device in flutterDevices) { for (final FlutterDevice device in flutterDevices) {
// If we're running in release mode, stop the app using the device logic. // If we're running in release mode, stop the app using the device logic.
if (device.vmService == null) { if (device.vmService == null) {
await device.device.stopApp(device.package); await device.device.stopApp(device.package, userIdentifier: device.userIdentifier);
} }
} }
await super.preExit(); await super.preExit();
......
...@@ -110,6 +110,7 @@ class FlutterOptions { ...@@ -110,6 +110,7 @@ class FlutterOptions {
static const String kBundleSkSLPathOption = 'bundle-sksl-path'; static const String kBundleSkSLPathOption = 'bundle-sksl-path';
static const String kPerformanceMeasurementFile = 'performance-measurement-file'; static const String kPerformanceMeasurementFile = 'performance-measurement-file';
static const String kNullSafety = 'sound-null-safety'; static const String kNullSafety = 'sound-null-safety';
static const String kDeviceUser = 'device-user';
} }
abstract class FlutterCommand extends Command<void> { abstract class FlutterCommand extends Command<void> {
...@@ -374,6 +375,12 @@ abstract class FlutterCommand extends Command<void> { ...@@ -374,6 +375,12 @@ abstract class FlutterCommand extends Command<void> {
"Normally there's only one, but when adding Flutter to a pre-existing app it's possible to create multiple."); "Normally there's only one, but when adding Flutter to a pre-existing app it's possible to create multiple.");
} }
void usesDeviceUserOption() {
argParser.addOption(FlutterOptions.kDeviceUser,
help: 'Identifier number for a user or work profile on Android only. Run "adb shell pm list users" for available identifiers.',
valueHelp: '10');
}
void addBuildModeFlags({ bool defaultToRelease = true, bool verboseHelp = false, bool excludeDebug = false }) { void addBuildModeFlags({ bool defaultToRelease = true, bool verboseHelp = false, bool excludeDebug = false }) {
// A release build must be the default if a debug build is not possible. // A release build must be the default if a debug build is not possible.
assert(defaultToRelease || !excludeDebug); assert(defaultToRelease || !excludeDebug);
......
...@@ -90,10 +90,16 @@ class FlutterTesterDevice extends Device { ...@@ -90,10 +90,16 @@ class FlutterTesterDevice extends Device {
} }
@override @override
Future<bool> installApp(ApplicationPackage app) async => true; Future<bool> installApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) async => false; Future<bool> isAppInstalled(
ApplicationPackage app, {
String userIdentifier,
}) async => false;
@override @override
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false; Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
...@@ -109,10 +115,11 @@ class FlutterTesterDevice extends Device { ...@@ -109,10 +115,11 @@ class FlutterTesterDevice extends Device {
ApplicationPackage package, { ApplicationPackage package, {
@required String mainPath, @required String mainPath,
String route, String route,
@required DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
final BuildInfo buildInfo = debuggingOptions.buildInfo; final BuildInfo buildInfo = debuggingOptions.buildInfo;
...@@ -218,14 +225,20 @@ class FlutterTesterDevice extends Device { ...@@ -218,14 +225,20 @@ class FlutterTesterDevice extends Device {
} }
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
_process?.kill(); _process?.kill();
_process = null; _process = null;
return true; return true;
} }
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async => true; Future<bool> uninstallApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
bool isSupportedForProject(FlutterProject flutterProject) => true; bool isSupportedForProject(FlutterProject flutterProject) => true;
......
...@@ -87,10 +87,16 @@ abstract class ChromiumDevice extends Device { ...@@ -87,10 +87,16 @@ abstract class ChromiumDevice extends Device {
} }
@override @override
Future<bool> installApp(ApplicationPackage app) async => true; Future<bool> installApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) async => true; Future<bool> isAppInstalled(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true; Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
...@@ -116,6 +122,7 @@ abstract class ChromiumDevice extends Device { ...@@ -116,6 +122,7 @@ abstract class ChromiumDevice extends Device {
Map<String, Object> platformArgs, Map<String, Object> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart // See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
// for the web initialization and server logic. // for the web initialization and server logic.
...@@ -137,7 +144,10 @@ abstract class ChromiumDevice extends Device { ...@@ -137,7 +144,10 @@ abstract class ChromiumDevice extends Device {
} }
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
await _chrome?.close(); await _chrome?.close();
return true; return true;
} }
...@@ -146,7 +156,10 @@ abstract class ChromiumDevice extends Device { ...@@ -146,7 +156,10 @@ abstract class ChromiumDevice extends Device {
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript; Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async => true; Future<bool> uninstallApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
bool isSupportedForProject(FlutterProject flutterProject) { bool isSupportedForProject(FlutterProject flutterProject) {
...@@ -333,10 +346,16 @@ class WebServerDevice extends Device { ...@@ -333,10 +346,16 @@ class WebServerDevice extends Device {
} }
@override @override
Future<bool> installApp(ApplicationPackage app) async => true; Future<bool> installApp(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) async => true; Future<bool> isAppInstalled(
ApplicationPackage app, {
String userIdentifier,
}) async => true;
@override @override
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true; Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
...@@ -375,6 +394,7 @@ class WebServerDevice extends Device { ...@@ -375,6 +394,7 @@ class WebServerDevice extends Device {
Map<String, Object> platformArgs, Map<String, Object> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
final String url = platformArgs['uri'] as String; final String url = platformArgs['uri'] as String;
if (debuggingOptions.startPaused) { if (debuggingOptions.startPaused) {
...@@ -387,7 +407,10 @@ class WebServerDevice extends Device { ...@@ -387,7 +407,10 @@ class WebServerDevice extends Device {
} }
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
return true; return true;
} }
...@@ -395,7 +418,10 @@ class WebServerDevice extends Device { ...@@ -395,7 +418,10 @@ class WebServerDevice extends Device {
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript; Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async { Future<bool> uninstallApp(
ApplicationPackage app, {
String userIdentifier,
}) async {
return true; return true;
} }
......
...@@ -326,16 +326,29 @@ void main() { ...@@ -326,16 +326,29 @@ void main() {
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).deleteSync(); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).deleteSync();
final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory); final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
await createTestCommandRunner(command).run(<String>['attach', '-t', foo.path, '-v']); await createTestCommandRunner(command).run(<String>[
'attach',
'-t',
foo.path,
'-v',
'--device-user',
'10',
]);
final VerificationResult verificationResult = verify(
mockHotRunnerFactory.build(
captureAny,
target: foo.path,
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
),
)..called(1);
verify(mockHotRunnerFactory.build( final List<FlutterDevice> flutterDevices = verificationResult.captured.first as List<FlutterDevice>;
any, expect(flutterDevices, hasLength(1));
target: foo.path, final FlutterDevice flutterDevice = flutterDevices.first;
debuggingOptions: anyNamed('debuggingOptions'), expect(flutterDevice.userIdentifier, '10');
packagesFilePath: anyNamed('packagesFilePath'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
)).called(1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => testFileSystem, FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -538,6 +551,19 @@ void main() { ...@@ -538,6 +551,19 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('fails when targeted device is not Android with --device-user', () async {
final MockIOSDevice device = MockIOSDevice();
testDeviceManager.addDevice(device);
expect(createTestCommandRunner(AttachCommand()).run(<String>[
'attach',
'--device-user',
'10',
]), throwsToolExit(message: '--device-user is only supported for Android'));
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('exits when multiple devices connected', () async { testUsingContext('exits when multiple devices connected', () async {
Device aDeviceWithId(String id) { Device aDeviceWithId(String id) {
final MockAndroidDevice device = MockAndroidDevice(); final MockAndroidDevice device = MockAndroidDevice();
......
...@@ -166,9 +166,30 @@ void main() { ...@@ -166,9 +166,30 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('returns 0 when test ends successfully', () async { testUsingContext('returns 1 when targeted device is not Android with --device-user', () async {
testDeviceManager.addDevice(MockDevice()); testDeviceManager.addDevice(MockDevice());
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
globals.fs.file(testApp).createSync(recursive: true);
final List<String> args = <String>[
'drive',
'--target=$testApp',
'--no-pub',
'--device-user',
'10',
];
expect(() async => await createTestCommandRunner(command).run(args),
throwsToolExit(message: '--device-user is only supported for Android'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 0 when test ends successfully', () async {
testDeviceManager.addDevice(MockAndroidDevice());
final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); final String testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
...@@ -195,6 +216,8 @@ void main() { ...@@ -195,6 +216,8 @@ void main() {
'drive', 'drive',
'--target=$testApp', '--target=$testApp',
'--no-pub', '--no-pub',
'--device-user',
'10',
]; ];
await createTestCommandRunner(command).run(args); await createTestCommandRunner(command).run(args);
expect(testLogger.errorText, isEmpty); expect(testLogger.errorText, isEmpty);
...@@ -358,14 +381,16 @@ void main() { ...@@ -358,14 +381,16 @@ void main() {
final MockLaunchResult mockLaunchResult = MockLaunchResult(); final MockLaunchResult mockLaunchResult = MockLaunchResult();
when(mockLaunchResult.started).thenReturn(true); when(mockLaunchResult.started).thenReturn(true);
when(mockDevice.startApp( when(mockDevice.startApp(
null, null,
mainPath: anyNamed('mainPath'), mainPath: anyNamed('mainPath'),
route: anyNamed('route'), route: anyNamed('route'),
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'), platformArgs: anyNamed('platformArgs'),
prebuiltApplication: anyNamed('prebuiltApplication'), prebuiltApplication: anyNamed('prebuiltApplication'),
userIdentifier: anyNamed('userIdentifier'),
)).thenAnswer((_) => Future<LaunchResult>.value(mockLaunchResult)); )).thenAnswer((_) => Future<LaunchResult>.value(mockLaunchResult));
when(mockDevice.isAppInstalled(any)).thenAnswer((_) => Future<bool>.value(false)); when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) => Future<bool>.value(false));
testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart'); testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
...@@ -407,6 +432,7 @@ void main() { ...@@ -407,6 +432,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'), platformArgs: anyNamed('platformArgs'),
prebuiltApplication: false, prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
)); ));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
...@@ -435,6 +461,7 @@ void main() { ...@@ -435,6 +461,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'), platformArgs: anyNamed('platformArgs'),
prebuiltApplication: false, prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
)); ));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
...@@ -463,6 +490,7 @@ void main() { ...@@ -463,6 +490,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'), platformArgs: anyNamed('platformArgs'),
prebuiltApplication: true, prebuiltApplication: true,
userIdentifier: anyNamed('userIdentifier'),
)); ));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
...@@ -494,11 +522,12 @@ void main() { ...@@ -494,11 +522,12 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'), platformArgs: anyNamed('platformArgs'),
prebuiltApplication: anyNamed('prebuiltApplication'), prebuiltApplication: anyNamed('prebuiltApplication'),
userIdentifier: anyNamed('userIdentifier'),
)).thenAnswer((Invocation invocation) async { )).thenAnswer((Invocation invocation) async {
debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions; debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions;
return mockLaunchResult; return mockLaunchResult;
}); });
when(mockDevice.isAppInstalled(any)) when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) => Future<bool>.value(false)); .thenAnswer((_) => Future<bool>.value(false));
testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart'); testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
...@@ -547,6 +576,7 @@ void main() { ...@@ -547,6 +576,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
platformArgs: anyNamed('platformArgs'), platformArgs: anyNamed('platformArgs'),
prebuiltApplication: false, prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
)); ));
expect(optionValue(), setToTrue ? isTrue : isFalse); expect(optionValue(), setToTrue ? isTrue : isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
...@@ -21,8 +21,10 @@ void main() { ...@@ -21,8 +21,10 @@ void main() {
applyMocksToCommand(command); applyMocksToCommand(command);
final MockAndroidDevice device = MockAndroidDevice(); final MockAndroidDevice device = MockAndroidDevice();
when(device.isAppInstalled(any)).thenAnswer((_) async => false); when(device.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
when(device.installApp(any)).thenAnswer((_) async => true); .thenAnswer((_) async => false);
when(device.installApp(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) async => true);
testDeviceManager.addDevice(device); testDeviceManager.addDevice(device);
await createTestCommandRunner(command).run(<String>['install']); await createTestCommandRunner(command).run(<String>['install']);
...@@ -30,6 +32,23 @@ void main() { ...@@ -30,6 +32,23 @@ void main() {
Cache: () => MockCache(), Cache: () => MockCache(),
}); });
testUsingContext('returns 1 when targeted device is not Android with --device-user', () async {
final InstallCommand command = InstallCommand();
applyMocksToCommand(command);
final MockIOSDevice device = MockIOSDevice();
when(device.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) async => false);
when(device.installApp(any, userIdentifier: anyNamed('userIdentifier')))
.thenAnswer((_) async => true);
testDeviceManager.addDevice(device);
expect(() async => await createTestCommandRunner(command).run(<String>['install', '--device-user', '10']),
throwsToolExit(message: '--device-user is only supported for Android'));
}, overrides: <Type, Generator>{
Cache: () => MockCache(),
});
testUsingContext('returns 0 when iOS is connected and ready for an install', () async { testUsingContext('returns 0 when iOS is connected and ready for an install', () async {
final InstallCommand command = InstallCommand(); final InstallCommand command = InstallCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
......
...@@ -203,6 +203,38 @@ void main() { ...@@ -203,6 +203,38 @@ void main() {
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
}); });
testUsingContext('fails when targeted device is not Android with --device-user', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').writeAsStringSync('\n');
globals.fs.file('lib/main.dart').createSync(recursive: true);
final FakeDevice device = FakeDevice(isLocalEmulator: true);
when(deviceManager.getAllConnectedDevices()).thenAnswer((Invocation invocation) async {
return <Device>[device];
});
when(deviceManager.getDevices()).thenAnswer((Invocation invocation) async {
return <Device>[device];
});
when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) async {
return <Device>[device];
});
when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
when(deviceManager.deviceDiscoverers).thenReturn(<DeviceDiscovery>[]);
final RunCommand command = RunCommand();
applyMocksToCommand(command);
await expectLater(createTestCommandRunner(command).run(<String>[
'run',
'--no-pub',
'--device-user',
'10',
]), throwsToolExit(message: '--device-user is only supported for Android. At least one Android device is required.'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
DeviceManager: () => MockDeviceManager(),
Stdio: () => mockStdio,
});
testUsingContext('shows unsupported devices when no supported devices are found', () async { testUsingContext('shows unsupported devices when no supported devices are found', () async {
final RunCommand command = RunCommand(); final RunCommand command = RunCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
...@@ -320,6 +352,7 @@ void main() { ...@@ -320,6 +352,7 @@ void main() {
route: anyNamed('route'), route: anyNamed('route'),
prebuiltApplication: anyNamed('prebuiltApplication'), prebuiltApplication: anyNamed('prebuiltApplication'),
ipv6: anyNamed('ipv6'), ipv6: anyNamed('ipv6'),
userIdentifier: anyNamed('userIdentifier'),
)).thenAnswer((Invocation invocation) => Future<LaunchResult>.value(LaunchResult.failed())); )).thenAnswer((Invocation invocation) => Future<LaunchResult>.value(LaunchResult.failed()));
when(mockArtifacts.getArtifactPath( when(mockArtifacts.getArtifactPath(
...@@ -690,6 +723,7 @@ class FakeDevice extends Fake implements Device { ...@@ -690,6 +723,7 @@ class FakeDevice extends Fake implements Device {
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool usesTerminalUi = true, bool usesTerminalUi = true,
bool ipv6 = false, bool ipv6 = false,
String userIdentifier,
}) async { }) async {
final String dartFlags = debuggingOptions.dartFlags; final String dartFlags = debuggingOptions.dartFlags;
// In release mode, --dart-flags should be set to the empty string and // In release mode, --dart-flags should be set to the empty string and
......
...@@ -188,17 +188,27 @@ void main() { ...@@ -188,17 +188,27 @@ void main() {
command: <String>['adb', '-s', '1234', 'shell', 'getprop'], command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
)); ));
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'am', 'force-stop', 'FlutterApp'], command: <String>['adb', '-s', '1234', 'shell', 'am', 'force-stop', '--user', '10', 'FlutterApp'],
)); ));
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', 'FlutterApp'], command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', '--user', '10', 'FlutterApp'],
)); ));
processManager.addCommand(kAdbVersionCommand); processManager.addCommand(kAdbVersionCommand);
processManager.addCommand(kStartServer); processManager.addCommand(kStartServer);
// TODO(jonahwilliams): investigate why this doesn't work. // TODO(jonahwilliams): investigate why this doesn't work.
// This doesn't work with the current Android log reader implementation. // This doesn't work with the current Android log reader implementation.
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['adb', '-s', '1234', 'install', '-t', '-r', 'app.apk'], command: <String>[
'adb',
'-s',
'1234',
'install',
'-t',
'-r',
'--user',
'10',
'app.apk'
],
stdout: '\n\nObservatory listening on http://127.0.0.1:456\n\n', stdout: '\n\nObservatory listening on http://127.0.0.1:456\n\n',
)); ));
processManager.addCommand(kShaCommand); processManager.addCommand(kShaCommand);
...@@ -244,6 +254,7 @@ void main() { ...@@ -244,6 +254,7 @@ void main() {
'--es', 'dart-flags', 'foo', '--es', 'dart-flags', 'foo',
'--ez', 'use-test-fonts', 'true', '--ez', 'use-test-fonts', 'true',
'--ez', 'verbose-logging', 'true', '--ez', 'verbose-logging', 'true',
'--user', '10',
'FlutterActivity', 'FlutterActivity',
], ],
)); ));
...@@ -268,6 +279,7 @@ void main() { ...@@ -268,6 +279,7 @@ void main() {
verboseSystemLogs: true, verboseSystemLogs: true,
), ),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
userIdentifier: '10',
); );
// This fails to start due to observatory discovery issues. // This fails to start due to observatory discovery issues.
......
...@@ -22,13 +22,46 @@ const FakeCommand kAdbStartServerCommand = FakeCommand( ...@@ -22,13 +22,46 @@ const FakeCommand kAdbStartServerCommand = FakeCommand(
command: <String>['adb', 'start-server'] command: <String>['adb', 'start-server']
); );
const FakeCommand kInstallCommand = FakeCommand( const FakeCommand kInstallCommand = FakeCommand(
command: <String>['adb', '-s', '1234', 'install', '-t', '-r', 'app.apk'], command: <String>[
'adb',
'-s',
'1234',
'install',
'-t',
'-r',
'--user',
'10',
'app.apk'
],
); );
const FakeCommand kStoreShaCommand = FakeCommand( const FakeCommand kStoreShaCommand = FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'echo', '-n', '', '>', '/data/local/tmp/sky.app.sha1'] command: <String>['adb', '-s', '1234', 'shell', 'echo', '-n', '', '>', '/data/local/tmp/sky.app.sha1']
); );
void main() { void main() {
FileSystem fileSystem;
BufferLogger logger;
setUp(() {
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
});
AndroidDevice setUpAndroidDevice({
AndroidSdk androidSdk,
ProcessManager processManager,
}) {
androidSdk ??= MockAndroidSdk();
when(androidSdk.adbPath).thenReturn('adb');
return AndroidDevice('1234',
logger: logger,
platform: FakePlatform(operatingSystem: 'linux'),
androidSdk: androidSdk,
fileSystem: fileSystem ?? MemoryFileSystem.test(),
processManager: processManager ?? FakeProcessManager.any(),
);
}
testWithoutContext('Cannot install app on API level below 16', () async { testWithoutContext('Cannot install app on API level below 16', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kAdbVersionCommand, kAdbVersionCommand,
...@@ -38,7 +71,6 @@ void main() { ...@@ -38,7 +71,6 @@ void main() {
stdout: '[ro.build.version.sdk]: [11]', stdout: '[ro.build.version.sdk]: [11]',
), ),
]); ]);
final FileSystem fileSystem = MemoryFileSystem.test();
final File apk = fileSystem.file('app.apk')..createSync(); final File apk = fileSystem.file('app.apk')..createSync();
final AndroidApk androidApk = AndroidApk( final AndroidApk androidApk = AndroidApk(
file: apk, file: apk,
...@@ -47,7 +79,6 @@ void main() { ...@@ -47,7 +79,6 @@ void main() {
launchActivity: 'Main', launchActivity: 'Main',
); );
final AndroidDevice androidDevice = setUpAndroidDevice( final AndroidDevice androidDevice = setUpAndroidDevice(
fileSystem: fileSystem,
processManager: processManager, processManager: processManager,
); );
...@@ -56,7 +87,6 @@ void main() { ...@@ -56,7 +87,6 @@ void main() {
}); });
testWithoutContext('Cannot install app if APK file is missing', () async { testWithoutContext('Cannot install app if APK file is missing', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final File apk = fileSystem.file('app.apk'); final File apk = fileSystem.file('app.apk');
final AndroidApk androidApk = AndroidApk( final AndroidApk androidApk = AndroidApk(
file: apk, file: apk,
...@@ -65,7 +95,6 @@ void main() { ...@@ -65,7 +95,6 @@ void main() {
launchActivity: 'Main', launchActivity: 'Main',
); );
final AndroidDevice androidDevice = setUpAndroidDevice( final AndroidDevice androidDevice = setUpAndroidDevice(
fileSystem: fileSystem,
); );
expect(await androidDevice.installApp(androidApk), false); expect(await androidDevice.installApp(androidApk), false);
...@@ -82,7 +111,6 @@ void main() { ...@@ -82,7 +111,6 @@ void main() {
kInstallCommand, kInstallCommand,
kStoreShaCommand, kStoreShaCommand,
]); ]);
final FileSystem fileSystem = MemoryFileSystem.test();
final File apk = fileSystem.file('app.apk')..createSync(); final File apk = fileSystem.file('app.apk')..createSync();
final AndroidApk androidApk = AndroidApk( final AndroidApk androidApk = AndroidApk(
file: apk, file: apk,
...@@ -91,11 +119,10 @@ void main() { ...@@ -91,11 +119,10 @@ void main() {
launchActivity: 'Main', launchActivity: 'Main',
); );
final AndroidDevice androidDevice = setUpAndroidDevice( final AndroidDevice androidDevice = setUpAndroidDevice(
fileSystem: fileSystem,
processManager: processManager, processManager: processManager,
); );
expect(await androidDevice.installApp(androidApk), true); expect(await androidDevice.installApp(androidApk, userIdentifier: '10'), true);
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}); });
...@@ -109,7 +136,6 @@ void main() { ...@@ -109,7 +136,6 @@ void main() {
kInstallCommand, kInstallCommand,
kStoreShaCommand, kStoreShaCommand,
]); ]);
final FileSystem fileSystem = MemoryFileSystem.test();
final File apk = fileSystem.file('app.apk')..createSync(); final File apk = fileSystem.file('app.apk')..createSync();
final AndroidApk androidApk = AndroidApk( final AndroidApk androidApk = AndroidApk(
file: apk, file: apk,
...@@ -118,29 +144,51 @@ void main() { ...@@ -118,29 +144,51 @@ void main() {
launchActivity: 'Main', launchActivity: 'Main',
); );
final AndroidDevice androidDevice = setUpAndroidDevice( final AndroidDevice androidDevice = setUpAndroidDevice(
fileSystem: fileSystem,
processManager: processManager, processManager: processManager,
); );
expect(await androidDevice.installApp(androidApk), true); expect(await androidDevice.installApp(androidApk, userIdentifier: '10'), true);
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}); });
}
AndroidDevice setUpAndroidDevice({ testWithoutContext('displays error if user not found', () async {
AndroidSdk androidSdk, final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FileSystem fileSystem, kAdbVersionCommand,
ProcessManager processManager, kAdbStartServerCommand,
}) { const FakeCommand(
androidSdk ??= MockAndroidSdk(); command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
when(androidSdk.adbPath).thenReturn('adb'); ),
return AndroidDevice('1234', const FakeCommand(
logger: BufferLogger.test(), command: <String>[
platform: FakePlatform(operatingSystem: 'linux'), 'adb',
androidSdk: androidSdk, '-s',
fileSystem: fileSystem ?? MemoryFileSystem.test(), '1234',
processManager: processManager ?? FakeProcessManager.any(), 'install',
); '-t',
'-r',
'--user',
'jane',
'app.apk'
],
exitCode: 1,
stderr: 'Exception occurred while executing: java.lang.IllegalArgumentException: Bad user number: jane',
),
]);
final File apk = fileSystem.file('app.apk')..createSync();
final AndroidApk androidApk = AndroidApk(
file: apk,
id: 'app',
versionCode: 22,
launchActivity: 'Main',
);
final AndroidDevice androidDevice = setUpAndroidDevice(
processManager: processManager,
);
expect(await androidDevice.installApp(androidApk, userIdentifier: 'jane'), false);
expect(logger.errorText, contains('Error: User "jane" not found. Run "adb shell pm list users" to see list of available identifiers.'));
expect(processManager.hasRemainingExpectations, false);
});
} }
class MockAndroidSdk extends Mock implements AndroidSdk {} class MockAndroidSdk extends Mock implements AndroidSdk {}
...@@ -170,7 +170,7 @@ void main() { ...@@ -170,7 +170,7 @@ void main() {
}); });
}); });
test('FlutterDevice can list views with a filter', () => testbed.run(() async { testUsingContext('FlutterDevice can list views with a filter', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
]); ]);
...@@ -184,7 +184,7 @@ void main() { ...@@ -184,7 +184,7 @@ void main() {
flutterDevice.vmService = fakeVmServiceHost.vmService; flutterDevice.vmService = fakeVmServiceHost.vmService;
})); }));
test('ResidentRunner can attach to device successfully', () => testbed.run(() async { testUsingContext('ResidentRunner can attach to device successfully', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -207,7 +207,7 @@ void main() { ...@@ -207,7 +207,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner suppresses errors for the initial compilation', () => testbed.run(() async { testUsingContext('ResidentRunner suppresses errors for the initial compilation', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart')) globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true); .createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...@@ -250,7 +250,7 @@ void main() { ...@@ -250,7 +250,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner does not suppressErrors if running with an applicationBinary', () => testbed.run(() async { testUsingContext('ResidentRunner does not suppressErrors if running with an applicationBinary', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart')) globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true); .createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...@@ -294,7 +294,7 @@ void main() { ...@@ -294,7 +294,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async { testUsingContext('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -372,7 +372,7 @@ void main() { ...@@ -372,7 +372,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async { testUsingContext('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -424,7 +424,7 @@ void main() { ...@@ -424,7 +424,7 @@ void main() {
Usage: () => MockUsage(), Usage: () => MockUsage(),
})); }));
test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async { testUsingContext('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -472,7 +472,7 @@ void main() { ...@@ -472,7 +472,7 @@ void main() {
Usage: () => MockUsage(), Usage: () => MockUsage(),
})); }));
test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async { testUsingContext('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -541,7 +541,7 @@ void main() { ...@@ -541,7 +541,7 @@ void main() {
Usage: () => MockUsage(), Usage: () => MockUsage(),
})); }));
test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { testUsingContext('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -593,7 +593,7 @@ void main() { ...@@ -593,7 +593,7 @@ void main() {
Usage: () => MockUsage(), Usage: () => MockUsage(),
})); }));
test('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() { testUsingContext('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
expect(residentRunner.artifactDirectory.path, contains('flutter_tool.')); expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
...@@ -608,7 +608,7 @@ void main() { ...@@ -608,7 +608,7 @@ void main() {
expect(otherRunner.artifactDirectory.path, contains('foobar')); expect(otherRunner.artifactDirectory.path, contains('foobar'));
})); }));
test('ResidentRunner deletes artifact directory on preExit', () => testbed.run(() async { testUsingContext('ResidentRunner deletes artifact directory on preExit', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
residentRunner.artifactDirectory.childFile('app.dill').createSync(); residentRunner.artifactDirectory.childFile('app.dill').createSync();
await residentRunner.preExit(); await residentRunner.preExit();
...@@ -616,7 +616,7 @@ void main() { ...@@ -616,7 +616,7 @@ void main() {
expect(residentRunner.artifactDirectory, isNot(exists)); expect(residentRunner.artifactDirectory, isNot(exists));
})); }));
test('ResidentRunner can run source generation', () => testbed.run(() async { testUsingContext('ResidentRunner can run source generation', () => testbed.run(() async {
final FakeProcessManager processManager = globals.processManager as FakeProcessManager; final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
final Directory dependencies = globals.fs.directory( final Directory dependencies = globals.fs.directory(
globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1')); globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1'));
...@@ -645,7 +645,7 @@ void main() { ...@@ -645,7 +645,7 @@ void main() {
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]), ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
})); }));
test('ResidentRunner can run source generation - generation fails', () => testbed.run(() async { testUsingContext('ResidentRunner can run source generation - generation fails', () => testbed.run(() async {
final FakeProcessManager processManager = globals.processManager as FakeProcessManager; final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
final Directory dependencies = globals.fs.directory( final Directory dependencies = globals.fs.directory(
globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1')); globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1'));
...@@ -673,7 +673,7 @@ void main() { ...@@ -673,7 +673,7 @@ void main() {
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]), ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
})); }));
test('ResidentRunner printHelpDetails', () => testbed.run(() { testUsingContext('ResidentRunner printHelpDetails', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.supportsScreenshot).thenReturn(true); when(mockDevice.supportsScreenshot).thenReturn(true);
...@@ -720,14 +720,14 @@ void main() { ...@@ -720,14 +720,14 @@ void main() {
)); ));
})); }));
test('ResidentRunner does support CanvasKit', () => testbed.run(() async { testUsingContext('ResidentRunner does support CanvasKit', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
expect(() => residentRunner.toggleCanvaskit(), expect(() => residentRunner.toggleCanvaskit(),
throwsA(isA<Exception>())); throwsA(isA<Exception>()));
})); }));
test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async { testUsingContext('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
FakeVmServiceRequest( FakeVmServiceRequest(
...@@ -746,7 +746,7 @@ void main() { ...@@ -746,7 +746,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async { testUsingContext('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
FakeVmServiceRequest( FakeVmServiceRequest(
...@@ -778,7 +778,7 @@ void main() { ...@@ -778,7 +778,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async { testUsingContext('ResidentRunner can take screenshot on debug device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
FakeVmServiceRequest( FakeVmServiceRequest(
...@@ -809,7 +809,7 @@ void main() { ...@@ -809,7 +809,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner clears the screen when it should', () => testbed.run(() async { testUsingContext('ResidentRunner clears the screen when it should', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
const String message = 'This should be cleared'; const String message = 'This should be cleared';
expect(testLogger.statusText, equals('')); expect(testLogger.statusText, equals(''));
...@@ -819,7 +819,7 @@ void main() { ...@@ -819,7 +819,7 @@ void main() {
expect(testLogger.statusText, equals('')); expect(testLogger.statusText, equals(''));
})); }));
test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async { testUsingContext('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
FakeVmServiceRequest( FakeVmServiceRequest(
...@@ -839,7 +839,7 @@ void main() { ...@@ -839,7 +839,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async { testUsingContext('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
FakeVmServiceRequest( FakeVmServiceRequest(
...@@ -866,7 +866,7 @@ void main() { ...@@ -866,7 +866,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async { testUsingContext('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
FakeVmServiceRequest( FakeVmServiceRequest(
...@@ -892,7 +892,7 @@ void main() { ...@@ -892,7 +892,7 @@ void main() {
expect(testLogger.errorText, contains('Error')); expect(testLogger.errorText, contains('Error'));
})); }));
test("ResidentRunner can't take screenshot on device without support", () => testbed.run(() { testUsingContext("ResidentRunner can't take screenshot on device without support", () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
when(mockDevice.supportsScreenshot).thenReturn(false); when(mockDevice.supportsScreenshot).thenReturn(false);
...@@ -901,7 +901,7 @@ void main() { ...@@ -901,7 +901,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async { testUsingContext('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
]); ]);
...@@ -925,7 +925,7 @@ void main() { ...@@ -925,7 +925,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async { testUsingContext('FlutterDevice will not exit a paused isolate', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
FakeVmServiceRequest( FakeVmServiceRequest(
method: '_flutter.listViews', method: '_flutter.listViews',
...@@ -951,11 +951,11 @@ void main() { ...@@ -951,11 +951,11 @@ void main() {
await flutterDevice.exitApps(); await flutterDevice.exitApps();
verify(mockDevice.stopApp(any)).called(1); verify(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).called(1);
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('FlutterDevice can exit from a release mode isolate with no VmService', () => testbed.run(() async { testUsingContext('FlutterDevice can exit from a release mode isolate with no VmService', () => testbed.run(() async {
final TestFlutterDevice flutterDevice = TestFlutterDevice( final TestFlutterDevice flutterDevice = TestFlutterDevice(
mockDevice, mockDevice,
); );
...@@ -963,10 +963,10 @@ void main() { ...@@ -963,10 +963,10 @@ void main() {
await flutterDevice.exitApps(); await flutterDevice.exitApps();
verify(mockDevice.stopApp(any)).called(1); verify(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).called(1);
})); }));
test('FlutterDevice will call stopApp if the exit request times out', () => testbed.run(() async { testUsingContext('FlutterDevice will call stopApp if the exit request times out', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
FakeVmServiceRequest( FakeVmServiceRequest(
method: '_flutter.listViews', method: '_flutter.listViews',
...@@ -1002,11 +1002,11 @@ void main() { ...@@ -1002,11 +1002,11 @@ void main() {
timeoutDelay: Duration.zero, timeoutDelay: Duration.zero,
); );
verify(mockDevice.stopApp(any)).called(1); verify(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).called(1);
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async { testUsingContext('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
FakeVmServiceRequest( FakeVmServiceRequest(
method: kListViewsMethod, method: kListViewsMethod,
...@@ -1044,77 +1044,77 @@ void main() { ...@@ -1044,77 +1044,77 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugDumpApp(); await residentRunner.debugDumpApp();
verify(mockFlutterDevice.debugDumpApp()).called(1); verify(mockFlutterDevice.debugDumpApp()).called(1);
})); }));
test('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugDumpRenderTree(); await residentRunner.debugDumpRenderTree();
verify(mockFlutterDevice.debugDumpRenderTree()).called(1); verify(mockFlutterDevice.debugDumpRenderTree()).called(1);
})); }));
test('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugDumpLayerTree(); await residentRunner.debugDumpLayerTree();
verify(mockFlutterDevice.debugDumpLayerTree()).called(1); verify(mockFlutterDevice.debugDumpLayerTree()).called(1);
})); }));
test('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugDumpSemanticsTreeInTraversalOrder(); await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
verify(mockFlutterDevice.debugDumpSemanticsTreeInTraversalOrder()).called(1); verify(mockFlutterDevice.debugDumpSemanticsTreeInTraversalOrder()).called(1);
})); }));
test('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
verify(mockFlutterDevice.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1); verify(mockFlutterDevice.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
})); }));
test('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugToggleDebugPaintSizeEnabled(); await residentRunner.debugToggleDebugPaintSizeEnabled();
verify(mockFlutterDevice.toggleDebugPaintSizeEnabled()).called(1); verify(mockFlutterDevice.toggleDebugPaintSizeEnabled()).called(1);
})); }));
test('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugToggleDebugCheckElevationsEnabled(); await residentRunner.debugToggleDebugCheckElevationsEnabled();
verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1); verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1);
})); }));
test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugTogglePerformanceOverlayOverride(); await residentRunner.debugTogglePerformanceOverlayOverride();
verify(mockFlutterDevice.debugTogglePerformanceOverlayOverride()).called(1); verify(mockFlutterDevice.debugTogglePerformanceOverlayOverride()).called(1);
})); }));
test('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugToggleWidgetInspector(); await residentRunner.debugToggleWidgetInspector();
verify(mockFlutterDevice.toggleWidgetInspector()).called(1); verify(mockFlutterDevice.toggleWidgetInspector()).called(1);
})); }));
test('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async { testUsingContext('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
await residentRunner.debugToggleProfileWidgetBuilds(); await residentRunner.debugToggleProfileWidgetBuilds();
verify(mockFlutterDevice.toggleProfileWidgetBuilds()).called(1); verify(mockFlutterDevice.toggleProfileWidgetBuilds()).called(1);
})); }));
test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async { testUsingContext('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -1139,7 +1139,7 @@ void main() { ...@@ -1139,7 +1139,7 @@ void main() {
expect(await globals.fs.file('foo').readAsString(), testUri.toString()); expect(await globals.fs.file('foo').readAsString(), testUri.toString());
})); }));
test('HotRunner copies compiled app.dill to cache during startup', () => testbed.run(() async { testUsingContext('HotRunner copies compiled app.dill to cache during startup', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -1165,7 +1165,7 @@ void main() { ...@@ -1165,7 +1165,7 @@ void main() {
expect(await globals.fs.file(globals.fs.path.join('build', 'cache.dill')).readAsString(), 'ABC'); expect(await globals.fs.file(globals.fs.path.join('build', 'cache.dill')).readAsString(), 'ABC');
})); }));
test('HotRunner does not copy app.dill if a dillOutputPath is given', () => testbed.run(() async { testUsingContext('HotRunner does not copy app.dill if a dillOutputPath is given', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -1192,7 +1192,7 @@ void main() { ...@@ -1192,7 +1192,7 @@ void main() {
expect(globals.fs.file(globals.fs.path.join('build', 'cache.dill')), isNot(exists)); expect(globals.fs.file(globals.fs.path.join('build', 'cache.dill')), isNot(exists));
})); }));
test('HotRunner copies compiled app.dill to cache during startup with --track-widget-creation', () => testbed.run(() async { testUsingContext('HotRunner copies compiled app.dill to cache during startup with --track-widget-creation', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -1224,7 +1224,7 @@ void main() { ...@@ -1224,7 +1224,7 @@ void main() {
})); }));
test('HotRunner unforwards device ports', () => testbed.run(() async { testUsingContext('HotRunner unforwards device ports', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -1255,7 +1255,7 @@ void main() { ...@@ -1255,7 +1255,7 @@ void main() {
verify(mockPortForwarder.dispose()).called(1); verify(mockPortForwarder.dispose()).called(1);
})); }));
test('HotRunner handles failure to write vmservice file', () => testbed.run(() async { testUsingContext('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
listViews, listViews,
...@@ -1283,7 +1283,7 @@ void main() { ...@@ -1283,7 +1283,7 @@ void main() {
})); }));
test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async { testUsingContext('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
]); ]);
...@@ -1308,7 +1308,7 @@ void main() { ...@@ -1308,7 +1308,7 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async { testUsingContext('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
...@@ -1337,7 +1337,7 @@ void main() { ...@@ -1337,7 +1337,7 @@ void main() {
); );
})); }));
test('connect sets up log reader', () => testbed.run(() async { testUsingContext('connect sets up log reader', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
final MockDeviceLogReader mockLogReader = MockDeviceLogReader(); final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
...@@ -1363,7 +1363,7 @@ void main() { ...@@ -1363,7 +1363,7 @@ void main() {
}) async => mockVMService, }) async => mockVMService,
})); }));
test('nextPlatform moves through expected platforms', () { testUsingContext('nextPlatform moves through expected platforms', () {
expect(nextPlatform('android', TestFeatureFlags()), 'iOS'); expect(nextPlatform('android', TestFeatureFlags()), 'iOS');
expect(nextPlatform('iOS', TestFeatureFlags()), 'fuchsia'); expect(nextPlatform('iOS', TestFeatureFlags()), 'fuchsia');
expect(nextPlatform('fuchsia', TestFeatureFlags()), 'android'); expect(nextPlatform('fuchsia', TestFeatureFlags()), 'android');
......
...@@ -1080,7 +1080,7 @@ void main() { ...@@ -1080,7 +1080,7 @@ void main() {
]); ]);
_setupMocks(); _setupMocks();
bool debugClosed = false; bool debugClosed = false;
when(mockDevice.stopApp(any)).thenAnswer((Invocation invocation) async { when(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).thenAnswer((Invocation invocation) async {
if (debugClosed) { if (debugClosed) {
throw StateError('debug connection closed twice'); throw StateError('debug connection closed twice');
} }
......
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