Unverified Commit 57b0ddbd authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Uninstall app flag (#53385)

parent be3a4b37
...@@ -19,14 +19,36 @@ void main() { ...@@ -19,14 +19,36 @@ void main() {
final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui')); final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui'));
await inDirectory(appDir, () async { await inDirectory(appDir, () async {
final Completer<void> ready = Completer<void>(); final Completer<void> ready = Completer<void>();
final List<String> stdout = <String>[];
final List<String> stderr = <String>[];
// Uninstall if the app is already installed on the device to get to a clean state.
print('uninstalling...');
final Process uninstall = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
<String>['--suppress-analytics', 'install', '--uninstall-only', '-d', device.deviceId],
)..stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('uninstall:stdout: $line');
})..stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('uninstall:stderr: $line');
stderr.add(line);
});
if (await uninstall.exitCode != 0) {
throw 'flutter install --uninstall-only failed.';
}
print('run: starting...'); print('run: starting...');
final Process run = await startProcess( final Process run = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'), path.join(flutterDirectory.path, 'bin', 'flutter'),
<String>['--suppress-analytics', 'run', '--release', '-d', device.deviceId, 'lib/main.dart'], <String>['--suppress-analytics', 'run', '--release', '-d', device.deviceId, 'lib/main.dart'],
isBot: false, // we just want to test the output, not have any debugging info isBot: false, // we just want to test the output, not have any debugging info
); );
final List<String> stdout = <String>[];
final List<String> stderr = <String>[];
int runExitCode; int runExitCode;
run.stdout run.stdout
.transform<String>(utf8.decoder) .transform<String>(utf8.decoder)
......
...@@ -15,6 +15,11 @@ import '../runner/flutter_command.dart'; ...@@ -15,6 +15,11 @@ import '../runner/flutter_command.dart';
class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
InstallCommand() { InstallCommand() {
requiresPubspecYaml(); requiresPubspecYaml();
argParser.addFlag('uninstall-only',
negatable: true,
defaultsTo: false,
help: 'Uninstall the app if already on the device. Skip install.',
);
} }
@override @override
...@@ -25,6 +30,8 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -25,6 +30,8 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
Device device; Device device;
bool get uninstallOnly => boolArg('uninstall-only');
@override @override
Future<void> validateCommand() async { Future<void> validateCommand() async {
await super.validateCommand(); await super.validateCommand();
...@@ -40,13 +47,31 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -40,13 +47,31 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
Cache.releaseLockEarly(); Cache.releaseLockEarly();
if (uninstallOnly) {
await _uninstallApp(package);
} else {
await _installApp(package);
}
return FlutterCommandResult.success();
}
Future<void> _uninstallApp(ApplicationPackage package) async {
if (await device.isAppInstalled(package)) {
globals.printStatus('Uninstalling $package from $device...');
if (!await device.uninstallApp(package)) {
globals.printError('Uninstalling old version failed');
}
} else {
globals.printStatus('$package not found on $device, skipping uninstall');
}
}
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)) {
throwToolExit('Install failed'); throwToolExit('Install failed');
} }
return FlutterCommandResult.success();
} }
} }
......
...@@ -145,6 +145,8 @@ class IOSDeploy { ...@@ -145,6 +145,8 @@ class IOSDeploy {
'--id', '--id',
deviceId, deviceId,
'--exists', '--exists',
'--timeout', // If the device is not connected, ios-deploy will wait forever.
'10',
'--bundle_id', '--bundle_id',
bundleId, bundleId,
]; ];
...@@ -152,10 +154,16 @@ class IOSDeploy { ...@@ -152,10 +154,16 @@ class IOSDeploy {
launchCommand, launchCommand,
environment: iosDeployEnv, environment: iosDeployEnv,
); );
// Device successfully connected, but app not installed.
if (result.exitCode == 255) {
_logger.printTrace('$bundleId not installed on $deviceId');
return false;
}
if (result.exitCode != 0) { if (result.exitCode != 0) {
_logger.printTrace('App install check failed: ${result.stderr}');
return false; return false;
} }
return result.stdout.contains(bundleId); return true;
} }
// Maps stdout line stream. Must return original line. // Maps stdout line stream. Must return original line.
......
...@@ -76,7 +76,8 @@ void main() { ...@@ -76,7 +76,8 @@ void main() {
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}); });
testWithoutContext('IOSDevice.isAppInstalled catches ProcessException from ios-deploy', () async { group('isAppInstalled', () {
testWithoutContext('catches ProcessException from ios-deploy', () async {
final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app');
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(command: const <String>[ FakeCommand(command: const <String>[
...@@ -84,6 +85,8 @@ void main() { ...@@ -84,6 +85,8 @@ void main() {
'--id', '--id',
'1234', '1234',
'--exists', '--exists',
'--timeout',
'10',
'--bundle_id', '--bundle_id',
'app', 'app',
], environment: const <String, String>{ ], environment: const <String, String>{
...@@ -100,6 +103,85 @@ void main() { ...@@ -100,6 +103,85 @@ void main() {
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}); });
testWithoutContext('returns true when app is installed', () async {
final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app');
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
'ios-deploy',
'--id',
'1234',
'--exists',
'--timeout',
'10',
'--bundle_id',
'app',
], environment: <String, String>{
'PATH': '/usr/bin:null',
...kDyLdLibEntry,
}, exitCode: 0)
]);
final IOSDevice device = setUpIOSDevice(processManager: processManager);
final bool isAppInstalled = await device.isAppInstalled(iosApp);
expect(isAppInstalled, isTrue);
expect(processManager.hasRemainingExpectations, false);
});
testWithoutContext('returns false when app is not installed', () async {
final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app');
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
'ios-deploy',
'--id',
'1234',
'--exists',
'--timeout',
'10',
'--bundle_id',
'app',
], environment: <String, String>{
'PATH': '/usr/bin:null',
...kDyLdLibEntry,
}, exitCode: 255)
]);
final BufferLogger logger = BufferLogger.test();
final IOSDevice device = setUpIOSDevice(processManager: processManager, logger: logger);
final bool isAppInstalled = await device.isAppInstalled(iosApp);
expect(isAppInstalled, isFalse);
expect(processManager.hasRemainingExpectations, false);
expect(logger.traceText, contains('${iosApp.id} not installed on ${device.id}'));
});
testWithoutContext('returns false on command timeout or other error', () async {
final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app');
const String stderr = '2020-03-26 17:48:43.484 ios-deploy[21518:5501783] [ !! ] Timed out waiting for device';
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
'ios-deploy',
'--id',
'1234',
'--exists',
'--timeout',
'10',
'--bundle_id',
'app',
], environment: <String, String>{
'PATH': '/usr/bin:null',
...kDyLdLibEntry,
}, stderr: stderr,
exitCode: 253)
]);
final BufferLogger logger = BufferLogger.test();
final IOSDevice device = setUpIOSDevice(processManager: processManager, logger: logger);
final bool isAppInstalled = await device.isAppInstalled(iosApp);
expect(isAppInstalled, isFalse);
expect(processManager.hasRemainingExpectations, false);
expect(logger.traceText, contains(stderr));
});
});
testWithoutContext('IOSDevice.installApp catches ProcessException from ios-deploy', () async { testWithoutContext('IOSDevice.installApp catches ProcessException from ios-deploy', () async {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final IOSApp iosApp = PrebuiltIOSApp( final IOSApp iosApp = PrebuiltIOSApp(
...@@ -154,7 +236,9 @@ void main() { ...@@ -154,7 +236,9 @@ void main() {
IOSDevice setUpIOSDevice({ IOSDevice setUpIOSDevice({
@required ProcessManager processManager, @required ProcessManager processManager,
FileSystem fileSystem, FileSystem fileSystem,
Logger logger,
}) { }) {
logger ??= BufferLogger.test();
final FakePlatform platform = FakePlatform( final FakePlatform platform = FakePlatform(
operatingSystem: 'macos', operatingSystem: 'macos',
environment: <String, String>{}, environment: <String, String>{},
...@@ -167,19 +251,19 @@ IOSDevice setUpIOSDevice({ ...@@ -167,19 +251,19 @@ IOSDevice setUpIOSDevice({
return IOSDevice( return IOSDevice(
'1234', '1234',
name: 'iPhone 1', name: 'iPhone 1',
logger: BufferLogger.test(), logger: logger,
fileSystem: fileSystem ?? MemoryFileSystem.test(), fileSystem: fileSystem ?? MemoryFileSystem.test(),
sdkVersion: '13.3', sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
platform: platform, platform: platform,
iMobileDevice: IMobileDevice( iMobileDevice: IMobileDevice(
logger: BufferLogger.test(), logger: logger,
processManager: processManager, processManager: processManager,
artifacts: artifacts, artifacts: artifacts,
cache: cache, cache: cache,
), ),
iosDeploy: IOSDeploy( iosDeploy: IOSDeploy(
logger: BufferLogger.test(), logger: logger,
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
artifacts: artifacts, artifacts: artifacts,
......
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