| // 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 'dart:convert'; |
| |
| import 'package:file/file.dart'; |
| import 'package:file/memory.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/base/utils.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| import 'package:flutter_tools/src/ios/plist_parser.dart'; |
| import 'package:flutter_tools/src/macos/application_package.dart'; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:test/fake.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| |
| void main() { |
| group('PrebuiltMacOSApp', () { |
| late FakeOperatingSystemUtils os; |
| late FileSystem fileSystem; |
| late BufferLogger logger; |
| |
| final Map<Type, Generator> overrides = <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => FakePlistUtils(fileSystem), |
| OperatingSystemUtils: () => os, |
| Logger: () => logger, |
| }; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| os = FakeOperatingSystemUtils(); |
| logger = BufferLogger.test(); |
| }); |
| |
| testUsingContext('Error on non-existing file', () { |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('not_existing.app')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('File "not_existing.app" does not exist.')); |
| }, overrides: overrides); |
| |
| testUsingContext('Error on non-app-bundle folder', () { |
| fileSystem.directory('regular_folder').createSync(); |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('regular_folder')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('Folder "regular_folder" is not an app bundle.')); |
| }, overrides: overrides); |
| |
| testUsingContext('Error on no info.plist', () { |
| fileSystem.directory('bundle.app').createSync(); |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('bundle.app')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('Invalid prebuilt macOS app. Does not contain Info.plist.')); |
| }, overrides: overrides); |
| |
| testUsingContext('Error on info.plist missing bundle identifier', () { |
| final String contentsDirectory = fileSystem.path.join('bundle.app', 'Contents'); |
| fileSystem.directory(contentsDirectory).createSync(recursive: true); |
| fileSystem.file(fileSystem.path.join('bundle.app', 'Contents', 'Info.plist')) |
| .writeAsStringSync(badPlistData); |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('bundle.app')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier')); |
| }, overrides: overrides); |
| |
| testUsingContext('Error on info.plist missing executable', () { |
| final String contentsDirectory = fileSystem.path.join('bundle.app', 'Contents'); |
| fileSystem.directory(contentsDirectory).createSync(recursive: true); |
| fileSystem.file(fileSystem.path.join('bundle.app', 'Contents', 'Info.plist')) |
| .writeAsStringSync(badPlistDataNoExecutable); |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('bundle.app')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('Invalid prebuilt macOS app. Info.plist does not contain bundle executable')); |
| }, overrides: overrides); |
| |
| testUsingContext('Success with app bundle', () { |
| final String appDirectory = fileSystem.path.join('bundle.app', 'Contents', 'MacOS'); |
| fileSystem.directory(appDirectory).createSync(recursive: true); |
| fileSystem.file(fileSystem.path.join('bundle.app', 'Contents', 'Info.plist')) |
| .writeAsStringSync(plistData); |
| fileSystem.file(fileSystem.path.join(appDirectory, executableName)) |
| .createSync(); |
| final PrebuiltMacOSApp macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('bundle.app'))! as PrebuiltMacOSApp; |
| |
| expect(logger.errorText, isEmpty); |
| expect(macosApp.uncompressedBundle.path, 'bundle.app'); |
| expect(macosApp.id, 'fooBundleId'); |
| expect(macosApp.bundleName, 'bundle.app'); |
| }, overrides: overrides); |
| |
| testUsingContext('Bad zipped app, no payload dir', () { |
| fileSystem.file('app.zip').createSync(); |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('app.zip')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('Archive "app.zip" does not contain a single app bundle.')); |
| }, overrides: overrides); |
| |
| testUsingContext('Bad zipped app, two app bundles', () { |
| fileSystem.file('app.zip').createSync(); |
| os.unzipOverride = (File zipFile, Directory targetDirectory) { |
| if (zipFile.path != 'app.zip') { |
| return; |
| } |
| final String bundlePath1 = fileSystem.path.join(targetDirectory.path, 'bundle1.app'); |
| final String bundlePath2 = fileSystem.path.join(targetDirectory.path, 'bundle2.app'); |
| fileSystem.directory(bundlePath1).createSync(recursive: true); |
| fileSystem.directory(bundlePath2).createSync(recursive: true); |
| }; |
| final PrebuiltMacOSApp? macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('app.zip')) as PrebuiltMacOSApp?; |
| |
| expect(macosApp, isNull); |
| expect(logger.errorText, contains('Archive "app.zip" does not contain a single app bundle.')); |
| }, overrides: overrides); |
| |
| testUsingContext('Success with zipped app', () { |
| fileSystem.file('app.zip').createSync(); |
| os.unzipOverride = (File zipFile, Directory targetDirectory) { |
| if (zipFile.path != 'app.zip') { |
| return; |
| } |
| final Directory bundleAppContentsDir = fileSystem.directory(fileSystem.path.join(targetDirectory.path, 'bundle.app', 'Contents')); |
| bundleAppContentsDir.createSync(recursive: true); |
| fileSystem.file(fileSystem.path.join(bundleAppContentsDir.path, 'Info.plist')) |
| .writeAsStringSync(plistData); |
| fileSystem.directory(fileSystem.path.join(bundleAppContentsDir.path, 'MacOS')) |
| .createSync(); |
| fileSystem.file(fileSystem.path |
| .join(bundleAppContentsDir.path, 'MacOS', executableName)) |
| .createSync(); |
| }; |
| final PrebuiltMacOSApp macosApp = MacOSApp.fromPrebuiltApp(fileSystem.file('app.zip'))! as PrebuiltMacOSApp; |
| |
| expect(logger.errorText, isEmpty); |
| expect(macosApp.uncompressedBundle.path, endsWith('bundle.app')); |
| expect(macosApp.id, 'fooBundleId'); |
| expect(macosApp.bundleName, endsWith('bundle.app')); |
| }, overrides: overrides); |
| |
| testUsingContext('Success with project', () { |
| final MacOSApp macosApp = MacOSApp.fromMacOSProject(FlutterProject.fromDirectory(globals.fs.currentDirectory).macos); |
| |
| expect(logger.errorText, isEmpty); |
| expect(macosApp.id, 'com.example.placeholder'); |
| expect(macosApp.name, 'macOS'); |
| }, overrides: overrides); |
| |
| testUsingContext('Chooses the correct directory for application.', () { |
| final MacOSProject project = FlutterProject.fromDirectory(globals.fs.currentDirectory).macos; |
| final BuildableMacOSApp macosApp = MacOSApp.fromMacOSProject(project) as BuildableMacOSApp; |
| |
| const BuildInfo vanillaApp = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); |
| String? applicationBundle = macosApp.bundleDirectory(vanillaApp); |
| expect(applicationBundle, 'Debug'); |
| |
| const BuildInfo flavoredApp = BuildInfo(BuildMode.release, 'flavor', treeShakeIcons: false); |
| applicationBundle = macosApp.bundleDirectory(flavoredApp); |
| expect(applicationBundle, 'Release-flavor'); |
| |
| }, overrides: overrides); |
| }); |
| } |
| |
| class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { |
| FakeOperatingSystemUtils(); |
| |
| void Function(File, Directory)? unzipOverride; |
| |
| @override |
| void unzip(File file, Directory targetDirectory) { |
| unzipOverride?.call(file, targetDirectory); |
| } |
| } |
| |
| class FakePlistUtils extends Fake implements PlistParser { |
| FakePlistUtils(this.fileSystem); |
| |
| final FileSystem? fileSystem; |
| |
| @override |
| Map<String, Object> parseFile(String plistFilePath) { |
| final File file = fileSystem!.file(plistFilePath); |
| if (!file.existsSync()) { |
| return <String, Object>{}; |
| } |
| return castStringKeyedMap(json.decode(file.readAsStringSync()))!.cast(); |
| } |
| } |
| |
| // Contains no bundle identifier. |
| const String badPlistData = ''' |
| {} |
| '''; |
| |
| // Contains no bundle executable. |
| const String badPlistDataNoExecutable = ''' |
| {"CFBundleIdentifier": "fooBundleId"} |
| '''; |
| |
| const String executableName = 'foo'; |
| |
| const String plistData = ''' |
| {"CFBundleIdentifier": "fooBundleId", "CFBundleExecutable": "$executableName"} |
| '''; |