// 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:io' as io; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import '../src/common.dart'; import 'test_utils.dart'; const String xcodeBackendPath = 'bin/xcode_backend.sh'; const String xcodeBackendErrorHeader = '========================================================================'; // Acceptable $CONFIGURATION/$FLUTTER_BUILD_MODE values should be debug, profile, or release const Map<String, String> unknownConfiguration = <String, String>{ 'CONFIGURATION': 'Custom', }; // $FLUTTER_BUILD_MODE will override $CONFIGURATION const Map<String, String> unknownFlutterBuildMode = <String, String>{ 'FLUTTER_BUILD_MODE': 'Custom', 'CONFIGURATION': 'Debug', }; void main() { Future<void> expectXcodeBackendFails(Map<String, String> environment) async { final ProcessResult result = await Process.run( xcodeBackendPath, <String>['build'], environment: environment, ); expect(result.stderr, startsWith(xcodeBackendErrorHeader)); expect(result.exitCode, isNot(0)); } test('Xcode backend fails with no arguments', () async { final ProcessResult result = await Process.run( xcodeBackendPath, <String>[], environment: <String, String>{ 'SOURCE_ROOT': '../examples/hello_world', 'FLUTTER_ROOT': '../..', }, ); expect(result.stderr, startsWith('error: Your Xcode project is incompatible with this version of Flutter.')); expect(result.exitCode, isNot(0)); }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain. test('Xcode backend fails for on unsupported configuration combinations', () async { await expectXcodeBackendFails(unknownConfiguration); await expectXcodeBackendFails(unknownFlutterBuildMode); }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain. test('Xcode backend warns archiving a non-release build.', () async { final ProcessResult result = await Process.run( xcodeBackendPath, <String>['build'], environment: <String, String>{ 'CONFIGURATION': 'Debug', 'ACTION': 'install', }, ); expect(result.stdout, contains('warning: Flutter archive not built in Release mode.')); expect(result.exitCode, isNot(0)); }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain. group('vmService Bonjour service keys', () { late Directory buildDirectory; late File infoPlist; setUp(() { buildDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_tools_xcode_backend_test.'); infoPlist = buildDirectory.childFile('Info.plist'); }); test('handles when the Info.plist is missing', () async { final ProcessResult result = await Process.run( xcodeBackendPath, <String>['test_vm_service_bonjour_service'], environment: <String, String>{ 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); expect(result, const ProcessResultMatcher(stdoutPattern: 'Info.plist does not exist.')); }); const String emptyPlist = ''' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> </dict> </plist>'''; test('does not add keys in Release', () async { infoPlist.writeAsStringSync(emptyPlist); final ProcessResult result = await Process.run( xcodeBackendPath, <String>['test_vm_service_bonjour_service'], environment: <String, String>{ 'CONFIGURATION': 'Release', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); final String actualInfoPlist = infoPlist.readAsStringSync(); expect(actualInfoPlist, isNot(contains('NSBonjourServices'))); expect(actualInfoPlist, isNot(contains('dartVmService'))); expect(actualInfoPlist, isNot(contains('NSLocalNetworkUsageDescription'))); expect(result, const ProcessResultMatcher()); }); for (final String buildConfiguration in <String>['Debug', 'Profile']) { test('add keys in $buildConfiguration', () async { infoPlist.writeAsStringSync(emptyPlist); final ProcessResult result = await Process.run( xcodeBackendPath, <String>['test_vm_service_bonjour_service'], environment: <String, String>{ 'CONFIGURATION': buildConfiguration, 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); final String actualInfoPlist = infoPlist.readAsStringSync(); expect(actualInfoPlist, contains('NSBonjourServices')); expect(actualInfoPlist, contains('dartVmService')); expect(actualInfoPlist, contains('NSLocalNetworkUsageDescription')); expect(result, const ProcessResultMatcher()); }); } test('adds to existing Bonjour services, does not override network usage description', () async { infoPlist.writeAsStringSync(''' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSBonjourServices</key> <array> <string>_bogus._tcp</string> </array> <key>NSLocalNetworkUsageDescription</key> <string>Don't override this</string> </dict> </plist>'''); final ProcessResult result = await Process.run( xcodeBackendPath, <String>['test_vm_service_bonjour_service'], environment: <String, String>{ 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); expect(infoPlist.readAsStringSync(), ''' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSBonjourServices</key> <array> <string>_dartVmService._tcp</string> <string>_bogus._tcp</string> </array> <key>NSLocalNetworkUsageDescription</key> <string>Don't override this</string> </dict> </plist> '''); expect(result, const ProcessResultMatcher()); }); test('does not add bonjour settings when port publication is disabled', () async { infoPlist.writeAsStringSync(''' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> </dict> </plist>'''); final ProcessResult result = await Process.run( xcodeBackendPath, <String>['test_vm_service_bonjour_service'], environment: <String, String>{ 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', 'DISABLE_PORT_PUBLICATION': 'YES', }, ); expect(infoPlist.readAsStringSync().contains('NSBonjourServices'), isFalse); expect(infoPlist.readAsStringSync().contains('NSLocalNetworkUsageDescription'), isFalse); expect(result, const ProcessResultMatcher()); }); }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain. }