build_ios_test.dart 32.6 KB
Newer Older
1 2 3 4 5 6
// 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.

// @dart = 2.8

7
import 'package:args/command_runner.dart';
8 9 10
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
11
import 'package:flutter_tools/src/build_info.dart';
12 13
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
14
import 'package:flutter_tools/src/commands/build_ios.dart';
15
import 'package:flutter_tools/src/ios/code_signing.dart';
16 17 18
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';

19
import '../../general.shard/ios/xcresult_test_data.dart';
20 21
import '../../src/common.dart';
import '../../src/context.dart';
22
import '../../src/test_flutter_command_runner.dart';
23 24

class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInterpreter {
25 26 27

  FakeXcodeProjectInterpreterWithBuildSettings({this.productBundleIdentifier, this.developmentTeam = 'abc'});

28 29 30
  @override
  Future<Map<String, String>> getBuildSettings(
      String projectPath, {
31
        XcodeProjectBuildContext buildContext,
32 33 34
        Duration timeout = const Duration(minutes: 1),
      }) async {
    return <String, String>{
35
      'PRODUCT_BUNDLE_IDENTIFIER': productBundleIdentifier ?? 'io.flutter.someProject',
36 37
      'TARGET_BUILD_DIR': 'build/ios/Release-iphoneos',
      'WRAPPER_NAME': 'Runner.app',
38
      if (developmentTeam != null) 'DEVELOPMENT_TEAM': developmentTeam,
39 40
    };
  }
41 42 43 44 45

  /// The value of 'PRODUCT_BUNDLE_IDENTIFIER'.
  final String productBundleIdentifier;

  final String developmentTeam;
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
}

final Platform macosPlatform = FakePlatform(
  operatingSystem: 'macos',
  environment: <String, String>{
    'FLUTTER_ROOT': '/',
    'HOME': '/',
  }
);
final Platform notMacosPlatform = FakePlatform(
  environment: <String, String>{
    'FLUTTER_ROOT': '/',
  }
);

