ios_test.dart 25.8 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:file/memory.dart';
6 7 8
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
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/platform.dart';
11 12 13
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/ios.dart';
14
import 'package:flutter_tools/src/convert.dart';
15
import 'package:flutter_tools/src/reporting/reporting.dart';
16 17

import '../../../src/common.dart';
18
import '../../../src/context.dart';
19
import '../../../src/fake_process_manager.dart';
20 21

final Platform macPlatform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{});
22 23 24

const List<String> _kSharedConfig = <String>[
  '-dynamiclib',
25
  '-miphoneos-version-min=11.0',
26 27 28 29 30 31 32 33 34 35 36
  '-Xlinker',
  '-rpath',
  '-Xlinker',
  '@executable_path/Frameworks',
  '-Xlinker',
  '-rpath',
  '-Xlinker',
  '@loader_path/Frameworks',
  '-install_name',
  '@rpath/App.framework/App',
  '-isysroot',
37
  'path/to/iPhoneOS.sdk',
38 39 40
];

void main() {
41 42 43 44 45
  late Environment environment;
  late FileSystem fileSystem;
  late FakeProcessManager processManager;
  late Artifacts artifacts;
  late BufferLogger logger;
46
  late TestUsage usage;
47 48

  setUp(() {
49
    fileSystem = MemoryFileSystem.test();
50
    processManager = FakeProcessManager.empty();
51 52
    logger = BufferLogger.test();
    artifacts = Artifacts.test();
53
    usage = TestUsage();
54 55 56 57 58 59 60 61 62 63 64
    environment = Environment.test(
      fileSystem.currentDirectory,
      defines: <String, String>{
        kTargetPlatform: 'ios',
      },
      inputs: <String, String>{},
      processManager: processManager,
      artifacts: artifacts,
      logger: logger,
      fileSystem: fileSystem,
      engineVersion: '2',
65
      usage: usage,
66
    );
67 68
  });

69
  testWithoutContext('iOS AOT targets has analyticsName', () {
70 71 72 73
    expect(const AotAssemblyRelease().analyticsName, 'ios_aot');
    expect(const AotAssemblyProfile().analyticsName, 'ios_aot');
  });

74
  testUsingContext('DebugUniversalFramework creates simulator binary', () async {
75 76
    environment.defines[kIosArchs] = 'x86_64';
    environment.defines[kSdkRoot] = 'path/to/iPhoneSimulator.sdk';
77 78
    final String appFrameworkPath = environment.buildDir.childDirectory('App.framework').childFile('App').path;
    processManager.addCommands(<FakeCommand>[
79 80 81 82 83 84 85 86 87 88
      FakeCommand(command: <String>[
        'xcrun',
        'clang',
        '-x',
        'c',
        '-arch',
        'x86_64',
        fileSystem.path.absolute(fileSystem.path.join(
            '.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')),
        '-dynamiclib',
89
        '-miphonesimulator-version-min=11.0',
90 91 92 93 94 95 96 97 98 99 100 101 102
        '-Xlinker',
        '-rpath',
        '-Xlinker',
        '@executable_path/Frameworks',
        '-Xlinker',
        '-rpath',
        '-Xlinker',
        '@loader_path/Frameworks',
        '-install_name',
        '@rpath/App.framework/App',
        '-isysroot',
        'path/to/iPhoneSimulator.sdk',
        '-o',
103
        appFrameworkPath,
104
      ]),
105 106 107 108 109 110 111 112 113
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        '--timestamp=none',
        appFrameworkPath,
      ]),
    ]);
114 115

    await const DebugUniversalFramework().build(environment);
116
    expect(processManager, hasNoRemainingExpectations);
117 118 119 120 121 122
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

123
  testUsingContext('DebugUniversalFramework creates expected binary with arm64 only arch', () async {
124
    environment.defines[kIosArchs] = 'arm64';
125
    environment.defines[kSdkRoot] = 'path/to/iPhoneOS.sdk';
126 127
    final String appFrameworkPath = environment.buildDir.childDirectory('App.framework').childFile('App').path;
    processManager.addCommands(<FakeCommand>[
128 129 130 131 132
      FakeCommand(command: <String>[
        'xcrun',
        'clang',
        '-x',
        'c',
133
        // iphone only gets 64 bit arch based on kIosArchs
134 135
        '-arch',
        'arm64',
136 137
        fileSystem.path.absolute(fileSystem.path.join(
            '.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')),
138 139
        ..._kSharedConfig,
        '-o',
140
        appFrameworkPath,
141
      ]),
142 143 144 145 146 147 148 149 150
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        '--timestamp=none',
        appFrameworkPath,
      ]),
    ]);
151

152
    await const DebugUniversalFramework().build(environment);
153
    expect(processManager, hasNoRemainingExpectations);
154
  }, overrides: <Type, Generator>{
155
    FileSystem: () => fileSystem,
156
    ProcessManager: () => processManager,
157 158
    Platform: () => macPlatform,
  });
