feat(shorebird_cli): support asset diffing in iOS frameworks (#1046)

This commit is contained in:
Bryan Oltman
2023-08-07 15:53:11 -04:00
committed by GitHub
parent 1ec6af9ad3
commit 4e9b202373
14 changed files with 347 additions and 133 deletions
@@ -1,4 +1,4 @@
export 'android_archive_differ.dart';
export 'file_set_diff.dart';
export 'ios_archive_differ.dart';
export 'ipa.dart';
export 'ipa_differ.dart';
@@ -2,7 +2,7 @@ import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
import 'package:shorebird_cli/src/archive_analysis/file_set_diff.dart';
/// Finds differences between two IPAs.
/// Finds differences between two IPAs or zipped Xcframeworks.
///
/// Asset changes will be in the `Assets.car` file (which is a combination of
/// the `.xcasset` catalogs in the Xcode project) and the `flutter_assets`
@@ -12,7 +12,7 @@ import 'package:shorebird_cli/src/archive_analysis/file_set_diff.dart';
/// Flutter.framework or App.framework files.
///
/// Dart changes will appear in the App.framework/App executable.
class IpaDiffer extends ArchiveDiffer {
class IosArchiveDiffer extends ArchiveDiffer {
static const binaryFiles = {
'App.framework/App',
'Flutter.framework/Flutter',
@@ -30,10 +30,10 @@ class PatchIosCommand extends ShorebirdCommand
/// {@macro patch_ios_command}
PatchIosCommand({
HashFunction? hashFn,
IpaDiffer? ipaDiffer,
IosArchiveDiffer? archiveDiffer,
IpaReader? ipaReader,
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()),
_ipaDiffer = ipaDiffer ?? IpaDiffer(),
_archiveDiffer = archiveDiffer ?? IosArchiveDiffer(),
_ipaReader = ipaReader ?? IpaReader() {
argParser
..addOption(
@@ -71,7 +71,7 @@ class PatchIosCommand extends ShorebirdCommand
'Publish new patches for a specific iOS release to Shorebird.';
final HashFunction _hashFn;
final IpaDiffer _ipaDiffer;
final IosArchiveDiffer _archiveDiffer;
final IpaReader _ipaReader;
@override
@@ -235,7 +235,7 @@ Please re-run the release command for this version or create a new release.''');
await patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: File(ipaPath),
releaseArtifactUrl: Uri.parse(releaseArtifact.url),
archiveDiffer: _ipaDiffer,
archiveDiffer: _archiveDiffer,
force: force,
);
@@ -1,10 +1,13 @@
import 'dart:io' hide Platform;
import 'package:archive/archive_io.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
@@ -12,6 +15,7 @@ import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/formatters/file_size_formatter.dart';
import 'package:shorebird_cli/src/ios.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/patch_diff_checker.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
@@ -23,7 +27,9 @@ class PatchIosFrameworkCommand extends ShorebirdCommand
with ShorebirdBuildMixin, ShorebirdArtifactMixin {
PatchIosFrameworkCommand({
HashFunction? hashFn,
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) {
IosArchiveDiffer? archiveDiffer,
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()),
_archiveDiffer = archiveDiffer ?? IosArchiveDiffer() {
argParser
..addOption(
'release-version',
@@ -46,6 +52,7 @@ of the iOS app that is using this module.''',
}
final HashFunction _hashFn;
final IosArchiveDiffer _archiveDiffer;
@override
String get name => 'ios-framework-alpha';
@@ -158,6 +165,36 @@ Please re-run the release command for this version or create a new release.''');
buildProgress.complete();
const zippedFrameworkFileName =
'${ShorebirdArtifactMixin.appXcframeworkName}.zip';
final tempDir = Directory.systemTemp.createTempSync();
final zippedFrameworkPath = p.join(
tempDir.path,
zippedFrameworkFileName,
);
ZipFileEncoder().zipDirectory(
Directory(getAppXcframeworkPath()),
filename: zippedFrameworkPath,
);
final releaseArtifact = await codePushClientWrapper.getReleaseArtifact(
appId: appId,
releaseId: release.id,
arch: 'xcframework',
platform: ReleasePlatform.ios,
);
final shouldContinue =
await patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: File(zippedFrameworkPath),
releaseArtifactUrl: Uri.parse(releaseArtifact.url),
archiveDiffer: _archiveDiffer,
force: force,
);
if (!shouldContinue) {
return ExitCode.success.code;
}
if (dryRun) {
logger
..info('No issues detected.')
@@ -1,5 +1,3 @@
import 'dart:io' hide Platform;
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
@@ -9,13 +7,14 @@ import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/ios.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
class ReleaseIosFrameworkCommand extends ShorebirdCommand
with ShorebirdBuildMixin {
with ShorebirdArtifactMixin, ShorebirdBuildMixin {
ReleaseIosFrameworkCommand() {
argParser
..addOption(
@@ -119,16 +118,10 @@ ${summary.join('\n')}
);
}
final iosBuildDir = p.join(Directory.current.path, 'build', 'ios');
final frameworkDirectory = Directory(
p.join(iosBuildDir, 'framework', 'Release'),
);
final xcframeworkPath = p.join(frameworkDirectory.path, 'App.xcframework');
await codePushClientWrapper.createIosFrameworkReleaseArtifacts(
appId: appId,
releaseId: release.id,
appFrameworkPath: xcframeworkPath,
appFrameworkPath: getAppXcframeworkPath(),
);
await codePushClientWrapper.updateReleaseStatus(
@@ -138,7 +131,8 @@ ${summary.join('\n')}
status: ReleaseStatus.active,
);
final relativeFrameworkDirectoryPath = p.relative(frameworkDirectory.path);
final relativeFrameworkDirectoryPath =
p.relative(getAppXcframeworkDirectory().path);
logger
..success('\n✅ Published Release!')
..info('''
@@ -96,6 +96,28 @@ mixin ShorebirdArtifactMixin on ShorebirdCommand {
return ipaFiles.single.path;
}
static const String appXcframeworkName = 'App.xcframework';
/// Returns the path to the App.xcframework generated by
/// `shorebird release ios-framework-alpha` or
/// `shorebird patch ios-framework-alpha`.
String getAppXcframeworkPath() {
return p.join(getAppXcframeworkDirectory().path, appXcframeworkName);
}
/// Returns the [Directory] containing the App.xcframework generated by
/// `shorebird release ios-framework-alpha` or
/// `shorebird patch ios-framework-alpha`.
Directory getAppXcframeworkDirectory() => Directory(
p.join(
Directory.current.path,
'build',
'ios',
'framework',
'Release',
),
);
/// Finds the most recently-edited app.dill file in the .dart_tool directory.
// TODO(bryanoltman): This is an enormous hack we don't know that this is
// the correct file.
@@ -0,0 +1,7 @@
The xcframework files in this folder were generated by building the stock Flutter counter app with `shorebird release ios-framework-alpha` and zipped with the `ditto` command.
Files:
- base.xcframework.zip is meant to represent an xcframework uploaded as part of a release.
- changed_asset.xcframework.zip is meant to represent an xcframework generated with a changed asset file.
- changed_dart.xcframework.zip is meant to represent an xcframework generated with a changed dart file.
@@ -0,0 +1,168 @@
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:test/test.dart';
void main() {
final ipaFixturesBasePath = p.join('test', 'fixtures', 'ipas');
final baseIpaPath = p.join(ipaFixturesBasePath, 'base.ipa');
final changedAssetIpaPath = p.join(ipaFixturesBasePath, 'asset_changes.ipa');
final changedDartIpaPath = p.join(ipaFixturesBasePath, 'dart_changes.ipa');
final changedSwiftIpaPath = p.join(ipaFixturesBasePath, 'swift_changes.ipa');
final xcframeworkFixturesBasePath = p.join(
'test',
'fixtures',
'xcframeworks',
);
final baseXcframeworkPath =
p.join(xcframeworkFixturesBasePath, 'base.xcframework.zip');
final changedAssetXcframeworkPath =
p.join(xcframeworkFixturesBasePath, 'changed_asset.xcframework.zip');
final changedDartXcframeworkPath =
p.join(xcframeworkFixturesBasePath, 'changed_dart.xcframework.zip');
group(IosArchiveDiffer, () {
late IosArchiveDiffer differ;
setUp(() {
differ = IosArchiveDiffer();
});
group('ipa', () {
group('changedPaths', () {
test('finds no differences between the same ipa', () {
expect(differ.changedFiles(baseIpaPath, baseIpaPath), isEmpty);
});
test('finds differences between two different ipas', () {
expect(
differ.changedFiles(baseIpaPath, changedAssetIpaPath).changedPaths,
{
'Payload/Runner.app/_CodeSignature/CodeResources',
'Payload/Runner.app/Runner',
'Payload/Runner.app/Frameworks/Flutter.framework/Flutter',
'Payload/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources',
'Payload/Runner.app/Frameworks/App.framework/App',
'Payload/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json',
'Symbols/4C4C4411-5555-3144-A13A-E47369D8ACD5.symbols',
'Symbols/BC970605-0A53-3457-8736-D7A870AB6E71.symbols',
'Symbols/0CBBC9EF-0745-3074-81B7-765F5B4515FD.symbols',
},
);
});
});
group('changedFiles', () {
test('detects asset changes', () {
final fileSetDiff =
differ.changedFiles(baseIpaPath, changedAssetIpaPath);
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
});
test('detects dart changes', () {
final fileSetDiff =
differ.changedFiles(baseIpaPath, changedDartIpaPath);
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
});
test('detects swift changes', () {
final fileSetDiff =
differ.changedFiles(baseIpaPath, changedSwiftIpaPath);
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
});
});
group('containsPotentiallyBreakingAssetDiffs', () {
test('returns true if a file in flutter_assets has changed', () {
final fileSetDiff = differ.changedFiles(
baseIpaPath,
changedAssetIpaPath,
);
expect(
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
isTrue,
);
});
test('returns false if no files in flutter_assets has changed', () {
final fileSetDiff = differ.changedFiles(
baseIpaPath,
changedDartIpaPath,
);
expect(
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
isFalse,
);
});
});
group('containsPotentiallyBreakingNativeDiffs', () {
test("always returns false, as we don't check for this yet", () {
final fileSetDiff = differ.changedFiles(
baseIpaPath,
changedSwiftIpaPath,
);
expect(
differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff),
isFalse,
);
});
});
});
group('xcframework', () {
group('changedPaths', () {
test('finds no differences between the same zipped xcframeworks', () {
expect(
differ.changedFiles(baseXcframeworkPath, baseXcframeworkPath),
isEmpty,
);
});
test('finds differences between two differed zipped xcframeworks', () {
expect(
differ
.changedFiles(baseXcframeworkPath, changedAssetXcframeworkPath)
.changedPaths,
{
'ios-arm64_x86_64-simulator/App.framework/_CodeSignature/CodeResources',
'ios-arm64_x86_64-simulator/App.framework/App',
'ios-arm64_x86_64-simulator/App.framework/flutter_assets/assets/asset.json',
'ios-arm64/App.framework/_CodeSignature/CodeResources',
'ios-arm64/App.framework/App',
'ios-arm64/App.framework/flutter_assets/assets/asset.json'
},
);
});
});
group('changedFiles', () {
test('detects asset changes', () {
final fileSetDiff = differ.changedFiles(
baseXcframeworkPath,
changedAssetXcframeworkPath,
);
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty);
});
test('detects dart changes', () {
final fileSetDiff = differ.changedFiles(
baseXcframeworkPath,
changedDartXcframeworkPath,
);
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty);
});
});
});
});
}
@@ -1,105 +0,0 @@
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:test/test.dart';
void main() {
final ipaFixturesBasePath = p.join('test', 'fixtures', 'ipas');
final baseIpaPath = p.join(ipaFixturesBasePath, 'base.ipa');
final changedAssetIpaPath = p.join(ipaFixturesBasePath, 'asset_changes.ipa');
final changedDartIpaPath = p.join(ipaFixturesBasePath, 'dart_changes.ipa');
final changedSwiftIpaPath = p.join(ipaFixturesBasePath, 'swift_changes.ipa');
late IpaDiffer differ;
setUp(() {
differ = IpaDiffer();
});
group(IpaDiffer, () {
group('changedPaths', () {
test('finds no differences between the same ipa', () {
expect(differ.changedFiles(baseIpaPath, baseIpaPath), isEmpty);
});
test('finds differences between two different ipas', () {
expect(
differ.changedFiles(baseIpaPath, changedAssetIpaPath).changedPaths,
{
'Payload/Runner.app/_CodeSignature/CodeResources',
'Payload/Runner.app/Runner',
'Payload/Runner.app/Frameworks/Flutter.framework/Flutter',
'Payload/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources',
'Payload/Runner.app/Frameworks/App.framework/App',
'Payload/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json',
'Symbols/4C4C4411-5555-3144-A13A-E47369D8ACD5.symbols',
'Symbols/BC970605-0A53-3457-8736-D7A870AB6E71.symbols',
'Symbols/0CBBC9EF-0745-3074-81B7-765F5B4515FD.symbols',
},
);
});
});
group('changedFiles', () {
test('detects asset changes', () {
final fileSetDiff =
differ.changedFiles(baseIpaPath, changedAssetIpaPath);
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
});
test('detects dart changes', () {
final fileSetDiff =
differ.changedFiles(baseIpaPath, changedDartIpaPath);
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
});
test('detects swift changes', () {
final fileSetDiff =
differ.changedFiles(baseIpaPath, changedSwiftIpaPath);
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
});
});
group('containsPotentiallyBreakingAssetDiffs', () {
test('returns true if a file in flutter_assets has changed', () {
final fileSetDiff = differ.changedFiles(
baseIpaPath,
changedAssetIpaPath,
);
expect(
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
isTrue,
);
});
test('returns false if no files in flutter_assets has changed', () {
final fileSetDiff = differ.changedFiles(
baseIpaPath,
changedDartIpaPath,
);
expect(
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
isFalse,
);
});
});
group('containsPotentiallyBreakingNativeDiffs', () {
test("always returns false, as we don't check for this yet", () {
final fileSetDiff = differ.changedFiles(
baseIpaPath,
changedSwiftIpaPath,
);
expect(
differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff),
isFalse,
);
});
});
});
}
@@ -39,7 +39,7 @@ class _MockDoctor extends Mock implements Doctor {}
class _MockIpa extends Mock implements Ipa {}
class _MockIpaDiffer extends Mock implements IpaDiffer {}
class _MockIpaDiffer extends Mock implements IosArchiveDiffer {}
class _MockIpaReader extends Mock implements IpaReader {}
@@ -130,7 +130,7 @@ flutter:
late File genSnapshotFile;
late Doctor doctor;
late Ipa ipa;
late IpaDiffer ipaDiffer;
late IosArchiveDiffer archiveDiffer;
late IpaReader ipaReader;
late Progress progress;
late Logger logger;
@@ -232,7 +232,7 @@ flutter:
),
);
ipa = _MockIpa();
ipaDiffer = _MockIpaDiffer();
archiveDiffer = _MockIpaDiffer();
ipaReader = _MockIpaReader();
progress = _MockProgress();
logger = _MockLogger();
@@ -328,13 +328,14 @@ flutter:
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: any(named: 'localArtifact'),
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
archiveDiffer: ipaDiffer,
archiveDiffer: archiveDiffer,
force: any(named: 'force'),
),
).thenAnswer((_) async => true);
command = runWithOverrides(
() => PatchIosCommand(ipaDiffer: ipaDiffer, ipaReader: ipaReader),
() =>
PatchIosCommand(archiveDiffer: archiveDiffer, ipaReader: ipaReader),
)..testArgResults = argResults;
});
@@ -774,7 +775,7 @@ Please re-run the release command for this version or create a new release.'''),
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: any(named: 'localArtifact'),
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
archiveDiffer: ipaDiffer,
archiveDiffer: archiveDiffer,
force: any(named: 'force'),
),
).thenAnswer((_) async => false);
@@ -791,7 +792,7 @@ Please re-run the release command for this version or create a new release.'''),
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: any(named: 'localArtifact'),
releaseArtifactUrl: Uri.parse(ipaArtifact.url),
archiveDiffer: ipaDiffer,
archiveDiffer: archiveDiffer,
force: false,
),
).called(1);
@@ -6,12 +6,14 @@ import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/patch_diff_checker.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
@@ -30,8 +32,12 @@ class _MockCodePushClientWrapper extends Mock
class _MockDoctor extends Mock implements Doctor {}
class _MockIosArchiveDiffer extends Mock implements IosArchiveDiffer {}
class _MockLogger extends Mock implements Logger {}
class _MockPatchDiffChecker extends Mock implements PatchDiffChecker {}
class _MockPlatform extends Mock implements Platform {}
class _MockProgress extends Mock implements Progress {}
@@ -72,6 +78,16 @@ flutter:
assets:
- shorebird.yaml''';
const appMetadata = AppMetadata(appId: appId, displayName: appDisplayName);
const arch = 'aarch64';
const xcframeworkArtifact = ReleaseArtifact(
id: 0,
releaseId: 0,
arch: arch,
platform: ReleasePlatform.ios,
hash: '#',
size: 42,
url: 'https://example.com/release.xcframework',
);
const release = Release(
id: 0,
appId: appId,
@@ -87,6 +103,8 @@ flutter:
late Directory flutterDirectory;
late File genSnapshotFile;
late Doctor doctor;
late IosArchiveDiffer archiveDiffer;
late PatchDiffChecker patchDiffChecker;
late Platform platform;
late Auth auth;
late Progress progress;
@@ -108,6 +126,7 @@ flutter:
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
doctorRef.overrideWith(() => doctor),
loggerRef.overrideWith(() => logger),
patchDiffCheckerRef.overrideWith(() => patchDiffChecker),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
@@ -136,6 +155,18 @@ flutter:
File(p.join(dir.path, 'build', elfAotSnapshotFileName)).createSync(
recursive: true,
);
Directory(
p.join(
dir.path,
'build',
'ios',
'framework',
'Release',
'App.xcframework',
),
).createSync(
recursive: true,
);
}
Directory setUpTempDir() {
@@ -150,13 +181,17 @@ flutter:
}
setUpAll(() {
registerFallbackValue(File(''));
registerFallbackValue(ReleasePlatform.ios);
registerFallbackValue(Uri.parse('https://example.com'));
});
setUp(() {
argResults = _MockArgResults();
archiveDiffer = _MockIosArchiveDiffer();
codePushClientWrapper = _MockCodePushClientWrapper();
doctor = _MockDoctor();
patchDiffChecker = _MockPatchDiffChecker();
platform = _MockPlatform();
shorebirdRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
@@ -224,6 +259,14 @@ flutter:
when(
() => codePushClientWrapper.getReleases(appId: any(named: 'appId')),
).thenAnswer((_) async => [release]);
when(
() => codePushClientWrapper.getReleaseArtifact(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
arch: any(named: 'arch'),
platform: any(named: 'platform'),
),
).thenAnswer((_) async => xcframeworkArtifact);
when(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
@@ -246,9 +289,18 @@ flutter:
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
),
).thenAnswer((_) async {});
when(
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: any(named: 'localArtifact'),
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
archiveDiffer: archiveDiffer,
force: any(named: 'force'),
),
).thenAnswer((_) async => true);
command = runWithOverrides(PatchIosFrameworkCommand.new)
..testArgResults = argResults;
command = runWithOverrides(
() => PatchIosFrameworkCommand(archiveDiffer: archiveDiffer),
)..testArgResults = argResults;
});
test('has a description', () {
@@ -560,6 +612,44 @@ Please re-run the release command for this version or create a new release.'''),
expect(exitCode, ExitCode.software.code);
});
test('exits if confirmUnpatchableDiffsIfNecessary returns false', () async {
when(() => argResults['force']).thenReturn(false);
when(
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: any(named: 'localArtifact'),
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
archiveDiffer: archiveDiffer,
force: any(named: 'force'),
),
).thenAnswer((_) async => false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArtifact: any(named: 'localArtifact'),
releaseArtifactUrl: Uri.parse(xcframeworkArtifact.url),
archiveDiffer: archiveDiffer,
force: false,
),
).called(1);
verifyNever(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
);
});
test('does not create patch on --dry-run', () async {
when(() => argResults['dry-run']).thenReturn(true);
final tempDir = setUpTempDir();