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

5 6
// @dart = 2.8

7
import 'package:file/memory.dart';
8
import 'package:flutter_tools/src/artifacts.dart';
9 10
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
11
import 'package:flutter_tools/src/base/logger.dart';
12
import 'package:flutter_tools/src/base/platform.dart';
13
import 'package:flutter_tools/src/base/version.dart';
14
import 'package:flutter_tools/src/build_info.dart';
15
import 'package:flutter_tools/src/ios/xcode_build_settings.dart';
16
import 'package:flutter_tools/src/ios/xcodeproj.dart';
17
import 'package:flutter_tools/src/project.dart';
18
import 'package:flutter_tools/src/reporting/reporting.dart';
19

20 21
import '../../src/common.dart';
import '../../src/context.dart';
22
import '../../src/fake_process_manager.dart';
23 24

const String xcodebuild = '/usr/bin/xcodebuild';
25 26

void main() {
27 28
  group('MockProcessManager', () {
    setUp(() {
29
      final FileSystem fileSystem = MemoryFileSystem.test();
30 31 32 33
      fileSystem.file(xcodebuild).createSync(recursive: true);
    });
  });

34 35 36 37 38 39 40
  const FakeCommand kWhichSysctlCommand = FakeCommand(
    command: <String>[
      'which',
      'sysctl',
    ],
  );

41 42
  // x64 host.
  const FakeCommand kx64CheckCommand = FakeCommand(
43 44 45 46 47 48 49
    command: <String>[
      'sysctl',
      'hw.optional.arm64',
    ],
    exitCode: 1,
  );

50 51 52 53 54 55 56 57 58
  // ARM host.
  const FakeCommand kARMCheckCommand = FakeCommand(
    command: <String>[
      'sysctl',
      'hw.optional.arm64',
    ],
    stdout: 'hw.optional.arm64: 1',
  );

59
  FakeProcessManager fakeProcessManager;
60 61 62 63 64 65
  XcodeProjectInterpreter xcodeProjectInterpreter;
  FakePlatform platform;
  FileSystem fileSystem;
  BufferLogger logger;

  setUp(() {
66
    fakeProcessManager = FakeProcessManager.empty();
67
    platform = FakePlatform(operatingSystem: 'macos');
68
    fileSystem = MemoryFileSystem.test();
69
    fileSystem.file(xcodebuild).createSync(recursive: true);
70
    logger = BufferLogger.test();
71 72 73 74
    xcodeProjectInterpreter = XcodeProjectInterpreter(
      logger: logger,
      fileSystem: fileSystem,
      platform: platform,
75
      processManager: fakeProcessManager,
76
      usage: TestUsage(),
77 78
    );
  });
79

80
  testWithoutContext('xcodebuild versionText returns null when xcodebuild is not fully installed', () {
81
    fakeProcessManager.addCommands(const <FakeCommand>[
82
      kWhichSysctlCommand,
83
      kx64CheckCommand,
84 85 86 87 88 89 90 91
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: "xcode-select: error: tool 'xcodebuild' requires Xcode, "
            "but active developer directory '/Library/Developer/CommandLineTools' "
            'is a command line tools instance',
        exitCode: 1,
      ),
    ]);
92

93
    expect(xcodeProjectInterpreter.versionText, isNull);
94
    expect(fakeProcessManager, hasNoRemainingExpectations);
95
  });
96

97 98 99
  testWithoutContext('xcodebuild versionText returns null when xcodebuild is not installed', () {
    fakeProcessManager.addCommands(const <FakeCommand>[
      kWhichSysctlCommand,
100
      kx64CheckCommand,
101 102 103 104 105 106 107 108 109
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        exception: ProcessException(xcodebuild, <String>['-version']),
      ),
    ]);

    expect(xcodeProjectInterpreter.versionText, isNull);
  });

110
  testWithoutContext('xcodebuild versionText returns formatted version text', () {
111
    fakeProcessManager.addCommands(const <FakeCommand>[
112
      kWhichSysctlCommand,
113
      kx64CheckCommand,
114 115 116 117 118
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode 8.3.3\nBuild version 8E3004b',
      ),
    ]);
119

120
    expect(xcodeProjectInterpreter.versionText, 'Xcode 8.3.3, Build version 8E3004b');
121
    expect(fakeProcessManager, hasNoRemainingExpectations);
122
  });
123

124
  testWithoutContext('xcodebuild versionText handles Xcode version string with unexpected format', () {
125
    fakeProcessManager.addCommands(const <FakeCommand>[
126
      kWhichSysctlCommand,
127
      kx64CheckCommand,
128 129 130 131 132
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
      ),
    ]);
133

134
    expect(xcodeProjectInterpreter.versionText, 'Xcode Ultra5000, Build version 8E3004b');
135
    expect(fakeProcessManager, hasNoRemainingExpectations);
136
  });
137

