flutter/packages/flutter_tools/test/general.shard/android/android_install_test.dart
Jonah Williams fd11d149f7
[flutter_tools] teach flutter drive to uninstall if install fails (#67936)
Work towards #39925

Currently flutter run will uninstall and reinstall if the initial install fails and the APK was previously installed. Allow drive to share this same logic by moving it into installApp and out of startApp.

This should reduce the occurrence of the error in the devicelab.
2020-10-12 12:49:36 -07:00

318 lines
11 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
const FakeCommand kAdbVersionCommand = FakeCommand(
command: <String>['adb', 'version'],
stdout: 'Android Debug Bridge version 1.0.39',
);
const FakeCommand kAdbStartServerCommand = FakeCommand(
command: <String>['adb', 'start-server']
);
const FakeCommand kInstallCommand = FakeCommand(
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 ??= FakeAndroidSdk();
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,
kAdbStartServerCommand,
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
stdout: '[ro.build.version.sdk]: [11]',
),
]);
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), false);
expect(processManager.hasRemainingExpectations, false);
});
testWithoutContext('Cannot install app if APK file is missing', () async {
final File apk = fileSystem.file('app.apk');
final AndroidApk androidApk = AndroidApk(
file: apk,
id: 'app',
versionCode: 22,
launchActivity: 'Main',
);
final AndroidDevice androidDevice = setUpAndroidDevice(
);
expect(await androidDevice.installApp(androidApk), false);
});
testWithoutContext('Can install app on API level 16 or greater', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kAdbVersionCommand,
kAdbStartServerCommand,
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
stdout: '[ro.build.version.sdk]: [16]',
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', '--user', '10', 'app'],
stdout: '\n'
),
kInstallCommand,
kStoreShaCommand,
]);
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: '10'), true);
expect(processManager.hasRemainingExpectations, false);
});
testWithoutContext('Defaults to API level 16 if adb returns a null response', () 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', 'shell', 'pm', 'list', 'packages', '--user', '10', 'app'],
stdout: '\n'
),
kInstallCommand,
kStoreShaCommand,
]);
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: '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'],
),
// This command is run before the user is checked and is allowed to fail.
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', '--user', 'jane', 'app'],
stderr: 'Blah blah',
exitCode: 1,
),
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);
});
testWithoutContext('Will skip install if the correct version is up to date', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kAdbVersionCommand,
kAdbStartServerCommand,
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
stdout: '[ro.build.version.sdk]: [16]',
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', '--user', '10', 'app'],
stdout: 'package:app\n'
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'cat', '/data/local/tmp/sky.app.sha1'],
stdout: 'example_sha',
),
]);
final File apk = fileSystem.file('app.apk')..createSync();
fileSystem.file('app.apk.sha1').writeAsStringSync('example_sha');
final AndroidApk androidApk = AndroidApk(
file: apk,
id: 'app',
versionCode: 22,
launchActivity: 'Main',
);
final AndroidDevice androidDevice = setUpAndroidDevice(
processManager: processManager,
);
expect(await androidDevice.installApp(androidApk, userIdentifier: '10'), true);
expect(processManager.hasRemainingExpectations, false);
});
testWithoutContext('Will uninstall if the correct version is not up to date and install fails', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kAdbVersionCommand,
kAdbStartServerCommand,
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
stdout: '[ro.build.version.sdk]: [16]',
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', '--user', '10', 'app'],
stdout: 'package:app\n'
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'cat', '/data/local/tmp/sky.app.sha1'],
stdout: 'different_example_sha',
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'install', '-t', '-r', '--user', '10', 'app.apk'],
exitCode: 1,
stderr: '[INSTALL_FAILED_INSUFFICIENT_STORAGE]',
),
const FakeCommand(command: <String>['adb', '-s', '1234', 'uninstall', '--user', '10', 'app']),
kInstallCommand,
const FakeCommand(command: <String>['adb', '-s', '1234', 'shell', 'echo', '-n', 'example_sha', '>', '/data/local/tmp/sky.app.sha1']),
]);
final File apk = fileSystem.file('app.apk')..createSync();
fileSystem.file('app.apk.sha1').writeAsStringSync('example_sha');
final AndroidApk androidApk = AndroidApk(
file: apk,
id: 'app',
versionCode: 22,
launchActivity: 'Main',
);
final AndroidDevice androidDevice = setUpAndroidDevice(
processManager: processManager,
);
expect(await androidDevice.installApp(androidApk, userIdentifier: '10'), true);
expect(processManager.hasRemainingExpectations, false);
});
testWithoutContext('Will fail to install if the apk was never installed and it fails the first time', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kAdbVersionCommand,
kAdbStartServerCommand,
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
stdout: '[ro.build.version.sdk]: [16]',
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'shell', 'pm', 'list', 'packages', '--user', '10', 'app'],
stdout: '\n'
),
const FakeCommand(
command: <String>['adb', '-s', '1234', 'install', '-t', '-r', '--user', '10', 'app.apk'],
exitCode: 1,
stderr: '[INSTALL_FAILED_INSUFFICIENT_STORAGE]',
),
]);
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: '10'), false);
expect(processManager.hasRemainingExpectations, false);
});
}
class FakeAndroidSdk extends Fake implements AndroidSdk {
@override
String get adbPath => 'adb';
}