void main() {
  FileSystem fileSystem;
  TestUsage usage;

  setUpAll(() {
    Cache.disableLocking();
  });

  setUp(() {
    fileSystem = MemoryFileSystem.test();
    usage = TestUsage();
  });

  // Sets up the minimal mock project files necessary to look like a Flutter project.
75
  void createCoreMockProjectFiles() {
76 77 78 79 80 81
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.packages').createSync();
    fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
  }

  // Sets up the minimal mock project files necessary for iOS builds to succeed.
82
  void createMinimalMockProjectFiles() {
83 84 85
    fileSystem.directory(fileSystem.path.join('ios', 'Runner.xcodeproj')).createSync(recursive: true);
    fileSystem.directory(fileSystem.path.join('ios', 'Runner.xcworkspace')).createSync(recursive: true);
    fileSystem.file(fileSystem.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj')).createSync();
86
    createCoreMockProjectFiles();
87 88 89
  }

  const FakeCommand xattrCommand = FakeCommand(command: <String>[
90
    'xattr', '-r', '-d', 'com.apple.FinderInfo', '/',
91 92
  ]);

93
  FakeCommand setUpRsyncCommand({void Function() onRun}) {
94 95 96
    return FakeCommand(
      command: const <String>[
        'rsync',
97
        '-8',
98 99 100 101 102
        '-av',
        '--delete',
        'build/ios/Release-iphoneos/Runner.app',
        'build/ios/iphoneos',
      ],
103 104 105 106
      onRun: onRun,
    );
  }

107
  FakeCommand setUpXCResultCommand({String stdout = '', void Function() onRun}) {
108 109 110 111 112 113 114 115 116 117 118 119 120
    return FakeCommand(
      command: const <String>[
        'xcrun',
        'xcresulttool',
        'get',
        '--path',
        _xcBundleFilePath,
        '--format',
        'json',
      ],
      stdout: stdout,
      onRun: onRun,
    );
121 122 123 124
  }

  // Creates a FakeCommand for the xcodebuild call to build the app
  // in the given configuration.
125
  FakeCommand setUpFakeXcodeBuildHandler({
126 127 128 129
    bool verbose = false,
    bool simulator = false,
    String deviceId,
    int exitCode = 0,
130
    String stdout,
131 132
    void Function() onRun,
  }) {
133 134 135 136
    return FakeCommand(
      command: <String>[
        'xcrun',
        'xcodebuild',
137 138 139 140 141
        '-configuration',
        if (simulator)
          'Debug'
        else
          'Release',
142 143 144 145 146 147 148
        if (verbose)
          'VERBOSE_SCRIPT_LOGGING=YES'
        else
          '-quiet',
        '-workspace', 'Runner.xcworkspace',
        '-scheme', 'Runner',
        'BUILD_DIR=/build/ios',
149
        '-sdk',
150 151
        if (simulator) ...<String>[
          'iphonesimulator',
152 153 154 155 156 157 158
        ] else ...<String>[
          'iphoneos',
        ],
        if (deviceId != null) ...<String>[
          '-destination',
          'id=$deviceId',
        ] else if (simulator) ...<String>[
159 160 161 162 163 164
          '-destination',
          'generic/platform=iOS Simulator',
        ] else ...<String>[
          '-destination',
          'generic/platform=iOS',
        ],
165 166
        '-resultBundlePath', _xcBundleFilePath,
        '-resultBundleVersion', '3',
167 168 169 170 171 172
        'FLUTTER_SUPPRESS_ANALYTICS=true',
        'COMPILER_INDEX_STORE_ENABLE=NO',
      ],
      stdout: '''
      TARGET_BUILD_DIR=build/ios/Release-iphoneos
      WRAPPER_NAME=Runner.app
173
      $stdout
174
''',
175
      exitCode: exitCode,
176 177 178 179 180 181
      onRun: onRun,
    );
  }

  testUsingContext('ios build fails when there is no ios project', () async {
    final BuildCommand command = BuildCommand();
182
    createCoreMockProjectFiles();
183 184 185 186 187 188 189 190 191 192 193 194 195

    expect(createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--no-pub']
    ), throwsToolExit(message: 'Application not configured for iOS'));
  }, overrides: <Type, Generator>{
    Platform: () => macosPlatform,
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

  testUsingContext('ios build fails in debug with code analysis', () async {
    final BuildCommand command = BuildCommand();
196
    createCoreMockProjectFiles();
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

    expect(createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--no-pub', '--debug', '--analyze-size']
    ), throwsToolExit(message: '--analyze-size" can only be used on release builds'));
  }, overrides: <Type, Generator>{
    Platform: () => macosPlatform,
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

  testUsingContext('ios build fails on non-macOS platform', () async {
    final BuildCommand command = BuildCommand();
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.packages').createSync();
    fileSystem.file(fileSystem.path.join('lib', 'main.dart'))
      .createSync(recursive: true);

215
    final bool supported = BuildIOSCommand(verboseHelp: false).supported;
216 217
    expect(createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--no-pub']
218
    ), supported ? throwsToolExit() : throwsA(isA<UsageException>()));
219 220 221 222 223 224 225 226 227
  }, overrides: <Type, Generator>{
    Platform: () => notMacosPlatform,
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

  testUsingContext('ios build invokes xcode build', () async {
    final BuildCommand command = BuildCommand();
228
    createMinimalMockProjectFiles();
229 230 231 232

    await createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--no-pub']
    );
233
    expect(testLogger.statusText, contains('build/ios/iphoneos/Runner.app'));
234 235 236 237
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
      xattrCommand,
238
      setUpFakeXcodeBuildHandler(onRun: () {
239 240
        fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true);
      }),
241
      setUpRsyncCommand(),
242 243 244 245 246
    ]),
    Platform: () => macosPlatform,
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

247 248
  testUsingContext('ios build invokes xcode build with device ID', () async {
    final BuildCommand command = BuildCommand();
249
    createMinimalMockProjectFiles();
250 251 252 253 254 255 256 257 258

    await createTestCommandRunner(command).run(
        const <String>['build', 'ios', '--no-pub', '--device-id', '1234']
    );
    expect(testLogger.statusText, contains('build/ios/iphoneos/Runner.app'));
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
      xattrCommand,
259
      setUpFakeXcodeBuildHandler(deviceId: '1234', onRun: () {
260 261
        fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true);
      }),
262
      setUpRsyncCommand(),
263 264 265 266 267
    ]),
    Platform: () => macosPlatform,
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

268 269
  testUsingContext('ios simulator build invokes xcode build', () async {
    final BuildCommand command = BuildCommand();
270
    createMinimalMockProjectFiles();
271 272 273 274 275 276 277 278

    await createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--simulator', '--no-pub']
    );
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
      xattrCommand,
279
      setUpFakeXcodeBuildHandler(simulator: true, onRun: () {
280 281
        fileSystem.directory('build/ios/Debug-iphonesimulator/Runner.app').createSync(recursive: true);
      }),
282
      setUpRsyncCommand(),
283 284 285 286 287 288 289
    ]),
    Platform: () => macosPlatform,
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

  testUsingContext('ios build invokes xcode build with verbosity', () async {
    final BuildCommand command = BuildCommand();
290
    createMinimalMockProjectFiles();
291 292 293 294 295 296 297 298

    await createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--no-pub', '-v']
    );
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
      xattrCommand,
299
      setUpFakeXcodeBuildHandler(verbose: true, onRun: () {
300 301
        fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true);
      }),
302
      setUpRsyncCommand(),
303 304 305 306 307 308 309
    ]),
    Platform: () => macosPlatform,
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });

  testUsingContext('Performs code size analysis and sends analytics', () async {
    final BuildCommand command = BuildCommand();
310
    createMinimalMockProjectFiles();
311 312 313 314 315 316 317 318 319 320 321 322 323 324

    await createTestCommandRunner(command).run(
      const <String>['build', 'ios', '--no-pub', '--analyze-size']
    );

    expect(testLogger.statusText, contains('A summary of your iOS bundle analysis can be found at'));
    expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
    expect(usage.events, contains(
      const TestUsageEvent('code-size-analysis', 'ios'),
    ));
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
      xattrCommand,
325
      setUpFakeXcodeBuildHandler(onRun: () {
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
        fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true);
        fileSystem.file('build/flutter_size_01/snapshot.arm64.json')
          ..createSync(recursive: true)
          ..writeAsStringSync('''
[
  {
    "l": "dart:_internal",
    "c": "SubListIterable",
    "n": "[Optimized] skip",
    "s": 2400
  }
]''');
        fileSystem.file('build/flutter_size_01/trace.arm64.json')
          ..createSync(recursive: true)
          ..writeAsStringSync('{}');
      }),
342
      setUpRsyncCommand(onRun: () => fileSystem.file('build/ios/iphoneos/Runner.app/Frameworks/App.framework/App')
343 344 345 346 347 348 349 350
        ..createSync(recursive: true)
        ..writeAsBytesSync(List<int>.generate(10000, (int index) => 0))),
    ]),
    Platform: () => macosPlatform,
    FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform),
    Usage: () => usage,
    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
  });