159

160
  testUsingContext('DebugIosApplicationBundle', () async {
161
    environment.defines[kBundleSkSLPath] = 'bundle.sksl';
162
    environment.defines[kBuildMode] = 'debug';
163
    environment.defines[kCodesignIdentity] = 'ABC123';
164
    // Precompiled dart data
165 166 167 168 169

    fileSystem.file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug))
      .createSync();
    fileSystem.file(artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug))
      .createSync();
170
    // Project info
171 172
    fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
    fileSystem.file('.packages').writeAsStringSync('\n');
173
    // Plist file
174
    fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
175
      .createSync(recursive: true);
176 177 178
    // App kernel
    environment.buildDir.childFile('app.dill').createSync(recursive: true);
    // Stub framework
179
    environment.buildDir
180
        .childDirectory('App.framework')
181 182
      .childFile('App')
      .createSync(recursive: true);
183
    // sksl bundle
184
    fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
185 186 187 188 189
      <String, Object>{
        'engineRevision': '2',
        'platform': 'ios',
        'data': <String, Object>{
          'A': 'B',
190 191
        },
      },
192
    ));
193

194 195
    final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
196
    processManager.addCommand(
197 198 199 200 201 202 203 204
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        'ABC123',
        '--timestamp=none',
        frameworkDirectoryBinary.path,
      ]),
205
    );
206

207
    await const DebugIosApplicationBundle().build(environment);
208
    expect(processManager, hasNoRemainingExpectations);
209

210
    expect(frameworkDirectoryBinary, exists);
211 212 213 214 215 216 217
    expect(frameworkDirectory.childFile('Info.plist'), exists);

    final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
    expect(assetDirectory.childFile('kernel_blob.bin'), exists);
    expect(assetDirectory.childFile('AssetManifest.json'), exists);
    expect(assetDirectory.childFile('vm_snapshot_data'), exists);
    expect(assetDirectory.childFile('isolate_snapshot_data'), exists);
218 219
    expect(assetDirectory.childFile('io.flutter.shaders.json'), exists);
    expect(assetDirectory.childFile('io.flutter.shaders.json').readAsStringSync(), '{"data":{"A":"B"}}');
220
  });
221

222
  testUsingContext('ReleaseIosApplicationBundle build', () async {
223
    environment.defines[kBuildMode] = 'release';
224
    environment.defines[kCodesignIdentity] = 'ABC123';
225
    environment.defines[kXcodeAction] = 'build';
226 227

    // Project info
228 229
    fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
    fileSystem.file('.packages').writeAsStringSync('\n');
230
    // Plist file
231
    fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
232
      .createSync(recursive: true);
233 234 235 236 237 238 239

    // Real framework
    environment.buildDir
      .childDirectory('App.framework')
      .childFile('App')
      .createSync(recursive: true);

240 241 242 243 244 245 246 247
    // Input dSYM
    environment.buildDir
      .childDirectory('App.framework.dSYM')
      .childDirectory('Contents')
      .childDirectory('Resources')
      .childDirectory('DWARF')
      .childFile('App')
      .createSync(recursive: true);
248 249

    final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
250
    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
251
    processManager.addCommand(
252 253 254 255 256 257 258
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        'ABC123',
        frameworkDirectoryBinary.path,
      ]),
259
    );
260 261

    await const ReleaseIosApplicationBundle().build(environment);
262
    expect(processManager, hasNoRemainingExpectations);
263 264

    expect(frameworkDirectoryBinary, exists);
265
    expect(frameworkDirectory.childFile('Info.plist'), exists);
266 267 268 269 270 271
    expect(environment.outputDir
      .childDirectory('App.framework.dSYM')
      .childDirectory('Contents')
      .childDirectory('Resources')
      .childDirectory('DWARF')
      .childFile('App'), exists);