138
  testWithoutContext('xcodebuild version parts can be parsed', () {
139
    fakeProcessManager.addCommands(const <FakeCommand>[
140
      kWhichSysctlCommand,
141
      kx64CheckCommand,
142 143 144 145 146
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode 11.4.1\nBuild version 11N111s',
      ),
    ]);
147

148
    expect(xcodeProjectInterpreter.version, Version(11, 4, 1));
149
    expect(fakeProcessManager, hasNoRemainingExpectations);
150
  });
151

152
  testWithoutContext('xcodebuild minor and patch version default to 0', () {
153
    fakeProcessManager.addCommands(const <FakeCommand>[
154
      kWhichSysctlCommand,
155
      kx64CheckCommand,
156 157 158 159 160
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode 11\nBuild version 11N111s',
      ),
    ]);
161

162
    expect(xcodeProjectInterpreter.version, Version(11, 0, 0));
163
    expect(fakeProcessManager, hasNoRemainingExpectations);
164
  });
165

166
  testWithoutContext('xcodebuild version parts is null when version has unexpected format', () {
167
    fakeProcessManager.addCommands(const <FakeCommand>[
168
      kWhichSysctlCommand,
169
      kx64CheckCommand,
170 171 172 173 174
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
      ),
    ]);
175
    expect(xcodeProjectInterpreter.version, isNull);
176
    expect(fakeProcessManager, hasNoRemainingExpectations);
177
  });
178

179
  testWithoutContext('xcodebuild isInstalled is false when not on MacOS', () {
180
    final Platform platform = FakePlatform(operatingSystem: 'notMacOS');
181 182 183 184
    xcodeProjectInterpreter = XcodeProjectInterpreter(
      logger: logger,
      fileSystem: fileSystem,
      platform: platform,
185
      processManager: fakeProcessManager,
186
      usage: TestUsage(),
187 188 189 190
    );
    fileSystem.file(xcodebuild).deleteSync();

    expect(xcodeProjectInterpreter.isInstalled, isFalse);
191
    expect(fakeProcessManager, hasNoRemainingExpectations);
192 193 194 195 196 197
  });

  testWithoutContext('xcodebuild isInstalled is false when xcodebuild does not exist', () {
    fileSystem.file(xcodebuild).deleteSync();

    expect(xcodeProjectInterpreter.isInstalled, isFalse);
198
    expect(fakeProcessManager, hasNoRemainingExpectations);
199 200
  });

201 202 203
  testWithoutContext(
      'xcodebuild isInstalled is false when Xcode is not fully installed', () {
    fakeProcessManager.addCommands(const <FakeCommand>[
204
      kWhichSysctlCommand,
205
      kx64CheckCommand,
206 207 208 209 210 211 212 213
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: "xcode-select: error: tool 'xcodebuild' requires Xcode, "
            "but active developer directory '/Library/Developer/CommandLineTools' "
            'is a command line tools instance',
        exitCode: 1,
      ),
    ]);
214

215
    expect(xcodeProjectInterpreter.isInstalled, isFalse);
216
    expect(fakeProcessManager, hasNoRemainingExpectations);
217
  });
218

219
  testWithoutContext('xcodebuild isInstalled is false when version has unexpected format', () {
220
    fakeProcessManager.addCommands(const <FakeCommand>[
221
      kWhichSysctlCommand,
222
      kx64CheckCommand,
223 224 225 226 227
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode Ultra5000\nBuild version 8E3004b',
      ),
    ]);
228

229
    expect(xcodeProjectInterpreter.isInstalled, isFalse);
230
    expect(fakeProcessManager, hasNoRemainingExpectations);
231
  });
232

233
  testWithoutContext('xcodebuild isInstalled is true when version has expected format', () {
234
    fakeProcessManager.addCommands(const <FakeCommand>[
235
      kWhichSysctlCommand,
236
      kx64CheckCommand,
237 238 239 240 241
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-version'],
        stdout: 'Xcode 8.3.3\nBuild version 8E3004b',
      ),
    ]);
242

243
    expect(xcodeProjectInterpreter.isInstalled, isTrue);
244
    expect(fakeProcessManager, hasNoRemainingExpectations);
245
  });
246

247 248
  testWithoutContext('xcrun runs natively on arm64', () {
    fakeProcessManager.addCommands(const <FakeCommand>[
249
      kWhichSysctlCommand,
250
      kARMCheckCommand,
251 252 253 254 255 256 257
    ]);

    expect(xcodeProjectInterpreter.xcrunCommand(), <String>[
      '/usr/bin/arch',
      '-arm64e',
      'xcrun',
    ]);
258
    expect(fakeProcessManager, hasNoRemainingExpectations);
259 260
  });

261
  testUsingContext('xcodebuild build settings is empty when xcodebuild failed to get the build settings', () async {
262
    platform.environment = const <String, String>{};
263

264
    fakeProcessManager.addCommands(<FakeCommand>[
265
      kWhichSysctlCommand,
266
      const FakeCommand(
267 268 269 270 271 272 273 274 275 276 277 278 279 280
        command: <String>[
          'sysctl',
          'hw.optional.arm64',
        ],
        exitCode: 1,
      ),
      FakeCommand(
        command: <String>[
          'xcrun',
          'xcodebuild',
          '-project',
          '/',
          '-scheme',
          'Free',
281 282
          '-destination',
          'id=123',
283 284
          '-showBuildSettings',
          'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}',
285 286 287 288
        ],
        exitCode: 1,
      ),
    ]);
289

290
    expect(
291
        await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(deviceId: '123', scheme: 'Free')),
292
        const <String, String>{});
293
    expect(fakeProcessManager, hasNoRemainingExpectations);
294 295 296
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
297 298
  });

