blob: d20ef7b7c6ffbbaf1dbf3fbd5b2ff6bd35c3d8b1 [file]
// 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/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/android/application_package.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/install.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
import '../../src/test_flutter_command_runner.dart';
void main() {
group('install', () {
setUpAll(() {
Cache.disableLocking();
});
late FileSystem fileSystem;
late BufferLogger logger;
late FakeToolContext toolContext;
setUp(() {
fileSystem = MemoryFileSystem.test();
fileSystem.file('pubspec.yaml').createSync(recursive: true);
logger = BufferLogger.test();
toolContext = FakeToolContext(fs: fileSystem, logger: logger);
});
testUsingContext('returns 0 when Android is connected and ready for an install', () async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final device = FakeAndroidDevice();
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install']);
expect(logger.statusText, contains('Installing FakeAndroidApk to Android...'));
});
testUsingContext('returns 1 when targeted device is not Android with --device-user', () async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final device = FakeIOSDevice();
testDeviceManager.addAttachedDevice(device);
expect(
() async =>
createTestCommandRunner(command).run(<String>['install', '--device-user', '10']),
throwsToolExit(message: '--device-user is only supported for Android'),
);
});
testUsingContext('returns 0 when iOS is connected and ready for an install', () async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeIOSApp());
final device = FakeIOSDevice();
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install']);
expect(logger.statusText, contains('Installing FakeIOSApp to iOS...'));
});
testUsingContext('fails when prebuilt binary not found', () async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final device = FakeAndroidDevice();
testDeviceManager.addAttachedDevice(device);
expect(
() async =>
createTestCommandRunner(command)
.run(<String>['install', '--use-application-binary', 'bogus']),
throwsToolExit(message: 'Prebuilt binary bogus does not exist'),
);
});
testUsingContext('succeeds using prebuilt binary', () async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final device = FakeAndroidDevice();
testDeviceManager.addAttachedDevice(device);
fileSystem.file('binary').createSync(recursive: true);
await createTestCommandRunner(command)
.run(<String>['install', '--use-application-binary', 'binary']);
expect(logger.statusText, contains('Installing FakeAndroidApk to Android...'));
});
testUsingContext('passes flavor to application package', () async {
const flavor = 'free';
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
final fakeAppFactory = FakeApplicationPackageFactory(FakeIOSApp());
command.applicationPackages = fakeAppFactory;
final device = FakeIOSDevice();
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install', '--flavor', flavor]);
expect(fakeAppFactory.buildInfo, isNotNull);
expect(fakeAppFactory.buildInfo!.flavor, flavor);
});
testUsingContext(
'uninstalls app when --uninstall-only is provided and app is installed',
() async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final device = FakeAndroidDevice(appInstalled: true);
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install', '--uninstall-only']);
expect(logger.statusText, contains('Uninstalling FakeAndroidApk from Android...'));
expect(device.uninstalled, isTrue);
},
);
testUsingContext(
'skips uninstall when --uninstall-only is provided and app is not installed',
() async {
final command = InstallCommand(toolContext: toolContext, verboseHelp: false);
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final device = FakeAndroidDevice();
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install', '--uninstall-only']);
expect(
logger.statusText,
contains('FakeAndroidApk not found on Android, skipping uninstall'),
);
expect(device.uninstalled, isFalse);
},
);
testWithoutContext('installApp uninstalls old version if already installed', () async {
final testLogger = BufferLogger.test();
final device = FakeAndroidDevice(appInstalled: true);
final app = FakeAndroidApk();
final bool result = await installApp(device, app, logger: testLogger);
expect(result, isTrue);
expect(device.uninstalled, isTrue);
expect(testLogger.statusText, contains('Uninstalling old version...'));
});
testWithoutContext('installApp logs warning if uninstalling old version fails', () async {
final testLogger = BufferLogger.test();
final device = FakeAndroidDevice(appInstalled: true, uninstallSucceeds: false);
final app = FakeAndroidApk();
final bool result = await installApp(device, app, logger: testLogger);
expect(result, isTrue);
expect(testLogger.warningText, contains('Warning: uninstalling old version failed'));
});
testWithoutContext('installApp logs error when ProcessException is thrown', () async {
final testLogger = BufferLogger.test();
final device = _ThrowingDevice();
final app = FakeAndroidApk();
final bool result = await installApp(device, app, logger: testLogger);
expect(result, isTrue);
expect(testLogger.errorText, contains('Error accessing device throwing-device:'));
expect(testLogger.errorText, contains('ADB failed'));
});
});
}
class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory {
FakeApplicationPackageFactory(this.app);
final ApplicationPackage app;
BuildInfo? buildInfo;
@override
Future<ApplicationPackage> getPackageForPlatform(
TargetPlatform platform, {
BuildInfo? buildInfo,
File? applicationBinary,
}) async {
this.buildInfo = buildInfo;
return app;
}
}
class FakeIOSApp extends Fake implements IOSApp {
@override
String toString() => 'FakeIOSApp';
}
class FakeAndroidApk extends Fake implements AndroidApk {
@override
String toString() => 'FakeAndroidApk';
}
class FakeIOSDevice extends Fake implements IOSDevice {
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
@override
Future<bool> isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async => false;
@override
Future<bool> installApp(IOSApp app, {String? userIdentifier}) async => true;
@override
String get name => 'iOS';
@override
String toString() => 'iOS';
}
class FakeAndroidDevice extends Fake implements AndroidDevice {
FakeAndroidDevice({this.appInstalled = false, this.uninstallSucceeds = true});
final bool appInstalled;
final bool uninstallSucceeds;
bool uninstalled = false;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
@override
Future<bool> isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async =>
appInstalled;
@override
Future<bool> uninstallApp(ApplicationPackage app, {String? userIdentifier}) async {
uninstalled = true;
return uninstallSucceeds;
}
@override
Future<bool> installApp(AndroidApk app, {String? userIdentifier}) async => true;
@override
String get name => 'Android';
@override
bool get ephemeral => true;
@override
String toString() => 'Android';
}
class _ThrowingDevice extends Fake implements Device {
@override
String get id => 'throwing-device';
@override
Future<bool> isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async {
throw const ProcessException('adb', <String>['shell'], 'ADB failed');
}
@override
Future<bool> installApp(ApplicationPackage app, {String? userIdentifier}) async => true;
}