272 273 274 275 276 277

    final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
    expect(assetDirectory.childFile('kernel_blob.bin'), isNot(exists));
    expect(assetDirectory.childFile('AssetManifest.json'), exists);
    expect(assetDirectory.childFile('vm_snapshot_data'), isNot(exists));
    expect(assetDirectory.childFile('isolate_snapshot_data'), isNot(exists));
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    expect(usage.events, isEmpty);
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

  testUsingContext('ReleaseIosApplicationBundle sends archive success event', () async {
    environment.defines[kBuildMode] = 'release';
    environment.defines[kXcodeAction] = 'install';

    fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
        .createSync(recursive: true);

    environment.buildDir
        .childDirectory('App.framework')
        .childFile('App')
        .createSync(recursive: true);

    final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
    processManager.addCommand(
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        frameworkDirectoryBinary.path,
      ]),
    );

    await const ReleaseIosApplicationBundle().build(environment);
    expect(usage.events, contains(const TestUsageEvent('assemble', 'ios-archive', label: 'success')));
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

  testUsingContext('ReleaseIosApplicationBundle sends archive fail event', () async {
    environment.defines[kBuildMode] = 'release';
    environment.defines[kXcodeAction] = 'install';

    // Throws because the project files are not set up.
    await expectLater(() => const ReleaseIosApplicationBundle().build(environment),
        throwsA(const TypeMatcher<FileSystemException>()));
    expect(usage.events, contains(const TestUsageEvent('assemble', 'ios-archive', label: 'fail')));
325 326 327 328 329
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });
330

331
  testUsingContext('AotAssemblyRelease throws exception if asked to build for simulator', () async {
332 333 334 335 336
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Environment environment = Environment.test(
      fileSystem.currentDirectory,
      defines: <String, String>{
        kTargetPlatform: 'ios',
337 338 339
        kSdkRoot: 'path/to/iPhoneSimulator.sdk',
        kBuildMode: 'release',
        kIosArchs: 'x86_64',
340 341
      },
      processManager: processManager,
342 343
      artifacts: artifacts,
      logger: logger,
344 345 346
      fileSystem: fileSystem,
    );

347
    expect(const AotAssemblyRelease().build(environment), throwsA(isException
348 349 350 351 352 353
      .having(
        (Exception exception) => exception.toString(),
        'description',
        contains('release/profile builds are only supported for physical devices.'),
      )
    ));
354
    expect(processManager, hasNoRemainingExpectations);
355 356 357 358 359
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });
360

361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
  testUsingContext('AotAssemblyRelease throws exception if sdk root is missing', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Environment environment = Environment.test(
      fileSystem.currentDirectory,
      defines: <String, String>{
        kTargetPlatform: 'ios',
      },
      processManager: processManager,
      artifacts: artifacts,
      logger: logger,
      fileSystem: fileSystem,
    );
    environment.defines[kBuildMode] = 'release';
    environment.defines[kIosArchs] = 'x86_64';

376 377
    expect(const AotAssemblyRelease().build(environment), throwsA(isException.having(
      (Exception exception) => exception.toString(),
378 379
      'description',
      contains('required define SdkRoot but it was not provided'),
380
    )));
381
    expect(processManager, hasNoRemainingExpectations);
382 383 384 385 386 387
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

388
  group('copies Flutter.framework', () {
389 390 391 392 393 394
    late Directory outputDir;
    late File binary;
    late FakeCommand copyPhysicalFrameworkCommand;
    late FakeCommand lipoCommandNonFatResult;
    late FakeCommand lipoVerifyArm64Command;
    late FakeCommand adHocCodesignCommand;
395 396

    setUp(() {
397
      final FileSystem fileSystem = MemoryFileSystem.test();
398
      outputDir = fileSystem.directory('output');
399
      binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter');
400 401 402 403 404 405 406 407 408
      copyPhysicalFrameworkCommand = FakeCommand(command: <String>[
        'rsync',
        '-av',
        '--delete',
        '--filter',
        '- .DS_Store/',
        'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical',
        outputDir.path,
      ]);
409 410 411 412 413 414 415 416 417 418 419 420 421 422

      lipoCommandNonFatResult = FakeCommand(command: <String>[
        'lipo',
        '-info',
        binary.path,
      ], stdout: 'Non-fat file:');

      lipoVerifyArm64Command = FakeCommand(command: <String>[
        'lipo',
        binary.path,
        '-verify_arch',
        'arm64',
      ]);

423 424 425 426 427 428 429 430
      adHocCodesignCommand = FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        '--timestamp=none',
        binary.path,
      ]);
431 432 433
    });

    testWithoutContext('iphonesimulator', () async {
434 435 436 437 438 439 440 441
      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
442
          kIosArchs: 'x86_64',
443 444 445
          kSdkRoot: 'path/to/iPhoneSimulator.sdk',
        },
      );
446

447
      processManager.addCommands(<FakeCommand>[
448 449 450 451 452 453 454 455
        FakeCommand(command: <String>[
          'rsync',
          '-av',
          '--delete',
          '--filter',
          '- .DS_Store/',
          'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.simulator',
          outputDir.path,
456 457 458
          ],
          onRun: () => binary.createSync(recursive: true),
        ),
459
        lipoCommandNonFatResult,
460
        FakeCommand(command: <String>[
461 462 463 464
          'lipo',
          binary.path,
          '-verify_arch',
          'x86_64',
465
        ]),
466
        adHocCodesignCommand,
467
      ]);
468
      await const DebugUnpackIOS().build(environment);
469 470

      expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
471
      expect(processManager, hasNoRemainingExpectations);
472
    });