299
  testUsingContext('build settings passes in the simulator SDK', () async {
300 301
    platform.environment = const <String, String>{};

302 303
    fakeProcessManager.addCommands(<FakeCommand>[
      kWhichSysctlCommand,
304
      kx64CheckCommand,
305 306 307 308 309 310 311 312
      FakeCommand(
        command: <String>[
          'xcrun',
          'xcodebuild',
          '-project',
          '/',
          '-sdk',
          'iphonesimulator',
313 314
          '-destination',
          'generic/platform=iOS Simulator',
315 316 317 318 319 320 321 322 323 324 325 326 327 328
          '-showBuildSettings',
          'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}',
        ],
        exitCode: 1,
      ),
    ]);

    expect(
      await xcodeProjectInterpreter.getBuildSettings(
        '',
        buildContext: const XcodeProjectBuildContext(environmentType: EnvironmentType.simulator),
      ),
      const <String, String>{},
    );
329
    expect(fakeProcessManager, hasNoRemainingExpectations);
330 331 332 333 334 335 336 337 338
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
  });

  testUsingContext('build settings accepts an empty scheme', () async {
    platform.environment = const <String, String>{};

    fakeProcessManager.addCommands(<FakeCommand>[
339
      kWhichSysctlCommand,
340
      kx64CheckCommand,
341 342 343 344 345 346
      FakeCommand(
        command: <String>[
          'xcrun',
          'xcodebuild',
          '-project',
          '/',
347 348
          '-destination',
          'generic/platform=iOS',
349 350
          '-showBuildSettings',
          'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}',
351 352 353 354
        ],
        exitCode: 1,
      ),
    ]);
355

356 357
    expect(await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext()),
        const <String, String>{});
358
    expect(fakeProcessManager, hasNoRemainingExpectations);
359 360 361
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
362 363
  });

