blob: 05855b0573f5febc6b5a1435ec40800c65e0d511 [file] [log] [blame]
Devon Carew14483582016-08-09 14:38:13 -07001// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Sigurd Meldgaard758711c2018-05-31 10:33:15 +02005import 'dart:convert';
Dan Field15f21192019-02-23 09:56:57 -08006import 'dart:io' show ProcessResult;
Devon Carew14483582016-08-09 14:38:13 -07007
Ian Hickson686d8f82018-08-14 20:33:58 -07008import 'package:file/file.dart';
9import 'package:file/memory.dart';
Dan Field15f21192019-02-23 09:56:57 -080010import 'package:flutter_tools/src/android/android_sdk.dart';
Todd Volkertc22ce952019-08-16 17:10:07 -070011import 'package:flutter_tools/src/application_package.dart';
Sigurd Meldgaard758711c2018-05-31 10:33:15 +020012import 'package:flutter_tools/src/base/context.dart';
13import 'package:flutter_tools/src/base/file_system.dart';
14import 'package:flutter_tools/src/base/logger.dart';
15import 'package:flutter_tools/src/base/os.dart';
Todd Volkertc22ce952019-08-16 17:10:07 -070016import 'package:flutter_tools/src/base/platform.dart';
17import 'package:flutter_tools/src/build_info.dart';
18import 'package:flutter_tools/src/cache.dart';
19import 'package:flutter_tools/src/fuchsia/application_package.dart';
20import 'package:flutter_tools/src/ios/plist_parser.dart';
21import 'package:flutter_tools/src/project.dart';
22import 'package:mockito/mockito.dart';
Dan Field15f21192019-02-23 09:56:57 -080023import 'package:process/process.dart';
Ian Hickson686d8f82018-08-14 20:33:58 -070024
Ian Hicksond919e692019-07-13 11:51:44 -070025import '../src/common.dart';
26import '../src/context.dart';
Devon Carew14483582016-08-09 14:38:13 -070027
Dan Field212374f2018-11-05 07:56:30 -080028final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
29final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
30 Platform: _kNoColorTerminalPlatform,
31};
32
Dan Field15f21192019-02-23 09:56:57 -080033class MockitoProcessManager extends Mock implements ProcessManager {}
34class MockitoAndroidSdk extends Mock implements AndroidSdk {}
35class MockitoAndroidSdkVersion extends Mock implements AndroidSdkVersion {}
36
Devon Carew14483582016-08-09 14:38:13 -070037void main() {
Dan Field15f21192019-02-23 09:56:57 -080038 group('Apk with partial Android SDK works', () {
39 AndroidSdk sdk;
40 ProcessManager mockProcessManager;
41 MemoryFileSystem fs;
Emmanuel Garcia4a1c62c2019-08-28 14:52:08 -070042 Cache mockCache;
Dan Field15f21192019-02-23 09:56:57 -080043 File gradle;
44 final Map<Type, Generator> overrides = <Type, Generator>{
45 AndroidSdk: () => sdk,
46 ProcessManager: () => mockProcessManager,
47 FileSystem: () => fs,
Emmanuel Garcia4a1c62c2019-08-28 14:52:08 -070048 Cache: () => mockCache,
Dan Field15f21192019-02-23 09:56:57 -080049 };
50
51 setUp(() async {
52 sdk = MockitoAndroidSdk();
53 mockProcessManager = MockitoProcessManager();
54 fs = MemoryFileSystem();
Emmanuel Garcia4a1c62c2019-08-28 14:52:08 -070055 mockCache = MockCache();
Dan Field15f21192019-02-23 09:56:57 -080056 Cache.flutterRoot = '../..';
57 when(sdk.licensesAvailable).thenReturn(true);
58 when(mockProcessManager.canRun(any)).thenReturn(true);
59 when(mockProcessManager.run(
60 any,
61 workingDirectory: anyNamed('workingDirectory'),
62 environment: anyNamed('environment'),
63 )).thenAnswer((_) async => ProcessResult(1, 0, 'stdout', 'stderr'));
64 when(mockProcessManager.runSync(any)).thenReturn(ProcessResult(1, 0, 'stdout', 'stderr'));
Jonah Williams4ff46712019-04-29 08:21:32 -070065 final FlutterProject project = FlutterProject.current();
Dan Field15f21192019-02-23 09:56:57 -080066 gradle = fs.file(project.android.hostAppGradleRoot.childFile(
67 platform.isWindows ? 'gradlew.bat' : 'gradlew',
68 ).path)..createSync(recursive: true);
69 });
70
Dan Fielddf465c72019-03-07 10:45:29 -080071 testUsingContext('Licenses not available, platform and buildtools available, apk exists', () async {
72 const String aaptPath = 'aaptPath';
73 final File apkFile = fs.file('app.apk');
74 final AndroidSdkVersion sdkVersion = MockitoAndroidSdkVersion();
75 when(sdkVersion.aaptPath).thenReturn(aaptPath);
76 when(sdk.latestVersion).thenReturn(sdkVersion);
77 when(sdk.platformToolsAvailable).thenReturn(true);
78 when(sdk.licensesAvailable).thenReturn(false);
79 when(mockProcessManager.runSync(
80 argThat(equals(<String>[
81 aaptPath,
82 'dump',
83 'xmltree',
84 apkFile.path,
85 'AndroidManifest.xml',
86 ])),
87 workingDirectory: anyNamed('workingDirectory'),
88 environment: anyNamed('environment'),
89 ),
Zachary Anderson73c10e82019-09-11 18:20:42 -070090 ).thenReturn(ProcessResult(0, 0, _aaptDataWithDefaultEnabledAndMainLauncherActivity, ''));
Dan Fielddf465c72019-03-07 10:45:29 -080091
92 final ApplicationPackage applicationPackage = await ApplicationPackageFactory.instance.getPackageForPlatform(
93 TargetPlatform.android_arm,
94 applicationBinary: apkFile,
95 );
96 expect(applicationPackage.name, 'app.apk');
97 }, overrides: overrides);
98
Dan Field15f21192019-02-23 09:56:57 -080099 testUsingContext('Licenses available, build tools not, apk exists', () async {
100 when(sdk.latestVersion).thenReturn(null);
Jonah Williams4ff46712019-04-29 08:21:32 -0700101 final FlutterProject project = FlutterProject.current();
Dan Field15f21192019-02-23 09:56:57 -0800102 final File gradle = project.android.hostAppGradleRoot.childFile(
103 platform.isWindows ? 'gradlew.bat' : 'gradlew',
104 )..createSync(recursive: true);
105
Emmanuel Garcia2c857b92019-09-13 19:06:40 -0700106 project.android.hostAppGradleRoot
107 .childFile('gradle.properties')
108 .writeAsStringSync('irrelevant');
109
Emmanuel Garcia4a1c62c2019-08-28 14:52:08 -0700110 final Directory gradleWrapperDir = fs.systemTempDirectory.createTempSync('gradle_wrapper.');
111 when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(gradleWrapperDir);
112
113 fs.directory(gradleWrapperDir.childDirectory('gradle').childDirectory('wrapper'))
114 .createSync(recursive: true);
115 fs.file(fs.path.join(gradleWrapperDir.path, 'gradlew')).writeAsStringSync('irrelevant');
116 fs.file(fs.path.join(gradleWrapperDir.path, 'gradlew.bat')).writeAsStringSync('irrelevant');
117
Dan Field15f21192019-02-23 09:56:57 -0800118 await ApplicationPackageFactory.instance.getPackageForPlatform(
119 TargetPlatform.android_arm,
120 applicationBinary: fs.file('app.apk'),
121 );
122 verify(
123 mockProcessManager.run(
124 argThat(equals(<String>[gradle.path, 'dependencies'])),
125 workingDirectory: anyNamed('workingDirectory'),
126 environment: anyNamed('environment'),
127 ),
128 ).called(1);
129 }, overrides: overrides);
130
131 testUsingContext('Licenses available, build tools available, does not call gradle dependencies', () async {
132 final AndroidSdkVersion sdkVersion = MockitoAndroidSdkVersion();
133 when(sdk.latestVersion).thenReturn(sdkVersion);
134
135 await ApplicationPackageFactory.instance.getPackageForPlatform(
136 TargetPlatform.android_arm,
137 );
138 verifyNever(
139 mockProcessManager.run(
140 argThat(equals(<String>[gradle.path, 'dependencies'])),
141 workingDirectory: anyNamed('workingDirectory'),
142 environment: anyNamed('environment'),
143 ),
144 );
145 }, overrides: overrides);
Emmanuel Garcia5a34e792019-07-18 10:45:37 -0700146
147 testUsingContext('returns null when failed to extract manifest', () async {
148 final AndroidSdkVersion sdkVersion = MockitoAndroidSdkVersion();
149 when(sdk.latestVersion).thenReturn(sdkVersion);
150 when(mockProcessManager.runSync(argThat(contains('logcat'))))
151 .thenReturn(ProcessResult(0, 1, '', ''));
152
153 expect(AndroidApk.fromApk(null), isNull);
154 }, overrides: overrides);
Dan Field15f21192019-02-23 09:56:57 -0800155 });
156
Devon Carew14483582016-08-09 14:38:13 -0700157 group('ApkManifestData', () {
Christopher Fujinoa4b9ef22019-03-21 09:11:58 -0700158 testUsingContext('Parses manifest with an Activity that has enabled set to true, action set to android.intent.action.MAIN and category set to android.intent.category.LAUNCHER', () {
KyleWong99497dc2019-02-08 05:24:03 +0800159 final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithExplicitEnabledAndMainLauncherActivity);
Devon Carew14483582016-08-09 14:38:13 -0700160 expect(data, isNotNull);
Sarah Zakariasdef76342018-06-25 13:38:08 +0200161 expect(data.packageName, 'io.flutter.examples.hello_world');
162 expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
Christopher Fujinoa4b9ef22019-03-21 09:11:58 -0700163 }, overrides: noColorTerminalOverride);
Josh Burton07dc2362019-06-16 19:18:39 +1200164
Christopher Fujinoa4b9ef22019-03-21 09:11:58 -0700165 testUsingContext('Parses manifest with an Activity that has no value for its enabled field, action set to android.intent.action.MAIN and category set to android.intent.category.LAUNCHER', () {
KyleWong99497dc2019-02-08 05:24:03 +0800166 final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithDefaultEnabledAndMainLauncherActivity);
Sarah Zakariasdef76342018-06-25 13:38:08 +0200167 expect(data, isNotNull);
168 expect(data.packageName, 'io.flutter.examples.hello_world');
169 expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
Christopher Fujinoa4b9ef22019-03-21 09:11:58 -0700170 }, overrides: noColorTerminalOverride);
Josh Burton07dc2362019-06-16 19:18:39 +1200171
172 testUsingContext('Parses manifest with a dist namespace', () {
173 final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithDistNamespace);
174 expect(data, isNotNull);
175 expect(data.packageName, 'io.flutter.examples.hello_world');
176 expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity');
177 }, overrides: noColorTerminalOverride);
178
KyleWong99497dc2019-02-08 05:24:03 +0800179 testUsingContext('Error when parsing manifest with no Activity that has enabled set to true nor has no value for its enabled field', () {
Sarah Zakariasdef76342018-06-25 13:38:08 +0200180 final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoEnabledActivity);
181 expect(data, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700182 final BufferLogger logger = context.get<Logger>();
Sarah Zakariasdef76342018-06-25 13:38:08 +0200183 expect(
184 logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
Dan Field212374f2018-11-05 07:56:30 -0800185 }, overrides: noColorTerminalOverride);
Josh Burton07dc2362019-06-16 19:18:39 +1200186
KyleWong99497dc2019-02-08 05:24:03 +0800187 testUsingContext('Error when parsing manifest with no Activity that has action set to android.intent.action.MAIN', () {
188 final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoMainActivity);
189 expect(data, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700190 final BufferLogger logger = context.get<Logger>();
KyleWong99497dc2019-02-08 05:24:03 +0800191 expect(
192 logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
Christopher Fujinoa4b9ef22019-03-21 09:11:58 -0700193 }, overrides: noColorTerminalOverride);
Josh Burton07dc2362019-06-16 19:18:39 +1200194
KyleWong99497dc2019-02-08 05:24:03 +0800195 testUsingContext('Error when parsing manifest with no Activity that has category set to android.intent.category.LAUNCHER', () {
196 final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoLauncherActivity);
197 expect(data, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700198 final BufferLogger logger = context.get<Logger>();
KyleWong99497dc2019-02-08 05:24:03 +0800199 expect(
200 logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
Christopher Fujinoa4b9ef22019-03-21 09:11:58 -0700201 }, overrides: noColorTerminalOverride);
Devon Carew14483582016-08-09 14:38:13 -0700202 });
xster6a494192017-07-12 18:35:08 -0700203
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200204 group('PrebuiltIOSApp', () {
205 final Map<Type, Generator> overrides = <Type, Generator>{
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200206 FileSystem: () => MemoryFileSystem(),
Todd Volkertc22ce952019-08-16 17:10:07 -0700207 PlistParser: () => MockPlistUtils(),
Dan Field212374f2018-11-05 07:56:30 -0800208 Platform: _kNoColorTerminalPlatform,
Jason Simmons311cde92019-05-29 19:04:35 -0700209 OperatingSystemUtils: () => MockOperatingSystemUtils(),
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200210 };
Josh Burton07dc2362019-06-16 19:18:39 +1200211
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200212 testUsingContext('Error on non-existing file', () {
213 final PrebuiltIOSApp iosApp =
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200214 IOSApp.fromPrebuiltApp(fs.file('not_existing.ipa'));
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200215 expect(iosApp, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700216 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200217 expect(
218 logger.errorText,
219 'File "not_existing.ipa" does not exist. Use an app bundle or an ipa.\n',
220 );
221 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200222
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200223 testUsingContext('Error on non-app-bundle folder', () {
224 fs.directory('regular_folder').createSync();
225 final PrebuiltIOSApp iosApp =
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200226 IOSApp.fromPrebuiltApp(fs.file('regular_folder'));
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200227 expect(iosApp, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700228 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200229 expect(
230 logger.errorText, 'Folder "regular_folder" is not an app bundle.\n');
231 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200232
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200233 testUsingContext('Error on no info.plist', () {
234 fs.directory('bundle.app').createSync();
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200235 final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('bundle.app'));
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200236 expect(iosApp, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700237 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200238 expect(
239 logger.errorText,
240 'Invalid prebuilt iOS app. Does not contain Info.plist.\n',
241 );
242 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200243
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200244 testUsingContext('Error on bad info.plist', () {
245 fs.directory('bundle.app').createSync();
246 fs.file('bundle.app/Info.plist').writeAsStringSync(badPlistData);
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200247 final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('bundle.app'));
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200248 expect(iosApp, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700249 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200250 expect(
251 logger.errorText,
252 contains(
253 'Invalid prebuilt iOS app. Info.plist does not contain bundle identifier\n'),
254 );
255 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200256
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200257 testUsingContext('Success with app bundle', () {
258 fs.directory('bundle.app').createSync();
259 fs.file('bundle.app/Info.plist').writeAsStringSync(plistData);
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200260 final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('bundle.app'));
Jonah Williams0acd3e62019-04-25 15:51:08 -0700261 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200262 expect(logger.errorText, isEmpty);
263 expect(iosApp.bundleDir.path, 'bundle.app');
264 expect(iosApp.id, 'fooBundleId');
265 expect(iosApp.bundleName, 'bundle.app');
266 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200267
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200268 testUsingContext('Bad ipa zip-file, no payload dir', () {
269 fs.file('app.ipa').createSync();
Alexandre Ardhuina0d1f932019-03-09 09:03:11 +0100270 when(os.unzip(fs.file('app.ipa'), any)).thenAnswer((Invocation _) { });
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200271 final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('app.ipa'));
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200272 expect(iosApp, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700273 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200274 expect(
275 logger.errorText,
276 'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.\n',
277 );
278 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200279
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200280 testUsingContext('Bad ipa zip-file, two app bundles', () {
281 fs.file('app.ipa').createSync();
282 when(os.unzip(any, any)).thenAnswer((Invocation invocation) {
283 final File zipFile = invocation.positionalArguments[0];
284 if (zipFile.path != 'app.ipa') {
285 return null;
286 }
287 final Directory targetDirectory = invocation.positionalArguments[1];
288 final String bundlePath1 =
289 fs.path.join(targetDirectory.path, 'Payload', 'bundle1.app');
290 final String bundlePath2 =
291 fs.path.join(targetDirectory.path, 'Payload', 'bundle2.app');
292 fs.directory(bundlePath1).createSync(recursive: true);
293 fs.directory(bundlePath2).createSync(recursive: true);
294 });
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200295 final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('app.ipa'));
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200296 expect(iosApp, isNull);
Jonah Williams0acd3e62019-04-25 15:51:08 -0700297 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200298 expect(logger.errorText,
299 'Invalid prebuilt iOS ipa. Does not contain a single app bundle.\n');
300 }, overrides: overrides);
Josh Burton07dc2362019-06-16 19:18:39 +1200301
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200302 testUsingContext('Success with ipa', () {
303 fs.file('app.ipa').createSync();
304 when(os.unzip(any, any)).thenAnswer((Invocation invocation) {
305 final File zipFile = invocation.positionalArguments[0];
306 if (zipFile.path != 'app.ipa') {
307 return null;
308 }
309 final Directory targetDirectory = invocation.positionalArguments[1];
310 final Directory bundleAppDir = fs.directory(
311 fs.path.join(targetDirectory.path, 'Payload', 'bundle.app'));
312 bundleAppDir.createSync(recursive: true);
313 fs
314 .file(fs.path.join(bundleAppDir.path, 'Info.plist'))
315 .writeAsStringSync(plistData);
316 });
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200317 final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('app.ipa'));
Jonah Williams0acd3e62019-04-25 15:51:08 -0700318 final BufferLogger logger = context.get<Logger>();
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200319 expect(logger.errorText, isEmpty);
320 expect(iosApp.bundleDir.path, endsWith('bundle.app'));
321 expect(iosApp.id, 'fooBundleId');
322 expect(iosApp.bundleName, 'bundle.app');
323 }, overrides: overrides);
Jonah Williams15f271e2019-04-23 09:49:49 -0700324
325 testUsingContext('returns null when there is no ios or .ios directory', () async {
326 fs.file('pubspec.yaml').createSync();
327 fs.file('.packages').createSync();
Jonah Williams4ff46712019-04-29 08:21:32 -0700328 final BuildableIOSApp iosApp = IOSApp.fromIosProject(FlutterProject.fromDirectory(fs.currentDirectory).ios);
Jonah Williams15f271e2019-04-23 09:49:49 -0700329
330 expect(iosApp, null);
331 }, overrides: overrides);
Jonah Williams48207832019-04-25 00:12:17 -0700332
333 testUsingContext('returns null when there is no Runner.xcodeproj', () async {
334 fs.file('pubspec.yaml').createSync();
335 fs.file('.packages').createSync();
336 fs.file('ios/FooBar.xcodeproj').createSync(recursive: true);
Jonah Williams4ff46712019-04-29 08:21:32 -0700337 final BuildableIOSApp iosApp = IOSApp.fromIosProject(FlutterProject.fromDirectory(fs.currentDirectory).ios);
Jonah Williams48207832019-04-25 00:12:17 -0700338
339 expect(iosApp, null);
340 }, overrides: overrides);
Jonah Williamsfdae7bb2019-04-25 15:51:25 -0700341
342 testUsingContext('returns null when there is no Runner.xcodeproj/project.pbxproj', () async {
343 fs.file('pubspec.yaml').createSync();
344 fs.file('.packages').createSync();
345 fs.file('ios/Runner.xcodeproj').createSync(recursive: true);
Jonah Williams4ff46712019-04-29 08:21:32 -0700346 final BuildableIOSApp iosApp = IOSApp.fromIosProject(FlutterProject.fromDirectory(fs.currentDirectory).ios);
Jonah Williamsfdae7bb2019-04-25 15:51:25 -0700347
348 expect(iosApp, null);
349 }, overrides: overrides);
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200350 });
Dan Field639c9932019-06-03 14:31:56 -0700351
352 group('FuchsiaApp', () {
353 final Map<Type, Generator> overrides = <Type, Generator>{
354 FileSystem: () => MemoryFileSystem(),
355 Platform: _kNoColorTerminalPlatform,
356 OperatingSystemUtils: () => MockOperatingSystemUtils(),
357 };
Josh Burton07dc2362019-06-16 19:18:39 +1200358
Dan Field639c9932019-06-03 14:31:56 -0700359 testUsingContext('Error on non-existing file', () {
360 final PrebuiltFuchsiaApp fuchsiaApp =
361 FuchsiaApp.fromPrebuiltApp(fs.file('not_existing.far'));
362 expect(fuchsiaApp, isNull);
363 final BufferLogger logger = context.get<Logger>();
364 expect(
365 logger.errorText,
366 'File "not_existing.far" does not exist or is not a .far file. Use far archive.\n',
367 );
368 }, overrides: overrides);
369
370 testUsingContext('Error on non-far file', () {
371 fs.directory('regular_folder').createSync();
372 final PrebuiltFuchsiaApp fuchsiaApp =
373 FuchsiaApp.fromPrebuiltApp(fs.file('regular_folder'));
374 expect(fuchsiaApp, isNull);
375 final BufferLogger logger = context.get<Logger>();
376 expect(
377 logger.errorText,
378 'File "regular_folder" does not exist or is not a .far file. Use far archive.\n',
379 );
380 }, overrides: overrides);
381
382 testUsingContext('Success with far file', () {
383 fs.file('bundle.far').createSync();
384 final PrebuiltFuchsiaApp fuchsiaApp = FuchsiaApp.fromPrebuiltApp(fs.file('bundle.far'));
385 final BufferLogger logger = context.get<Logger>();
386 expect(logger.errorText, isEmpty);
387 expect(fuchsiaApp.id, 'bundle.far');
388 }, overrides: overrides);
389
390 testUsingContext('returns null when there is no fuchsia', () async {
391 fs.file('pubspec.yaml').createSync();
392 fs.file('.packages').createSync();
393 final BuildableFuchsiaApp fuchsiaApp = FuchsiaApp.fromFuchsiaProject(FlutterProject.fromDirectory(fs.currentDirectory).fuchsia);
394
395 expect(fuchsiaApp, null);
396 }, overrides: overrides);
397 });
Devon Carew14483582016-08-09 14:38:13 -0700398}
399
KyleWong99497dc2019-02-08 05:24:03 +0800400const String _aaptDataWithExplicitEnabledAndMainLauncherActivity =
Sarah Zakariasdef76342018-06-25 13:38:08 +0200401'''N: android=http://schemas.android.com/apk/res/android
402 E: manifest (line=7)
403 A: android:versionCode(0x0101021b)=(type 0x10)0x1
404 A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
405 A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
406 E: uses-sdk (line=12)
407 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
408 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
409 E: uses-permission (line=21)
410 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
411 E: application (line=29)
412 A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
413 A: android:icon(0x01010002)=@0x7f010000
414 A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
415 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
416 E: activity (line=34)
417 A: android:theme(0x01010000)=@0x1030009
418 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
419 A: android:enabled(0x0101000e)=(type 0x12)0x0
420 A: android:launchMode(0x0101001d)=(type 0x10)0x1
421 A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
422 A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
423 A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
424 E: intent-filter (line=42)
425 E: action (line=43)
426 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
427 E: category (line=45)
428 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
429 E: activity (line=48)
430 A: android:theme(0x01010000)=@0x1030009
431 A: android:label(0x01010001)="app2" (Raw: "app2")
432 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity2" (Raw: "io.flutter.examples.hello_world.MainActivity2")
433 A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
434 E: intent-filter (line=53)
435 E: action (line=54)
436 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
437 E: category (line=56)
438 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
439
440
KyleWong99497dc2019-02-08 05:24:03 +0800441const String _aaptDataWithDefaultEnabledAndMainLauncherActivity =
Sarah Zakariasdef76342018-06-25 13:38:08 +0200442'''N: android=http://schemas.android.com/apk/res/android
443 E: manifest (line=7)
444 A: android:versionCode(0x0101021b)=(type 0x10)0x1
445 A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
446 A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
447 E: uses-sdk (line=12)
448 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
449 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
450 E: uses-permission (line=21)
451 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
452 E: application (line=29)
453 A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
454 A: android:icon(0x01010002)=@0x7f010000
455 A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
456 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
457 E: activity (line=34)
458 A: android:theme(0x01010000)=@0x1030009
459 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
460 A: android:enabled(0x0101000e)=(type 0x12)0x0
461 A: android:launchMode(0x0101001d)=(type 0x10)0x1
462 A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
463 A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
464 A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
465 E: intent-filter (line=42)
466 E: action (line=43)
467 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
468 E: category (line=45)
469 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
470 E: activity (line=48)
471 A: android:theme(0x01010000)=@0x1030009
472 A: android:label(0x01010001)="app2" (Raw: "app2")
473 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity2" (Raw: "io.flutter.examples.hello_world.MainActivity2")
474 E: intent-filter (line=53)
475 E: action (line=54)
476 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
477 E: category (line=56)
478 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
479
480
481const String _aaptDataWithNoEnabledActivity =
482'''N: android=http://schemas.android.com/apk/res/android
483 E: manifest (line=7)
484 A: android:versionCode(0x0101021b)=(type 0x10)0x1
485 A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
486 A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
487 E: uses-sdk (line=12)
488 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
489 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
490 E: uses-permission (line=21)
491 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
492 E: application (line=29)
493 A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
494 A: android:icon(0x01010002)=@0x7f010000
495 A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
496 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
497 E: activity (line=34)
498 A: android:theme(0x01010000)=@0x1030009
499 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
500 A: android:enabled(0x0101000e)=(type 0x12)0x0
501 A: android:launchMode(0x0101001d)=(type 0x10)0x1
502 A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
503 A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
504 A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
505 E: intent-filter (line=42)
506 E: action (line=43)
507 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
508 E: category (line=45)
509 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
510
KyleWong99497dc2019-02-08 05:24:03 +0800511const String _aaptDataWithNoMainActivity =
512'''N: android=http://schemas.android.com/apk/res/android
513 E: manifest (line=7)
514 A: android:versionCode(0x0101021b)=(type 0x10)0x1
515 A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
516 A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
517 E: uses-sdk (line=12)
518 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
519 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
520 E: uses-permission (line=21)
521 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
522 E: application (line=29)
523 A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
524 A: android:icon(0x01010002)=@0x7f010000
525 A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
526 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
527 E: activity (line=34)
528 A: android:theme(0x01010000)=@0x1030009
529 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
530 A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
531 A: android:launchMode(0x0101001d)=(type 0x10)0x1
532 A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
533 A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
534 A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
535 E: intent-filter (line=42)
536 E: category (line=43)
537 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
538
539const String _aaptDataWithNoLauncherActivity =
540'''N: android=http://schemas.android.com/apk/res/android
541 E: manifest (line=7)
542 A: android:versionCode(0x0101021b)=(type 0x10)0x1
543 A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
544 A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
545 E: uses-sdk (line=12)
546 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
547 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
548 E: uses-permission (line=21)
549 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
550 E: application (line=29)
551 A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
552 A: android:icon(0x01010002)=@0x7f010000
553 A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
554 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
555 E: activity (line=34)
556 A: android:theme(0x01010000)=@0x1030009
557 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
558 A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
559 A: android:launchMode(0x0101001d)=(type 0x10)0x1
560 A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
561 A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
562 A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
563 E: intent-filter (line=42)
564 E: action (line=43)
565 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")''';
566
Josh Burton07dc2362019-06-16 19:18:39 +1200567const String _aaptDataWithDistNamespace =
568'''N: android=http://schemas.android.com/apk/res/android
569 N: dist=http://schemas.android.com/apk/distribution
570 E: manifest (line=7)
571 A: android:versionCode(0x0101021b)=(type 0x10)0x1
572 A: android:versionName(0x0101021c)="1.0" (Raw: "1.0")
573 A: android:compileSdkVersion(0x01010572)=(type 0x10)0x1c
574 A: android:compileSdkVersionCodename(0x01010573)="9" (Raw: "9")
575 A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
576 A: platformBuildVersionCode=(type 0x10)0x1
577 A: platformBuildVersionName=(type 0x4)0x3f800000
578 E: uses-sdk (line=13)
579 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
580 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1c
581 E: dist:module (line=17)
582 A: dist:instant=(type 0x12)0xffffffff
583 E: uses-permission (line=24)
584 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
585 E: application (line=32)
586 A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
587 A: android:icon(0x01010002)=@0x7f010000
588 A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
589 E: activity (line=36)
590 A: android:theme(0x01010000)=@0x01030009
591 A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
592 A: android:launchMode(0x0101001d)=(type 0x10)0x1
593 A: android:configChanges(0x0101001f)=(type 0x11)0x400037b4
594 A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
595 A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
596 E: intent-filter (line=43)
597 E: action (line=44)
598 A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
599 E: category (line=46)
600 A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
601''';
602
Sarah Zakariasdef76342018-06-25 13:38:08 +0200603
Todd Volkertc22ce952019-08-16 17:10:07 -0700604class MockPlistUtils extends Mock implements PlistParser {
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200605 @override
Todd Volkertc22ce952019-08-16 17:10:07 -0700606 String getValueFromFile(String path, String key) {
Sigurd Meldgaard758711c2018-05-31 10:33:15 +0200607 final File file = fs.file(path);
608 if (!file.existsSync()) {
609 return null;
610 }
611 return json.decode(file.readAsStringSync())[key];
612 }
613}
614
615// Contains no bundle identifier.
616const String badPlistData = '''
617{}
618''';
619
620const String plistData = '''
621{"CFBundleIdentifier": "fooBundleId"}
622''';
Jason Simmons311cde92019-05-29 19:04:35 -0700623
Emmanuel Garcia4a1c62c2019-08-28 14:52:08 -0700624class MockCache extends Mock implements Cache {}
Jason Simmons311cde92019-05-29 19:04:35 -0700625class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils { }