mac_test.dart 14.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:file/file.dart';
6
import 'package:file/memory.dart';
7
import 'package:flutter_tools/src/artifacts.dart';
8
import 'package:flutter_tools/src/base/file_system.dart';
9
import 'package:flutter_tools/src/base/logger.dart';
10
import 'package:flutter_tools/src/base/process.dart';
11
import 'package:flutter_tools/src/build_info.dart';
12
import 'package:flutter_tools/src/cache.dart';
13
import 'package:flutter_tools/src/ios/code_signing.dart';
14
import 'package:flutter_tools/src/ios/iproxy.dart';
15
import 'package:flutter_tools/src/ios/mac.dart';
16
import 'package:flutter_tools/src/ios/xcresult.dart';
17
import 'package:flutter_tools/src/project.dart';
18
import 'package:flutter_tools/src/reporting/reporting.dart';
19
import 'package:test/fake.dart';
20

21
import '../../src/common.dart';
22
import '../../src/fake_process_manager.dart';
23
import '../../src/fakes.dart';
24 25

void main() {
26
  late BufferLogger logger;
27 28 29 30 31

  setUp(() {
    logger = BufferLogger.test();
  });

32
  group('IMobileDevice', () {
33 34
    late Artifacts artifacts;
    late Cache cache;
35 36

    setUp(() {
37 38 39 40
      artifacts = Artifacts.test();
      cache = Cache.test(
        artifacts: <ArtifactSet>[
          FakeDyldEnvironmentArtifact(),
41 42 43
        ],
        processManager: FakeProcessManager.any(),
      );
44 45
    });

46
    group('screenshot', () {
47 48
      late FakeProcessManager fakeProcessManager;
      late File outputFile;
49 50

      setUp(() {
51
        fakeProcessManager = FakeProcessManager.empty();
52
        outputFile = MemoryFileSystem.test().file('image.png');
53 54
      });

55
      testWithoutContext('error if idevicescreenshot is not installed', () async {
56
        // Let `idevicescreenshot` fail with exit code 1.
57 58
        fakeProcessManager.addCommand(FakeCommand(
          command: <String>[
59
            'HostArtifact.idevicescreenshot',
60 61 62 63 64 65 66 67 68
            outputFile.path,
            '--udid',
            '1234',
          ],
          environment: const <String, String>{
            'DYLD_LIBRARY_PATH': '/path/to/libraries',
          },
          exitCode: 1,
        ));
69

70
        final IMobileDevice iMobileDevice = IMobileDevice(
71 72
          artifacts: artifacts,
          cache: cache,
73
          processManager: fakeProcessManager,
74 75 76
          logger: logger,
        );

77
        expect(() async => iMobileDevice.takeScreenshot(
78
          outputFile,
79
          '1234',
80
          IOSDeviceConnectionInterface.usb,
81
        ), throwsA(anything));
82
        expect(fakeProcessManager, hasNoRemainingExpectations);
83 84
      });

85
      testWithoutContext('idevicescreenshot captures and returns USB screenshot', () async {
86 87
        fakeProcessManager.addCommand(FakeCommand(
          command: <String>[
88
            'HostArtifact.idevicescreenshot', outputFile.path, '--udid', '1234',
89 90 91
          ],
          environment: const <String, String>{'DYLD_LIBRARY_PATH': '/path/to/libraries'},
        ));
92

93
        final IMobileDevice iMobileDevice = IMobileDevice(
94 95
          artifacts: artifacts,
          cache: cache,
96
          processManager: fakeProcessManager,
97 98 99
          logger: logger,
        );

100
        await iMobileDevice.takeScreenshot(
101
          outputFile,
102
          '1234',
103
          IOSDeviceConnectionInterface.usb,
104
        );
105
        expect(fakeProcessManager, hasNoRemainingExpectations);
106
      });
107 108

      testWithoutContext('idevicescreenshot captures and returns network screenshot', () async {
109 110
        fakeProcessManager.addCommand(FakeCommand(
          command: <String>[
111
            'HostArtifact.idevicescreenshot', outputFile.path, '--udid', '1234', '--network',
112 113 114
          ],
          environment: const <String, String>{'DYLD_LIBRARY_PATH': '/path/to/libraries'},
        ));
115 116

        final IMobileDevice iMobileDevice = IMobileDevice(
117 118
          artifacts: artifacts,
          cache: cache,
119
          processManager: fakeProcessManager,
120 121 122 123
          logger: logger,
        );

        await iMobileDevice.takeScreenshot(
124
          outputFile,
125
          '1234',
126
          IOSDeviceConnectionInterface.network,
127
        );
128
        expect(fakeProcessManager, hasNoRemainingExpectations);
129
      });
130 131 132
    });
  });

133
  group('Diagnose Xcode build failure', () {
134 135
    late Map<String, String> buildSettings;
    late TestUsage testUsage;
136 137

    setUp(() {
xster's avatar
xster committed
138 139 140
      buildSettings = <String, String>{
        'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
      };
141
      testUsage = TestUsage();
142 143
    });

144
    testWithoutContext('Sends analytics when bitcode fails', () async {
145 146 147 148 149 150 151
      const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
      final XcodeBuildResult buildResult = XcodeBuildResult(
        success: false,
        stdout: 'BITCODE_ENABLED = YES',
        xcodeBuildExecution: XcodeBuildExecution(
          buildCommands: buildCommands,
          appDirectory: '/blah/blah',
152
          environmentType: EnvironmentType.physical,
153 154 155 156
          buildSettings: buildSettings,
        ),
      );

157 158 159 160
      await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
      expect(testUsage.events, contains(
        TestUsageEvent(
          'build',
161
          'ios',
162
          label: 'xcode-bitcode-failure',
163 164 165 166
          parameters: CustomDimensions(
            buildEventCommand: buildCommands.toString(),
            buildEventSettings: buildSettings.toString(),
          ),
167 168
        ),
      ));
169 170
    });

171 172 173 174 175
    testWithoutContext('fallback to stdout: No provisioning profile shows message', () async {
      final Map<String, String> buildSettingsWithDevTeam = <String, String>{
        'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
        'DEVELOPMENT_TEAM': 'a team',
      };
176
      final XcodeBuildResult buildResult = XcodeBuildResult(
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
        success: false,
        stdout: '''
Launching lib/main.dart on iPhone in debug mode...
Signing iOS app for device deployment using developer identity: "iPhone Developer: test@flutter.io (1122334455)"
Running Xcode build...                                1.3s
Failed to build iOS app
Error output from Xcode build:

    ** BUILD FAILED **


    The following build commands failed:
    	Check dependencies
    (1 failure)
Xcode's output:

    Build settings from command line:
        ARCHS = arm64
        BUILD_DIR = /Users/blah/blah
        DEVELOPMENT_TEAM = AABBCCDDEE
        ONLY_ACTIVE_ARCH = YES
        SDKROOT = iphoneos10.3

    === CLEAN TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===

    Check dependencies
203
    [BCEROR]"Runner" requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor.
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
    [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
    [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'

    Create product structure
    /bin/mkdir -p /Users/blah/Runner.app

    Clean.Remove clean /Users/blah/Runner.app.dSYM
        builtin-rm -rf /Users/blah/Runner.app.dSYM

    Clean.Remove clean /Users/blah/Runner.app
        builtin-rm -rf /Users/blah/Runner.app

    Clean.Remove clean /Users/blah/Runner-dfvicjniknvzghgwsthwtgcjhtsk/Build/Intermediates/Runner.build/Release-iphoneos/Runner.build
        builtin-rm -rf /Users/blah/Runner-dfvicjniknvzghgwsthwtgcjhtsk/Build/Intermediates/Runner.build/Release-iphoneos/Runner.build

    ** CLEAN SUCCEEDED **

    === BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===

    Check dependencies
225
    No profiles for 'com.example.test' were found:  Xcode couldn't find a provisioning profile matching 'com.example.test'.
226 227 228 229 230 231 232
    Code signing is required for product type 'Application' in SDK 'iOS 10.3'
    Code signing is required for product type 'Application' in SDK 'iOS 10.3'
    Code signing is required for product type 'Application' in SDK 'iOS 10.3'

Could not build the precompiled application for the device.

Error launching application on iPhone.''',
233
        xcodeBuildExecution: XcodeBuildExecution(
xster's avatar
xster committed
234 235
          buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
          appDirectory: '/blah/blah',
236
          environmentType: EnvironmentType.physical,
237
          buildSettings: buildSettingsWithDevTeam,
238 239 240
        ),
      );

241
      await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
242
      expect(
243
        logger.errorText,
244
        contains(noProvisioningProfileInstruction),
245
      );
246
    });
247

248
    testWithoutContext('No development team shows message', () async {
249
      final XcodeBuildResult buildResult = XcodeBuildResult(
250 251
        success: false,
        stdout: '''
252
Running "flutter pub get" in flutter_gallery...  0.6s
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
Launching lib/main.dart on x in release mode...
Running pod install...                                1.2s
Running Xcode build...                                1.4s
Failed to build iOS app
Error output from Xcode build:

    ** BUILD FAILED **


    The following build commands failed:
    	Check dependencies
    (1 failure)
Xcode's output:

    blah

    Check dependencies
    [BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor.

272 273 274 275 276 277 278 279
Could not build the precompiled application for the device.''',
        xcodeBuildExecution: XcodeBuildExecution(
          buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
          appDirectory: '/blah/blah',
          environmentType: EnvironmentType.physical,
          buildSettings: buildSettings,
        ),
      );
280

281 282 283 284 285 286
      await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
      expect(
        logger.errorText,
        contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'),
      );
    });
287

288 289 290 291 292 293 294 295 296 297 298 299
    testWithoutContext('does not show no development team message when other Xcode issues detected', () async {
      final XcodeBuildResult buildResult = XcodeBuildResult(
        success: false,
        stdout: '''
Running "flutter pub get" in flutter_gallery...  0.6s
Launching lib/main.dart on x in release mode...
Running pod install...                                1.2s
Running Xcode build...                                1.4s
Failed to build iOS app
Error output from Xcode build:

    ** BUILD FAILED **
300 301


302 303 304 305 306
    The following build commands failed:
    	Check dependencies
    (1 failure)
Xcode's output:

307 308 309
    blah

    Check dependencies
310
    [BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor.
311 312

Could not build the precompiled application for the device.''',
313
        xcodeBuildExecution: XcodeBuildExecution(
xster's avatar
xster committed
314 315
          buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
          appDirectory: '/blah/blah',
316
          environmentType: EnvironmentType.physical,
xster's avatar
xster committed
317
          buildSettings: buildSettings,
318
        ),
319 320 321
        xcResult: XCResult.test(issues: <XCResultIssue>[
          XCResultIssue.test(message: 'Target aot_assembly_release failed', subType: 'Error'),
        ])
322 323
      );

324
      await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
325 326
      expect(logger.errorText, contains('Error (Xcode): Target aot_assembly_release failed'));
      expect(logger.errorText, isNot(contains('Building a deployable iOS app requires a selected Development Team')));
327
    });
328
  });
329 330

  group('Upgrades project.pbxproj for old asset usage', () {
331 332 333 334 335 336 337 338 339 340 341 342 343
    const String flutterAssetPbxProjLines =
      '/* flutter_assets */\n'
      '/* App.framework\n'
      'another line';

    const String appFlxPbxProjLines =
      '/* app.flx\n'
      '/* App.framework\n'
      'another line';

    const String cleanPbxProjLines =
      '/* App.framework\n'
      'another line';
344

345
    testWithoutContext('upgradePbxProjWithFlutterAssets', () async {
346 347
      final File pbxprojFile = MemoryFileSystem.test().file('project.pbxproj')
        ..writeAsStringSync(flutterAssetPbxProjLines);
348
      final FakeIosProject project = FakeIosProject(pbxprojFile);
349

350
      bool result = upgradePbxProjWithFlutterAssets(project, logger);
351 352
      expect(result, true);
      expect(
353
        logger.statusText,
354 355
        contains('Removing obsolete reference to flutter_assets'),
      );
356
      logger.clear();
357

358
      pbxprojFile.writeAsStringSync(appFlxPbxProjLines);
359
      result = upgradePbxProjWithFlutterAssets(project, logger);
360 361
      expect(result, true);
      expect(
362
        logger.statusText,
363 364
        contains('Removing obsolete reference to app.flx'),
      );
365
      logger.clear();
366

367
      pbxprojFile.writeAsStringSync(cleanPbxProjLines);
368
      result = upgradePbxProjWithFlutterAssets(project, logger);
369 370
      expect(result, true);
      expect(
371
        logger.statusText,
372 373 374 375
        isEmpty,
      );
    });
  });
376 377

  group('remove Finder extended attributes', () {
378
    late Directory projectDirectory;
379 380
    setUp(() {
      final MemoryFileSystem fs = MemoryFileSystem.test();
381
      projectDirectory = fs.directory('flutter_project');
382 383 384 385 386 387 388 389 390
    });

    testWithoutContext('removes xattr', () async {
      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
        FakeCommand(command: <String>[
          'xattr',
          '-r',
          '-d',
          'com.apple.FinderInfo',
391
          projectDirectory.path,
392
        ]),
393 394
      ]);

395
      await removeFinderExtendedAttributes(projectDirectory, ProcessUtils(processManager: processManager, logger: logger), logger);
396
      expect(processManager, hasNoRemainingExpectations);
397 398 399 400
    });

    testWithoutContext('ignores errors', () async {
      final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
401 402 403 404 405 406 407 408 409 410
        FakeCommand(
          command: <String>[
            'xattr',
            '-r',
            '-d',
            'com.apple.FinderInfo',
            projectDirectory.path,
          ],
          exitCode: 1,
        ),
411 412
      ]);

413
      await removeFinderExtendedAttributes(projectDirectory, ProcessUtils(processManager: processManager, logger: logger), logger);
414
      expect(logger.traceText, contains('Failed to remove xattr com.apple.FinderInfo'));
415
      expect(processManager, hasNoRemainingExpectations);
416 417
    });
  });
418
}
419 420 421 422 423 424 425

class FakeIosProject extends Fake implements IosProject {
  FakeIosProject(this.xcodeProjectInfoFile);
  @override
  final File xcodeProjectInfoFile;

  @override
426
  Future<String> hostAppBundleName(BuildInfo? buildInfo) async => 'UnitTestRunner.app';
427 428

  @override
429
  Directory get xcodeProject => xcodeProjectInfoFile.fileSystem.directory('Runner.xcodeproj');
430
}