364
  testUsingContext('xcodebuild build settings contains Flutter Xcode environment variables', () async {
365
    platform.environment = const <String, String>{
366 367
      'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',
      'FLUTTER_XCODE_ARCHS': 'arm64'
368
    };
369
    fakeProcessManager.addCommands(<FakeCommand>[
370
      kWhichSysctlCommand,
371
      kx64CheckCommand,
372 373 374 375 376 377 378 379
      FakeCommand(
        command: <String>[
          'xcrun',
          'xcodebuild',
          '-project',
          fileSystem.path.separator,
          '-scheme',
          'Free',
380 381
          '-destination',
          'generic/platform=iOS',
382
          '-showBuildSettings',
383
          'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}',
384 385 386 387 388
          'CODE_SIGN_STYLE=Manual',
          'ARCHS=arm64'
        ],
      ),
    ]);
389 390 391
    expect(
        await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Free')),
        const <String, String>{});
392
    expect(fakeProcessManager, hasNoRemainingExpectations);
393 394 395
  }, overrides: <Type, Generator>{
    FileSystem: () => fileSystem,
    ProcessManager: () => FakeProcessManager.any(),
396 397 398
  });

  testWithoutContext('xcodebuild clean contains Flutter Xcode environment variables', () async {
399
    platform.environment = const <String, String>{
400 401
      'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',
      'FLUTTER_XCODE_ARCHS': 'arm64'
402 403
    };

404
    fakeProcessManager.addCommands(const <FakeCommand>[
405
      kWhichSysctlCommand,
406
      kx64CheckCommand,
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
      FakeCommand(
        command: <String>[
          'xcrun',
          'xcodebuild',
          '-workspace',
          'workspace_path',
          '-scheme',
          'Free',
          '-quiet',
          'clean',
          'CODE_SIGN_STYLE=Manual',
          'ARCHS=arm64'
        ],
      ),
    ]);
422

423
    await xcodeProjectInterpreter.cleanWorkspace('workspace_path', 'Free');
424
    expect(fakeProcessManager, hasNoRemainingExpectations);
425 426 427 428
  });

  testWithoutContext('xcodebuild -list getInfo returns something when xcodebuild -list succeeds', () async {
    const String workingDirectory = '/';
429
    fakeProcessManager.addCommands(const <FakeCommand>[
430
      kWhichSysctlCommand,
431
      kx64CheckCommand,
432 433 434 435
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-list'],
      ),
    ]);
436

437 438 439 440
    final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter(
      logger: logger,
      fileSystem: fileSystem,
      platform: platform,
441
      processManager: fakeProcessManager,
442
      usage: TestUsage(),
443 444 445
    );

    expect(await xcodeProjectInterpreter.getInfo(workingDirectory), isNotNull);
446
    expect(fakeProcessManager, hasNoRemainingExpectations);
447 448 449 450 451
  });

  testWithoutContext('xcodebuild -list getInfo throws a tool exit when it is unable to find a project', () async {
    const String workingDirectory = '/';
    const String stderr = 'Useful Xcode failure message about missing project.';
452

453
    fakeProcessManager.addCommands(const <FakeCommand>[
454
      kWhichSysctlCommand,
455
      kx64CheckCommand,
456 457 458 459 460 461
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-list'],
        exitCode: 66,
        stderr: stderr,
      ),
    ]);
462

463 464 465 466
    final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter(
      logger: logger,
      fileSystem: fileSystem,
      platform: platform,
467
      processManager: fakeProcessManager,
468
      usage: TestUsage(),
469 470
    );

471 472 473 474 475 476 477 478 479 480
    expect(() => xcodeProjectInterpreter.getInfo(workingDirectory), throwsToolExit(message: stderr));
    expect(fakeProcessManager, hasNoRemainingExpectations);
  });

  testWithoutContext('xcodebuild -list getInfo throws a tool exit when project is corrupted', () async {
    const String workingDirectory = '/';
    const String stderr = 'Useful Xcode failure message about corrupted project.';

    fakeProcessManager.addCommands(const <FakeCommand>[
      kWhichSysctlCommand,
481
      kx64CheckCommand,
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
      FakeCommand(
        command: <String>['xcrun', 'xcodebuild', '-list'],
        exitCode: 74,
        stderr: stderr,
      ),
    ]);

    final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter(
      logger: logger,
      fileSystem: fileSystem,
      platform: platform,
      processManager: fakeProcessManager,
      usage: TestUsage(),
    );

    expect(() => xcodeProjectInterpreter.getInfo(workingDirectory), throwsToolExit(message: stderr));
    expect(fakeProcessManager, hasNoRemainingExpectations);
499 500 501 502
  });

  testWithoutContext('Xcode project properties from default project can be parsed', () {
    const String output = '''
503 504 505 506 507 508 509 510 511 512 513 514 515 516
Information about project "Runner":
    Targets:
        Runner

    Build Configurations:
        Debug
        Release

    If no build configuration is specified and -scheme is not passed then "Release" is used.

    Schemes:
        Runner

''';
517
    final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output, logger);
518 519 520 521 522 523 524
    expect(info.targets, <String>['Runner']);
    expect(info.schemes, <String>['Runner']);
    expect(info.buildConfigurations, <String>['Debug', 'Release']);
  });

  testWithoutContext('Xcode project properties from project with custom schemes can be parsed', () {
    const String output = '''
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
Information about project "Runner":
    Targets:
        Runner

    Build Configurations:
        Debug (Free)
        Debug (Paid)
        Release (Free)
        Release (Paid)

    If no build configuration is specified and -scheme is not passed then "Release (Free)" is used.

    Schemes:
        Free
        Paid

''';
542
    final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output, logger);
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
    expect(info.targets, <String>['Runner']);
    expect(info.schemes, <String>['Free', 'Paid']);
    expect(info.buildConfigurations, <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)']);
  });

  testWithoutContext('expected scheme for non-flavored build is Runner', () {
    expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.debug), 'Runner');
    expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.profile), 'Runner');
    expect(XcodeProjectInfo.expectedSchemeFor(BuildInfo.release), 'Runner');
  });

  testWithoutContext('expected build configuration for non-flavored build is derived from BuildMode', () {
    expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
    expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.profile, 'Runner'), 'Profile');
    expect(XcodeProjectInfo.expectedBuildConfigurationFor(BuildInfo.release, 'Runner'), 'Release');
558
  });
559

560
  testWithoutContext('expected scheme for flavored build is the title-cased flavor', () {
561 562 563
    expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.debug, 'hello', treeShakeIcons: false)), 'Hello');
    expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.profile, 'HELLO', treeShakeIcons: false)), 'HELLO');
    expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello', treeShakeIcons: false)), 'Hello');
564 565
  });
  testWithoutContext('expected build configuration for flavored build is Mode-Flavor', () {
566 567 568
    expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.debug, 'hello', treeShakeIcons: false), 'Hello'), 'Debug-Hello');
    expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.profile, 'HELLO', treeShakeIcons: false), 'Hello'), 'Profile-Hello');
    expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello', treeShakeIcons: false), 'Hello'), 'Release-Hello');
