// 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 unknownConfiguration = { 'CONFIGURATION': 'Custom', }; // $FLUTTER_BUILD_MODE will override $CONFIGURATION const Map 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 archiving a non-release build.', () async { final ProcessResult result = await Process.run( xcodeBackendPath, ['build'], environment: { '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, ['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 String 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 String buildConfiguration in ['Debug', 'Profile']) { test('add keys in $buildConfiguration', () async { infoPlist.writeAsStringSync(emptyPlist); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_vm_service_bonjour_service'], environment: { '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(''' 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, 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. }