ios_test.dart 30.2 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
  '-Xlinker',
  '-rpath',
  '-Xlinker',
  '@executable_path/Frameworks',
  '-Xlinker',
  '-rpath',
  '-Xlinker',
  '@loader_path/Frameworks',
34
  '-fapplication-extension',
35 36 37
  '-install_name',
  '@rpath/App.framework/App',
  '-isysroot',
38
  'path/to/iPhoneOS.sdk',
39 40 41
];

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

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

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

75
  testUsingContext('DebugUniversalFramework creates simulator binary', () async {
76 77
    environment.defines[kIosArchs] = 'x86_64';
    environment.defines[kSdkRoot] = 'path/to/iPhoneSimulator.sdk';
78 79
    final String appFrameworkPath = environment.buildDir.childDirectory('App.framework').childFile('App').path;
    processManager.addCommands(<FakeCommand>[
80 81 82 83 84 85 86 87 88 89
      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',
90
        '-miphonesimulator-version-min=11.0',
91 92 93 94 95 96 97 98
        '-Xlinker',
        '-rpath',
        '-Xlinker',
        '@executable_path/Frameworks',
        '-Xlinker',
        '-rpath',
        '-Xlinker',
        '@loader_path/Frameworks',
99
        '-fapplication-extension',
100 101 102 103 104
        '-install_name',
        '@rpath/App.framework/App',
        '-isysroot',
        'path/to/iPhoneSimulator.sdk',
        '-o',
105
        appFrameworkPath,
106
      ]),
107 108 109 110 111 112 113
      FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        appFrameworkPath,
      ]),
114 115 116 117 118 119 120 121 122
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        '--timestamp=none',
        appFrameworkPath,
      ]),
    ]);
123 124

    await const DebugUniversalFramework().build(environment);
125
    expect(processManager, hasNoRemainingExpectations);
126 127 128 129 130 131
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

132
  testUsingContext('DebugUniversalFramework creates expected binary with arm64 only arch', () async {
133
    environment.defines[kIosArchs] = 'arm64';
134
    environment.defines[kSdkRoot] = 'path/to/iPhoneOS.sdk';
135 136
    final String appFrameworkPath = environment.buildDir.childDirectory('App.framework').childFile('App').path;
    processManager.addCommands(<FakeCommand>[
137 138 139 140 141
      FakeCommand(command: <String>[
        'xcrun',
        'clang',
        '-x',
        'c',
142
        // iphone only gets 64 bit arch based on kIosArchs
143 144
        '-arch',
        'arm64',
145 146
        fileSystem.path.absolute(fileSystem.path.join(
            '.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')),
147 148
        ..._kSharedConfig,
        '-o',
149
        appFrameworkPath,
150
      ]),
151 152 153 154 155 156 157
      FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        appFrameworkPath,
      ]),
158 159 160 161 162 163 164 165 166
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        '--timestamp=none',
        appFrameworkPath,
      ]),
    ]);
167

168
    await const DebugUniversalFramework().build(environment);
169
    expect(processManager, hasNoRemainingExpectations);
170
  }, overrides: <Type, Generator>{
171
    FileSystem: () => fileSystem,
172
    ProcessManager: () => processManager,
173 174
    Platform: () => macPlatform,
  });
175

176
  testUsingContext('DebugIosApplicationBundle', () async {
177
    environment.defines[kBundleSkSLPath] = 'bundle.sksl';
178
    environment.defines[kBuildMode] = 'debug';
179
    environment.defines[kCodesignIdentity] = 'ABC123';
180
    // Precompiled dart data
181 182 183 184 185

    fileSystem.file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug))
      .createSync();
    fileSystem.file(artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug))
      .createSync();
186
    // Project info
187 188
    fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
    fileSystem.file('.packages').writeAsStringSync('\n');
189
    // Plist file
190
    fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
191
      .createSync(recursive: true);
192 193 194
    // App kernel
    environment.buildDir.childFile('app.dill').createSync(recursive: true);
    // Stub framework
195
    environment.buildDir
196
        .childDirectory('App.framework')
197 198
      .childFile('App')
      .createSync(recursive: true);
199
    // sksl bundle
200
    fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
201 202 203 204 205
      <String, Object>{
        'engineRevision': '2',
        'platform': 'ios',
        'data': <String, Object>{
          'A': 'B',
206 207
        },
      },
208
    ));
209

210 211
    final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
212 213 214 215 216 217 218 219
    processManager.addCommands(<FakeCommand>[
      FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        frameworkDirectoryBinary.path,
      ]),
220 221 222 223 224 225 226 227
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        'ABC123',
        '--timestamp=none',
        frameworkDirectoryBinary.path,
      ]),
228
    ]);
229

230
    await const DebugIosApplicationBundle().build(environment);
231
    expect(processManager, hasNoRemainingExpectations);
