// 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 xcodeBackendPath = 'bin/xcode_backend.sh'; const xcodeBackendErrorHeader = '========================================================================'; // Acceptable $CONFIGURATION/$FLUTTER_BUILD_MODE values should be debug, profile, or release const unknownConfiguration = {'CONFIGURATION': 'Custom'}; // $FLUTTER_BUILD_MODE will override $CONFIGURATION const unknownFlutterBuildMode = { 'FLUTTER_BUILD_MODE': 'Custom', 'CONFIGURATION': 'Debug', }; void main() { Future expectXcodeBackendFails(Map environment) async { final ProcessResult result = await Process.run(xcodeBackendPath, [ '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, [], environment: { '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 when unable to determine platform', () async { final ProcessResult result = await Process.run( xcodeBackendPath, ['build', 'asdf'], environment: {'CONFIGURATION': 'Debug', 'ACTION': 'install'}, ); expect(result.stderr, contains('warning: Unrecognized platform: asdf. Defaulting to iOS.')); 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, ['test_vm_service_bonjour_service'], environment: { 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); expect(result, const ProcessResultMatcher(stdoutPattern: 'Info.plist does not exist.')); }); const emptyPlist = ''' '''; test('does not add keys in Release', () async { infoPlist.writeAsStringSync(emptyPlist); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_vm_service_bonjour_service'], environment: { '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 buildConfiguration in ['Debug', 'Profile']) { for (final verbose in [true, false]) { test( 'add keys in $buildConfiguration under ${verbose ? 'verbose' : 'non-verbose'} mode', () async { infoPlist.writeAsStringSync(emptyPlist); final File pipe = fileSystem.file('/tmp/pipe')..createSync(recursive: true); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_vm_service_bonjour_service'], environment: { 'CONFIGURATION': buildConfiguration, 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', if (verbose) 'VERBOSE_SCRIPT_LOGGING': 'YES', 'SCRIPT_OUTPUT_STREAM_FILE': pipe.path, }, ); final String actualInfoPlist = infoPlist.readAsStringSync(); expect(actualInfoPlist, contains('NSBonjourServices')); expect(actualInfoPlist, contains('dartVmService')); expect(actualInfoPlist, contains('NSLocalNetworkUsageDescription')); // Make sure no Xcode compilation error. expect(result.stderr, isNot(startsWith('error:'))); const plutilErrorMessage = 'Could not extract value, error: No value at that key path or invalid key path: NSBonjourServices'; expect(pipe.readAsStringSync(), isNot(contains(plutilErrorMessage))); expect(result.stderr, isNot(contains(plutilErrorMessage))); if (verbose) { expect(result.stdout, contains(plutilErrorMessage)); } else { expect(result.stdout, isNot(contains(plutilErrorMessage))); } expect(result, const ProcessResultMatcher()); }, ); } } test( 'adds to existing Bonjour services, does not override network usage description', () async { infoPlist.writeAsStringSync(''' NSBonjourServices _bogus._tcp NSLocalNetworkUsageDescription Don't override this '''); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_vm_service_bonjour_service'], environment: { 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); expect(infoPlist.readAsStringSync(), ''' NSBonjourServices _dartVmService._tcp _bogus._tcp NSLocalNetworkUsageDescription Don't override this '''); expect(result.stderr, isNot(startsWith('error:'))); expect(result, const ProcessResultMatcher()); }, ); test('does not add bonjour settings when port publication is disabled', () async { infoPlist.writeAsStringSync(''' '''); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_vm_service_bonjour_service'], environment: { '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. }