473

474
    testWithoutContext('fails when frameworks missing', () async {
475 476 477 478 479 480 481 482 483
      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
484
          kSdkRoot: 'path/to/iPhoneOS.sdk',
485 486
        },
      );
487
      processManager.addCommand(copyPhysicalFrameworkCommand);
488
      await expectLater(
489
        const DebugUnpackIOS().build(environment),
490
        throwsA(isException.having(
491 492
          (Exception exception) => exception.toString(),
          'description',
493
          contains('Flutter.framework/Flutter does not exist, cannot thin'),
494 495 496
        )));
    });

497
    testWithoutContext('fails when requested archs missing from framework', () async {
498
      binary.createSync(recursive: true);
499 500 501 502 503 504 505 506 507 508

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64 armv7',
509
          kSdkRoot: 'path/to/iPhoneOS.sdk',
510 511 512
        },
      );

513 514
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
515 516 517
        FakeCommand(command: <String>[
          'lipo',
          '-info',
518
          binary.path,
519 520 521
        ], stdout: 'Architectures in the fat file:'),
        FakeCommand(command: <String>[
          'lipo',
522
          binary.path,
523 524 525 526
          '-verify_arch',
          'arm64',
          'armv7',
        ], exitCode: 1),
527
      ]);
528

529
      await expectLater(
530 531 532 533 534 535 536
        const DebugUnpackIOS().build(environment),
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
          'description',
          contains('does not contain arm64 armv7. Running lipo -info:\nArchitectures in the fat file:'),
        )),
      );
537 538
    });

539
    testWithoutContext('fails when lipo extract fails', () async {
540
      binary.createSync(recursive: true);
541 542 543 544 545 546 547 548 549 550

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64 armv7',
551
          kSdkRoot: 'path/to/iPhoneOS.sdk',
552 553 554
        },
      );

555 556
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
557 558 559
        FakeCommand(command: <String>[
          'lipo',
          '-info',
560
          binary.path,
561 562 563
        ], stdout: 'Architectures in the fat file:'),
        FakeCommand(command: <String>[
          'lipo',
564
          binary.path,
565 566 567 568 569 570 571
          '-verify_arch',
          'arm64',
          'armv7',
        ]),
        FakeCommand(command: <String>[
          'lipo',
          '-output',
572
          binary.path,
573 574 575 576
          '-extract',
          'arm64',
          '-extract',
          'armv7',
577
          binary.path,
578 579
        ], exitCode: 1,
        stderr: 'lipo error'),
580
      ]);
581

582
      await expectLater(
583
        const DebugUnpackIOS().build(environment),
584 585
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
586
          'description',
587
          contains('Failed to extract arm64 armv7 for output/Flutter.framework/Flutter.\nlipo error\nRunning lipo -info:\nArchitectures in the fat file:'),
588 589
        )),
      );
590 591
    });

592
    testWithoutContext('skips thin framework', () async {
593
      binary.createSync(recursive: true);
594 595 596 597 598 599 600 601 602 603

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
604
          kSdkRoot: 'path/to/iPhoneOS.sdk',
605 606 607
        },
      );

608 609 610 611
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
612
        adHocCodesignCommand,
613
      ]);
614
      await const DebugUnpackIOS().build(environment);
615

616
      expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
617

618
      expect(processManager, hasNoRemainingExpectations);
619 620
    });

621
    testWithoutContext('thins fat framework', () async {
622
      binary.createSync(recursive: true);
623 624 625 626 627 628 629 630 631 632

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64 armv7',
633
          kSdkRoot: 'path/to/iPhoneOS.sdk',
634 635 636
        },
      );

637 638
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
639 640 641
        FakeCommand(command: <String>[
          'lipo',
          '-info',
642
          binary.path,
643 644 645
        ], stdout: 'Architectures in the fat file:'),
        FakeCommand(command: <String>[
          'lipo',
646
          binary.path,
647 648 649 650 651 652 653
          '-verify_arch',
          'arm64',
          'armv7',
        ]),
        FakeCommand(command: <String>[
          'lipo',
          '-output',
654
          binary.path,
655 656 657 658
          '-extract',
          'arm64',
          '-extract',
          'armv7',
659
          binary.path,
660
        ]),
661
        adHocCodesignCommand,
662
      ]);
663

664
      await const DebugUnpackIOS().build(environment);
665
      expect(processManager, hasNoRemainingExpectations);
666
    });
667 668

    testWithoutContext('fails when bitcode strip fails', () async {
669
      binary.createSync(recursive: true);
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
          kSdkRoot: 'path/to/iPhoneOS.sdk',
        },
      );

      processManager.addCommands(<FakeCommand>[
685 686 687 688 689 690 691 692 693
        FakeCommand(command: <String>[
          'rsync',
          '-av',
          '--delete',
          '--filter',
          '- .DS_Store/',
          'Artifact.flutterFramework.TargetPlatform.ios.release.EnvironmentType.physical',
          outputDir.path,
        ]),
694 695
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
696 697 698 699
        FakeCommand(command: <String>[
          'xcrun',
          'bitcode_strip',
          binary.path,
700
          '-r',
701 702 703 704 705 706
          '-o',
          binary.path,
        ], exitCode: 1, stderr: 'bitcode_strip error'),
      ]);

      await expectLater(
707
        const ReleaseUnpackIOS().build(environment),
708 709 710 711 712 713
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
          'description',
          contains('Failed to strip bitcode for output/Flutter.framework/Flutter.\nbitcode_strip error'),
        )),
      );
714

715
      expect(processManager, hasNoRemainingExpectations);
716 717 718
    });

    testWithoutContext('strips framework', () async {
719
      binary.createSync(recursive: true);
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
          kSdkRoot: 'path/to/iPhoneOS.sdk',
        },
      );

      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
736 737
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
738
        adHocCodesignCommand,
739 740 741
      ]);
      await const DebugUnpackIOS().build(environment);

742
      expect(processManager, hasNoRemainingExpectations);
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
    });

    testWithoutContext('fails when codesign fails', () async {
      binary.createSync(recursive: true);

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
          kSdkRoot: 'path/to/iPhoneOS.sdk',
          kCodesignIdentity: 'ABC123',
        },
      );

      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
766
        FakeCommand(command: <String>[
767 768 769 770 771
          'codesign',
          '--force',
          '--sign',
          'ABC123',
          '--timestamp=none',
772
          binary.path,
773 774 775 776
        ], exitCode: 1, stderr: 'codesign error'),
      ]);

      await expectLater(
777 778 779 780 781 782 783
        const DebugUnpackIOS().build(environment),
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
          'description',
          contains('Failed to codesign output/Flutter.framework/Flutter with identity ABC123.\ncodesign error'),
        )),
      );
784

785
      expect(processManager, hasNoRemainingExpectations);
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
    });

    testWithoutContext('codesigns framework', () async {
      binary.createSync(recursive: true);

      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
          kSdkRoot: 'path/to/iPhoneOS.sdk',
          kCodesignIdentity: 'ABC123',
        },
      );

      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
809
        FakeCommand(command: <String>[
810 811 812 813 814
          'codesign',
          '--force',
          '--sign',
          'ABC123',
          '--timestamp=none',
815 816 817 818 819
          binary.path,
        ]),
      ]);
      await const DebugUnpackIOS().build(environment);

820
      expect(processManager, hasNoRemainingExpectations);
821
    });
822
  });
823
}