232

233
    expect(frameworkDirectoryBinary, exists);
234 235 236 237 238 239 240
    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);
241 242
    expect(assetDirectory.childFile('io.flutter.shaders.json'), exists);
    expect(assetDirectory.childFile('io.flutter.shaders.json').readAsStringSync(), '{"data":{"A":"B"}}');
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

  testUsingContext('DebugIosApplicationBundle with impeller and shader compilation', () async {
    // Create impellerc to work around fallback detection logic.
    fileSystem.file(artifacts.getHostArtifact(HostArtifact.impellerc)).createSync(recursive: true);

    environment.defines[kBuildMode] = 'debug';
    environment.defines[kCodesignIdentity] = 'ABC123';
    // Precompiled dart data

    fileSystem.file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug))
      .createSync();
    fileSystem.file(artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug))
      .createSync();
    // Project info
    fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello\nflutter:\n  shaders:\n    - shader.glsl');
    fileSystem.file('.packages').writeAsStringSync('\n');
    // Plist file
    fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
      .createSync(recursive: true);
    // Shader file
    fileSystem.file('shader.glsl').writeAsStringSync('test');
    // App kernel
    environment.buildDir.childFile('app.dill').createSync(recursive: true);
    // Stub framework
    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.addCommands(<FakeCommand>[
      const FakeCommand(command: <String>[
        'HostArtifact.impellerc',
        '--runtime-stage-metal',
        '--iplr',
        '--sl=/App.framework/flutter_assets/shader.glsl',
        '--spirv=/App.framework/flutter_assets/shader.glsl.spirv',
        '--input=/shader.glsl',
        '--input-type=frag',
288
        '--include=/',
289
        '--include=/./shader_lib',
290
      ]),
291 292 293 294 295 296 297
      FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        frameworkDirectoryBinary.path,
      ]),
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
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        'ABC123',
        '--timestamp=none',
        frameworkDirectoryBinary.path,
      ]),
    ]);

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

    expect(frameworkDirectoryBinary, exists);
    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);
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
323
  });
324

325
  testUsingContext('ReleaseIosApplicationBundle build', () async {
326
    environment.defines[kBuildMode] = 'release';
327
    environment.defines[kCodesignIdentity] = 'ABC123';
328
    environment.defines[kXcodeAction] = 'build';
329 330

    // Project info
331 332
    fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
    fileSystem.file('.packages').writeAsStringSync('\n');
333
    // Plist file
334
    fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
335
      .createSync(recursive: true);
336 337 338 339 340 341 342

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

343 344 345 346 347 348 349 350
    // Input dSYM
    environment.buildDir
      .childDirectory('App.framework.dSYM')
      .childDirectory('Contents')
      .childDirectory('Resources')
      .childDirectory('DWARF')
      .childFile('App')
      .createSync(recursive: true);
351 352

    final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
353
    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
354 355 356 357 358 359 360 361
    processManager.addCommands(<FakeCommand>[
      FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        frameworkDirectoryBinary.path,
      ]),
362 363 364 365 366 367 368
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        'ABC123',
        frameworkDirectoryBinary.path,
      ]),
369
    ]);
370 371

    await const ReleaseIosApplicationBundle().build(environment);
372
    expect(processManager, hasNoRemainingExpectations);
373 374

    expect(frameworkDirectoryBinary, exists);
375
    expect(frameworkDirectory.childFile('Info.plist'), exists);
376 377 378 379 380 381
    expect(environment.outputDir
      .childDirectory('App.framework.dSYM')
      .childDirectory('Contents')
      .childDirectory('Resources')
      .childDirectory('DWARF')
      .childFile('App'), exists);
382 383 384 385 386 387

    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));
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
    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');
409 410 411 412 413 414 415 416
    processManager.addCommands(<FakeCommand>[
      FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        frameworkDirectoryBinary.path,
      ]),
417 418 419 420 421 422 423
      FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        frameworkDirectoryBinary.path,
      ]),
424
    ]);
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441

    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')));
442 443 444 445 446
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });
447

448
  testUsingContext('AotAssemblyRelease throws exception if asked to build for simulator', () async {
449 450 451 452 453
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Environment environment = Environment.test(
      fileSystem.currentDirectory,
      defines: <String, String>{
        kTargetPlatform: 'ios',
454 455 456
        kSdkRoot: 'path/to/iPhoneSimulator.sdk',
        kBuildMode: 'release',
        kIosArchs: 'x86_64',
457 458
      },
      processManager: processManager,
459 460
      artifacts: artifacts,
      logger: logger,
461 462 463
      fileSystem: fileSystem,
    );

464
    expect(const AotAssemblyRelease().build(environment), throwsA(isException
465 466 467 468 469 470
      .having(
        (Exception exception) => exception.toString(),
        'description',
        contains('release/profile builds are only supported for physical devices.'),
      )
    ));
471
    expect(processManager, hasNoRemainingExpectations);
472 473 474 475 476
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });
477

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
  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';

