Skip to content

Commit a6c5e60

Browse files
cbrackengspencergoog
authored andcommitted
Use ProcessManager for Xcode tool invocations (flutter#10955)
1. Run all Xcode tool invocations through ProcessManager, which allows us to mock out failures, etc. for tests. 2. Add said tests.
1 parent f909034 commit a6c5e60

File tree

2 files changed

+103
-6
lines changed

2 files changed

+103
-6
lines changed

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class Xcode {
8686
String get xcodeSelectPath {
8787
if (_xcodeSelectPath == null) {
8888
try {
89-
_xcodeSelectPath = runSync(<String>['/usr/bin/xcode-select', '--print-path'])?.trim();
89+
_xcodeSelectPath = processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']).stdout.trim();
9090
} on ProcessException {
9191
// Ignore: return null below.
9292
}
@@ -125,7 +125,7 @@ class Xcode {
125125
String get xcodeVersionText {
126126
if (_xcodeVersionText == null) {
127127
try {
128-
_xcodeVersionText = runSync(<String>['/usr/bin/xcodebuild', '-version']).replaceAll('\n', ', ');
128+
_xcodeVersionText = processManager.runSync(<String>['/usr/bin/xcodebuild', '-version']).stdout.replaceAll('\n', ', ');
129129
} on ProcessException {
130130
// Ignore: return null below.
131131
}
@@ -155,10 +155,15 @@ class Xcode {
155155
}
156156

157157
Future<String> getAvailableDevices() async {
158-
final RunResult result = await runAsync(<String>['/usr/bin/instruments', '-s', 'devices']);
159-
if (result.exitCode != 0)
158+
try {
159+
final ProcessResult result = await processManager.run(
160+
<String>['/usr/bin/instruments', '-s', 'devices']);
161+
if (result.exitCode != 0)
162+
throw new ToolExit('/usr/bin/instruments returned an error:\n${result.stderr}');
163+
return result.stdout;
164+
} on ProcessException {
160165
throw new ToolExit('Failed to invoke /usr/bin/instruments. Is Xcode installed?');
161-
return result.stdout;
166+
}
162167
}
163168
}
164169

packages/flutter_tools/test/ios/mac_test.dart

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import 'dart:async';
77
import 'package:file/file.dart';
88
import 'package:flutter_tools/src/application_package.dart';
99
import 'package:flutter_tools/src/base/file_system.dart';
10-
import 'package:flutter_tools/src/base/io.dart' show ProcessResult;
10+
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
1111
import 'package:flutter_tools/src/ios/mac.dart';
1212
import 'package:mockito/mockito.dart';
1313
import 'package:platform/platform.dart';
1414
import 'package:process/process.dart';
1515
import 'package:test/test.dart';
1616

17+
import '../src/common.dart';
1718
import '../src/context.dart';
1819

1920
class MockProcessManager extends Mock implements ProcessManager {}
@@ -65,6 +66,97 @@ void main() {
6566
});
6667
});
6768

69+
group('Xcode', () {
70+
MockProcessManager mockProcessManager;
71+
Xcode xcode;
72+
73+
setUp(() {
74+
mockProcessManager = new MockProcessManager();
75+
xcode = new Xcode();
76+
});
77+
78+
testUsingContext('xcodeSelectPath returns null when xcode-select is not installed', () {
79+
when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
80+
.thenThrow(const ProcessException('/usr/bin/xcode-select', const <String>['--print-path']));
81+
expect(xcode.xcodeSelectPath, isNull);
82+
}, overrides: <Type, Generator>{
83+
ProcessManager: () => mockProcessManager,
84+
});
85+
86+
testUsingContext('xcodeSelectPath returns path when xcode-select is installed', () {
87+
final String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
88+
when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
89+
.thenReturn(new ProcessResult(1, 0, xcodePath, ''));
90+
expect(xcode.xcodeSelectPath, xcodePath);
91+
}, overrides: <Type, Generator>{
92+
ProcessManager: () => mockProcessManager,
93+
});
94+
95+
testUsingContext('xcodeVersionText returns null when xcodebuild is not installed', () {
96+
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
97+
.thenThrow(const ProcessException('/usr/bin/xcodebuild', const <String>['-version']));
98+
expect(xcode.xcodeVersionText, isNull);
99+
}, overrides: <Type, Generator>{
100+
ProcessManager: () => mockProcessManager,
101+
});
102+
103+
testUsingContext('xcodeVersionText returns null when xcodebuild is not installed', () {
104+
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
105+
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
106+
expect(xcode.xcodeVersionText, 'Xcode 8.3.3, Build version 8E3004b');
107+
}, overrides: <Type, Generator>{
108+
ProcessManager: () => mockProcessManager,
109+
});
110+
111+
testUsingContext('eulaSigned is false when clang is not installed', () {
112+
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
113+
.thenThrow(const ProcessException('/usr/bin/xcrun', const <String>['clang']));
114+
expect(xcode.eulaSigned, isFalse);
115+
}, overrides: <Type, Generator>{
116+
ProcessManager: () => mockProcessManager,
117+
});
118+
119+
testUsingContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
120+
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
121+
.thenReturn(new ProcessResult(1, 1, '', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.'));
122+
expect(xcode.eulaSigned, isFalse);
123+
}, overrides: <Type, Generator>{
124+
ProcessManager: () => mockProcessManager,
125+
});
126+
127+
testUsingContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
128+
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
129+
.thenReturn(new ProcessResult(1, 1, '', 'clang: error: no input files'));
130+
expect(xcode.eulaSigned, isTrue);
131+
}, overrides: <Type, Generator>{
132+
ProcessManager: () => mockProcessManager,
133+
});
134+
135+
testUsingContext('getAvailableDevices throws ToolExit when instruments is not installed', () async {
136+
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
137+
.thenThrow(const ProcessException('/usr/bin/instruments', const <String>['-s', 'devices']));
138+
expect(() async => await xcode.getAvailableDevices(), throwsToolExit());
139+
}, overrides: <Type, Generator>{
140+
ProcessManager: () => mockProcessManager,
141+
});
142+
143+
testUsingContext('getAvailableDevices throws ToolExit when instruments returns non-zero', () async {
144+
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
145+
.thenReturn(new ProcessResult(1, 1, '', 'Sad today'));
146+
expect(() async => await xcode.getAvailableDevices(), throwsToolExit());
147+
}, overrides: <Type, Generator>{
148+
ProcessManager: () => mockProcessManager,
149+
});
150+
151+
testUsingContext('getAvailableDevices returns instruments output when installed', () async {
152+
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
153+
.thenReturn(new ProcessResult(1, 0, 'Known Devices:\niPhone 6s (10.3.3) [foo]', ''));
154+
expect(await xcode.getAvailableDevices(), 'Known Devices:\niPhone 6s (10.3.3) [foo]');
155+
}, overrides: <Type, Generator>{
156+
ProcessManager: () => mockProcessManager,
157+
});
158+
});
159+
68160
group('Diagnose Xcode build failure', () {
69161
BuildableIOSApp app;
70162

0 commit comments

Comments
 (0)