569 570 571
  });

  testWithoutContext('scheme for default project is Runner', () {
572
    final XcodeProjectInfo info = XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner'], logger);
573 574 575 576

    expect(info.schemeFor(BuildInfo.debug), 'Runner');
    expect(info.schemeFor(BuildInfo.profile), 'Runner');
    expect(info.schemeFor(BuildInfo.release), 'Runner');
577
    expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown', treeShakeIcons: false)), isNull);
578 579 580
  });

  testWithoutContext('build configuration for default project is matched against BuildMode', () {
581
    final XcodeProjectInfo info = XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Profile', 'Release'], <String>['Runner'], logger);
582 583 584 585 586 587 588 589 590 591 592

    expect(info.buildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
    expect(info.buildConfigurationFor(BuildInfo.profile, 'Runner'), 'Profile');
    expect(info.buildConfigurationFor(BuildInfo.release, 'Runner'), 'Release');
  });

  testWithoutContext('scheme for project with custom schemes is matched against flavor', () {
    final XcodeProjectInfo info = XcodeProjectInfo(
      <String>['Runner'],
      <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)'],
      <String>['Free', 'Paid'],
593
      logger,
594 595
    );

596 597 598
    expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false)), 'Free');
    expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false)), 'Free');
    expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid', treeShakeIcons: false)), 'Paid');
599
    expect(info.schemeFor(BuildInfo.debug), isNull);
600
    expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown', treeShakeIcons: false)), isNull);
601 602
  });

603 604 605 606 607 608 609 610 611
  testWithoutContext('reports default scheme error and exit', () {
    final XcodeProjectInfo defaultInfo = XcodeProjectInfo(
      <String>[],
      <String>[],
      <String>['Runner'],
      logger,
    );

    expect(
612
      defaultInfo.reportFlavorNotFoundAndExit,
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
      throwsToolExit(
        message: 'The Xcode project does not define custom schemes. You cannot use the --flavor option.'
      ),
    );
  });

  testWithoutContext('reports custom scheme error and exit', () {
    final XcodeProjectInfo info = XcodeProjectInfo(
      <String>[],
      <String>[],
      <String>['Free', 'Paid'],
      logger,
    );

    expect(
628
      info.reportFlavorNotFoundAndExit,
629 630 631 632 633 634
      throwsToolExit(
        message: 'You must specify a --flavor option to select one of the available schemes.'
      ),
    );
  });

635 636 637 638 639
  testWithoutContext('build configuration for project with custom schemes is matched against BuildMode and flavor', () {
    final XcodeProjectInfo info = XcodeProjectInfo(
      <String>['Runner'],
      <String>['debug (free)', 'Debug paid', 'profile - Free', 'Profile-Paid', 'release - Free', 'Release-Paid'],
      <String>['Free', 'Paid'],
640
      logger,
641 642
    );

643 644 645 646
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false), 'Free'), 'debug (free)');
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid', treeShakeIcons: false), 'Paid'), 'Debug paid');
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE', treeShakeIcons: false), 'Free'), 'profile - Free');
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid', treeShakeIcons: false), 'Paid'), 'Release-Paid');
647 648 649 650 651 652 653
  });

  testWithoutContext('build configuration for project with inconsistent naming is null', () {
    final XcodeProjectInfo info = XcodeProjectInfo(
      <String>['Runner'],
      <String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
      <String>['Free', 'Paid'],
654
      logger,
655
    );
656 657 658
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free', treeShakeIcons: false), 'Free'), null);
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false), 'Free'), null);
    expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid', treeShakeIcons: false), 'Paid'), null);
659 660
  });
 group('environmentVariablesAsXcodeBuildSettings', () {
661 662 663
    FakePlatform platform;

    setUp(() {
664
      platform = FakePlatform();
665 666
    });

667
    testWithoutContext('environment variables as Xcode build settings', () {
668
      platform.environment = const <String, String>{
669 670 671 672
        'Ignored': 'Bogus',
        'FLUTTER_NOT_XCODE': 'Bogus',
        'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',
        'FLUTTER_XCODE_ARCHS': 'arm64'
673
      };
674
      final List<String> environmentVariablesAsBuildSettings = environmentVariablesAsXcodeBuildSettings(platform);
675 676 677 678
      expect(environmentVariablesAsBuildSettings, <String>['CODE_SIGN_STYLE=Manual', 'ARCHS=arm64']);
    });
  });