351 352 353 354
  group('xcresults device', () {
    testUsingContext('Trace error if xcresult is empty.', () async {
      final BuildCommand command = BuildCommand();

355
      createMinimalMockProjectFiles();
356 357 358 359 360 361 362 363 364 365 366

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.traceText, contains('xcresult parser: Unrecognized top level json format.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
367
        setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
368 369
          fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
        }),
370 371
        setUpXCResultCommand(),
        setUpRsyncCommand(),
372 373 374 375 376 377 378 379
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Display xcresult issues on console if parsed.', () async {
      final BuildCommand command = BuildCommand();

380
      createMinimalMockProjectFiles();
381 382 383 384 385 386 387 388 389 390 391 392

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
      expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
393
        setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
394 395
          fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
        }),
396 397
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
        setUpRsyncCommand(),
398 399 400 401 402 403 404 405
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Do not display xcresult issues that needs to be discarded.', () async {
      final BuildCommand command = BuildCommand();

406
      createMinimalMockProjectFiles();
407 408 409 410 411 412 413 414 415 416 417 418 419 420

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
      expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
      expect(testLogger.errorText, isNot(contains('Command PhaseScriptExecution failed with a nonzero exit code')));
      expect(testLogger.warningText, isNot(contains("The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.")));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
421
        setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
422 423
          fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
        }),
424 425
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssuesToBeDiscarded),
        setUpRsyncCommand(),
426 427 428 429 430 431 432 433
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Trace if xcresult bundle does not exist.', () async {
      final BuildCommand command = BuildCommand();

434
      createMinimalMockProjectFiles();
435 436 437 438 439 440 441 442 443 444 445

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.traceText, contains('The xcresult bundle are not generated. Displaying xcresult is disabled.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
446 447 448
        setUpFakeXcodeBuildHandler(exitCode: 1),
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
        setUpRsyncCommand(),
449 450 451 452 453
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

454
    testUsingContext('Extra error message for provision profile issue in xcresult bundle.', () async {
455 456
      final BuildCommand command = BuildCommand();

457
      createMinimalMockProjectFiles();
458 459 460 461 462 463 464 465 466 467

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains('Some Provisioning profile issue.'));
      expect(testLogger.errorText, contains('It appears that there was a problem signing your application prior to installation on the device.'));
      expect(testLogger.errorText, contains('Verify that the Bundle Identifier in your project is your signing id in Xcode'));
      expect(testLogger.errorText, contains('open ios/Runner.xcworkspace'));
468
      expect(testLogger.errorText, contains("Also try selecting 'Product > Build' to fix the problem."));
469 470 471 472
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
473
        setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
474 475
          fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
        }),
476 477
        setUpXCResultCommand(stdout: kSampleResultJsonWithProvisionIssue),
        setUpRsyncCommand(),
478 479 480 481
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });
482

483
   testUsingContext('Default bundle identifier error should be hidden if there is another xcresult issue.', () async {
484 485
      final BuildCommand command = BuildCommand();

486
      createMinimalMockProjectFiles();
487 488 489 490 491 492 493 494

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
      expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
495
      expect(testLogger.errorText, isNot(contains('It appears that your application still contains the default signing identifier.')));
496 497 498 499
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
500
        setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
501 502
          fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
        }),
503 504
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
        setUpRsyncCommand(),
505 506 507 508 509 510
      ]),
      Platform: () => macosPlatform,
      EnvironmentType: () => EnvironmentType.physical,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(productBundleIdentifier: 'com.example'),
    });

511 512 513
    testUsingContext('Show default bundle identifier error if there are no other errors.', () async {
      final BuildCommand command = BuildCommand();

514
      createMinimalMockProjectFiles();
515 516 517 518 519 520 521 522 523 524 525

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains('It appears that your application still contains the default signing identifier.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
526
        setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
527 528
          fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
        }),
529 530
        setUpXCResultCommand(stdout: kSampleResultJsonNoIssues),
        setUpRsyncCommand(),
531 532 533 534 535 536 537
      ]),
      Platform: () => macosPlatform,
      EnvironmentType: () => EnvironmentType.physical,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(productBundleIdentifier: 'com.example'),
    });


538 539 540
    testUsingContext('Display xcresult issues with no provisioning profile.', () async {
      final BuildCommand command = BuildCommand();

541
      createMinimalMockProjectFiles();
542 543 544 545 546 547 548 549 550 551 552 553

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains('Runner requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor'));
      expect(testLogger.errorText, contains(noProvisioningProfileInstruction));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
554
        setUpFakeXcodeBuildHandler(
555 556 557 558 559
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
560 561
        setUpXCResultCommand(stdout: kSampleResultJsonWithNoProvisioningProfileIssue),
        setUpRsyncCommand(),
562 563 564 565 566 567 568 569
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Failed to parse xcresult but display missing provisioning profile issue from stdout.', () async {
      final BuildCommand command = BuildCommand();

570
      createMinimalMockProjectFiles();
571 572 573 574 575 576 577 578 579 580 581

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains(noProvisioningProfileInstruction));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
582
        setUpFakeXcodeBuildHandler(
583 584 585 586 587 588 589 590
          exitCode: 1,
          stdout: '''
Runner requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor
''',
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
591 592
        setUpXCResultCommand(stdout: kSampleResultJsonInvalidIssuesMap),
        setUpRsyncCommand(),
593 594 595 596 597 598 599 600
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Failed to parse xcresult but detected no development team issue.', () async {
      final BuildCommand command = BuildCommand();

601
      createMinimalMockProjectFiles();
602 603 604 605 606 607 608 609 610 611 612

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains(noDevelopmentTeamInstruction));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
613
        setUpFakeXcodeBuildHandler(
614 615 616 617 618
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
619 620
        setUpXCResultCommand(stdout: kSampleResultJsonInvalidIssuesMap),
        setUpRsyncCommand(),
621 622 623 624 625 626 627 628 629
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
    });


    testUsingContext('xcresult did not detect issue but detected by stdout.', () async {
      final BuildCommand command = BuildCommand();

630
      createMinimalMockProjectFiles();
631 632 633 634 635 636 637 638 639 640 641

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains(noProvisioningProfileInstruction));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
642
        setUpFakeXcodeBuildHandler(
643 644 645 646 647 648 649 650
          exitCode: 1,
          stdout: '''
Runner requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor
''',
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
651 652
        setUpXCResultCommand(stdout: kSampleResultJsonNoIssues),
        setUpRsyncCommand(),
653 654 655 656 657 658 659 660 661
      ]),
      EnvironmentType: () => EnvironmentType.physical,
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('xcresult did not detect issue, no development team is detected from build setting.', () async {
      final BuildCommand command = BuildCommand();

662
      createMinimalMockProjectFiles();
663 664 665 666 667 668 669 670 671 672 673

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains(noDevelopmentTeamInstruction));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
674
        setUpFakeXcodeBuildHandler(
675 676 677 678 679
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
680 681
        setUpXCResultCommand(stdout: kSampleResultJsonInvalidIssuesMap),
        setUpRsyncCommand(),
682 683 684 685 686 687 688 689
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
    });

    testUsingContext('No development team issue error message is not displayed if no provisioning profile issue is detected from xcresult first.', () async {
      final BuildCommand command = BuildCommand();

690
      createMinimalMockProjectFiles();
691 692 693 694 695 696 697 698 699 700 701 702

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains(noProvisioningProfileInstruction));
      expect(testLogger.errorText, isNot(contains(noDevelopmentTeamInstruction)));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
703
        setUpFakeXcodeBuildHandler(
704 705 706 707 708
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
709 710
        setUpXCResultCommand(stdout: kSampleResultJsonWithNoProvisioningProfileIssue),
        setUpRsyncCommand(),
711 712 713 714 715 716 717 718
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
    });

    testUsingContext('General provisioning profile issue error message is not displayed if no development team issue is detected first.', () async {
      final BuildCommand command = BuildCommand();

719
      createMinimalMockProjectFiles();
720 721 722 723 724 725 726 727 728 729 730 731

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains(noDevelopmentTeamInstruction));
      expect(testLogger.errorText, isNot(contains('It appears that there was a problem signing your application prior to installation on the device.')));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
732
        setUpFakeXcodeBuildHandler(
733 734 735 736 737
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          }
        ),
