blob: d4e4dedf9166ea1d5e78315c6d9dbe81caeaa515 [file] [log] [blame]
// Copyright 2016 The Chromium 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 'dart:convert';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:test/test.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:mockito/mockito.dart';
import 'src/context.dart';
void main() {
group('ApkManifestData', () {
testUsingContext('parse sdk', () {
final ApkManifestData data =
ApkManifestData.parseFromAaptBadging(_aaptData);
expect(data, isNotNull);
expect(data.packageName, 'io.flutter.gallery');
expect(data.launchableActivityName, 'io.flutter.app.FlutterActivity');
expect(data.data['application']['label'], 'Flutter Gallery');
});
});
group('BuildableIOSApp', () {
testUsingContext('check isSwift', () {
final BuildableIOSApp buildableIOSApp = new BuildableIOSApp(
projectBundleId: 'blah',
appDirectory: 'not/important',
buildSettings: _swiftBuildSettings,
);
expect(buildableIOSApp.isSwift, true);
});
});
group('PrebuiltIOSApp', () {
final Map<Type, Generator> overrides = <Type, Generator>{
FileSystem: () => new MemoryFileSystem(),
IOSWorkflow: () => new MockIosWorkFlow()
};
testUsingContext('Error on non-existing file', () {
final PrebuiltIOSApp iosApp =
new IOSApp.fromPrebuiltApp('not_existing.ipa');
expect(iosApp, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText,
'File "not_existing.ipa" does not exist. Use an app bundle or an ipa.\n',
);
}, overrides: overrides);
testUsingContext('Error on non-app-bundle folder', () {
fs.directory('regular_folder').createSync();
final PrebuiltIOSApp iosApp =
new IOSApp.fromPrebuiltApp('regular_folder');
expect(iosApp, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText, 'Folder "regular_folder" is not an app bundle.\n');
}, overrides: overrides);
testUsingContext('Error on no info.plist', () {
fs.directory('bundle.app').createSync();
final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('bundle.app');
expect(iosApp, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText,
'Invalid prebuilt iOS app. Does not contain Info.plist.\n',
);
}, overrides: overrides);
testUsingContext('Error on bad info.plist', () {
fs.directory('bundle.app').createSync();
fs.file('bundle.app/Info.plist').writeAsStringSync(badPlistData);
final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('bundle.app');
expect(iosApp, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText,
contains(
'Invalid prebuilt iOS app. Info.plist does not contain bundle identifier\n'),
);
}, overrides: overrides);
testUsingContext('Success with app bundle', () {
fs.directory('bundle.app').createSync();
fs.file('bundle.app/Info.plist').writeAsStringSync(plistData);
final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('bundle.app');
final BufferLogger logger = context[Logger];
expect(logger.errorText, isEmpty);
expect(iosApp.bundleDir.path, 'bundle.app');
expect(iosApp.id, 'fooBundleId');
expect(iosApp.bundleName, 'bundle.app');
}, overrides: overrides);
testUsingContext('Bad ipa zip-file, no payload dir', () {
fs.file('app.ipa').createSync();
when(os.unzip(fs.file('app.ipa'), any)).thenAnswer((Invocation _) {});
final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('app.ipa');
expect(iosApp, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText,
'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.\n',
);
}, overrides: overrides);
testUsingContext('Bad ipa zip-file, two app bundles', () {
fs.file('app.ipa').createSync();
when(os.unzip(any, any)).thenAnswer((Invocation invocation) {
final File zipFile = invocation.positionalArguments[0];
if (zipFile.path != 'app.ipa') {
return null;
}
final Directory targetDirectory = invocation.positionalArguments[1];
final String bundlePath1 =
fs.path.join(targetDirectory.path, 'Payload', 'bundle1.app');
final String bundlePath2 =
fs.path.join(targetDirectory.path, 'Payload', 'bundle2.app');
fs.directory(bundlePath1).createSync(recursive: true);
fs.directory(bundlePath2).createSync(recursive: true);
});
final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('app.ipa');
expect(iosApp, isNull);
final BufferLogger logger = context[Logger];
expect(logger.errorText,
'Invalid prebuilt iOS ipa. Does not contain a single app bundle.\n');
}, overrides: overrides);
testUsingContext('Success with ipa', () {
fs.file('app.ipa').createSync();
when(os.unzip(any, any)).thenAnswer((Invocation invocation) {
final File zipFile = invocation.positionalArguments[0];
if (zipFile.path != 'app.ipa') {
return null;
}
final Directory targetDirectory = invocation.positionalArguments[1];
final Directory bundleAppDir = fs.directory(
fs.path.join(targetDirectory.path, 'Payload', 'bundle.app'));
bundleAppDir.createSync(recursive: true);
fs
.file(fs.path.join(bundleAppDir.path, 'Info.plist'))
.writeAsStringSync(plistData);
});
final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('app.ipa');
final BufferLogger logger = context[Logger];
expect(logger.errorText, isEmpty);
expect(iosApp.bundleDir.path, endsWith('bundle.app'));
expect(iosApp.id, 'fooBundleId');
expect(iosApp.bundleName, 'bundle.app');
}, overrides: overrides);
});
}
const String _aaptData = '''
package: name='io.flutter.gallery' versionCode='1' versionName='0.0.1' platformBuildVersionName='NMR1'
sdkVersion:'14'
targetSdkVersion:'21'
uses-permission: name='android.permission.INTERNET'
application-label:'Flutter Gallery'
application-icon-160:'res/mipmap-mdpi-v4/ic_launcher.png'
application-icon-240:'res/mipmap-hdpi-v4/ic_launcher.png'
application-icon-320:'res/mipmap-xhdpi-v4/ic_launcher.png'
application-icon-480:'res/mipmap-xxhdpi-v4/ic_launcher.png'
application-icon-640:'res/mipmap-xxxhdpi-v4/ic_launcher.png'
application: label='Flutter Gallery' icon='res/mipmap-mdpi-v4/ic_launcher.png'
application-debuggable
launchable-activity: name='io.flutter.app.FlutterActivity' label='' icon=''
feature-group: label=''
uses-feature: name='android.hardware.screen.portrait'
uses-implied-feature: name='android.hardware.screen.portrait' reason='one or more activities have specified a portrait orientation'
uses-feature: name='android.hardware.touchscreen'
uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps'
main
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--'
densities: '160' '240' '320' '480' '640'
native-code: 'armeabi-v7a'
''';
final Map<String, String> _swiftBuildSettings = <String, String>{
'ARCHS': 'arm64',
'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon',
'CLANG_ENABLE_MODULES': 'YES',
'ENABLE_BITCODE': 'NO',
'INFOPLIST_FILE': 'Runner/Info.plist',
'PRODUCT_BUNDLE_IDENTIFIER': 'com.example.test',
'PRODUCT_NAME': 'blah',
'SWIFT_OBJC_BRIDGING_HEADER': 'Runner/Runner-Bridging-Header.h',
'SWIFT_OPTIMIZATION_LEVEL': '-Onone',
'SWIFT_VERSION': '3.0',
};
class MockIosWorkFlow extends Mock implements IOSWorkflow {
@override
String getPlistValueFromFile(String path, String key) {
final File file = fs.file(path);
if (!file.existsSync()) {
return null;
}
return json.decode(file.readAsStringSync())[key];
}
}
// Contains no bundle identifier.
const String badPlistData = '''
{}
''';
const String plistData = '''
{"CFBundleIdentifier": "fooBundleId"}
''';