blob: 7110f4e5964bad7eb09107ef3c7ee559efec4f6b [file] [log] [blame]
xsterc2b0a302017-06-07 15:56:13 -07001// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Chris Bracken66502132017-06-16 14:33:49 -07005import 'dart:async';
6
7import 'package:file/file.dart';
Chris Bracken66502132017-06-16 14:33:49 -07008import 'package:flutter_tools/src/base/file_system.dart';
Chris Bracken2ebb9e52017-06-23 18:50:27 -07009import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
xsterc2b0a302017-06-07 15:56:13 -070010import 'package:flutter_tools/src/ios/mac.dart';
Chris Bracken66502132017-06-16 14:33:49 -070011import 'package:mockito/mockito.dart';
12import 'package:platform/platform.dart';
13import 'package:process/process.dart';
xsterc2b0a302017-06-07 15:56:13 -070014import 'package:test/test.dart';
15
Chris Bracken2ebb9e52017-06-23 18:50:27 -070016import '../src/common.dart';
xsterc2b0a302017-06-07 15:56:13 -070017import '../src/context.dart';
18
Chris Bracken66502132017-06-16 14:33:49 -070019class MockProcessManager extends Mock implements ProcessManager {}
20class MockFile extends Mock implements File {}
21
xsterc2b0a302017-06-07 15:56:13 -070022void main() {
Chris Bracken66502132017-06-16 14:33:49 -070023 group('IMobileDevice', () {
Chris Brackeneba6ceb2017-09-01 10:10:49 -070024 final FakePlatform osx = new FakePlatform.fromPlatform(const LocalPlatform())
25 ..operatingSystem = 'macos';
26 MockProcessManager mockProcessManager;
27
28 setUp(() {
29 mockProcessManager = new MockProcessManager();
30 });
31
32 testUsingContext('getAvailableDeviceIDs throws ToolExit when libimobiledevice is not installed', () async {
33 when(mockProcessManager.run(<String>['idevice_id', '-l']))
34 .thenThrow(const ProcessException('idevice_id', const <String>['-l']));
35 expect(() async => await iMobileDevice.getAvailableDeviceIDs(), throwsToolExit());
36 }, overrides: <Type, Generator>{
37 ProcessManager: () => mockProcessManager,
38 });
39
40 testUsingContext('getAvailableDeviceIDs throws ToolExit when idevice_id returns non-zero', () async {
41 when(mockProcessManager.run(<String>['idevice_id', '-l']))
42 .thenReturn(new ProcessResult(1, 1, '', 'Sad today'));
43 expect(() async => await iMobileDevice.getAvailableDeviceIDs(), throwsToolExit());
44 }, overrides: <Type, Generator>{
45 ProcessManager: () => mockProcessManager,
46 });
47
48 testUsingContext('getAvailableDeviceIDs returns idevice_id output when installed', () async {
49 when(mockProcessManager.run(<String>['idevice_id', '-l']))
50 .thenReturn(new ProcessResult(1, 0, 'foo', ''));
51 expect(await iMobileDevice.getAvailableDeviceIDs(), 'foo');
52 }, overrides: <Type, Generator>{
53 ProcessManager: () => mockProcessManager,
54 });
Chris Brackencf96c7d2017-07-13 16:19:11 -070055
Chris Bracken66502132017-06-16 14:33:49 -070056 group('screenshot', () {
57 final String outputPath = fs.path.join('some', 'test', 'path', 'image.png');
58 MockProcessManager mockProcessManager;
59 MockFile mockOutputFile;
60
61 setUp(() {
62 mockProcessManager = new MockProcessManager();
63 mockOutputFile = new MockFile();
64 });
65
66 testUsingContext('error if idevicescreenshot is not installed', () async {
67 when(mockOutputFile.path).thenReturn(outputPath);
68
69 // Let `idevicescreenshot` fail with exit code 1.
70 when(mockProcessManager.run(<String>['idevicescreenshot', outputPath],
71 environment: null,
72 workingDirectory: null
73 )).thenReturn(new ProcessResult(4, 1, '', ''));
74
75 expect(() async => await iMobileDevice.takeScreenshot(mockOutputFile), throwsA(anything));
76 }, overrides: <Type, Generator>{
77 ProcessManager: () => mockProcessManager,
78 Platform: () => osx,
79 });
80
81 testUsingContext('idevicescreenshot captures and returns screenshot', () async {
82 when(mockOutputFile.path).thenReturn(outputPath);
Alexandre Ardhuinc02b6a82018-02-02 23:27:29 +010083 when(mockProcessManager.run(any, environment: null, workingDirectory: null)).thenAnswer(
Alan Russian30720bd2017-12-19 13:13:57 -080084 (Invocation invocation) => new Future<ProcessResult>.value(new ProcessResult(4, 0, '', '')));
Chris Bracken66502132017-06-16 14:33:49 -070085
86 await iMobileDevice.takeScreenshot(mockOutputFile);
87 verify(mockProcessManager.run(<String>['idevicescreenshot', outputPath],
88 environment: null,
89 workingDirectory: null
90 ));
91 }, overrides: <Type, Generator>{
92 ProcessManager: () => mockProcessManager,
93 });
94 });
95 });
96
Chris Bracken2ebb9e52017-06-23 18:50:27 -070097 group('Xcode', () {
98 MockProcessManager mockProcessManager;
99 Xcode xcode;
100
101 setUp(() {
102 mockProcessManager = new MockProcessManager();
103 xcode = new Xcode();
104 });
105
106 testUsingContext('xcodeSelectPath returns null when xcode-select is not installed', () {
107 when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
108 .thenThrow(const ProcessException('/usr/bin/xcode-select', const <String>['--print-path']));
109 expect(xcode.xcodeSelectPath, isNull);
110 }, overrides: <Type, Generator>{
111 ProcessManager: () => mockProcessManager,
112 });
113
114 testUsingContext('xcodeSelectPath returns path when xcode-select is installed', () {
Alexandre Ardhuin841d5d72018-02-01 07:51:26 +0100115 const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
Chris Bracken2ebb9e52017-06-23 18:50:27 -0700116 when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
117 .thenReturn(new ProcessResult(1, 0, xcodePath, ''));
118 expect(xcode.xcodeSelectPath, xcodePath);
119 }, overrides: <Type, Generator>{
120 ProcessManager: () => mockProcessManager,
121 });
122
123 testUsingContext('xcodeVersionText returns null when xcodebuild is not installed', () {
124 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
125 .thenThrow(const ProcessException('/usr/bin/xcodebuild', const <String>['-version']));
126 expect(xcode.xcodeVersionText, isNull);
127 }, overrides: <Type, Generator>{
128 ProcessManager: () => mockProcessManager,
129 });
130
Chris Bracken5141c1e2017-06-26 13:11:49 -0700131 testUsingContext('xcodeVersionText returns formatted version text', () {
Chris Bracken2ebb9e52017-06-23 18:50:27 -0700132 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
133 .thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
134 expect(xcode.xcodeVersionText, 'Xcode 8.3.3, Build version 8E3004b');
135 }, overrides: <Type, Generator>{
136 ProcessManager: () => mockProcessManager,
137 });
138
Chris Bracken5141c1e2017-06-26 13:11:49 -0700139 testUsingContext('xcodeVersionText handles Xcode version string with unexpected format', () {
140 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
141 .thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
142 expect(xcode.xcodeVersionText, 'Xcode Ultra5000, Build version 8E3004b');
143 }, overrides: <Type, Generator>{
144 ProcessManager: () => mockProcessManager,
145 });
146
Chris Brackene04907a2017-06-26 13:37:59 -0700147 testUsingContext('xcodeMajorVersion returns major version', () {
148 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
149 .thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
150 expect(xcode.xcodeMajorVersion, 8);
151 }, overrides: <Type, Generator>{
152 ProcessManager: () => mockProcessManager,
153 });
154
155 testUsingContext('xcodeMajorVersion is null when version has unexpected format', () {
156 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
157 .thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
158 expect(xcode.xcodeMajorVersion, isNull);
159 }, overrides: <Type, Generator>{
160 ProcessManager: () => mockProcessManager,
161 });
162
163 testUsingContext('xcodeMinorVersion returns minor version', () {
164 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
165 .thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
166 expect(xcode.xcodeMinorVersion, 3);
167 }, overrides: <Type, Generator>{
168 ProcessManager: () => mockProcessManager,
169 });
170
171 testUsingContext('xcodeMinorVersion returns 0 when minor version is unspecified', () {
172 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
173 .thenReturn(new ProcessResult(1, 0, 'Xcode 8\nBuild version 8E3004b', ''));
174 expect(xcode.xcodeMinorVersion, 0);
175 }, overrides: <Type, Generator>{
176 ProcessManager: () => mockProcessManager,
177 });
178
Chris Brackenbb732b42017-06-26 14:34:44 -0700179 testUsingContext('xcodeMinorVersion is null when version has unexpected format', () {
Chris Brackene04907a2017-06-26 13:37:59 -0700180 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
181 .thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
182 expect(xcode.xcodeMinorVersion, isNull);
183 }, overrides: <Type, Generator>{
184 ProcessManager: () => mockProcessManager,
185 });
186
Chris Brackenbb732b42017-06-26 14:34:44 -0700187 testUsingContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
188 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
Chris Bracken7fb78522017-12-15 18:13:10 -0800189 .thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
Chris Brackenbb732b42017-06-26 14:34:44 -0700190 expect(xcode.xcodeVersionSatisfactory, isFalse);
191 }, overrides: <Type, Generator>{
192 ProcessManager: () => mockProcessManager,
193 });
194
195 testUsingContext('xcodeVersionSatisfactory is false when version in unknown format', () {
196 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
197 .thenReturn(new ProcessResult(1, 0, 'Xcode SuperHD\nBuild version 7A1001', ''));
198 expect(xcode.xcodeVersionSatisfactory, isFalse);
199 }, overrides: <Type, Generator>{
200 ProcessManager: () => mockProcessManager,
201 });
202
203 testUsingContext('xcodeVersionSatisfactory is true when version meets minimum', () {
204 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
Chris Bracken7fb78522017-12-15 18:13:10 -0800205 .thenReturn(new ProcessResult(1, 0, 'Xcode 9.0\nBuild version 9A235', ''));
Chris Brackenbb732b42017-06-26 14:34:44 -0700206 expect(xcode.xcodeVersionSatisfactory, isTrue);
207 }, overrides: <Type, Generator>{
208 ProcessManager: () => mockProcessManager,
209 });
210
211 testUsingContext('xcodeVersionSatisfactory is true when version exceeds minimum', () {
212 when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
Chris Bracken7fb78522017-12-15 18:13:10 -0800213 .thenReturn(new ProcessResult(1, 0, 'Xcode 10.0\nBuild version 10A123', ''));
Chris Brackenbb732b42017-06-26 14:34:44 -0700214 expect(xcode.xcodeVersionSatisfactory, isTrue);
215 }, overrides: <Type, Generator>{
216 ProcessManager: () => mockProcessManager,
217 });
218
Chris Bracken2ebb9e52017-06-23 18:50:27 -0700219 testUsingContext('eulaSigned is false when clang is not installed', () {
220 when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
221 .thenThrow(const ProcessException('/usr/bin/xcrun', const <String>['clang']));
222 expect(xcode.eulaSigned, isFalse);
223 }, overrides: <Type, Generator>{
224 ProcessManager: () => mockProcessManager,
225 });
226
227 testUsingContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
228 when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
229 .thenReturn(new ProcessResult(1, 1, '', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.'));
230 expect(xcode.eulaSigned, isFalse);
231 }, overrides: <Type, Generator>{
232 ProcessManager: () => mockProcessManager,
233 });
234
235 testUsingContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
236 when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
237 .thenReturn(new ProcessResult(1, 1, '', 'clang: error: no input files'));
238 expect(xcode.eulaSigned, isTrue);
239 }, overrides: <Type, Generator>{
240 ProcessManager: () => mockProcessManager,
241 });
Chris Bracken2ebb9e52017-06-23 18:50:27 -0700242 });
243
xsterc2b0a302017-06-07 15:56:13 -0700244 group('Diagnose Xcode build failure', () {
xsterd401bd72018-02-13 01:56:13 -0800245 Map<String, String> buildSettings;
xsterc2b0a302017-06-07 15:56:13 -0700246
247 setUp(() {
xsterd401bd72018-02-13 01:56:13 -0800248 buildSettings = <String, String>{
249 'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
250 };
xsterc2b0a302017-06-07 15:56:13 -0700251 });
252
253 testUsingContext('No provisioning profile shows message', () async {
254 final XcodeBuildResult buildResult = new XcodeBuildResult(
255 success: false,
256 stdout: '''
257Launching lib/main.dart on iPhone in debug mode...
258Signing iOS app for device deployment using developer identity: "iPhone Developer: test@flutter.io (1122334455)"
259Running Xcode build... 1.3s
260Failed to build iOS app
261Error output from Xcode build:
262
263 ** BUILD FAILED **
264
265
266 The following build commands failed:
267 Check dependencies
268 (1 failure)
269Xcode's output:
270
271 Build settings from command line:
272 ARCHS = arm64
273 BUILD_DIR = /Users/blah/blah
274 DEVELOPMENT_TEAM = AABBCCDDEE
275 ONLY_ACTIVE_ARCH = YES
276 SDKROOT = iphoneos10.3
277
278 === CLEAN TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
279
280 Check dependencies
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100281 [BCEROR]No profiles for 'com.example.test' were found: Xcode couldn't find a provisioning profile matching 'com.example.test'.
xsterc2b0a302017-06-07 15:56:13 -0700282 [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
283 [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
284 [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
285
286 Create product structure
287 /bin/mkdir -p /Users/blah/Runner.app
288
289 Clean.Remove clean /Users/blah/Runner.app.dSYM
290 builtin-rm -rf /Users/blah/Runner.app.dSYM
291
292 Clean.Remove clean /Users/blah/Runner.app
293 builtin-rm -rf /Users/blah/Runner.app
294
295 Clean.Remove clean /Users/blah/Runner-dfvicjniknvzghgwsthwtgcjhtsk/Build/Intermediates/Runner.build/Release-iphoneos/Runner.build
296 builtin-rm -rf /Users/blah/Runner-dfvicjniknvzghgwsthwtgcjhtsk/Build/Intermediates/Runner.build/Release-iphoneos/Runner.build
297
298 ** CLEAN SUCCEEDED **
299
300 === BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
301
302 Check dependencies
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100303 No profiles for 'com.example.test' were found: Xcode couldn't find a provisioning profile matching 'com.example.test'.
xsterc2b0a302017-06-07 15:56:13 -0700304 Code signing is required for product type 'Application' in SDK 'iOS 10.3'
305 Code signing is required for product type 'Application' in SDK 'iOS 10.3'
306 Code signing is required for product type 'Application' in SDK 'iOS 10.3'
307
308Could not build the precompiled application for the device.
309
310Error launching application on iPhone.''',
311 xcodeBuildExecution: new XcodeBuildExecution(
xsterd401bd72018-02-13 01:56:13 -0800312 buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
313 appDirectory: '/blah/blah',
314 buildForPhysicalDevice: true,
315 buildSettings: buildSettings,
xsterc2b0a302017-06-07 15:56:13 -0700316 ),
317 );
318
xsterd401bd72018-02-13 01:56:13 -0800319 await diagnoseXcodeBuildFailure(buildResult);
xsterc2b0a302017-06-07 15:56:13 -0700320 expect(
321 testLogger.errorText,
322 contains('No Provisioning Profile was found for your project\'s Bundle Identifier or your device.'),
323 );
324 });
325
xsterc2b0a302017-06-07 15:56:13 -0700326 testUsingContext('No development team shows message', () async {
327 final XcodeBuildResult buildResult = new XcodeBuildResult(
328 success: false,
329 stdout: '''
330Running "flutter packages get" in flutter_gallery... 0.6s
331Launching lib/main.dart on x in release mode...
332Running pod install... 1.2s
333Running Xcode build... 1.4s
334Failed to build iOS app
335Error output from Xcode build:
336
337 ** BUILD FAILED **
338
339
340 The following build commands failed:
341 Check dependencies
342 (1 failure)
343Xcode's output:
344
345 blah
346
347 === CLEAN TARGET url_launcher OF PROJECT Pods WITH CONFIGURATION Release ===
348
349 Check dependencies
350
351 blah
352
353 === CLEAN TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release ===
354
355 Check dependencies
356
357 blah
358
359 === CLEAN TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
360
361 Check dependencies
362 [BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor.
363 [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
364 [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
365 [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
366
367 blah
368
369 ** CLEAN SUCCEEDED **
370
371 === BUILD TARGET url_launcher OF PROJECT Pods WITH CONFIGURATION Release ===
372
373 Check dependencies
374
375 blah
376
377 === BUILD TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release ===
378
379 Check dependencies
380
381 blah
382
383 === BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
384
385 Check dependencies
386 Signing for "Runner" requires a development team. Select a development team in the project editor.
387 Code signing is required for product type 'Application' in SDK 'iOS 10.3'
388 Code signing is required for product type 'Application' in SDK 'iOS 10.3'
389 Code signing is required for product type 'Application' in SDK 'iOS 10.3'
390
391Could not build the precompiled application for the device.''',
392 xcodeBuildExecution: new XcodeBuildExecution(
xsterd401bd72018-02-13 01:56:13 -0800393 buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
394 appDirectory: '/blah/blah',
395 buildForPhysicalDevice: true,
396 buildSettings: buildSettings,
xsterc2b0a302017-06-07 15:56:13 -0700397 ),
398 );
399
xsterd401bd72018-02-13 01:56:13 -0800400 await diagnoseXcodeBuildFailure(buildResult);
xsterc2b0a302017-06-07 15:56:13 -0700401 expect(
402 testLogger.errorText,
403 contains('Building a deployable iOS app requires a selected Development Team with a Provisioning Profile'),
404 );
405 });
406 });
407}