493 494
    expect(const AotAssemblyRelease().build(environment), throwsA(isException.having(
      (Exception exception) => exception.toString(),
495 496
      'description',
      contains('required define SdkRoot but it was not provided'),
497
    )));
498
    expect(processManager, hasNoRemainingExpectations);
499 500 501 502 503 504
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => processManager,
    Platform: () => macPlatform,
  });

505
  group('copies Flutter.framework', () {
506 507 508 509 510
    late Directory outputDir;
    late File binary;
    late FakeCommand copyPhysicalFrameworkCommand;
    late FakeCommand lipoCommandNonFatResult;
    late FakeCommand lipoVerifyArm64Command;
511
    late FakeCommand xattrCommand;
512
    late FakeCommand adHocCodesignCommand;
513 514

    setUp(() {
515
      final FileSystem fileSystem = MemoryFileSystem.test();
516
      outputDir = fileSystem.directory('output');
517
      binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter');
518 519 520 521 522 523 524 525 526
      copyPhysicalFrameworkCommand = FakeCommand(command: <String>[
        'rsync',
        '-av',
        '--delete',
        '--filter',
        '- .DS_Store/',
        'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical',
        outputDir.path,
      ]);
527 528 529 530 531 532 533 534 535 536 537 538 539 540

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

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

541 542 543 544 545 546 547 548
      xattrCommand = FakeCommand(command: <String>[
        'xattr',
        '-r',
        '-d',
        'com.apple.FinderInfo',
        binary.path,
      ]);

549 550 551 552 553 554 555 556
      adHocCodesignCommand = FakeCommand(command: <String>[
        'codesign',
        '--force',
        '--sign',
        '-',
        '--timestamp=none',
        binary.path,
      ]);
557 558 559
    });

    testWithoutContext('iphonesimulator', () async {
560 561 562 563 564 565 566 567
      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
568
          kIosArchs: 'x86_64',
569 570 571
          kSdkRoot: 'path/to/iPhoneSimulator.sdk',
        },
      );
572

573
      processManager.addCommands(<FakeCommand>[
574 575 576 577 578 579 580 581
        FakeCommand(command: <String>[
          'rsync',
          '-av',
          '--delete',
          '--filter',
          '- .DS_Store/',
          'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.simulator',
          outputDir.path,
582 583 584
          ],
          onRun: () => binary.createSync(recursive: true),
        ),
585
        lipoCommandNonFatResult,
586
        FakeCommand(command: <String>[
587 588 589 590
          'lipo',
          binary.path,
          '-verify_arch',
          'x86_64',
591
        ]),
592
        xattrCommand,
593
        adHocCodesignCommand,
594
      ]);
595
      await const DebugUnpackIOS().build(environment);
596 597

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

601
    testWithoutContext('fails when frameworks missing', () async {
602 603 604 605 606 607 608 609 610
      final Environment environment = Environment.test(
        fileSystem.currentDirectory,
        processManager: processManager,
        artifacts: artifacts,
        logger: logger,
        fileSystem: fileSystem,
        outputDir: outputDir,
        defines: <String, String>{
          kIosArchs: 'arm64',
611
          kSdkRoot: 'path/to/iPhoneOS.sdk',
612 613
        },
      );
614
      processManager.addCommand(copyPhysicalFrameworkCommand);
615
      await expectLater(
616
        const DebugUnpackIOS().build(environment),
617
        throwsA(isException.having(
618 619
          (Exception exception) => exception.toString(),
          'description',
620
          contains('Flutter.framework/Flutter does not exist, cannot thin'),
621 622 623
        )));
    });

624
    testWithoutContext('fails when requested archs missing from framework', () async {
625
      binary.createSync(recursive: true);
626 627 628 629 630 631 632 633 634 635

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

640 641
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
642 643 644
        FakeCommand(command: <String>[
          'lipo',
          '-info',
645
          binary.path,
646 647 648
        ], stdout: 'Architectures in the fat file:'),
        FakeCommand(command: <String>[
          'lipo',
649
          binary.path,
650 651 652 653
          '-verify_arch',
          'arm64',
          'armv7',
        ], exitCode: 1),
654
      ]);
655

656
      await expectLater(
657 658 659 660 661 662 663
        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:'),
        )),
      );
664 665
    });

