flutter/packages/flutter_tools/test/ios/xcodeproj_test.dart
Danny Tuppeny a74f591d0c
Change asset_bundle_package_font_test to memory file system (#21114)
* Change assert_bundle_package_font_test to memory file system

This is to work towards being able to run the tests without `-j1` (#21113). These tests were using the real filesystem and setting/relying on fs.currentDirectory. There was a comment about this being because the memory provider didnt' support POSIX and Windows, however that seems to have changed since (and many other asset tests already do something similar to this).

* Trim trailing whitespace

* Add a workaround for Windows path slash directions

Strictly this is correct, but the real FS can tolerate either path. The in-memory file system is more strict (see https://github.com/google/file.dart/issues/112).

* Extract a helper for writing schema files in tests

* Missed file when saving!

* Remove redundant comment

* Rename writeBasicSchema -> writeEmptySchema

* Use the file we already have to write contents

* Make comments more descriptive

* Remove another dupe of writeSchema to use the shared one

* Rename schema -> pubspec_schema

* Trim whitespace
2018-09-04 17:12:24 +01:00

542 lines
21 KiB
Dart

// Copyright 2018 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:async';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/pubspec_schema.dart';
const String xcodebuild = '/usr/bin/xcodebuild';
void main() {
group('xcodebuild versioning', () {
MockProcessManager mockProcessManager;
XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform macOS;
FileSystem fs;
setUp(() {
mockProcessManager = new MockProcessManager();
xcodeProjectInterpreter = new XcodeProjectInterpreter();
macOS = fakePlatform('macos');
fs = new MemoryFileSystem();
fs.file(xcodebuild).createSync(recursive: true);
});
void testUsingOsxContext(String description, dynamic testMethod()) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => macOS,
FileSystem: () => fs,
});
}
testUsingOsxContext('versionText returns null when xcodebuild is not installed', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenThrow(const ProcessException(xcodebuild, <String>['-version']));
expect(xcodeProjectInterpreter.versionText, isNull);
});
testUsingOsxContext('versionText returns null when xcodebuild is not fully installed', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version'])).thenReturn(
new ProcessResult(
0,
1,
"xcode-select: error: tool 'xcodebuild' requires Xcode, "
"but active developer directory '/Library/Developer/CommandLineTools' "
'is a command line tools instance',
'',
),
);
expect(xcodeProjectInterpreter.versionText, isNull);
});
testUsingOsxContext('versionText returns formatted version text', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.versionText, 'Xcode 8.3.3, Build version 8E3004b');
});
testUsingOsxContext('versionText handles Xcode version string with unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.versionText, 'Xcode Ultra5000, Build version 8E3004b');
});
testUsingOsxContext('majorVersion returns major version', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.majorVersion, 8);
});
testUsingOsxContext('majorVersion is null when version has unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.majorVersion, isNull);
});
testUsingOsxContext('minorVersion returns minor version', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.minorVersion, 3);
});
testUsingOsxContext('minorVersion returns 0 when minor version is unspecified', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.minorVersion, 0);
});
testUsingOsxContext('minorVersion is null when version has unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.minorVersion, isNull);
});
testUsingContext('isInstalled is false when not on MacOS', () {
fs.file(xcodebuild).deleteSync();
expect(xcodeProjectInterpreter.isInstalled, isFalse);
}, overrides: <Type, Generator>{
Platform: () => fakePlatform('notMacOS')
});
testUsingOsxContext('isInstalled is false when xcodebuild does not exist', () {
fs.file(xcodebuild).deleteSync();
expect(xcodeProjectInterpreter.isInstalled, isFalse);
});
testUsingOsxContext('isInstalled is false when Xcode is not fully installed', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version'])).thenReturn(
new ProcessResult(
0,
1,
"xcode-select: error: tool 'xcodebuild' requires Xcode, "
"but active developer directory '/Library/Developer/CommandLineTools' "
'is a command line tools instance',
'',
),
);
expect(xcodeProjectInterpreter.isInstalled, isFalse);
});
testUsingOsxContext('isInstalled is false when version has unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.isInstalled, isFalse);
});
testUsingOsxContext('isInstalled is true when version has expected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.isInstalled, isTrue);
});
});
group('Xcode project properties', () {
test('properties from default project can be parsed', () {
const String output = '''
Information about project "Runner":
Targets:
Runner
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
Runner
''';
final XcodeProjectInfo info = new XcodeProjectInfo.fromXcodeBuildOutput(output);
expect(info.targets, <String>['Runner']);
expect(info.schemes, <String>['Runner']);
expect(info.buildConfigurations, <String>['Debug', 'Release']);
});
test('properties from project with custom schemes can be parsed', () {
const String output = '''
Information about project "Runner":
Targets:
Runner
Build Configurations:
Debug (Free)
Debug (Paid)
Release (Free)
Release (Paid)
If no build configuration is specified and -scheme is not passed then "Release (Free)" is used.
Schemes:
Free
Paid
''';
final XcodeProjectInfo info = new XcodeProjectInfo.fromXcodeBuildOutput(output);
expect(info.targets, <String>['Runner']);
expect(info.schemes, <String>['Free', 'Paid']);
expect(info.buildConfigurations, <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)']);
});
test('expected scheme for non-flavored build is Runner', () {
expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.debug), 'Runner');
expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.profile), 'Runner');
expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.release), 'Runner');
});
test('expected build configuration for non-flavored build is derived from BuildMode', () {
expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.profile, 'Runner'), 'Release');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.release, 'Runner'), 'Release');
});
test('expected scheme for flavored build is the title-cased flavor', () {
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.debug, 'hello')), 'Hello');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.profile, 'HELLO')), 'HELLO');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello')), 'Hello');
});
test('expected build configuration for flavored build is Mode-Flavor', () {
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.debug, 'hello'), 'Hello'), 'Debug-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.profile, 'HELLO'), 'Hello'), 'Release-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello'), 'Hello'), 'Release-Hello');
});
test('scheme for default project is Runner', () {
final XcodeProjectInfo info = new XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner']);
expect(info.schemeFor(BuildInfo.debug), 'Runner');
expect(info.schemeFor(BuildInfo.profile), 'Runner');
expect(info.schemeFor(BuildInfo.release), 'Runner');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull);
});
test('build configuration for default project is matched against BuildMode', () {
final XcodeProjectInfo info = new XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner']);
expect(info.buildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
expect(info.buildConfigurationFor(BuildInfo.profile, 'Runner'), 'Release');
expect(info.buildConfigurationFor(BuildInfo.release, 'Runner'), 'Release');
});
test('scheme for project with custom schemes is matched against flavor', () {
final XcodeProjectInfo info = new XcodeProjectInfo(
<String>['Runner'],
<String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)'],
<String>['Free', 'Paid'],
);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free')), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free')), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid')), 'Paid');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, null)), isNull);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull);
});
test('build configuration for project with custom schemes is matched against BuildMode and flavor', () {
final XcodeProjectInfo info = new XcodeProjectInfo(
<String>['Runner'],
<String>['debug (free)', 'Debug paid', 'release - Free', 'Release-Paid'],
<String>['Free', 'Paid'],
);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free'), 'Free'), 'debug (free)');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid'), 'Paid'), 'Debug paid');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE'), 'Free'), 'release - Free');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid'), 'Paid'), 'Release-Paid');
});
test('build configuration for project with inconsistent naming is null', () {
final XcodeProjectInfo info = new XcodeProjectInfo(
<String>['Runner'],
<String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
<String>['Free', 'Paid'],
);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free'), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free'), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid'), 'Paid'), null);
});
});
group('updateGeneratedXcodeProperties', () {
MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
FakePlatform macOS;
FileSystem fs;
setUp(() {
fs = new MemoryFileSystem();
mockArtifacts = new MockLocalEngineArtifacts();
mockProcessManager = new MockProcessManager();
macOS = fakePlatform('macos');
fs.file(xcodebuild).createSync(recursive: true);
});
void testUsingOsxContext(String description, dynamic testMethod()) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
ProcessManager: () => mockProcessManager,
Platform: () => macOS,
FileSystem: () => fs,
});
}
testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null,
previewDart2: true,
targetPlatform: TargetPlatform.ios,
);
final FlutterProject project = await FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
previewDart2: true,
);
final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents.contains('ARCHS=armv7'), isTrue);
});
testUsingOsxContext('sets TRACK_WIDGET_CREATION=true when trackWidgetCreation is true', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null,
previewDart2: true,
trackWidgetCreation: true,
targetPlatform: TargetPlatform.ios,
);
final FlutterProject project = await FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
previewDart2: true,
);
final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents.contains('TRACK_WIDGET_CREATION=true'), isTrue);
});
testUsingOsxContext('does not set TRACK_WIDGET_CREATION when trackWidgetCreation is false', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null,
previewDart2: true,
targetPlatform: TargetPlatform.ios,
);
final FlutterProject project = await FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
previewDart2: true,
);
final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents.contains('TRACK_WIDGET_CREATION=true'), isFalse);
});
testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null,
previewDart2: true,
targetPlatform: TargetPlatform.ios,
);
final FlutterProject project = await FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
previewDart2: true,
);
final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents.contains('ARCHS=arm64'), isTrue);
});
String propertyFor(String key, File file) {
final List<String> properties = file
.readAsLinesSync()
.where((String line) => line.startsWith('$key='))
.map((String line) => line.split('=')[1])
.toList();
return properties.isEmpty ? null : properties.first;
}
Future<void> checkBuildVersion({
String manifestString,
BuildInfo buildInfo,
String expectedBuildName,
String expectedBuildNumber,
}) async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios'));
final File manifestFile = fs.file('path/to/project/pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync(manifestString);
// write schemaData otherwise pubspec.yaml file can't be loaded
writeEmptySchemaFile(fs);
await updateGeneratedXcodeProperties(
project: await FlutterProject.fromPath('path/to/project'),
buildInfo: buildInfo,
previewDart2: true,
);
final File localPropertiesFile = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(propertyFor('FLUTTER_BUILD_NAME', localPropertiesFile), expectedBuildName);
expect(propertyFor('FLUTTER_BUILD_NUMBER', localPropertiesFile), expectedBuildNumber);
}
testUsingOsxContext('extract build name and number from pubspec.yaml', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.0',
expectedBuildNumber: '1',
);
});
testUsingOsxContext('extract build name from pubspec.yaml', () async {
const String manifest = '''
name: test
version: 1.0.0
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.0',
expectedBuildNumber: null,
);
});
testUsingOsxContext('allow build info to override build name', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2');
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '1',
);
});
testUsingOsxContext('allow build info to override build number', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: 3);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.0',
expectedBuildNumber: '3',
);
});
testUsingOsxContext('allow build info to override build name and number', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
testUsingOsxContext('allow build info to override build name and set number', () async {
const String manifest = '''
name: test
version: 1.0.0
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
testUsingOsxContext('allow build info to set build name and number', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
});
}
Platform fakePlatform(String name) {
return new FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name;
}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter { }