Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 1 | // 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 Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 5 | import 'dart:convert'; |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 6 | |
Ian Hickson | 686d8f8 | 2018-08-14 20:33:58 -0700 | [diff] [blame] | 7 | import 'package:file/file.dart'; |
| 8 | import 'package:file/memory.dart'; |
| 9 | import 'package:mockito/mockito.dart'; |
| 10 | |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 11 | import 'package:flutter_tools/src/application_package.dart'; |
| 12 | import 'package:flutter_tools/src/base/context.dart'; |
| 13 | import 'package:flutter_tools/src/base/file_system.dart'; |
| 14 | import 'package:flutter_tools/src/base/logger.dart'; |
| 15 | import 'package:flutter_tools/src/base/os.dart'; |
| 16 | import 'package:flutter_tools/src/ios/ios_workflow.dart'; |
Ian Hickson | 686d8f8 | 2018-08-14 20:33:58 -0700 | [diff] [blame] | 17 | |
| 18 | import 'src/common.dart'; |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 19 | import 'src/context.dart'; |
| 20 | |
| 21 | void main() { |
| 22 | group('ApkManifestData', () { |
Sarah Zakarias | def7634 | 2018-06-25 13:38:08 +0200 | [diff] [blame] | 23 | test('Select explicity enabled activity', () { |
| 24 | final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithExplicitEnabledActivity); |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 25 | expect(data, isNotNull); |
Sarah Zakarias | def7634 | 2018-06-25 13:38:08 +0200 | [diff] [blame] | 26 | expect(data.packageName, 'io.flutter.examples.hello_world'); |
| 27 | expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2'); |
| 28 | }); |
| 29 | test('Select default enabled activity', () { |
| 30 | final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithDefaultEnabledActivity); |
| 31 | expect(data, isNotNull); |
| 32 | expect(data.packageName, 'io.flutter.examples.hello_world'); |
| 33 | expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2'); |
| 34 | }); |
| 35 | testUsingContext('Error on no enabled activity', () { |
| 36 | final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoEnabledActivity); |
| 37 | expect(data, isNull); |
| 38 | final BufferLogger logger = context[Logger]; |
| 39 | expect( |
| 40 | logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n'); |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 41 | }); |
| 42 | }); |
xster | 6a49419 | 2017-07-12 18:35:08 -0700 | [diff] [blame] | 43 | |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 44 | group('PrebuiltIOSApp', () { |
| 45 | final Map<Type, Generator> overrides = <Type, Generator>{ |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 46 | FileSystem: () => MemoryFileSystem(), |
| 47 | IOSWorkflow: () => MockIosWorkFlow() |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 48 | }; |
| 49 | testUsingContext('Error on non-existing file', () { |
| 50 | final PrebuiltIOSApp iosApp = |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 51 | IOSApp.fromPrebuiltApp(fs.file('not_existing.ipa')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 52 | expect(iosApp, isNull); |
| 53 | final BufferLogger logger = context[Logger]; |
| 54 | expect( |
| 55 | logger.errorText, |
| 56 | 'File "not_existing.ipa" does not exist. Use an app bundle or an ipa.\n', |
| 57 | ); |
| 58 | }, overrides: overrides); |
| 59 | testUsingContext('Error on non-app-bundle folder', () { |
| 60 | fs.directory('regular_folder').createSync(); |
| 61 | final PrebuiltIOSApp iosApp = |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 62 | IOSApp.fromPrebuiltApp(fs.file('regular_folder')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 63 | expect(iosApp, isNull); |
| 64 | final BufferLogger logger = context[Logger]; |
| 65 | expect( |
| 66 | logger.errorText, 'Folder "regular_folder" is not an app bundle.\n'); |
| 67 | }, overrides: overrides); |
| 68 | testUsingContext('Error on no info.plist', () { |
| 69 | fs.directory('bundle.app').createSync(); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 70 | final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('bundle.app')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 71 | expect(iosApp, isNull); |
| 72 | final BufferLogger logger = context[Logger]; |
| 73 | expect( |
| 74 | logger.errorText, |
| 75 | 'Invalid prebuilt iOS app. Does not contain Info.plist.\n', |
| 76 | ); |
| 77 | }, overrides: overrides); |
| 78 | testUsingContext('Error on bad info.plist', () { |
| 79 | fs.directory('bundle.app').createSync(); |
| 80 | fs.file('bundle.app/Info.plist').writeAsStringSync(badPlistData); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 81 | final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('bundle.app')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 82 | expect(iosApp, isNull); |
| 83 | final BufferLogger logger = context[Logger]; |
| 84 | expect( |
| 85 | logger.errorText, |
| 86 | contains( |
| 87 | 'Invalid prebuilt iOS app. Info.plist does not contain bundle identifier\n'), |
| 88 | ); |
| 89 | }, overrides: overrides); |
| 90 | testUsingContext('Success with app bundle', () { |
| 91 | fs.directory('bundle.app').createSync(); |
| 92 | fs.file('bundle.app/Info.plist').writeAsStringSync(plistData); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 93 | final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('bundle.app')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 94 | final BufferLogger logger = context[Logger]; |
| 95 | expect(logger.errorText, isEmpty); |
| 96 | expect(iosApp.bundleDir.path, 'bundle.app'); |
| 97 | expect(iosApp.id, 'fooBundleId'); |
| 98 | expect(iosApp.bundleName, 'bundle.app'); |
| 99 | }, overrides: overrides); |
| 100 | testUsingContext('Bad ipa zip-file, no payload dir', () { |
| 101 | fs.file('app.ipa').createSync(); |
| 102 | when(os.unzip(fs.file('app.ipa'), any)).thenAnswer((Invocation _) {}); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 103 | final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('app.ipa')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 104 | expect(iosApp, isNull); |
| 105 | final BufferLogger logger = context[Logger]; |
| 106 | expect( |
| 107 | logger.errorText, |
| 108 | 'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.\n', |
| 109 | ); |
| 110 | }, overrides: overrides); |
| 111 | testUsingContext('Bad ipa zip-file, two app bundles', () { |
| 112 | fs.file('app.ipa').createSync(); |
| 113 | when(os.unzip(any, any)).thenAnswer((Invocation invocation) { |
| 114 | final File zipFile = invocation.positionalArguments[0]; |
| 115 | if (zipFile.path != 'app.ipa') { |
| 116 | return null; |
| 117 | } |
| 118 | final Directory targetDirectory = invocation.positionalArguments[1]; |
| 119 | final String bundlePath1 = |
| 120 | fs.path.join(targetDirectory.path, 'Payload', 'bundle1.app'); |
| 121 | final String bundlePath2 = |
| 122 | fs.path.join(targetDirectory.path, 'Payload', 'bundle2.app'); |
| 123 | fs.directory(bundlePath1).createSync(recursive: true); |
| 124 | fs.directory(bundlePath2).createSync(recursive: true); |
| 125 | }); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 126 | final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('app.ipa')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 127 | expect(iosApp, isNull); |
| 128 | final BufferLogger logger = context[Logger]; |
| 129 | expect(logger.errorText, |
| 130 | 'Invalid prebuilt iOS ipa. Does not contain a single app bundle.\n'); |
| 131 | }, overrides: overrides); |
| 132 | testUsingContext('Success with ipa', () { |
| 133 | fs.file('app.ipa').createSync(); |
| 134 | when(os.unzip(any, any)).thenAnswer((Invocation invocation) { |
| 135 | final File zipFile = invocation.positionalArguments[0]; |
| 136 | if (zipFile.path != 'app.ipa') { |
| 137 | return null; |
| 138 | } |
| 139 | final Directory targetDirectory = invocation.positionalArguments[1]; |
| 140 | final Directory bundleAppDir = fs.directory( |
| 141 | fs.path.join(targetDirectory.path, 'Payload', 'bundle.app')); |
| 142 | bundleAppDir.createSync(recursive: true); |
| 143 | fs |
| 144 | .file(fs.path.join(bundleAppDir.path, 'Info.plist')) |
| 145 | .writeAsStringSync(plistData); |
| 146 | }); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 147 | final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(fs.file('app.ipa')); |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 148 | final BufferLogger logger = context[Logger]; |
| 149 | expect(logger.errorText, isEmpty); |
| 150 | expect(iosApp.bundleDir.path, endsWith('bundle.app')); |
| 151 | expect(iosApp.id, 'fooBundleId'); |
| 152 | expect(iosApp.bundleName, 'bundle.app'); |
| 153 | }, overrides: overrides); |
| 154 | }); |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 155 | } |
| 156 | |
Sarah Zakarias | def7634 | 2018-06-25 13:38:08 +0200 | [diff] [blame] | 157 | const String _aaptDataWithExplicitEnabledActivity = |
| 158 | '''N: android=http://schemas.android.com/apk/res/android |
| 159 | E: manifest (line=7) |
| 160 | A: android:versionCode(0x0101021b)=(type 0x10)0x1 |
| 161 | A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1") |
| 162 | A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world") |
| 163 | E: uses-sdk (line=12) |
| 164 | A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10 |
| 165 | A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b |
| 166 | E: uses-permission (line=21) |
| 167 | A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET") |
| 168 | E: application (line=29) |
| 169 | A: android:label(0x01010001)="hello_world" (Raw: "hello_world") |
| 170 | A: android:icon(0x01010002)=@0x7f010000 |
| 171 | A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication") |
| 172 | A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff |
| 173 | E: activity (line=34) |
| 174 | A: android:theme(0x01010000)=@0x1030009 |
| 175 | A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity") |
| 176 | A: android:enabled(0x0101000e)=(type 0x12)0x0 |
| 177 | A: android:launchMode(0x0101001d)=(type 0x10)0x1 |
| 178 | A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4 |
| 179 | A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10 |
| 180 | A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff |
| 181 | E: intent-filter (line=42) |
| 182 | E: action (line=43) |
| 183 | A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") |
| 184 | E: category (line=45) |
| 185 | A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER") |
| 186 | E: activity (line=48) |
| 187 | A: android:theme(0x01010000)=@0x1030009 |
| 188 | A: android:label(0x01010001)="app2" (Raw: "app2") |
| 189 | A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity2" (Raw: "io.flutter.examples.hello_world.MainActivity2") |
| 190 | A: android:enabled(0x0101000e)=(type 0x12)0xffffffff |
| 191 | E: intent-filter (line=53) |
| 192 | E: action (line=54) |
| 193 | A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") |
| 194 | E: category (line=56) |
| 195 | A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")'''; |
| 196 | |
| 197 | |
| 198 | const String _aaptDataWithDefaultEnabledActivity = |
| 199 | '''N: android=http://schemas.android.com/apk/res/android |
| 200 | E: manifest (line=7) |
| 201 | A: android:versionCode(0x0101021b)=(type 0x10)0x1 |
| 202 | A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1") |
| 203 | A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world") |
| 204 | E: uses-sdk (line=12) |
| 205 | A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10 |
| 206 | A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b |
| 207 | E: uses-permission (line=21) |
| 208 | A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET") |
| 209 | E: application (line=29) |
| 210 | A: android:label(0x01010001)="hello_world" (Raw: "hello_world") |
| 211 | A: android:icon(0x01010002)=@0x7f010000 |
| 212 | A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication") |
| 213 | A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff |
| 214 | E: activity (line=34) |
| 215 | A: android:theme(0x01010000)=@0x1030009 |
| 216 | A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity") |
| 217 | A: android:enabled(0x0101000e)=(type 0x12)0x0 |
| 218 | A: android:launchMode(0x0101001d)=(type 0x10)0x1 |
| 219 | A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4 |
| 220 | A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10 |
| 221 | A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff |
| 222 | E: intent-filter (line=42) |
| 223 | E: action (line=43) |
| 224 | A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") |
| 225 | E: category (line=45) |
| 226 | A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER") |
| 227 | E: activity (line=48) |
| 228 | A: android:theme(0x01010000)=@0x1030009 |
| 229 | A: android:label(0x01010001)="app2" (Raw: "app2") |
| 230 | A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity2" (Raw: "io.flutter.examples.hello_world.MainActivity2") |
| 231 | E: intent-filter (line=53) |
| 232 | E: action (line=54) |
| 233 | A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") |
| 234 | E: category (line=56) |
| 235 | A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")'''; |
| 236 | |
| 237 | |
| 238 | const String _aaptDataWithNoEnabledActivity = |
| 239 | '''N: android=http://schemas.android.com/apk/res/android |
| 240 | E: manifest (line=7) |
| 241 | A: android:versionCode(0x0101021b)=(type 0x10)0x1 |
| 242 | A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1") |
| 243 | A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world") |
| 244 | E: uses-sdk (line=12) |
| 245 | A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10 |
| 246 | A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b |
| 247 | E: uses-permission (line=21) |
| 248 | A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET") |
| 249 | E: application (line=29) |
| 250 | A: android:label(0x01010001)="hello_world" (Raw: "hello_world") |
| 251 | A: android:icon(0x01010002)=@0x7f010000 |
| 252 | A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication") |
| 253 | A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff |
| 254 | E: activity (line=34) |
| 255 | A: android:theme(0x01010000)=@0x1030009 |
| 256 | A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity") |
| 257 | A: android:enabled(0x0101000e)=(type 0x12)0x0 |
| 258 | A: android:launchMode(0x0101001d)=(type 0x10)0x1 |
| 259 | A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4 |
| 260 | A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10 |
| 261 | A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff |
| 262 | E: intent-filter (line=42) |
| 263 | E: action (line=43) |
| 264 | A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") |
| 265 | E: category (line=45) |
| 266 | A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")'''; |
| 267 | |
| 268 | |
Sigurd Meldgaard | 758711c | 2018-05-31 10:33:15 +0200 | [diff] [blame] | 269 | class MockIosWorkFlow extends Mock implements IOSWorkflow { |
| 270 | @override |
| 271 | String getPlistValueFromFile(String path, String key) { |
| 272 | final File file = fs.file(path); |
| 273 | if (!file.existsSync()) { |
| 274 | return null; |
| 275 | } |
| 276 | return json.decode(file.readAsStringSync())[key]; |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | // Contains no bundle identifier. |
| 281 | const String badPlistData = ''' |
| 282 | {} |
| 283 | '''; |
| 284 | |
| 285 | const String plistData = ''' |
| 286 | {"CFBundleIdentifier": "fooBundleId"} |
| 287 | '''; |