666
    testWithoutContext('fails when lipo extract fails', () async {
667
      binary.createSync(recursive: true);
668 669 670 671 672 673 674 675 676 677

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

682 683
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
684 685 686
        FakeCommand(command: <String>[
          'lipo',
          '-info',
687
          binary.path,
688 689 690
        ], stdout: 'Architectures in the fat file:'),
        FakeCommand(command: <String>[
          'lipo',
691
          binary.path,
692 693 694 695 696 697 698
          '-verify_arch',
          'arm64',
          'armv7',
        ]),
        FakeCommand(command: <String>[
          'lipo',
          '-output',
699
          binary.path,
700 701 702 703
          '-extract',
          'arm64',
          '-extract',
          'armv7',
704
          binary.path,
705 706
        ], exitCode: 1,
        stderr: 'lipo error'),
707
      ]);
708

709
      await expectLater(
710
        const DebugUnpackIOS().build(environment),
711 712
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
713
          'description',
714
          contains('Failed to extract arm64 armv7 for output/Flutter.framework/Flutter.\nlipo error\nRunning lipo -info:\nArchitectures in the fat file:'),
715 716
        )),
      );
717 718
    });

719
    testWithoutContext('skips thin framework', () async {
720
      binary.createSync(recursive: true);
721 722 723 724 725 726 727 728 729 730

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

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

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

746
      expect(processManager, hasNoRemainingExpectations);
747 748
    });

749
    testWithoutContext('thins fat framework', () async {
750
      binary.createSync(recursive: true);
751 752 753 754 755 756 757 758 759 760

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

765 766
      processManager.addCommands(<FakeCommand>[
        copyPhysicalFrameworkCommand,
767 768 769
        FakeCommand(command: <String>[
          'lipo',
          '-info',
770
          binary.path,
771 772 773
        ], stdout: 'Architectures in the fat file:'),
        FakeCommand(command: <String>[
          'lipo',
774
          binary.path,
775 776 777 778 779 780 781
          '-verify_arch',
          'arm64',
          'armv7',
        ]),
        FakeCommand(command: <String>[
          'lipo',
          '-output',
782
          binary.path,
783 784 785 786
          '-extract',
          'arm64',
          '-extract',
          'armv7',
787
          binary.path,
788
        ]),
789
        xattrCommand,
790
        adHocCodesignCommand,
791
      ]);
792

793
      await const DebugUnpackIOS().build(environment);
794
      expect(processManager, hasNoRemainingExpectations);
795
    });
796 797

    testWithoutContext('fails when bitcode strip fails', () async {
798
      binary.createSync(recursive: true);
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813

      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>[
814 815 816 817 818 819 820 821 822
        FakeCommand(command: <String>[
          'rsync',
          '-av',
          '--delete',
          '--filter',
          '- .DS_Store/',
          'Artifact.flutterFramework.TargetPlatform.ios.release.EnvironmentType.physical',
          outputDir.path,
        ]),
823 824
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
825 826 827 828
        FakeCommand(command: <String>[
          'xcrun',
          'bitcode_strip',
          binary.path,
829
          '-r',
830 831 832 833 834 835
          '-o',
          binary.path,
        ], exitCode: 1, stderr: 'bitcode_strip error'),
      ]);

      await expectLater(
836
        const ReleaseUnpackIOS().build(environment),
837 838 839 840 841 842
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
          'description',
          contains('Failed to strip bitcode for output/Flutter.framework/Flutter.\nbitcode_strip error'),
        )),
      );
843

844
      expect(processManager, hasNoRemainingExpectations);
845 846 847
    });

    testWithoutContext('strips framework', () async {
848
      binary.createSync(recursive: true);
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864

      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,
865 866
        lipoCommandNonFatResult,
        lipoVerifyArm64Command,
867
        xattrCommand,
868
        adHocCodesignCommand,
869 870 871
      ]);
      await const DebugUnpackIOS().build(environment);

872
      expect(processManager, hasNoRemainingExpectations);
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
    });

    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,
896
        xattrCommand,
897
        FakeCommand(command: <String>[
898 899 900 901 902 903 904 905 906 907 908
            'codesign',
            '--force',
            '--sign',
            'ABC123',
            '--timestamp=none',
            binary.path,
          ],
          exitCode: 1,
          stderr: 'codesign error',
          stdout: 'codesign info',
        ),
909 910 911
      ]);

      await expectLater(
912 913 914 915
        const DebugUnpackIOS().build(environment),
        throwsA(isException.having(
          (Exception exception) => exception.toString(),
          'description',
916
          contains('Failed to codesign output/Flutter.framework/Flutter with identity ABC123.\ncodesign info\ncodesign error'),
917 918
        )),
      );
919

920
      expect(processManager, hasNoRemainingExpectations);
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
    });

    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,
944
        xattrCommand,
945
        FakeCommand(command: <String>[
946 947 948 949 950
          'codesign',
          '--force',
          '--sign',
          'ABC123',
          '--timestamp=none',
951 952 953 954 955
          binary.path,
        ]),
      ]);
      await const DebugUnpackIOS().build(environment);

956
      expect(processManager, hasNoRemainingExpectations);
957
    });
958
  });
959
}