738 739
        setUpXCResultCommand(stdout: kSampleResultJsonWithProvisionIssue),
        setUpRsyncCommand(),
740 741 742 743
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
    });
744 745 746 747 748 749
  });

  group('xcresults simulator', () {
    testUsingContext('Trace error if xcresult is empty.', () async {
      final BuildCommand command = BuildCommand();

750
      createMinimalMockProjectFiles();
751 752 753 754 755 756 757 758 759 760 761

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--simulator', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.traceText, contains('xcresult parser: Unrecognized top level json format.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
762
        setUpFakeXcodeBuildHandler(
763 764 765 766 767 768
          simulator: true,
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          },
        ),
769 770
        setUpXCResultCommand(),
        setUpRsyncCommand(),
771 772 773 774 775 776 777 778
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Display xcresult issues on console if parsed.', () async {
      final BuildCommand command = BuildCommand();

779
      createMinimalMockProjectFiles();
780 781 782 783 784 785 786 787 788 789 790 791

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--simulator',  '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
      expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
792
        setUpFakeXcodeBuildHandler(
793 794 795 796 797 798
          simulator: true,
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          },
        ),
799 800
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
        setUpRsyncCommand(),
801 802 803 804 805 806 807 808
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Do not display xcresult issues that needs to be discarded.', () async {
      final BuildCommand command = BuildCommand();

809
      createMinimalMockProjectFiles();
810 811 812 813 814 815 816 817 818 819 820 821 822 823

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--simulator', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
      expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
      expect(testLogger.errorText, isNot(contains('Command PhaseScriptExecution failed with a nonzero exit code')));
      expect(testLogger.warningText, isNot(contains("The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.")));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
824
        setUpFakeXcodeBuildHandler(
825 826 827 828 829 830
          simulator: true,
          exitCode: 1,
          onRun: () {
            fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
          },
        ),
831 832
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssuesToBeDiscarded),
        setUpRsyncCommand(),
833 834 835 836 837 838 839 840
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });

    testUsingContext('Trace if xcresult bundle does not exist.', () async {
      final BuildCommand command = BuildCommand();

841
      createMinimalMockProjectFiles();
842 843 844 845 846 847 848 849 850 851 852

      await expectLater(
        createTestCommandRunner(command).run(const <String>['build', 'ios', '--simulator', '--no-pub']),
        throwsToolExit(),
      );

      expect(testLogger.traceText, contains('The xcresult bundle are not generated. Displaying xcresult is disabled.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
        xattrCommand,
853
        setUpFakeXcodeBuildHandler(
854 855 856
          simulator: true,
          exitCode: 1,
        ),
857 858
        setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
        setUpRsyncCommand(),
859 860 861 862 863
      ]),
      Platform: () => macosPlatform,
      XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
    });
  });
864
}
865 866

const String _xcBundleFilePath = '/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle';