Support work profiles and multiple Android users for run, install, attach, drive (#58815)
This commit is contained in:
parent
f0174b176a
commit
886313393f
@ -389,10 +389,21 @@ class AndroidDevice extends Device {
|
||||
String get name => modelID;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(AndroidApk app) async {
|
||||
Future<bool> isAppInstalled(
|
||||
AndroidApk app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
// This call takes 400ms - 600ms.
|
||||
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}');
|
||||
} on Exception catch (error) {
|
||||
_logger.printTrace('$error');
|
||||
@ -407,7 +418,10 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> installApp(AndroidApk app) async {
|
||||
Future<bool> installApp(
|
||||
AndroidApk app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!app.file.existsSync()) {
|
||||
_logger.printError('"${_fileSystem.path.relative(app.file.path)}" does not exist.');
|
||||
return false;
|
||||
@ -423,7 +437,14 @@ class AndroidDevice extends Device {
|
||||
timeout: _timeoutConfiguration.slowOperation,
|
||||
);
|
||||
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();
|
||||
// Some versions of adb exit with exit code 0 even on failure :(
|
||||
// Parsing the output to check for failures.
|
||||
@ -434,8 +455,12 @@ class AndroidDevice extends Device {
|
||||
return false;
|
||||
}
|
||||
if (installResult.exitCode != 0) {
|
||||
_logger.printError('Error: ADB exited with exit code ${installResult.exitCode}');
|
||||
_logger.printError('$installResult');
|
||||
if (installResult.stderr.contains('Bad user number')) {
|
||||
_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;
|
||||
}
|
||||
try {
|
||||
@ -450,7 +475,10 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(AndroidApk app) async {
|
||||
Future<bool> uninstallApp(
|
||||
AndroidApk app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!await _checkForSupportedAdbVersion() ||
|
||||
!await _checkForSupportedAndroidVersion()) {
|
||||
return false;
|
||||
@ -459,7 +487,11 @@ class AndroidDevice extends Device {
|
||||
String uninstallOut;
|
||||
try {
|
||||
final RunResult uninstallResult = await _processUtils.run(
|
||||
adbCommandForDevice(<String>['uninstall', app.id]),
|
||||
adbCommandForDevice(<String>[
|
||||
'uninstall',
|
||||
if (userIdentifier != null)
|
||||
...<String>['--user', userIdentifier],
|
||||
app.id]),
|
||||
throwOnError: true,
|
||||
);
|
||||
uninstallOut = uninstallResult.stdout;
|
||||
@ -477,8 +509,8 @@ class AndroidDevice extends Device {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> _installLatestApp(AndroidApk package) async {
|
||||
final bool wasInstalled = await isAppInstalled(package);
|
||||
Future<bool> _installLatestApp(AndroidApk package, String userIdentifier) async {
|
||||
final bool wasInstalled = await isAppInstalled(package, userIdentifier: userIdentifier);
|
||||
if (wasInstalled) {
|
||||
if (await isLatestBuildInstalled(package)) {
|
||||
_logger.printTrace('Latest build already installed.');
|
||||
@ -486,15 +518,15 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
}
|
||||
_logger.printTrace('Installing APK.');
|
||||
if (!await installApp(package)) {
|
||||
if (!await installApp(package, userIdentifier: userIdentifier)) {
|
||||
_logger.printTrace('Warning: Failed to install APK.');
|
||||
if (wasInstalled) {
|
||||
_logger.printStatus('Uninstalling old version...');
|
||||
if (!await uninstallApp(package)) {
|
||||
if (!await uninstallApp(package, userIdentifier: userIdentifier)) {
|
||||
_logger.printError('Error: Uninstalling old version failed.');
|
||||
return false;
|
||||
}
|
||||
if (!await installApp(package)) {
|
||||
if (!await installApp(package, userIdentifier: userIdentifier)) {
|
||||
_logger.printError('Error: Failed to install APK again.');
|
||||
return false;
|
||||
}
|
||||
@ -516,6 +548,7 @@ class AndroidDevice extends Device {
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!await _checkForSupportedAdbVersion() ||
|
||||
!await _checkForSupportedAndroidVersion()) {
|
||||
@ -570,9 +603,9 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
@ -636,6 +669,8 @@ class AndroidDevice extends Device {
|
||||
...<String>['--ez', 'use-test-fonts', 'true'],
|
||||
if (debuggingOptions.verboseSystemLogs)
|
||||
...<String>['--ez', 'verbose-logging', 'true'],
|
||||
if (userIdentifier != null)
|
||||
...<String>['--user', userIdentifier],
|
||||
],
|
||||
package.launchActivity,
|
||||
];
|
||||
@ -687,11 +722,21 @@ class AndroidDevice extends Device {
|
||||
bool get supportsFastStart => true;
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(AndroidApk app) {
|
||||
Future<bool> stopApp(
|
||||
AndroidApk app, {
|
||||
String userIdentifier,
|
||||
}) {
|
||||
if (app == null) {
|
||||
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>(
|
||||
(int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode, _platform));
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ class AttachCommand extends FlutterCommand {
|
||||
usesFilesystemOptions(hide: !verboseHelp);
|
||||
usesFuchsiaOptions(hide: !verboseHelp);
|
||||
usesDartDefineOption();
|
||||
usesDeviceUserOption();
|
||||
argParser
|
||||
..addOption(
|
||||
'debug-port',
|
||||
@ -136,6 +137,8 @@ class AttachCommand extends FlutterCommand {
|
||||
return stringArg('app-id');
|
||||
}
|
||||
|
||||
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
|
||||
|
||||
@override
|
||||
Future<void> validateCommand() async {
|
||||
await super.validateCommand();
|
||||
@ -159,6 +162,13 @@ class AttachCommand extends FlutterCommand {
|
||||
throwToolExit(
|
||||
'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
|
||||
@ -356,6 +366,7 @@ class AttachCommand extends FlutterCommand {
|
||||
target: stringArg('target'),
|
||||
targetModel: TargetModel(stringArg('target-model')),
|
||||
buildInfo: getBuildInfo(),
|
||||
userIdentifier: userIdentifier,
|
||||
);
|
||||
flutterDevice.observatoryUris = observatoryUris;
|
||||
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
|
||||
|
@ -10,6 +10,7 @@ import 'package:vm_service/vm_service.dart' as vm_service;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:webdriver/async_io.dart' as async_io;
|
||||
|
||||
import '../android/android_device.dart';
|
||||
import '../application_package.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
@ -22,7 +23,7 @@ import '../device.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../runner/flutter_command.dart' show FlutterCommandResult;
|
||||
import '../runner/flutter_command.dart' show FlutterCommandResult, FlutterOptions;
|
||||
import '../vmservice.dart';
|
||||
import '../web/web_runner.dart';
|
||||
import 'run.dart';
|
||||
@ -136,11 +137,23 @@ class DriveCommand extends RunCommandBase {
|
||||
bool get shouldBuild => boolArg('build');
|
||||
|
||||
bool get verboseSystemLogs => boolArg('verbose-system-logs');
|
||||
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
|
||||
|
||||
/// Subscription to log messages printed on the device or simulator.
|
||||
// ignore: cancel_subscriptions
|
||||
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
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final String testFile = _getTestFile();
|
||||
@ -195,7 +208,7 @@ class DriveCommand extends RunCommandBase {
|
||||
device,
|
||||
flutterProject: flutterProject,
|
||||
target: targetFile,
|
||||
buildInfo: buildInfo
|
||||
buildInfo: buildInfo,
|
||||
);
|
||||
residentRunner = webRunnerFactory.createWebRunner(
|
||||
flutterDevice,
|
||||
@ -412,7 +425,11 @@ void restoreAppStarter() {
|
||||
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);
|
||||
if (await globals.fs.type(mainPath) != FileSystemEntityType.file) {
|
||||
globals.printError('Tried to run $mainPath, but that file does not exist.');
|
||||
@ -427,10 +444,10 @@ Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async {
|
||||
|
||||
if (command.shouldBuild) {
|
||||
globals.printTrace('Installing application package.');
|
||||
if (await command.device.isAppInstalled(package)) {
|
||||
await command.device.uninstallApp(package);
|
||||
if (await command.device.isAppInstalled(package, userIdentifier: userIdentifier)) {
|
||||
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>{};
|
||||
@ -469,6 +486,7 @@ Future<LaunchResult> _startApp(DriveCommand command, Uri webUri) async {
|
||||
),
|
||||
platformArgs: platformArgs,
|
||||
prebuiltApplication: !command.shouldBuild,
|
||||
userIdentifier: userIdentifier,
|
||||
);
|
||||
|
||||
if (!result.started) {
|
||||
@ -520,7 +538,7 @@ Future<bool> _stopApp(DriveCommand command) async {
|
||||
await command.device.targetPlatform,
|
||||
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();
|
||||
return stopped;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../android/android_device.dart';
|
||||
import '../application_package.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/io.dart';
|
||||
@ -15,6 +16,7 @@ import '../runner/flutter_command.dart';
|
||||
class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
||||
InstallCommand() {
|
||||
requiresPubspecYaml();
|
||||
usesDeviceUserOption();
|
||||
argParser.addFlag('uninstall-only',
|
||||
negatable: true,
|
||||
defaultsTo: false,
|
||||
@ -31,6 +33,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
||||
Device device;
|
||||
|
||||
bool get uninstallOnly => boolArg('uninstall-only');
|
||||
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
|
||||
|
||||
@override
|
||||
Future<void> validateCommand() async {
|
||||
@ -39,6 +42,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
||||
if (device == null) {
|
||||
throwToolExit('No target device found');
|
||||
}
|
||||
if (userIdentifier != null && device is! AndroidDevice) {
|
||||
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -59,9 +65,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
||||
}
|
||||
|
||||
Future<void> _uninstallApp(ApplicationPackage package) async {
|
||||
if (await device.isAppInstalled(package)) {
|
||||
if (await device.isAppInstalled(package, userIdentifier: userIdentifier)) {
|
||||
globals.printStatus('Uninstalling $package from $device...');
|
||||
if (!await device.uninstallApp(package)) {
|
||||
if (!await device.uninstallApp(package, userIdentifier: userIdentifier)) {
|
||||
globals.printError('Uninstalling old version failed');
|
||||
}
|
||||
} else {
|
||||
@ -72,21 +78,26 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
||||
Future<void> _installApp(ApplicationPackage package) async {
|
||||
globals.printStatus('Installing $package to $device...');
|
||||
|
||||
if (!await installApp(device, package)) {
|
||||
if (!await installApp(device, package, userIdentifier: userIdentifier)) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (uninstall && await device.isAppInstalled(package)) {
|
||||
if (uninstall && await device.isAppInstalled(package, userIdentifier: userIdentifier)) {
|
||||
globals.printStatus('Uninstalling old version...');
|
||||
if (!await device.uninstallApp(package)) {
|
||||
if (!await device.uninstallApp(package, userIdentifier: userIdentifier)) {
|
||||
globals.printError('Warning: uninstalling old version failed');
|
||||
}
|
||||
}
|
||||
@ -94,5 +105,5 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst
|
||||
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';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
|
||||
import '../android/android_device.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
@ -67,6 +68,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
|
||||
usesTrackWidgetCreation(verboseHelp: verboseHelp);
|
||||
usesIsolateFilterOption(hide: !verboseHelp);
|
||||
addNullSafetyModeOptions();
|
||||
usesDeviceUserOption();
|
||||
}
|
||||
|
||||
bool get traceStartup => boolArg('trace-startup');
|
||||
@ -221,6 +223,8 @@ class RunCommand extends RunCommandBase {
|
||||
|
||||
List<Device> devices;
|
||||
|
||||
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
|
||||
|
||||
@override
|
||||
Future<String> get usagePath async {
|
||||
final String command = await super.usagePath;
|
||||
@ -336,6 +340,13 @@ class RunCommand extends RunCommandBase {
|
||||
if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
|
||||
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() {
|
||||
@ -491,6 +502,7 @@ class RunCommand extends RunCommandBase {
|
||||
experimentalFlags: expFlags,
|
||||
target: stringArg('target'),
|
||||
buildInfo: getBuildInfo(),
|
||||
userIdentifier: userIdentifier,
|
||||
),
|
||||
];
|
||||
// Only support "web mode" with a single web device due to resident runner
|
||||
|
@ -33,7 +33,10 @@ abstract class DesktopDevice extends Device {
|
||||
// Since the host and target devices are the same, no work needs to be done
|
||||
// to install the application.
|
||||
@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
|
||||
// to install the application.
|
||||
@ -43,12 +46,18 @@ abstract class DesktopDevice extends Device {
|
||||
// Since the host and target devices are the same, no work needs to be done
|
||||
// to install the application.
|
||||
@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
|
||||
// to uninstall the application.
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app) async => true;
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> get isLocalEmulator async => false;
|
||||
@ -86,6 +95,7 @@ abstract class DesktopDevice extends Device {
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!prebuiltApplication) {
|
||||
Cache.releaseLockEarly();
|
||||
@ -138,7 +148,10 @@ abstract class DesktopDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(ApplicationPackage app) async {
|
||||
Future<bool> stopApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
bool succeeded = true;
|
||||
// Walk a copy of _runningProcesses, since the exit handler removes from the
|
||||
// set.
|
||||
|
@ -413,17 +413,33 @@ abstract class Device {
|
||||
/// Whether the device is supported for the current project directory.
|
||||
bool isSupportedForProject(FlutterProject flutterProject);
|
||||
|
||||
/// Check if a version of the given app is already installed
|
||||
Future<bool> isAppInstalled(covariant ApplicationPackage app);
|
||||
/// Check if a version of the given app is already installed.
|
||||
///
|
||||
/// 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.
|
||||
Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app);
|
||||
|
||||
/// Install an app package on the current device
|
||||
Future<bool> installApp(covariant ApplicationPackage app);
|
||||
/// Install an app package on the current device.
|
||||
///
|
||||
/// 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
|
||||
Future<bool> uninstallApp(covariant ApplicationPackage app);
|
||||
/// Uninstall an app package from the current device.
|
||||
///
|
||||
/// 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
|
||||
bool isSupported();
|
||||
@ -471,6 +487,7 @@ abstract class Device {
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
});
|
||||
|
||||
/// Whether this device implements support for hot reload.
|
||||
@ -491,7 +508,12 @@ abstract class Device {
|
||||
bool get supportsFastStart => false;
|
||||
|
||||
/// 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..
|
||||
///
|
||||
|
@ -237,16 +237,25 @@ class FuchsiaDevice extends Device {
|
||||
bool get supportsStartPaused => false;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(ApplicationPackage app) async => false;
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> installApp(ApplicationPackage app) => Future<bool>.value(false);
|
||||
Future<bool> installApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) => Future<bool>.value(false);
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app) async => false;
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => false;
|
||||
|
||||
@override
|
||||
bool isSupported() => true;
|
||||
@ -263,6 +272,7 @@ class FuchsiaDevice extends Device {
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!prebuiltApplication) {
|
||||
await buildFuchsia(fuchsiaProject: FlutterProject.current().fuchsia,
|
||||
@ -434,7 +444,10 @@ class FuchsiaDevice extends Device {
|
||||
}
|
||||
|
||||
@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);
|
||||
if (appKey != -1) {
|
||||
if (!await fuchsiaDeviceTools.tilesCtl.remove(this, appKey)) {
|
||||
|
@ -225,7 +225,10 @@ class IOSDevice extends Device {
|
||||
bool get supportsStartPaused => false;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(IOSApp app) async {
|
||||
Future<bool> isAppInstalled(
|
||||
IOSApp app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
bool result;
|
||||
try {
|
||||
result = await _iosDeploy.isAppInstalled(
|
||||
@ -243,7 +246,10 @@ class IOSDevice extends Device {
|
||||
Future<bool> isLatestBuildInstalled(IOSApp app) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> installApp(IOSApp app) async {
|
||||
Future<bool> installApp(
|
||||
IOSApp app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
final Directory bundle = _fileSystem.directory(app.deviceBundlePath);
|
||||
if (!bundle.existsSync()) {
|
||||
_logger.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?');
|
||||
@ -273,7 +279,10 @@ class IOSDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(IOSApp app) async {
|
||||
Future<bool> uninstallApp(
|
||||
IOSApp app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
int uninstallationResult;
|
||||
try {
|
||||
uninstallationResult = await _iosDeploy.uninstallApp(
|
||||
@ -304,6 +313,7 @@ class IOSDevice extends Device {
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
@visibleForTesting Duration fallbackPollingDelay,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
String packageId;
|
||||
|
||||
@ -443,7 +453,10 @@ class IOSDevice extends Device {
|
||||
}
|
||||
|
||||
@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.
|
||||
return false;
|
||||
}
|
||||
|
@ -319,7 +319,10 @@ class IOSSimulator extends Device {
|
||||
String get xcrunPath => globals.fs.path.join('/usr', 'bin', 'xcrun');
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(ApplicationPackage app) {
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) {
|
||||
return _simControl.isInstalled(id, app.id);
|
||||
}
|
||||
|
||||
@ -327,7 +330,10 @@ class IOSSimulator extends Device {
|
||||
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> installApp(covariant IOSApp app) async {
|
||||
Future<bool> installApp(
|
||||
covariant IOSApp app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
try {
|
||||
final IOSApp iosApp = app;
|
||||
await _simControl.install(id, iosApp.simulatorBundlePath);
|
||||
@ -338,7 +344,10 @@ class IOSSimulator extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app) async {
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
try {
|
||||
await _simControl.uninstall(id, app.id);
|
||||
return true;
|
||||
@ -384,6 +393,7 @@ class IOSSimulator extends Device {
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!prebuiltApplication && package is BuildableIOSApp) {
|
||||
globals.printTrace('Building ${package.name} for $id.');
|
||||
@ -495,7 +505,10 @@ class IOSSimulator extends Device {
|
||||
}
|
||||
|
||||
@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.
|
||||
return false;
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ class FlutterDevice {
|
||||
TargetModel targetModel = TargetModel.flutter,
|
||||
TargetPlatform targetPlatform,
|
||||
ResidentCompiler generator,
|
||||
this.userIdentifier,
|
||||
}) : assert(buildInfo.trackWidgetCreation != null),
|
||||
generator = generator ?? ResidentCompiler(
|
||||
globals.artifacts.getArtifactPath(
|
||||
@ -76,6 +77,7 @@ class FlutterDevice {
|
||||
TargetModel targetModel = TargetModel.flutter,
|
||||
List<String> experimentalFlags,
|
||||
ResidentCompiler generator,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
ResidentCompiler generator;
|
||||
final TargetPlatform targetPlatform = await device.targetPlatform;
|
||||
@ -150,12 +152,14 @@ class FlutterDevice {
|
||||
targetPlatform: targetPlatform,
|
||||
generator: generator,
|
||||
buildInfo: buildInfo,
|
||||
userIdentifier: userIdentifier,
|
||||
);
|
||||
}
|
||||
|
||||
final Device device;
|
||||
final ResidentCompiler generator;
|
||||
final BuildInfo buildInfo;
|
||||
final String userIdentifier;
|
||||
Stream<Uri> observatoryUris;
|
||||
vm_service.VmService vmService;
|
||||
DevFS devFS;
|
||||
@ -239,11 +243,11 @@ class FlutterDevice {
|
||||
@visibleForTesting Duration timeoutDelay = const Duration(seconds: 10),
|
||||
}) async {
|
||||
if (!device.supportsFlutterExit || vmService == null) {
|
||||
return device.stopApp(package);
|
||||
return device.stopApp(package, userIdentifier: userIdentifier);
|
||||
}
|
||||
final List<FlutterView> views = await vmService.getFlutterViews();
|
||||
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
|
||||
// cleanly exit since the service extension may not have been registered.
|
||||
@ -254,7 +258,7 @@ class FlutterDevice {
|
||||
continue;
|
||||
}
|
||||
if (isPauseEvent(isolate.pauseEvent.kind)) {
|
||||
return device.stopApp(package);
|
||||
return device.stopApp(package, userIdentifier: userIdentifier);
|
||||
}
|
||||
}
|
||||
for (final FlutterView view in views) {
|
||||
@ -277,7 +281,7 @@ class FlutterDevice {
|
||||
// flutter_attach_android_test. This log should help verify this
|
||||
// is where the tool is getting stuck.
|
||||
globals.logger.printTrace('error: vm service shutdown failed');
|
||||
return device.stopApp(package);
|
||||
return device.stopApp(package, userIdentifier: userIdentifier);
|
||||
});
|
||||
}
|
||||
|
||||
@ -502,6 +506,7 @@ class FlutterDevice {
|
||||
route: route,
|
||||
prebuiltApplication: prebuiltMode,
|
||||
ipv6: hotRunner.ipv6,
|
||||
userIdentifier: userIdentifier,
|
||||
);
|
||||
|
||||
final LaunchResult result = await futureResult;
|
||||
@ -575,6 +580,7 @@ class FlutterDevice {
|
||||
route: route,
|
||||
prebuiltApplication: prebuiltMode,
|
||||
ipv6: coldRunner.ipv6,
|
||||
userIdentifier: userIdentifier,
|
||||
);
|
||||
|
||||
if (!result.started) {
|
||||
|
@ -202,7 +202,7 @@ class ColdRunner extends ResidentRunner {
|
||||
for (final FlutterDevice device in flutterDevices) {
|
||||
// If we're running in release mode, stop the app using the device logic.
|
||||
if (device.vmService == null) {
|
||||
await device.device.stopApp(device.package);
|
||||
await device.device.stopApp(device.package, userIdentifier: device.userIdentifier);
|
||||
}
|
||||
}
|
||||
await super.preExit();
|
||||
|
@ -110,6 +110,7 @@ class FlutterOptions {
|
||||
static const String kBundleSkSLPathOption = 'bundle-sksl-path';
|
||||
static const String kPerformanceMeasurementFile = 'performance-measurement-file';
|
||||
static const String kNullSafety = 'sound-null-safety';
|
||||
static const String kDeviceUser = 'device-user';
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
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 }) {
|
||||
// A release build must be the default if a debug build is not possible.
|
||||
assert(defaultToRelease || !excludeDebug);
|
||||
|
@ -90,10 +90,16 @@ class FlutterTesterDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> installApp(ApplicationPackage app) async => true;
|
||||
Future<bool> installApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(ApplicationPackage app) async => false;
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
|
||||
@ -109,10 +115,11 @@ class FlutterTesterDevice extends Device {
|
||||
ApplicationPackage package, {
|
||||
@required String mainPath,
|
||||
String route,
|
||||
@required DebuggingOptions debuggingOptions,
|
||||
DebuggingOptions debuggingOptions,
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
final BuildInfo buildInfo = debuggingOptions.buildInfo;
|
||||
|
||||
@ -218,14 +225,20 @@ class FlutterTesterDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(ApplicationPackage app) async {
|
||||
Future<bool> stopApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
_process?.kill();
|
||||
_process = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app) async => true;
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
bool isSupportedForProject(FlutterProject flutterProject) => true;
|
||||
|
@ -87,10 +87,16 @@ abstract class ChromiumDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> installApp(ApplicationPackage app) async => true;
|
||||
Future<bool> installApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(ApplicationPackage app) async => true;
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
|
||||
@ -116,6 +122,7 @@ abstract class ChromiumDevice extends Device {
|
||||
Map<String, Object> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
|
||||
// for the web initialization and server logic.
|
||||
@ -137,7 +144,10 @@ abstract class ChromiumDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(ApplicationPackage app) async {
|
||||
Future<bool> stopApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
await _chrome?.close();
|
||||
return true;
|
||||
}
|
||||
@ -146,7 +156,10 @@ abstract class ChromiumDevice extends Device {
|
||||
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app) async => true;
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
bool isSupportedForProject(FlutterProject flutterProject) {
|
||||
@ -333,10 +346,16 @@ class WebServerDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> installApp(ApplicationPackage app) async => true;
|
||||
Future<bool> installApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(ApplicationPackage app) async => true;
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
|
||||
@ -375,6 +394,7 @@ class WebServerDevice extends Device {
|
||||
Map<String, Object> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
final String url = platformArgs['uri'] as String;
|
||||
if (debuggingOptions.startPaused) {
|
||||
@ -387,7 +407,10 @@ class WebServerDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(ApplicationPackage app) async {
|
||||
Future<bool> stopApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -395,7 +418,10 @@ class WebServerDevice extends Device {
|
||||
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app) async {
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -326,16 +326,29 @@ void main() {
|
||||
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).deleteSync();
|
||||
|
||||
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(
|
||||
any,
|
||||
target: foo.path,
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
packagesFilePath: anyNamed('packagesFilePath'),
|
||||
flutterProject: anyNamed('flutterProject'),
|
||||
ipv6: false,
|
||||
)).called(1);
|
||||
final List<FlutterDevice> flutterDevices = verificationResult.captured.first as List<FlutterDevice>;
|
||||
expect(flutterDevices, hasLength(1));
|
||||
final FlutterDevice flutterDevice = flutterDevices.first;
|
||||
expect(flutterDevice.userIdentifier, '10');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
@ -538,6 +551,19 @@ void main() {
|
||||
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 {
|
||||
Device aDeviceWithId(String id) {
|
||||
final MockAndroidDevice device = MockAndroidDevice();
|
||||
|
@ -166,9 +166,30 @@ void main() {
|
||||
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());
|
||||
|
||||
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 testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
|
||||
|
||||
@ -195,6 +216,8 @@ void main() {
|
||||
'drive',
|
||||
'--target=$testApp',
|
||||
'--no-pub',
|
||||
'--device-user',
|
||||
'10',
|
||||
];
|
||||
await createTestCommandRunner(command).run(args);
|
||||
expect(testLogger.errorText, isEmpty);
|
||||
@ -358,14 +381,16 @@ void main() {
|
||||
final MockLaunchResult mockLaunchResult = MockLaunchResult();
|
||||
when(mockLaunchResult.started).thenReturn(true);
|
||||
when(mockDevice.startApp(
|
||||
null,
|
||||
mainPath: anyNamed('mainPath'),
|
||||
route: anyNamed('route'),
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: anyNamed('prebuiltApplication'),
|
||||
null,
|
||||
mainPath: anyNamed('mainPath'),
|
||||
route: anyNamed('route'),
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: anyNamed('prebuiltApplication'),
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
)).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');
|
||||
testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
|
||||
@ -407,6 +432,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: false,
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
@ -435,6 +461,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: false,
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
@ -463,6 +490,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: true,
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
@ -494,11 +522,12 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: anyNamed('prebuiltApplication'),
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
)).thenAnswer((Invocation invocation) async {
|
||||
debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions;
|
||||
return mockLaunchResult;
|
||||
});
|
||||
when(mockDevice.isAppInstalled(any))
|
||||
when(mockDevice.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
|
||||
.thenAnswer((_) => Future<bool>.value(false));
|
||||
|
||||
testApp = globals.fs.path.join(tempDir.path, 'test', 'e2e.dart');
|
||||
@ -547,6 +576,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
platformArgs: anyNamed('platformArgs'),
|
||||
prebuiltApplication: false,
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
));
|
||||
expect(optionValue(), setToTrue ? isTrue : isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
|
@ -21,8 +21,10 @@ void main() {
|
||||
applyMocksToCommand(command);
|
||||
|
||||
final MockAndroidDevice device = MockAndroidDevice();
|
||||
when(device.isAppInstalled(any)).thenAnswer((_) async => false);
|
||||
when(device.installApp(any)).thenAnswer((_) async => true);
|
||||
when(device.isAppInstalled(any, userIdentifier: anyNamed('userIdentifier')))
|
||||
.thenAnswer((_) async => false);
|
||||
when(device.installApp(any, userIdentifier: anyNamed('userIdentifier')))
|
||||
.thenAnswer((_) async => true);
|
||||
testDeviceManager.addDevice(device);
|
||||
|
||||
await createTestCommandRunner(command).run(<String>['install']);
|
||||
@ -30,6 +32,23 @@ void main() {
|
||||
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 {
|
||||
final InstallCommand command = InstallCommand();
|
||||
applyMocksToCommand(command);
|
||||
|
@ -203,6 +203,38 @@ void main() {
|
||||
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 {
|
||||
final RunCommand command = RunCommand();
|
||||
applyMocksToCommand(command);
|
||||
@ -320,6 +352,7 @@ void main() {
|
||||
route: anyNamed('route'),
|
||||
prebuiltApplication: anyNamed('prebuiltApplication'),
|
||||
ipv6: anyNamed('ipv6'),
|
||||
userIdentifier: anyNamed('userIdentifier'),
|
||||
)).thenAnswer((Invocation invocation) => Future<LaunchResult>.value(LaunchResult.failed()));
|
||||
|
||||
when(mockArtifacts.getArtifactPath(
|
||||
@ -690,6 +723,7 @@ class FakeDevice extends Fake implements Device {
|
||||
bool prebuiltApplication = false,
|
||||
bool usesTerminalUi = true,
|
||||
bool ipv6 = false,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
final String dartFlags = debuggingOptions.dartFlags;
|
||||
// In release mode, --dart-flags should be set to the empty string and
|
||||
|
@ -188,17 +188,27 @@ void main() {
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
));
|
||||
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(
|
||||
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(kStartServer);
|
||||
// TODO(jonahwilliams): investigate why this doesn't work.
|
||||
// This doesn't work with the current Android log reader implementation.
|
||||
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',
|
||||
));
|
||||
processManager.addCommand(kShaCommand);
|
||||
@ -244,6 +254,7 @@ void main() {
|
||||
'--es', 'dart-flags', 'foo',
|
||||
'--ez', 'use-test-fonts', 'true',
|
||||
'--ez', 'verbose-logging', 'true',
|
||||
'--user', '10',
|
||||
'FlutterActivity',
|
||||
],
|
||||
));
|
||||
@ -268,6 +279,7 @@ void main() {
|
||||
verboseSystemLogs: true,
|
||||
),
|
||||
platformArgs: <String, dynamic>{},
|
||||
userIdentifier: '10',
|
||||
);
|
||||
|
||||
// This fails to start due to observatory discovery issues.
|
||||
|
@ -22,13 +22,46 @@ const FakeCommand kAdbStartServerCommand = FakeCommand(
|
||||
command: <String>['adb', 'start-server']
|
||||
);
|
||||
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(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'echo', '-n', '', '>', '/data/local/tmp/sky.app.sha1']
|
||||
);
|
||||
|
||||
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 {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
kAdbVersionCommand,
|
||||
@ -38,7 +71,6 @@ void main() {
|
||||
stdout: '[ro.build.version.sdk]: [11]',
|
||||
),
|
||||
]);
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
@ -47,7 +79,6 @@ void main() {
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
);
|
||||
|
||||
@ -56,7 +87,6 @@ void main() {
|
||||
});
|
||||
|
||||
testWithoutContext('Cannot install app if APK file is missing', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk');
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
@ -65,7 +95,6 @@ void main() {
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
);
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), false);
|
||||
@ -82,7 +111,6 @@ void main() {
|
||||
kInstallCommand,
|
||||
kStoreShaCommand,
|
||||
]);
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
@ -91,11 +119,10 @@ void main() {
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
);
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), true);
|
||||
expect(await androidDevice.installApp(androidApk, userIdentifier: '10'), true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
@ -109,7 +136,6 @@ void main() {
|
||||
kInstallCommand,
|
||||
kStoreShaCommand,
|
||||
]);
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
@ -118,29 +144,51 @@ void main() {
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
);
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), true);
|
||||
expect(await androidDevice.installApp(androidApk, userIdentifier: '10'), true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testWithoutContext('displays error if user not found', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
kAdbVersionCommand,
|
||||
kAdbStartServerCommand,
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'adb',
|
||||
'-s',
|
||||
'1234',
|
||||
'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);
|
||||
});
|
||||
}
|
||||
|
||||
AndroidDevice setUpAndroidDevice({
|
||||
AndroidSdk androidSdk,
|
||||
FileSystem fileSystem,
|
||||
ProcessManager processManager,
|
||||
}) {
|
||||
androidSdk ??= MockAndroidSdk();
|
||||
when(androidSdk.adbPath).thenReturn('adb');
|
||||
return AndroidDevice('1234',
|
||||
logger: BufferLogger.test(),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
androidSdk: androidSdk,
|
||||
fileSystem: fileSystem ?? MemoryFileSystem.test(),
|
||||
processManager: processManager ?? FakeProcessManager.any(),
|
||||
);
|
||||
}
|
||||
|
||||
class MockAndroidSdk extends Mock implements AndroidSdk {}
|
||||
|
@ -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>[
|
||||
listViews,
|
||||
]);
|
||||
@ -184,7 +184,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -207,7 +207,7 @@ void main() {
|
||||
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'))
|
||||
.createSync(recursive: true);
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
@ -250,7 +250,7 @@ void main() {
|
||||
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'))
|
||||
.createSync(recursive: true);
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
@ -294,7 +294,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -372,7 +372,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -424,7 +424,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -472,7 +472,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -541,7 +541,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -593,7 +593,7 @@ void main() {
|
||||
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>[]);
|
||||
expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
|
||||
|
||||
@ -608,7 +608,7 @@ void main() {
|
||||
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>[]);
|
||||
residentRunner.artifactDirectory.childFile('app.dill').createSync();
|
||||
await residentRunner.preExit();
|
||||
@ -616,7 +616,7 @@ void main() {
|
||||
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 Directory dependencies = globals.fs.directory(
|
||||
globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1'));
|
||||
@ -645,7 +645,7 @@ void main() {
|
||||
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 Directory dependencies = globals.fs.directory(
|
||||
globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1'));
|
||||
@ -673,7 +673,7 @@ void main() {
|
||||
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
|
||||
}));
|
||||
|
||||
test('ResidentRunner printHelpDetails', () => testbed.run(() {
|
||||
testUsingContext('ResidentRunner printHelpDetails', () => testbed.run(() {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
||||
when(mockDevice.supportsHotRestart).thenReturn(true);
|
||||
when(mockDevice.supportsScreenshot).thenReturn(true);
|
||||
@ -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>[]);
|
||||
|
||||
expect(() => residentRunner.toggleCanvaskit(),
|
||||
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>[
|
||||
listViews,
|
||||
FakeVmServiceRequest(
|
||||
@ -746,7 +746,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
FakeVmServiceRequest(
|
||||
@ -778,7 +778,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
FakeVmServiceRequest(
|
||||
@ -809,7 +809,7 @@ void main() {
|
||||
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>[]);
|
||||
const String message = 'This should be cleared';
|
||||
expect(testLogger.statusText, equals(''));
|
||||
@ -819,7 +819,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
FakeVmServiceRequest(
|
||||
@ -839,7 +839,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
FakeVmServiceRequest(
|
||||
@ -866,7 +866,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
FakeVmServiceRequest(
|
||||
@ -892,7 +892,7 @@ void main() {
|
||||
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>[]);
|
||||
when(mockDevice.supportsScreenshot).thenReturn(false);
|
||||
|
||||
@ -901,7 +901,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
]);
|
||||
@ -925,7 +925,7 @@ void main() {
|
||||
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>[
|
||||
FakeVmServiceRequest(
|
||||
method: '_flutter.listViews',
|
||||
@ -951,11 +951,11 @@ void main() {
|
||||
|
||||
await flutterDevice.exitApps();
|
||||
|
||||
verify(mockDevice.stopApp(any)).called(1);
|
||||
verify(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).called(1);
|
||||
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(
|
||||
mockDevice,
|
||||
);
|
||||
@ -963,10 +963,10 @@ void main() {
|
||||
|
||||
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>[
|
||||
FakeVmServiceRequest(
|
||||
method: '_flutter.listViews',
|
||||
@ -1002,11 +1002,11 @@ void main() {
|
||||
timeoutDelay: Duration.zero,
|
||||
);
|
||||
|
||||
verify(mockDevice.stopApp(any)).called(1);
|
||||
verify(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).called(1);
|
||||
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>[
|
||||
FakeVmServiceRequest(
|
||||
method: kListViewsMethod,
|
||||
@ -1044,77 +1044,77 @@ void main() {
|
||||
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>[]);
|
||||
await residentRunner.debugDumpApp();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugDumpRenderTree();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugDumpLayerTree();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugToggleDebugPaintSizeEnabled();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugToggleDebugCheckElevationsEnabled();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugTogglePerformanceOverlayOverride();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugToggleWidgetInspector();
|
||||
|
||||
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>[]);
|
||||
await residentRunner.debugToggleProfileWidgetBuilds();
|
||||
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -1139,7 +1139,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -1165,7 +1165,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -1192,7 +1192,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -1255,7 +1255,7 @@ void main() {
|
||||
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>[
|
||||
listViews,
|
||||
listViews,
|
||||
@ -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>[
|
||||
listViews,
|
||||
]);
|
||||
@ -1308,7 +1308,7 @@ void main() {
|
||||
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>[]);
|
||||
final MockDevice mockDevice = MockDevice();
|
||||
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
|
||||
@ -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>[]);
|
||||
final MockDevice mockDevice = MockDevice();
|
||||
final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
|
||||
@ -1363,7 +1363,7 @@ void main() {
|
||||
}) async => mockVMService,
|
||||
}));
|
||||
|
||||
test('nextPlatform moves through expected platforms', () {
|
||||
testUsingContext('nextPlatform moves through expected platforms', () {
|
||||
expect(nextPlatform('android', TestFeatureFlags()), 'iOS');
|
||||
expect(nextPlatform('iOS', TestFeatureFlags()), 'fuchsia');
|
||||
expect(nextPlatform('fuchsia', TestFeatureFlags()), 'android');
|
||||
|
@ -1080,7 +1080,7 @@ void main() {
|
||||
]);
|
||||
_setupMocks();
|
||||
bool debugClosed = false;
|
||||
when(mockDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
|
||||
when(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).thenAnswer((Invocation invocation) async {
|
||||
if (debugClosed) {
|
||||
throw StateError('debug connection closed twice');
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user