feat(shorebird_cli): show changed files when warning user of patch content differences (#704)

This commit is contained in:
Bryan Oltman
2023-06-23 16:09:33 -04:00
committed by GitHub
parent de4c1a3cef
commit 2a6a096f8f
18 changed files with 372 additions and 234 deletions
@@ -2,7 +2,8 @@ import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
import 'package:shorebird_cli/src/archive_analysis/mf_reader.dart';
/// Finds differences between two AABs.
@@ -25,16 +26,17 @@ import 'package:shorebird_cli/src/archive_analysis/mf_reader.dart';
///
/// See https://developer.android.com/guide/app-bundle/app-bundle-format for
/// reference.
class AabDiffer extends AndroidArchiveDiffer {
class AabDiffer extends ArchiveDiffer {
/// Returns a set of file paths whose hashes differ between the AABs at the
/// provided paths.
@override
Set<String> changedFiles(String aabPath1, String aabPath2) {
final mfContents1 = _metaInfMfContent(File(aabPath1));
final mfContents2 = _metaInfMfContent(File(aabPath2));
final mfEntries1 = MfReader.parse(mfContents1).toSet();
final mfEntries2 = MfReader.parse(mfContents2).toSet();
return mfEntries1.difference(mfEntries2).map((entry) => entry.name).toSet();
FileSetDiff changedFiles(String oldAabPath, String newAabPath) {
final oldMfContents = _metaInfMfContent(File(oldAabPath));
final newMfContents = _metaInfMfContent(File(newAabPath));
return FileSetDiff.fromPathHashes(
oldPathHashes: MfReader.parse(oldMfContents),
newPathHashes: MfReader.parse(newMfContents),
);
}
/// Reads the contents of META-INF/MANIFEST.MF from an AAB.
@@ -2,7 +2,8 @@ import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:crypto/crypto.dart';
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
/// Finds differences between two AABs.
///
@@ -18,20 +19,23 @@ import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
/// https://developer.android.com/studio/projects/android-library.html#aar-contents
/// for reference. Note that .aars produced by Flutter modules do not contain
/// .jar files, so only asset and dart changes are possible.
class AarDiffer extends AndroidArchiveDiffer {
class AarDiffer extends ArchiveDiffer {
String _hash(List<int> bytes) => sha256.convert(bytes).toString();
@override
Set<String> changedFiles(String archivePath1, String archivePath2) =>
_fileHashes(File(archivePath1))
.difference(_fileHashes(File(archivePath2)))
.map((pair) => pair.$1)
.toSet();
FileSetDiff changedFiles(String oldArchivePath, String newArchivePath) =>
FileSetDiff.fromPathHashes(
oldPathHashes: _fileHashes(File(oldArchivePath)),
newPathHashes: _fileHashes(File(newArchivePath)),
);
Set<(String, String)> _fileHashes(File aar) => ZipDecoder()
.decodeBuffer(InputFileStream(aar.path))
.files
.where((file) => file.isFile)
.map((file) => (file.name, _hash(file.content as List<int>)))
.toSet();
PathHashes _fileHashes(File aar) {
final files = ZipDecoder()
.decodeBuffer(InputFileStream(aar.path))
.files
.where((file) => file.isFile);
return {
for (final file in files) file.name: _hash(file.content as List<int>)
};
}
}
@@ -1,49 +0,0 @@
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
abstract class AndroidArchiveDiffer {
/// Files that have changed between the archives at the two provided paths.
Set<String> changedFiles(String archivePath1, String archivePath2);
/// Whether any changed files correspond to a change in assets.
bool hasAssetChanges(Set<String> paths) {
const assetDirNames = ['assets', 'res'];
const assetFileNames = ['AssetManifest.json'];
return paths.any(
(path) =>
p.split(path).any((component) => assetDirNames.contains(component)) ||
assetFileNames.contains(p.basename(path)),
);
}
/// Whether any changed files correspond to a change in Dart code.
bool hasDartChanges(Set<String> paths) {
const dartFileNames = ['libapp.so', 'libflutter.so'];
return paths.any((path) => dartFileNames.contains(p.basename(path)));
}
/// Whether changed files correspond to a change in Java or Kotlin code.
bool hasNativeChanges(Set<String> path) {
return path.any((path) => p.extension(path) == '.dex');
}
/// The types of differences between the archives at the two provided paths.
Set<ArchiveDifferences> contentDifferences(
String archivePath1,
String archivePath2,
) {
final changedFilePaths = changedFiles(archivePath1, archivePath2);
final differences = <ArchiveDifferences>{};
if (hasDartChanges(changedFilePaths)) {
differences.add(ArchiveDifferences.dart);
}
if (hasAssetChanges(changedFilePaths)) {
differences.add(ArchiveDifferences.assets);
}
if (hasNativeChanges(changedFilePaths)) {
differences.add(ArchiveDifferences.native);
}
return differences;
}
}
@@ -1,4 +1,4 @@
export 'aab_differ.dart';
export 'aar_differ.dart';
export 'archive_differences.dart';
export 'file_set_diff.dart';
export 'ipa.dart';
@@ -0,0 +1,38 @@
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
/// Computes content differences between two archives.
abstract class ArchiveDiffer {
/// Files that have been added, removed, or that have changed between the
/// archives at the two provided paths.
FileSetDiff changedFiles(String oldArchivePath, String newArchivePath);
/// Whether any changed files correspond to a change in assets.
static Set<String> assetChanges(Set<String> paths) {
const assetDirNames = ['assets', 'res'];
const assetFileNames = ['AssetManifest.json'];
return paths
.where(
(path) =>
p
.split(path)
.any((component) => assetDirNames.contains(component)) ||
assetFileNames.contains(p.basename(path)),
)
.toSet();
}
/// Whether any changed files correspond to a change in Dart code.
static Set<String> dartChanges(Set<String> paths) {
const dartFileNames = ['libapp.so', 'libflutter.so'];
return paths
.where((path) => dartFileNames.contains(p.basename(path)))
.toSet();
}
/// Whether changed files correspond to a change in native code.
static Set<String> nativeChanges(Set<String> path) {
// TODO(bryanoltman): add support for iOS native code changes.
return path.where((path) => p.extension(path) == '.dex').toSet();
}
}
@@ -1,6 +0,0 @@
/// Types of code changes that we care about.
enum ArchiveDifferences {
dart,
native,
assets,
}
@@ -0,0 +1,98 @@
import 'package:collection/collection.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
/// Maps file paths to SHA-256 hash digests.
typedef PathHashes = Map<String, String>;
/// Sets of [PathHashes] that represent changes between two sets of files.
class FileSetDiff {
FileSetDiff({
required this.addedPaths,
required this.removedPaths,
required this.changedPaths,
});
/// Creates a [FileSetDiff] showing added, changed, and removed file sets
factory FileSetDiff.fromPathHashes({
required PathHashes oldPathHashes,
required PathHashes newPathHashes,
}) {
final oldPaths = oldPathHashes.keys.toSet();
final newPaths = newPathHashes.keys.toSet();
return FileSetDiff(
addedPaths: newPaths.difference(oldPaths),
removedPaths: oldPaths.difference(newPaths),
changedPaths: oldPaths
.intersection(newPaths)
.where((name) => oldPathHashes[name] != newPathHashes[name])
.toSet(),
);
}
FileSetDiff.empty()
: addedPaths = {},
removedPaths = {},
changedPaths = {};
/// File paths that were added.
final Set<String> addedPaths;
/// File paths that were removed.
final Set<String> removedPaths;
/// File paths that were changed.
final Set<String> changedPaths;
/// Whether all path sets are empty.
bool get isEmpty => !isNotEmpty;
/// Whether any files were added, changed, or removed.
bool get isNotEmpty =>
addedPaths.isNotEmpty ||
removedPaths.isNotEmpty ||
changedPaths.isNotEmpty;
/// A subset of this [FileSetDiff] that only contains paths that correspond
/// to a change in Dart code.
FileSetDiff get dartChanges => FileSetDiff(
addedPaths: ArchiveDiffer.dartChanges(addedPaths),
removedPaths: ArchiveDiffer.dartChanges(removedPaths),
changedPaths: ArchiveDiffer.dartChanges(changedPaths),
);
/// A subset of this [FileSetDiff] that only contains paths that correspond
/// to changes in native code.
FileSetDiff get nativeChanges => FileSetDiff(
addedPaths: ArchiveDiffer.nativeChanges(addedPaths),
removedPaths: ArchiveDiffer.nativeChanges(removedPaths),
changedPaths: ArchiveDiffer.nativeChanges(changedPaths),
);
/// A subset of this [FileSetDiff] that only contains paths that correspond
/// to changes in bundled assets.
FileSetDiff get assetChanges => FileSetDiff(
addedPaths: ArchiveDiffer.assetChanges(addedPaths),
removedPaths: ArchiveDiffer.assetChanges(removedPaths),
changedPaths: ArchiveDiffer.assetChanges(changedPaths),
);
/// A printable string representation of this [FileSetDiff].
String get prettyString => [
if (addedPaths.isNotEmpty)
_prettyFileSetString(title: 'Added files', paths: addedPaths),
if (changedPaths.isNotEmpty)
_prettyFileSetString(title: 'Changed files', paths: changedPaths),
if (removedPaths.isNotEmpty)
_prettyFileSetString(title: 'Removed files', paths: removedPaths),
].join('\n');
static String _prettyFileSetString({
required String title,
required Set<String> paths,
}) {
const padding = ' ';
return '''
$padding$title:
${paths.sorted().map((p) => '${padding * 2}$p').join('\n')}''';
}
}
@@ -1,63 +1,31 @@
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
/// {@template mf_entry}
/// A single entry from an .MF file.
/// {@endtemplate}
@immutable
class MfEntry {
/// {@macro mf_entry}
const MfEntry({
required this.name,
required this.sha256Digest,
});
/// Contents of the `Name` field.
final String name;
/// Contents of the `SHA-256-Digest` field.
final String sha256Digest;
@override
String toString() => 'MfEntry(name: $name, sha256Digest: $sha256Digest)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MfEntry &&
runtimeType == other.runtimeType &&
name == other.name &&
sha256Digest == other.sha256Digest;
@override
int get hashCode => Object.hashAll([name, sha256Digest]);
}
/// Parses a .MF file into a list of [MfEntry]s.
/// Parses a .MF file into a [PathHashes] map.
class MfReader {
static final nameRegex = RegExp(r'^Name: (.+)$');
static final nameContinuedRegex = RegExp(r'^ (.+)$');
static final shaDigestRegex = RegExp(r'^SHA-256-Digest: (.+)$');
/// Parses the content of [mfFile] into a list of [MfEntry]s.
/// Parses the content of [mfFile] into a [PathHashes] map.
///
/// [mfFile] should be a JAR manifest file, as described in
/// https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html.
static List<MfEntry> read(File mfFile) => parse(mfFile.readAsStringSync());
static PathHashes read(File mfFile) => parse(mfFile.readAsStringSync());
/// Parses the contents [mfContents] file into a list of [MfEntry]s.
/// Parses the contents [mfContents] file into a [PathHashes] map.
///
/// [mfContents] should be a JAR manifest file, as described in
/// https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html.
static List<MfEntry> parse(String mfContents) {
static PathHashes parse(String mfContents) {
final lines = mfContents.split('\n').map((line) => line.trimRight());
final entries = <MfEntry>[];
final entries = <String, String>{};
var currentHash = '';
var currentName = '';
for (final line in lines) {
if (line.isEmpty && currentName.isNotEmpty && currentHash.isNotEmpty) {
entries.add(MfEntry(name: currentName, sha256Digest: currentHash));
entries[currentName] = currentHash;
currentHash = '';
currentName = '';
} else if (nameRegex.hasMatch(line)) {
@@ -70,7 +38,7 @@ class MfReader {
}
if (currentName.isNotEmpty && currentHash.isNotEmpty) {
entries.add(MfEntry(name: currentName, sha256Digest: currentHash));
entries[currentName] = currentHash;
}
return entries;
@@ -223,7 +223,7 @@ https://github.com/shorebirdtech/shorebird/issues/472
final aarDiffProgress =
logger.progress('Checking for aar content differences');
final contentDiffs = _aarDiffer.contentDifferences(
final contentDiffs = _aarDiffer.changedFiles(
releaseAarPath,
aarArtifactPath(
packageName: androidPackageName!,
@@ -233,7 +233,7 @@ https://github.com/shorebirdtech/shorebird/issues/472
aarDiffProgress.complete();
if (contentDiffs.contains(ArchiveDifferences.assets)) {
if (contentDiffs.assetChanges.isNotEmpty) {
logger.info(
yellow.wrap(
'''⚠️ The Android Archive contains asset changes, which will not be included in the patch.''',
@@ -205,8 +205,7 @@ https://github.com/shorebirdtech/shorebird/issues/472
platform: platformName,
);
final releaseAabArtifact =
await codePushClientWrapper.maybeGetReleaseArtifact(
final releaseAabArtifact = await codePushClientWrapper.getReleaseArtifact(
releaseId: release.id,
arch: 'aab',
platform: platformName,
@@ -229,14 +228,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
}
}
String? releaseAabPath;
String releaseAabPath;
try {
if (releaseAabArtifact != null) {
releaseAabPath = await downloadReleaseArtifact(
Uri.parse(releaseAabArtifact.url),
httpClient: _httpClient,
);
}
releaseAabPath = await downloadReleaseArtifact(
Uri.parse(releaseAabArtifact.url),
httpClient: _httpClient,
);
} catch (error) {
downloadReleaseArtifactProgress.fail('$error');
return ExitCode.software.code;
@@ -244,20 +241,19 @@ https://github.com/shorebirdtech/shorebird/issues/472
downloadReleaseArtifactProgress.complete();
final contentDiffs = releaseAabPath == null
? <ArchiveDifferences>{}
: _aabDiffer.contentDifferences(
releaseAabPath,
bundlePath,
);
final contentDiffs = _aabDiffer.changedFiles(
releaseAabPath,
bundlePath,
);
logger.detail('aab content differences: $contentDiffs');
if (contentDiffs.contains(ArchiveDifferences.native)) {
if (contentDiffs.nativeChanges.isNotEmpty) {
logger
..err(
'''The Android App Bundle appears to contain Kotlin or Java changes, which cannot be applied via a patch.''',
)
..info(yellow.wrap(contentDiffs.nativeChanges.prettyString))
..info(
yellow.wrap(
'''
@@ -269,12 +265,12 @@ If you believe you're seeing this in error, please reach out to us for support a
return ExitCode.software.code;
}
if (contentDiffs.contains(ArchiveDifferences.assets)) {
logger.info(
yellow.wrap(
'''⚠️ The Android App Bundle contains asset changes, which will not be included in the patch.''',
),
);
if (contentDiffs.assetChanges.isNotEmpty) {
logger
..warn(
'''The Android App Bundle contains asset changes, which will not be included in the patch.''',
)
..info(yellow.wrap(contentDiffs.assetChanges.prettyString));
final shouldContinue = logger.confirm('Continue anyways?');
if (!shouldContinue) {
return ExitCode.success.code;
Binary file not shown.
Binary file not shown.
@@ -20,14 +20,14 @@ void main() {
differ = AabDiffer();
});
group('changedFiles', () {
group('changedPaths', () {
test('finds no differences between the same aab', () {
expect(differ.changedFiles(baseAabPath, baseAabPath), isEmpty);
});
test('finds differences between two different aabs', () {
expect(
differ.changedFiles(baseAabPath, changedDartAabPath).toSet(),
differ.changedFiles(baseAabPath, changedDartAabPath).changedPaths,
{
'BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb',
'base/lib/arm64-v8a/libapp.so',
@@ -40,38 +40,39 @@ void main() {
group('contentDifferences', () {
test('detects no differences between the same aab', () {
expect(differ.contentDifferences(baseAabPath, baseAabPath), isEmpty);
expect(differ.changedFiles(baseAabPath, baseAabPath), isEmpty);
});
test('detects asset changes', () {
expect(
differ.contentDifferences(baseAabPath, changedAssetAabPath),
{ArchiveDifferences.assets},
);
final fileSetDiff =
differ.changedFiles(baseAabPath, changedAssetAabPath);
expect(fileSetDiff.assetChanges.isEmpty, isFalse);
expect(fileSetDiff.dartChanges.isEmpty, isTrue);
expect(fileSetDiff.nativeChanges.isEmpty, isTrue);
});
test('detects kotlin changes', () {
expect(
differ.contentDifferences(baseAabPath, changedKotlinAabPath),
{ArchiveDifferences.native},
);
final fileSetDiff =
differ.changedFiles(baseAabPath, changedKotlinAabPath);
expect(fileSetDiff.assetChanges.isEmpty, isTrue);
expect(fileSetDiff.dartChanges.isEmpty, isTrue);
expect(fileSetDiff.nativeChanges.isEmpty, isFalse);
});
test('detects dart changes', () {
expect(
differ.contentDifferences(baseAabPath, changedDartAabPath),
{ArchiveDifferences.dart},
);
final fileSetDiff =
differ.changedFiles(baseAabPath, changedDartAabPath);
expect(fileSetDiff.assetChanges.isEmpty, isTrue);
expect(fileSetDiff.dartChanges.isEmpty, isFalse);
expect(fileSetDiff.nativeChanges.isEmpty, isTrue);
});
test('detects dart and asset changes', () {
expect(
differ.contentDifferences(baseAabPath, changedDartAndAssetAabPath),
{
ArchiveDifferences.assets,
ArchiveDifferences.dart,
},
);
final fileSetDiff =
differ.changedFiles(baseAabPath, changedDartAndAssetAabPath);
expect(fileSetDiff.assetChanges.isEmpty, isFalse);
expect(fileSetDiff.dartChanges.isEmpty, isFalse);
expect(fileSetDiff.nativeChanges.isEmpty, isTrue);
});
});
});
@@ -16,46 +16,44 @@ void main() {
differ = AarDiffer();
});
group('changedFiles', () {
group('changedPaths', () {
test('finds no differences between the same aar', () {
expect(differ.changedFiles(baseAarPath, baseAarPath), isEmpty);
});
test('finds differences between two different aars', () {
expect(
differ.changedFiles(baseAarPath, changedDartAarPath).toSet(),
differ.changedFiles(baseAarPath, changedDartAarPath).changedPaths,
{'jni/arm64-v8a/libapp.so'},
);
});
});
group('contentDifferences', () {
group('changedFiles', () {
test('detects no differences between the same aar', () {
expect(differ.contentDifferences(baseAarPath, baseAarPath), isEmpty);
expect(differ.changedFiles(baseAarPath, baseAarPath), isEmpty);
});
test('detects asset changes', () {
expect(
differ.contentDifferences(baseAarPath, changedAssetAarPath),
{ArchiveDifferences.assets},
);
final fileSetDiff = differ.changedFiles(baseAarPath, changedAssetAarPath);
expect(fileSetDiff.assetChanges.isEmpty, isFalse);
expect(fileSetDiff.dartChanges.isEmpty, isTrue);
expect(fileSetDiff.nativeChanges.isEmpty, isTrue);
});
test('detects dart changes', () {
expect(
differ.contentDifferences(baseAarPath, changedDartAarPath),
{ArchiveDifferences.dart},
);
final fileSetDiff = differ.changedFiles(baseAarPath, changedDartAarPath);
expect(fileSetDiff.assetChanges.isEmpty, isTrue);
expect(fileSetDiff.dartChanges.isEmpty, isFalse);
expect(fileSetDiff.nativeChanges.isEmpty, isTrue);
});
test('detects dart and asset changes', () {
expect(
differ.contentDifferences(baseAarPath, changedDartAndAssetAarPath),
{
ArchiveDifferences.assets,
ArchiveDifferences.dart,
},
);
final fileSetDiff =
differ.changedFiles(baseAarPath, changedDartAndAssetAarPath);
expect(fileSetDiff.assetChanges.isEmpty, isFalse);
expect(fileSetDiff.dartChanges.isEmpty, isFalse);
expect(fileSetDiff.nativeChanges.isEmpty, isTrue);
});
});
}
@@ -0,0 +1,79 @@
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:test/test.dart';
void main() {
group(FileSetDiff, () {
group('fromPathHashes', () {
test('detects added, changed, and removed files', () {
final oldPathHashes = {
'a': 'asdf',
'b': 'qwer',
};
final newPathHashes = {
'a': 'qwer',
'c': 'zxcv',
};
final fileSetDiff = FileSetDiff.fromPathHashes(
oldPathHashes: oldPathHashes,
newPathHashes: newPathHashes,
);
expect(fileSetDiff.addedPaths, {'c'});
expect(fileSetDiff.changedPaths, {'a'});
expect(fileSetDiff.removedPaths, {'b'});
});
});
group('prettyString', () {
test('returns a string with added, changed, and removed files', () {
final fileSetDiff = FileSetDiff(
addedPaths: {'a', 'b'},
changedPaths: {'c'},
removedPaths: {'d'},
);
expect(
fileSetDiff.prettyString,
'''
Added files:
a
b
Changed files:
c
Removed files:
d''',
);
});
test('does not include empty path sets', () {
final fileSetDiff = FileSetDiff(
addedPaths: {'a', 'b'},
changedPaths: {},
removedPaths: {},
);
expect(
fileSetDiff.prettyString,
'''
Added files:
a
b''',
);
});
});
test('isEmpty is true if all path sets are empty', () {
final fileSetDiff = FileSetDiff.empty();
expect(fileSetDiff.isEmpty, isTrue);
expect(fileSetDiff.isNotEmpty, isFalse);
});
test('isEmpty is false if any path sets are not empty', () {
final fileSetDiff = FileSetDiff(
addedPaths: {'a'},
changedPaths: {},
removedPaths: {},
);
expect(fileSetDiff.isEmpty, isFalse);
expect(fileSetDiff.isNotEmpty, isTrue);
});
});
}
@@ -5,13 +5,6 @@ import 'package:shorebird_cli/src/archive_analysis/mf_reader.dart';
import 'package:test/test.dart';
void main() {
group(MfEntry, () {
test('toString contains name and sha', () {
const entry = MfEntry(name: 'name', sha256Digest: '1234abcd');
expect(entry.toString(), 'MfEntry(name: name, sha256Digest: 1234abcd)');
});
});
group(MfReader, () {
const fileContent = '''
Manifest-Version: 1.0
@@ -35,35 +28,21 @@ SHA-256-Digest: WkS2WF7aQZuQ91vLhCXq0vXjRx702kn5K6nAYwQiDec=
Name: base/dex/classes.dex
SHA-256-Digest: wCxl8B3GnKZdBjEOIaW/AhLmIQYXIlYwVC9VRisYGsw=''';
final expectedEntries = {
const MfEntry(
name:
'BUNDLE-METADATA/com.android.tools.build.gradle/app-metadata.properties',
sha256Digest: 'Y9a0mKIrJP9ygajC+nXOu9HRrHGFilTiRYHA5x3cZRs=',
),
const MfEntry(
name:
'BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb',
sha256Digest: 'b23DKN21/V4M5TUGFR9G37D30i886zSRJ93jFr8hEfQ=',
),
const MfEntry(
name:
'BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map',
sha256Digest: 'pJO/g5sJghLBCx1iv55JLFRgrYHQF9blCp3j/sIpv/s=',
),
const MfEntry(
name: 'base/assets/flutter_assets/shorebird.yaml',
sha256Digest: 'WkS2WF7aQZuQ91vLhCXq0vXjRx702kn5K6nAYwQiDec=',
),
const MfEntry(
name: 'base/dex/classes.dex',
sha256Digest: 'wCxl8B3GnKZdBjEOIaW/AhLmIQYXIlYwVC9VRisYGsw=',
),
final expectedPathHashes = {
'BUNDLE-METADATA/com.android.tools.build.gradle/app-metadata.properties':
'Y9a0mKIrJP9ygajC+nXOu9HRrHGFilTiRYHA5x3cZRs=',
'BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb':
'b23DKN21/V4M5TUGFR9G37D30i886zSRJ93jFr8hEfQ=',
'BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map':
'pJO/g5sJghLBCx1iv55JLFRgrYHQF9blCp3j/sIpv/s=',
'base/assets/flutter_assets/shorebird.yaml':
'WkS2WF7aQZuQ91vLhCXq0vXjRx702kn5K6nAYwQiDec=',
'base/dex/classes.dex': 'wCxl8B3GnKZdBjEOIaW/AhLmIQYXIlYwVC9VRisYGsw=',
};
test('parses content string into MfEntry list', () {
final entries = MfReader.parse(fileContent);
expect(entries.toSet(), expectedEntries);
expect(entries, expectedPathHashes);
});
test('parses file contents into MfEntry list', () {
@@ -71,7 +50,7 @@ SHA-256-Digest: wCxl8B3GnKZdBjEOIaW/AhLmIQYXIlYwVC9VRisYGsw=''';
final file = File(p.join(tempDir.path, 'MANIFEST.MF'))
..writeAsStringSync(fileContent);
final entries = MfReader.read(file);
expect(entries.toSet(), expectedEntries);
expect(entries, expectedPathHashes);
});
});
}
@@ -246,7 +246,8 @@ flutter:
return patchProcessResult;
});
when(() => aarDiffer.contentDifferences(any(), any())).thenReturn({});
when(() => aarDiffer.changedFiles(any(), any()))
.thenReturn(FileSetDiff.empty());
when(() => argResults.rest).thenReturn([]);
when(() => argResults['channel']).thenReturn(channelName);
when(() => argResults['dry-run']).thenReturn(false);
@@ -517,8 +518,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
});
test('prompts user to continue when asset changes are detected', () async {
when(() => aarDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.assets},
when(() => aarDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {'assets/test.json'},
removedPaths: {},
changedPaths: {},
),
);
final tempDir = setUpTempDir();
@@ -544,8 +549,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
test(
'''does not warn user of asset or code changes if only dart changes are detected''',
() async {
when(() => aarDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.dart},
when(() => aarDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {},
removedPaths: {},
changedPaths: {'some/path/libapp.so'},
),
);
final tempDir = setUpTempDir();
@@ -571,8 +580,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
test(
'''exits if user decides to not proceed after being warned of non-dart changes''',
() async {
when(() => aarDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.assets},
when(() => aarDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {'assets/test.json'},
removedPaths: {},
changedPaths: {},
),
);
when(
() => logger.confirm(any(that: contains('Continue anyways?'))),
@@ -247,7 +247,8 @@ flutter:
: releaseVersionNameProcessResult;
});
when(() => aabDiffer.contentDifferences(any(), any())).thenReturn({});
when(() => aabDiffer.changedFiles(any(), any()))
.thenReturn(FileSetDiff.empty());
when(() => argResults.rest).thenReturn([]);
when(() => argResults['arch']).thenReturn(arch);
when(() => argResults['channel']).thenReturn(channelName);
@@ -311,7 +312,7 @@ flutter:
},
);
when(
() => codePushClientWrapper.maybeGetReleaseArtifact(
() => codePushClientWrapper.getReleaseArtifact(
releaseId: any(named: 'releaseId'),
arch: 'aab',
platform: 'android',
@@ -558,8 +559,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
});
test('throws error when Java/Kotlin code changes are detected', () async {
when(() => aabDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.native},
when(() => aabDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {},
removedPaths: {},
changedPaths: {'some/path/to/changed.dex'},
),
);
final tempDir = setUpTempDir();
@@ -578,8 +583,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
});
test('prompts user to continue when asset changes are detected', () async {
when(() => aabDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.assets},
when(() => aabDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {},
removedPaths: {},
changedPaths: {'assets/test.json'},
),
);
final tempDir = setUpTempDir();
@@ -591,7 +600,7 @@ https://github.com/shorebirdtech/shorebird/issues/472
expect(exitCode, ExitCode.success.code);
verify(
() => logger.info(
() => logger.warn(
any(
that: contains(
'''The Android App Bundle contains asset changes, which will not be included in the patch.''',
@@ -605,8 +614,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
test(
'''does not warn user of asset or code changes if only dart changes are detected''',
() async {
when(() => aabDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.dart},
when(() => aabDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {},
removedPaths: {},
changedPaths: {'some/path/to/libapp.so'},
),
);
final tempDir = setUpTempDir();
@@ -637,8 +650,12 @@ https://github.com/shorebirdtech/shorebird/issues/472
test(
'''exits if user decides to not proceed after being warned of non-dart changes''',
() async {
when(() => aabDiffer.contentDifferences(any(), any())).thenReturn(
{ArchiveDifferences.assets},
when(() => aabDiffer.changedFiles(any(), any())).thenReturn(
FileSetDiff(
addedPaths: {},
removedPaths: {},
changedPaths: {'assets/test.json'},
),
);
when(
() => logger.confirm(any(that: contains('Continue anyways?'))),