679
  group('updateGeneratedXcodeProperties', () {
680
    Artifacts localArtifacts;
681 682 683 684
    FakePlatform macOS;
    FileSystem fs;

    setUp(() {
685
      fs = MemoryFileSystem.test();
686
      localArtifacts = Artifacts.test(localEngine: 'out/ios_profile_arm');
687
      macOS = FakePlatform(operatingSystem: 'macos');
688
      fs.file(xcodebuild).createSync(recursive: true);
689 690
    });

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
    group('arm simulator', () {
      FakeProcessManager fakeProcessManager;
      XcodeProjectInterpreter xcodeProjectInterpreter;

      setUp(() {
        fakeProcessManager = FakeProcessManager.empty();
        xcodeProjectInterpreter = XcodeProjectInterpreter.test(processManager: fakeProcessManager);
      });

      testUsingContext('does not exclude arm64 simulator when supported by all plugins', () async {
        const BuildInfo buildInfo = BuildInfo.debug;
        final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
        final Directory podXcodeProject = project.ios.hostAppRoot.childDirectory('Pods').childDirectory('Pods.xcodeproj')
          ..createSync(recursive: true);

706
        final String buildDirectory = fileSystem.path.absolute('build', 'ios');
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
        fakeProcessManager.addCommands(<FakeCommand>[
          kWhichSysctlCommand,
          kARMCheckCommand,
          FakeCommand(
            command: <String>[
              '/usr/bin/arch',
              '-arm64e',
              'xcrun',
              'xcodebuild',
              '-alltargets',
              '-sdk',
              'iphonesimulator',
              '-project',
              podXcodeProject.path,
              '-showBuildSettings',
722 723
              'BUILD_DIR=$buildDirectory',
              'OBJROOT=$buildDirectory',
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
            ],
            stdout: '''
Build settings for action build and target plugin1:
    ENABLE_BITCODE = NO;
    EXCLUDED_ARCHS = i386;
    INFOPLIST_FILE = Runner/Info.plist;
    UNRELATED_BUILD_SETTING = arm64;

Build settings for action build and target plugin2:
    ENABLE_BITCODE = NO;
    EXCLUDED_ARCHS = i386;
    INFOPLIST_FILE = Runner/Info.plist;
    UNRELATED_BUILD_SETTING = arm64;
				'''
          ),
        ]);
        await updateGeneratedXcodeProperties(
          project: project,
          buildInfo: buildInfo,
        );

        final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
        expect(config.readAsStringSync(), contains('EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386\n'));
        expect(fakeProcessManager, hasNoRemainingExpectations);
      }, overrides: <Type, Generator>{
        Artifacts: () => localArtifacts,
        Platform: () => macOS,
        FileSystem: () => fs,
        ProcessManager: () => fakeProcessManager,
        XcodeProjectInterpreter: () => xcodeProjectInterpreter,
      });

      testUsingContext('excludes arm64 simulator when build setting fetch fails', () async {
        const BuildInfo buildInfo = BuildInfo.debug;
        final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
        final Directory podXcodeProject = project.ios.hostAppRoot.childDirectory('Pods').childDirectory('Pods.xcodeproj')
          ..createSync(recursive: true);

762
        final String buildDirectory = fileSystem.path.absolute('build', 'ios');
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
        fakeProcessManager.addCommands(<FakeCommand>[
          kWhichSysctlCommand,
          kARMCheckCommand,
          FakeCommand(
              command: <String>[
                '/usr/bin/arch',
                '-arm64e',
                'xcrun',
                'xcodebuild',
                '-alltargets',
                '-sdk',
                'iphonesimulator',
                '-project',
                podXcodeProject.path,
                '-showBuildSettings',
778 779
                'BUILD_DIR=$buildDirectory',
                'OBJROOT=$buildDirectory',
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
              ],
              exitCode: 1,
          ),
        ]);
        await updateGeneratedXcodeProperties(
          project: project,
          buildInfo: buildInfo,
        );

        final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
        expect(config.readAsStringSync(), contains('EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 arm64\n'));
        expect(fakeProcessManager, hasNoRemainingExpectations);
      }, overrides: <Type, Generator>{
        Artifacts: () => localArtifacts,
        Platform: () => macOS,
        FileSystem: () => fs,
        ProcessManager: () => fakeProcessManager,
        XcodeProjectInterpreter: () => xcodeProjectInterpreter,
      });

      testUsingContext('excludes arm64 simulator when unsupported by plugins', () async {
        const BuildInfo buildInfo = BuildInfo.debug;
        final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
        final Directory podXcodeProject = project.ios.hostAppRoot.childDirectory('Pods').childDirectory('Pods.xcodeproj')
          ..createSync(recursive: true);

806
        final String buildDirectory = fileSystem.path.absolute('build', 'ios');
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
        fakeProcessManager.addCommands(<FakeCommand>[
          kWhichSysctlCommand,
          kARMCheckCommand,
          FakeCommand(
              command: <String>[
                '/usr/bin/arch',
                '-arm64e',
                'xcrun',
                'xcodebuild',
                '-alltargets',
                '-sdk',
                'iphonesimulator',
                '-project',
                podXcodeProject.path,
                '-showBuildSettings',
822 823
                'BUILD_DIR=$buildDirectory',
                'OBJROOT=$buildDirectory',
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
              ],
              stdout: '''
Build settings for action build and target plugin1:
    ENABLE_BITCODE = NO;
    EXCLUDED_ARCHS = i386;
    INFOPLIST_FILE = Runner/Info.plist;
    UNRELATED_BUILD_SETTING = arm64;

Build settings for action build and target plugin2:
    ENABLE_BITCODE = NO;
    EXCLUDED_ARCHS = i386 arm64;
    INFOPLIST_FILE = Runner/Info.plist;
    UNRELATED_BUILD_SETTING = arm64;
				'''
          ),
        ]);
        await updateGeneratedXcodeProperties(
          project: project,
          buildInfo: buildInfo,
        );

        final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
        expect(config.readAsStringSync(), contains('EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 arm64\n'));
        expect(fakeProcessManager, hasNoRemainingExpectations);
      }, overrides: <Type, Generator>{
        Artifacts: () => localArtifacts,
        Platform: () => macOS,
        FileSystem: () => fs,
        ProcessManager: () => fakeProcessManager,
        XcodeProjectInterpreter: () => xcodeProjectInterpreter,
      });
    });

857
    void testUsingOsxContext(String description, dynamic Function() testMethod) {
858
      testUsingContext(description, testMethod, overrides: <Type, Generator>{
859
        Artifacts: () => localArtifacts,
860 861
        Platform: () => macOS,
        FileSystem: () => fs,
862
        ProcessManager: () => FakeProcessManager.any(),
863 864 865
      });
    }

866
    testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async {
867
      const BuildInfo buildInfo = BuildInfo.debug;
868
      final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
869 870
      await updateGeneratedXcodeProperties(
        project: project,
871 872 873
        buildInfo: buildInfo,
      );

874
      final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
875 876 877 878
      expect(config.existsSync(), isTrue);

      final String contents = config.readAsStringSync();
      expect(contents.contains('ARCHS=armv7'), isTrue);
879
      expect(contents.contains('EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386'), isTrue);
880

881
      final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh');
882 883 884 885
      expect(buildPhaseScript.existsSync(), isTrue);

      final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync();
      expect(buildPhaseScriptContents.contains('ARCHS=armv7'), isTrue);
886
      expect(buildPhaseScriptContents.contains('EXCLUDED_ARCHS'), isFalse);
887 888
    });

889
    testUsingOsxContext('does not exclude arm64 simulator when there are no plugins', () async {
890 891 892 893 894 895 896 897 898 899 900 901 902 903
      const BuildInfo buildInfo = BuildInfo.debug;
      final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
      await updateGeneratedXcodeProperties(
        project: project,
        buildInfo: buildInfo,
      );

      final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
      expect(config.readAsStringSync(), contains('EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386\n'));

      final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh');
      expect(buildPhaseScript.readAsStringSync(), isNot(contains('EXCLUDED_ARCHS')));
    });

904
    testUsingOsxContext('sets TRACK_WIDGET_CREATION=true when trackWidgetCreation is true', () async {
905
      const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, treeShakeIcons: false);
906
      final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
907 908
      await updateGeneratedXcodeProperties(
        project: project,
909 910 911
        buildInfo: buildInfo,
      );

912
      final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
913 914 915 916
      expect(config.existsSync(), isTrue);

      final String contents = config.readAsStringSync();
      expect(contents.contains('TRACK_WIDGET_CREATION=true'), isTrue);
917

918
      final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh');
919 920 921 922
      expect(buildPhaseScript.existsSync(), isTrue);

      final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync();
      expect(buildPhaseScriptContents.contains('TRACK_WIDGET_CREATION=true'), isTrue);
923 924 925
    });

    testUsingOsxContext('does not set TRACK_WIDGET_CREATION when trackWidgetCreation is false', () async {
926
      const BuildInfo buildInfo = BuildInfo.debug;
927
      final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
928 929
      await updateGeneratedXcodeProperties(
        project: project,
930 931 932
        buildInfo: buildInfo,
      );

933
      final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
934 935 936 937
      expect(config.existsSync(), isTrue);

      final String contents = config.readAsStringSync();
      expect(contents.contains('TRACK_WIDGET_CREATION=true'), isFalse);
938

939
      final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh');
940 941 942 943
      expect(buildPhaseScript.existsSync(), isTrue);

      final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync();
      expect(buildPhaseScriptContents.contains('TRACK_WIDGET_CREATION=true'), isFalse);
944 945
    });

946
    group('sim local engine', () {
947 948 949 950 951 952 953
      testUsingContext('sets ARCHS=x86_64 when x86 sim local engine is set', () async {
        const BuildInfo buildInfo = BuildInfo.debug;
        final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
        await updateGeneratedXcodeProperties(
          project: project,
          buildInfo: buildInfo,
        );
954

955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
        final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
        expect(config.existsSync(), isTrue);

        final String contents = config.readAsStringSync();
        expect(contents.contains('ARCHS=x86_64'), isTrue);

        final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh');
        expect(buildPhaseScript.existsSync(), isTrue);

        final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync();
        expect(buildPhaseScriptContents.contains('ARCHS=x86_64'), isTrue);
      }, overrides: <Type, Generator>{
        Artifacts: () => Artifacts.test(localEngine: 'out/ios_debug_sim_unopt'),
        Platform: () => macOS,
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
971
      });
972

973
      testUsingContext('sets ARCHS=arm64 when arm64 sim local engine is set', () async {
974 975 976 977 978 979
        const BuildInfo buildInfo = BuildInfo.debug;
        final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
        await updateGeneratedXcodeProperties(
          project: project,
          buildInfo: buildInfo,
        );
980

981 982 983 984
        final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
        expect(config.existsSync(), isTrue);

        final String contents = config.readAsStringSync();
985
        expect(contents.contains('ARCHS=arm64'), isTrue);
986 987 988 989 990

        final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh');
        expect(buildPhaseScript.existsSync(), isTrue);

        final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync();
991
        expect(buildPhaseScriptContents.contains('ARCHS=arm64'), isTrue);
992
      }, overrides: <Type, Generator>{
993
        Artifacts: () => Artifacts.test(localEngine: 'out/ios_debug_sim_arm64'),
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
        Platform: () => macOS,
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
      });
    });

    group('armv7 local engine', () {
      Artifacts localArtifacts;

      setUp(() {
        localArtifacts = Artifacts.test(localEngine: 'out/ios_profile');
      });

      testUsingContext('sets ARCHS=armv7 when armv7 local engine is set', () async {
        const BuildInfo buildInfo = BuildInfo.debug;

        final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
        await updateGeneratedXcodeProperties(
          project: project,
          buildInfo: buildInfo,
        );

        final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
        expect(config.existsSync(), isTrue);

        final String contents = config.readAsStringSync();
        expect(contents.contains('ARCHS=arm64'), isTrue);
      }, overrides: <Type, Generator>{
        Artifacts: () => localArtifacts,
        Platform: () => macOS,
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
      });
1027
    });
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038

    String propertyFor(String key, File file) {
      final List<String> properties = file
          .readAsLinesSync()
          .where((String line) => line.startsWith('$key='))
          .map((String line) => line.split('=')[1])
          .toList();
      return properties.isEmpty ? null : properties.first;
    }

    Future<void> checkBuildVersion({
1039
      String manifestString,
1040 1041 1042 1043
      BuildInfo buildInfo,
      String expectedBuildName,
      String expectedBuildNumber,
    }) async {
1044
      final File manifestFile = fs.file('path/to/project/pubspec.yaml');
1045 1046 1047
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync(manifestString);

1048
      await updateGeneratedXcodeProperties(
1049
        project: FlutterProject.fromDirectoryTest(fs.directory('path/to/project')),
1050 1051 1052
        buildInfo: buildInfo,
      );

1053
      final File localPropertiesFile = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
1054 1055
      expect(propertyFor('FLUTTER_BUILD_NAME', localPropertiesFile), expectedBuildName);
      expect(propertyFor('FLUTTER_BUILD_NUMBER', localPropertiesFile), expectedBuildNumber);
1056
      expect(propertyFor('FLUTTER_BUILD_NUMBER', localPropertiesFile), isNotNull);
1057 1058
    }

1059
    testUsingOsxContext('extract build name and number from pubspec.yaml', () async {
1060 1061 1062 1063 1064 1065 1066 1067 1068
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';

1069
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
1070
      await checkBuildVersion(
1071
        manifestString: manifest,
1072 1073 1074 1075 1076 1077
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '1',
      );
    });

1078
    testUsingOsxContext('extract build name from pubspec.yaml', () async {
1079 1080 1081 1082 1083 1084 1085 1086
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1087
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
1088
      await checkBuildVersion(
1089
        manifestString: manifest,
1090 1091
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
1092
        expectedBuildNumber: '1.0.0',
1093 1094 1095
      );
    });

1096
    testUsingOsxContext('allow build info to override build name', () async {
1097 1098 1099 1100 1101 1102 1103 1104
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1105
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
1106
      await checkBuildVersion(
1107
        manifestString: manifest,
1108 1109 1110 1111 1112 1113
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '1',
      );
    });

1114 1115 1116 1117 1118 1119 1120 1121 1122
    testUsingOsxContext('allow build info to override build name with build number fallback', () async {
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1123
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
1124 1125 1126 1127 1128 1129 1130 1131
      await checkBuildVersion(
        manifestString: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '1.0.2',
      );
    });

1132
    testUsingOsxContext('allow build info to override build number', () async {
1133 1134 1135 1136 1137 1138 1139 1140
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1141
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
1142
      await checkBuildVersion(
1143
        manifestString: manifest,
1144 1145 1146 1147 1148 1149
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '3',
      );
    });

1150
    testUsingOsxContext('allow build info to override build name and number', () async {
1151 1152 1153 1154 1155 1156 1157 1158
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1159
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
1160
      await checkBuildVersion(
1161
        manifestString: manifest,
1162 1163 1164 1165 1166 1167
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });

1168
    testUsingOsxContext('allow build info to override build name and set number', () async {
1169 1170 1171 1172 1173 1174 1175 1176
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1177
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
1178
      await checkBuildVersion(
1179
        manifestString: manifest,
1180 1181 1182 1183 1184 1185
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });

1186
    testUsingOsxContext('allow build info to set build name and number', () async {
1187 1188 1189 1190 1191 1192 1193
      const String manifest = '''
name: test
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1194
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
1195
      await checkBuildVersion(
1196
        manifestString: manifest,
1197 1198 1199 1200 1201
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });
1202 1203 1204 1205 1206 1207 1208 1209 1210

    testUsingOsxContext('default build name and number when version is missing', () async {
      const String manifest = '''
name: test
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
1211
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
1212 1213 1214 1215 1216 1217 1218
      await checkBuildVersion(
        manifestString: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '1',
      );
    });
1219
  });
1220
}