fix(shorebird_cli): shorebird should work within child directories (#1492)

This commit is contained in:
Felix Angelov
2023-11-14 13:19:37 -06:00
committed by GitHub
parent a73a4f254e
commit 8df2e3cb86
27 changed files with 1076 additions and 1343 deletions
@@ -363,6 +363,7 @@ Please create a release using "shorebird release" and try again.
required String appId,
required int releaseId,
required ReleasePlatform platform,
required String projectRoot,
required String aabPath,
required Map<Arch, ArchMetadata> architectures,
String? flavor,
@@ -370,7 +371,7 @@ Please create a release using "shorebird release" and try again.
final createArtifactProgress = logger.progress('Creating artifacts');
for (final archMetadata in architectures.values) {
final artifactPath = p.join(
Directory.current.path,
projectRoot,
'build',
'app',
'intermediates',
@@ -52,7 +52,7 @@ class InitCommand extends ShorebirdCommand {
if (!shorebirdEnv.hasPubspecYaml) {
logger.err('''
Could not find a "pubspec.yaml".
Please make sure you are running "shorebird init" from the root of your Flutter project.
Please make sure you are running "shorebird init" from within your Flutter project.
''');
return ExitCode.noInput.code;
}
@@ -66,11 +66,12 @@ Please make sure you are running "shorebird init" from the root of your Flutter
Set<String>? androidFlavors;
Set<String>? iosFlavors;
var productFlavors = <String>{};
final projectRoot = shorebirdEnv.getFlutterProjectRoot()!;
final detectFlavorsProgress = logger.progress('Detecting product flavors');
try {
final flavors = await Future.wait([
_maybeGetAndroidFlavors(Directory.current.path),
_maybeGetiOSFlavors(Directory.current.path),
_maybeGetAndroidFlavors(projectRoot.path),
_maybeGetiOSFlavors(projectRoot.path),
]);
androidFlavors = flavors[0];
iosFlavors = flavors[1];
@@ -133,7 +134,8 @@ Please make sure you are running "shorebird init" from the root of your Flutter
flavorsToAppIds[flavor] = app.id;
}
_addShorebirdYamlToProject(
shorebirdYaml.appId,
projectRoot: projectRoot,
appId: shorebirdYaml.appId,
flavors: flavorsToAppIds,
);
updateShorebirdYamlProgress.complete('Flavors added to shorebird.yaml');
@@ -201,10 +203,16 @@ Please make sure you are running "shorebird init" from the root of your Flutter
return ExitCode.software.code;
}
_addShorebirdYamlToProject(appId, flavors: flavors);
_addShorebirdYamlToProject(
projectRoot: projectRoot,
appId: appId,
flavors: flavors,
);
if (!shorebirdEnv.pubspecContainsShorebirdYaml) {
_addShorebirdYamlToPubspecAssets();
_addShorebirdYamlToPubspecAssets(
shorebirdEnv.getPubspecYamlFile(cwd: projectRoot),
);
}
logger.info(
@@ -274,8 +282,9 @@ For more information about Shorebird, visit ${link(uri: Uri.parse('https://shore
}
}
ShorebirdYaml _addShorebirdYamlToProject(
String appId, {
ShorebirdYaml _addShorebirdYamlToProject({
required String appId,
required Directory projectRoot,
Map<String, String>? flavors,
}) {
const content = '''
@@ -299,13 +308,14 @@ app_id:
if (flavors != null) editor.update(['flavors'], flavors);
shorebirdEnv.getShorebirdYamlFile().writeAsStringSync(editor.toString());
shorebirdEnv
.getShorebirdYamlFile(cwd: projectRoot)
.writeAsStringSync(editor.toString());
return ShorebirdYaml(appId: appId);
}
void _addShorebirdYamlToPubspecAssets() {
final pubspecFile = shorebirdEnv.getPubspecYamlFile();
void _addShorebirdYamlToPubspecAssets(File pubspecFile) {
final pubspecContents = pubspecFile.readAsStringSync();
final yaml = loadYaml(pubspecContents, sourceUrl: pubspecFile.uri) as Map;
final editor = YamlEditor(pubspecContents);
@@ -128,9 +128,18 @@ If this option is not provided, the version number will be determined from the p
return ExitCode.software.code;
}
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final bundleDirPath = p.join(
projectRoot.path,
'build',
'app',
'outputs',
'bundle',
);
final bundlePath = flavor != null
? './build/app/outputs/bundle/${flavor}Release/app-$flavor-release.aab'
: './build/app/outputs/bundle/release/app-release.aab';
? p.join(bundleDirPath, '${flavor}Release', 'app-$flavor-release.aab')
: p.join(bundleDirPath, 'release', 'app-release.aab');
final String releaseVersion;
final argReleaseVersion = results['release-version'] as String?;
@@ -251,7 +260,7 @@ Current Flutter Revision: $originalFlutterRevision
for (final releaseArtifactPath in releaseArtifactPaths.entries) {
final archMetadata = architectures[releaseArtifactPath.key]!;
final patchArtifactPath = p.join(
Directory.current.path,
projectRoot.path,
'build',
'app',
'intermediates',
@@ -278,8 +278,11 @@ ${summary.join('\n')}
return ExitCode.success.code;
}
String get _aotOutputPath =>
p.join(Directory.current.path, 'build', 'out.aot');
String get _aotOutputPath => p.join(
shorebirdEnv.getShorebirdProjectRoot()!.path,
'build',
'out.aot',
);
Future<void> _buildPatch() async {
final target = results['target'] as String?;
@@ -158,7 +158,11 @@ Please re-run the release command for this version or create a new release.''');
final newestDillFile = newestAppDill();
aotFile = await buildElfAotSnapshot(
appDillPath: newestDillFile.path,
outFilePath: p.join(Directory.current.path, 'build', 'out.aot'),
outFilePath: p.join(
shorebirdEnv.getShorebirdProjectRoot()!.path,
'build',
'out.aot',
),
);
} catch (error) {
buildProgress.fail('$error');
@@ -156,13 +156,10 @@ ${summary.join('\n')}
// Copy release AAR to a new directory to avoid overwriting with subsequent
// patch builds.
final sourceLibraryDirectory = Directory(
aarLibraryPath(
packageName: shorebirdEnv.androidPackageName!,
),
final sourceLibraryDirectory = Directory(aarLibraryPath);
final targetLibraryDirectory = Directory(
p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'release'),
);
final targetLibraryDirectory =
Directory(p.join(Directory.current.path, 'release'));
await copyPath(sourceLibraryDirectory.path, targetLibraryDirectory.path);
final extractAarProgress = logger.progress('Creating artifacts');
@@ -209,7 +206,7 @@ dependencyResolutionManagement {
google()
mavenCentral()
+ maven {
+ url '../${p.basename(Directory.current.path)}/${p.relative(targetLibraryDirectory.path)}'
+ url '../${p.basename(shorebirdEnv.getShorebirdProjectRoot()!.path)}/${p.relative(targetLibraryDirectory.path)}'
+ }
+ maven {
- url 'https://storage.googleapis.com/download.flutter.io'
@@ -110,15 +110,28 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
return ExitCode.software.code;
}
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
final bundleDirPath = p.join('build', 'app', 'outputs', 'bundle');
final bundleDirPath = p.join(
projectRoot.path,
'build',
'app',
'outputs',
'bundle',
);
final apkDirPath = p.join(
projectRoot.path,
'build',
'app',
'outputs',
'apk',
);
final bundlePath = flavor != null
? p.join(bundleDirPath, '${flavor}Release', 'app-$flavor-release.aab')
: p.join(bundleDirPath, 'release', 'app-release.aab');
final apkDirPath = p.join('build', 'app', 'outputs', 'apk');
final apkPath = flavor != null
? p.join(apkDirPath, flavor, 'release', 'app-$flavor-release.apk')
: p.join(apkDirPath, 'release', 'app-release.apk');
@@ -194,6 +207,7 @@ ${summary.join('\n')}
await codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: release.id,
projectRoot: projectRoot.path,
aabPath: bundlePath,
platform: platform,
architectures: architectures,
@@ -4,22 +4,26 @@ import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
mixin ShorebirdArtifactMixin on ShorebirdCommand {
String aarLibraryPath({required String packageName}) => p.joinAll([
Directory.current.path,
'build',
'host',
'outputs',
'repo',
]);
String get aarLibraryPath {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
return p.joinAll([
projectRoot.path,
'build',
'host',
'outputs',
'repo',
]);
}
String aarArtifactDirectory({
required String packageName,
required String buildNumber,
}) =>
p.joinAll([
aarLibraryPath(packageName: packageName),
aarLibraryPath,
...packageName.split('.'),
'flutter_release',
buildNumber,
@@ -69,18 +73,17 @@ mixin ShorebirdArtifactMixin on ShorebirdCommand {
/// Returns the .xcarchive directory generated by `flutter build ipa`. This
/// was traditionally named `Runner.xcarchive`, but can now be renamed.
Directory? getXcarchiveDirectory() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final archiveDirectory = Directory(
p.join(
Directory.current.path,
projectRoot.path,
'build',
'ios',
'archive',
),
);
if (!archiveDirectory.existsSync()) {
return null;
}
if (!archiveDirectory.existsSync()) return null;
return archiveDirectory
.listSync()
@@ -118,9 +121,10 @@ mixin ShorebirdArtifactMixin on ShorebirdCommand {
/// an exception if there is not exactly one .ipa file in the build directory,
/// or if there is no build directory.
String getIpaPath() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final ipaBuildDirectory = Directory(
p.join(
Directory.current.path,
projectRoot.path,
'build',
'ios',
'ipa',
@@ -161,23 +165,27 @@ mixin ShorebirdArtifactMixin on ShorebirdCommand {
/// 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',
),
);
Directory getAppXcframeworkDirectory() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
return Directory(
p.join(
projectRoot.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.
File newestAppDill() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final dartToolBuildDir = Directory(
p.join(
Directory.current.path,
projectRoot.path,
'.dart_tool',
'flutter_build',
),
@@ -7,7 +7,6 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:yaml/yaml.dart';
/// A reference to a [ShorebirdEnv] instance.
final shorebirdEnvRef = create(ShorebirdEnv.new);
@@ -94,13 +93,48 @@ class ShorebirdEnv {
}
/// The `shorebird.yaml` file for this project.
File getShorebirdYamlFile() {
return File(p.join(Directory.current.path, 'shorebird.yaml'));
File getShorebirdYamlFile({required Directory cwd}) {
return File(p.join(cwd.path, 'shorebird.yaml'));
}
/// The `pubspec.yaml` file for this project.
File getPubspecYamlFile() {
return File(p.join(Directory.current.path, 'pubspec.yaml'));
File getPubspecYamlFile({required Directory cwd}) {
return File(p.join(cwd.path, 'pubspec.yaml'));
}
/// Finds nearest ancestor file
/// relative to the [cwd] that satisfies [where].
File? findNearestAncestor({
required File? Function(String path) where,
Directory? cwd,
}) {
Directory? prev;
var dir = cwd ?? Directory.current;
while (prev?.path != dir.path) {
final file = where(dir.path);
if (file?.existsSync() ?? false) return file;
prev = dir;
dir = dir.parent;
}
return null;
}
/// Returns the root directory of the nearest Shorebird project.
Directory? getShorebirdProjectRoot() {
final file = findNearestAncestor(
where: (path) => getShorebirdYamlFile(cwd: Directory(path)),
);
if (file == null || !file.existsSync()) return null;
return Directory(p.dirname(file.path));
}
/// Returns the root directory of the nearest Flutter project.
Directory? getFlutterProjectRoot() {
final file = findNearestAncestor(
where: (path) => getPubspecYamlFile(cwd: Directory(path)),
);
if (file == null || !file.existsSync()) return null;
return Directory(p.dirname(file.path));
}
/// The `shorebird.yaml` file for this project, parsed into a [ShorebirdYaml]
@@ -109,9 +143,9 @@ class ShorebirdEnv {
/// Returns `null` if the file does not exist.
/// Throws a [ParsedYamlException] if the file exists but is invalid.
ShorebirdYaml? getShorebirdYaml() {
final file = getShorebirdYamlFile();
if (!file.existsSync()) return null;
final yaml = file.readAsStringSync();
final root = getShorebirdProjectRoot();
if (root == null) return null;
final yaml = getShorebirdYamlFile(cwd: root).readAsStringSync();
return checkedYamlDecode(yaml, (m) => ShorebirdYaml.fromJson(m!));
}
@@ -120,10 +154,14 @@ class ShorebirdEnv {
/// Returns `null` if the file does not exist.
/// Throws a [ParsedYamlException] if the file exists but is invalid.
Pubspec? getPubspecYaml() {
final file = getPubspecYamlFile();
if (!file.existsSync()) return null;
final yaml = file.readAsStringSync();
return Pubspec.parse(yaml);
final root = getFlutterProjectRoot();
if (root == null) return null;
final yaml = getPubspecYamlFile(cwd: root).readAsStringSync();
try {
return Pubspec.parse(yaml);
} catch (_) {
return null;
}
}
/// Whether `shorebird init` has been run in the current project.
@@ -132,7 +170,7 @@ class ShorebirdEnv {
}
/// Whether the current project has a `shorebird.yaml` file.
bool get hasShorebirdYaml => getShorebirdYamlFile().existsSync();
bool get hasShorebirdYaml => getShorebirdYaml() != null;
/// Whether the current project has a `pubspec.yaml` file.
bool get hasPubspecYaml => getPubspecYaml() != null;
@@ -140,13 +178,11 @@ class ShorebirdEnv {
/// Whether the current project's `pubspec.yaml` file contains a reference to
/// `shorebird.yaml` in its `assets` section.
bool get pubspecContainsShorebirdYaml {
final file = File(p.join(Directory.current.path, 'pubspec.yaml'));
final pubspecContents = file.readAsStringSync();
final yaml = loadYaml(pubspecContents, sourceUrl: file.uri) as Map;
if (!yaml.containsKey('flutter')) return false;
if (yaml['flutter'] is! Map) return false;
if (!(yaml['flutter'] as Map).containsKey('assets')) return false;
final assets = (yaml['flutter'] as Map)['assets'] as List;
final pubspec = getPubspecYaml();
if (pubspec == null) return false;
if (pubspec.flutter == null) return false;
if (pubspec.flutter!['assets'] == null) return false;
final assets = pubspec.flutter!['assets'] as List;
return assets.contains('shorebird.yaml');
}
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:xml/xml.dart';
@@ -22,20 +23,20 @@ class AndroidInternetPermissionValidator extends Validator {
'AndroidManifest.xml files contain INTERNET permission';
@override
bool canRunInCurrentContext() => _androidSrcDirectory.existsSync();
bool canRunInCurrentContext() => _androidSrcDirectory?.existsSync() ?? false;
// coverage:ignore-start
@override
String get incorrectContextMessage => '''
The ${_androidSrcDirectory.path} directory does not exist.
The ${_androidSrcDirectory?.path ?? 'android/app/src'} directory does not exist.
The command you are running must be run at the root of a Flutter app project that supports the Android platform. If you are releasing a Flutter module, use 'aar' in place of 'android' in your shorebird command.''';
The command you are running must be run within a Flutter app project that supports the Android platform. If you are releasing a Flutter module, use 'aar' in place of 'android' in your shorebird command.''';
// coverage:ignore-end
@override
Future<List<ValidationIssue>> validate() async {
final manifestFilePath = p.join(
_androidSrcDirectory.path,
_androidSrcDirectory!.path,
'main',
'AndroidManifest.xml',
);
@@ -63,14 +64,18 @@ The command you are running must be run at the root of a Flutter app project tha
return [];
}
Directory get _androidSrcDirectory => Directory(
p.join(
Directory.current.path,
'android',
'app',
'src',
),
);
Directory? get _androidSrcDirectory {
final root = shorebirdEnv.getFlutterProjectRoot();
if (root == null) return null;
return Directory(
p.join(
root.path,
'android',
'app',
'src',
),
);
}
bool _androidManifestHasInternetPermission(String path) {
final xmlDocument = XmlDocument.parse(File(path).readAsStringSync());
@@ -119,6 +119,7 @@ void main() {
late Progress progress;
late CodePushClientWrapper codePushClientWrapper;
late Platform platform;
late Directory projectRoot;
R runWithOverrides<R>(R Function() body) {
return runScoped(
@@ -143,6 +144,7 @@ void main() {
logger = MockLogger();
platform = MockPlatform();
progress = MockProgress();
projectRoot = Directory.systemTemp.createTempSync();
codePushClientWrapper = runWithOverrides(
() => CodePushClientWrapper(codePushClient: codePushClient),
@@ -720,7 +722,7 @@ Please bump your version number and try again.''',
platform: any(named: 'platform'),
status: any(named: 'status'),
),
).thenAnswer((_) async => {});
).thenAnswer((_) async {});
final result = await runWithOverrides(
() async => codePushClientWrapper.createRelease(
@@ -989,13 +991,12 @@ Please bump your version number and try again.''',
group('createAndroidReleaseArtifacts', () {
final aabPath = p.join('path', 'to', 'app.aab');
Directory setUpTempDir({String? flavor}) {
final tempDir = Directory.systemTemp.createTempSync();
File(p.join(tempDir.path, aabPath)).createSync(recursive: true);
void setUpProjectRoot({String? flavor}) {
File(p.join(projectRoot.path, aabPath)).createSync(recursive: true);
for (final archMetadata
in ShorebirdBuildMixin.allAndroidArchitectures.values) {
final artifactPath = p.join(
tempDir.path,
projectRoot.path,
'build',
'app',
'intermediates',
@@ -1008,7 +1009,6 @@ Please bump your version number and try again.''',
);
File(artifactPath).createSync(recursive: true);
}
return tempDir;
}
setUp(() {
@@ -1022,7 +1022,7 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
).thenAnswer((_) async {});
});
test('exits with code 70 when artifact creation fails', () async {
@@ -1038,22 +1038,20 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aabPath: p.join(tempDir.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
await expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
projectRoot: projectRoot.path,
aabPath: p.join(projectRoot.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1072,22 +1070,20 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aabPath: p.join(tempDir.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
await expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
projectRoot: projectRoot.path,
aabPath: p.join(projectRoot.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1107,18 +1103,16 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(const CodePushConflictException(message: error));
final tempDir = setUpTempDir();
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aabPath: p.join(tempDir.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
projectRoot: projectRoot.path,
aabPath: p.join(projectRoot.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1144,18 +1138,16 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(const CodePushConflictException(message: error));
final tempDir = setUpTempDir();
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aabPath: p.join(tempDir.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
projectRoot: projectRoot.path,
aabPath: p.join(projectRoot.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1178,19 +1170,17 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
final tempDir = setUpTempDir();
).thenAnswer((_) async {});
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aabPath: p.join(tempDir.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
projectRoot: projectRoot.path,
aabPath: p.join(projectRoot.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1210,20 +1200,18 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
final tempDir = setUpTempDir(flavor: flavorName);
).thenAnswer((_) async {});
setUpProjectRoot(flavor: flavorName);
await runWithOverrides(
() async => IOOverrides.runZoned(
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aabPath: p.join(tempDir.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
flavor: flavorName,
),
getCurrentDirectory: () => tempDir,
() async => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
projectRoot: projectRoot.path,
aabPath: p.join(projectRoot.path, aabPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
flavor: flavorName,
),
);
@@ -1263,12 +1251,11 @@ Please bump your version number and try again.''',
final aarPath = p.join(aarDir, 'flutter_release-$buildNumber.aar');
final extractedAarPath = p.join(aarDir, 'flutter_release-$buildNumber');
Directory setUpTempDir({String? flavor}) {
final tempDir = Directory.systemTemp.createTempSync();
void setUpProjectRoot({String? flavor}) {
for (final archMetadata
in ShorebirdBuildMixin.allAndroidArchitectures.values) {
final artifactPath = p.join(
tempDir.path,
projectRoot.path,
extractedAarPath,
'jni',
archMetadata.path,
@@ -1276,8 +1263,7 @@ Please bump your version number and try again.''',
);
File(artifactPath).createSync(recursive: true);
}
File(p.join(tempDir.path, aarPath)).createSync(recursive: true);
return tempDir;
File(p.join(projectRoot.path, aarPath)).createSync(recursive: true);
}
setUp(() {
@@ -1291,7 +1277,7 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
).thenAnswer((_) async {});
});
test('exits with code 70 when artifact creation fails', () async {
@@ -1307,24 +1293,21 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(tempDir.path, aarPath),
extractedAarDir: p.join(tempDir.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
await expectLater(
() async => runWithOverrides(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(projectRoot.path, aarPath),
extractedAarDir: p.join(projectRoot.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1343,24 +1326,21 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(tempDir.path, aarPath),
extractedAarDir: p.join(tempDir.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
await expectLater(
() async => runWithOverrides(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(projectRoot.path, aarPath),
extractedAarDir: p.join(projectRoot.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1380,20 +1360,17 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(const CodePushConflictException(message: error));
final tempDir = setUpTempDir();
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(tempDir.path, aarPath),
extractedAarDir: p.join(tempDir.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(projectRoot.path, aarPath),
extractedAarDir: p.join(projectRoot.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1419,20 +1396,17 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(const CodePushConflictException(message: error));
final tempDir = setUpTempDir();
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(tempDir.path, aarPath),
extractedAarDir: p.join(tempDir.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(projectRoot.path, aarPath),
extractedAarDir: p.join(projectRoot.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1455,21 +1429,18 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
final tempDir = setUpTempDir();
).thenAnswer((_) async {});
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(tempDir.path, aarPath),
extractedAarDir: p.join(tempDir.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(projectRoot.path, aarPath),
extractedAarDir: p.join(projectRoot.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1489,21 +1460,18 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
final tempDir = setUpTempDir(flavor: flavorName);
).thenAnswer((_) async {});
setUpProjectRoot(flavor: flavorName);
await runWithOverrides(
() async => IOOverrides.runZoned(
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(tempDir.path, aarPath),
extractedAarDir: p.join(tempDir.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
getCurrentDirectory: () => tempDir,
() async =>
codePushClientWrapper.createAndroidArchiveReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
platform: releasePlatform,
aarPath: p.join(projectRoot.path, aarPath),
extractedAarDir: p.join(projectRoot.path, extractedAarPath),
architectures: ShorebirdBuildMixin.allAndroidArchitectures,
),
);
@@ -1528,12 +1496,13 @@ Please bump your version number and try again.''',
final xcarchivePath = p.join('path', 'to', 'app.xcarchive');
final runnerPath = p.join('path', 'to', 'runner.app');
Directory setUpTempDir({String? flavor}) {
final tempDir = Directory.systemTemp.createTempSync();
Directory(p.join(tempDir.path, xcarchivePath))
.createSync(recursive: true);
Directory(p.join(tempDir.path, runnerPath)).createSync(recursive: true);
return tempDir;
void setUpProjectRoot({String? flavor}) {
Directory(
p.join(projectRoot.path, xcarchivePath),
).createSync(recursive: true);
Directory(
p.join(projectRoot.path, runnerPath),
).createSync(recursive: true);
}
setUp(() {
@@ -1547,7 +1516,7 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
).thenAnswer((_) async {});
});
test('exits with code 70 when xcarchive artifact creation fails',
@@ -1565,22 +1534,19 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(tempDir.path, xcarchivePath),
runnerPath: p.join(tempDir.path, runnerPath),
isCodesigned: true,
),
await expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(projectRoot.path, xcarchivePath),
runnerPath: p.join(projectRoot.path, runnerPath),
isCodesigned: true,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1592,8 +1558,10 @@ Please bump your version number and try again.''',
when(
() => codePushClient.createReleaseArtifact(
appId: any(named: 'appId'),
artifactPath:
any(named: 'artifactPath', that: endsWith('.xcarchive.zip')),
artifactPath: any(
named: 'artifactPath',
that: endsWith('.xcarchive.zip'),
),
releaseId: any(named: 'releaseId'),
arch: any(named: 'arch'),
platform: any(named: 'platform'),
@@ -1601,22 +1569,19 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(const CodePushConflictException(message: error));
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(tempDir.path, xcarchivePath),
runnerPath: p.join(tempDir.path, runnerPath),
isCodesigned: false,
),
await expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(projectRoot.path, xcarchivePath),
runnerPath: p.join(projectRoot.path, runnerPath),
isCodesigned: false,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1639,22 +1604,19 @@ Please bump your version number and try again.''',
canSideload: any(named: 'canSideload'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(tempDir.path, xcarchivePath),
runnerPath: p.join(tempDir.path, runnerPath),
isCodesigned: false,
),
await expectLater(
() async => runWithOverrides(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(projectRoot.path, xcarchivePath),
runnerPath: p.join(projectRoot.path, runnerPath),
isCodesigned: false,
),
exitsWithCode(ExitCode.software),
),
getCurrentDirectory: () => tempDir,
exitsWithCode(ExitCode.software),
);
verify(() => progress.fail(any(that: contains(error)))).called(1);
@@ -1671,19 +1633,16 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
final tempDir = setUpTempDir();
).thenAnswer((_) async {});
setUpProjectRoot();
await runWithOverrides(
() async => IOOverrides.runZoned(
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(tempDir.path, xcarchivePath),
runnerPath: p.join(tempDir.path, runnerPath),
isCodesigned: true,
),
getCurrentDirectory: () => tempDir,
() async => codePushClientWrapper.createIosReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
xcarchivePath: p.join(projectRoot.path, xcarchivePath),
runnerPath: p.join(projectRoot.path, runnerPath),
isCodesigned: true,
),
);
@@ -1695,11 +1654,10 @@ Please bump your version number and try again.''',
group('createIosFrameworkReleaseArtifacts', () {
final frameworkPath = p.join('path', 'to', 'App.xcframework');
Directory setUpTempDir({String? flavor}) {
final tempDir = Directory.systemTemp.createTempSync();
Directory(p.join(tempDir.path, frameworkPath))
.createSync(recursive: true);
return tempDir;
void setUpProjectRoot({String? flavor}) {
Directory(
p.join(projectRoot.path, frameworkPath),
).createSync(recursive: true);
}
test(
@@ -1715,17 +1673,15 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenThrow(
Exception('oh no'),
);
final tempDir = setUpTempDir();
).thenThrow(Exception('oh no'));
setUpProjectRoot();
await expectLater(
() async => runWithOverrides(
() => codePushClientWrapper.createIosFrameworkReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
appFrameworkPath: p.join(tempDir.path, frameworkPath),
appFrameworkPath: p.join(projectRoot.path, frameworkPath),
),
),
exitsWithCode(ExitCode.software),
@@ -1744,21 +1700,18 @@ Please bump your version number and try again.''',
hash: any(named: 'hash'),
canSideload: any(named: 'canSideload'),
),
).thenAnswer((_) async => {});
final tempDir = setUpTempDir();
).thenAnswer((_) async {});
setUpProjectRoot();
await IOOverrides.runZoned(
() async => expectLater(
runWithOverrides(
() => codePushClientWrapper.createIosFrameworkReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
appFrameworkPath: p.join(tempDir.path, frameworkPath),
),
await expectLater(
runWithOverrides(
() => codePushClientWrapper.createIosFrameworkReleaseArtifacts(
appId: app.appId,
releaseId: releaseId,
appFrameworkPath: p.join(projectRoot.path, frameworkPath),
),
completes,
),
getCurrentDirectory: () => tempDir,
completes,
);
});
});
@@ -1798,7 +1751,7 @@ Please bump your version number and try again.''',
platform: any(named: 'platform'),
status: any(named: 'status'),
),
).thenAnswer((_) async => {});
).thenAnswer((_) async {});
await runWithOverrides(
() => codePushClientWrapper.updateReleaseStatus(
@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
@@ -124,14 +122,10 @@ void main() {
test('exits with code 70 when building apk fails', () async {
when(() => buildProcessResult.exitCode).thenReturn(1);
when(() => buildProcessResult.stderr).thenReturn('oops');
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
expect(exitCode, equals(ExitCode.software.code));
verify(
() => shorebirdProcess.run(
'flutter',
@@ -143,13 +137,9 @@ void main() {
test('exits with code 0 when building apk succeeds', () async {
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
@@ -170,12 +160,8 @@ ${lightCyan.wrap(p.join('build', 'app', 'outputs', 'apk', 'release', 'app-releas
test('runs flutter pub get with system flutter after successful build',
() async {
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -195,13 +181,9 @@ ${lightCyan.wrap(p.join('build', 'app', 'outputs', 'apk', 'release', 'app-releas
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
@@ -125,14 +123,10 @@ void main() {
test('exits with code 70 when building appbundle fails', () async {
when(() => buildProcessResult.exitCode).thenReturn(1);
when(() => buildProcessResult.stderr).thenReturn('oops');
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
expect(exitCode, equals(ExitCode.software.code));
verify(
() => shorebirdProcess.run(
'flutter',
@@ -144,13 +138,9 @@ void main() {
test('exits with code 0 when building appbundle succeeds', () async {
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
'flutter',
@@ -176,13 +166,9 @@ ${lightCyan.wrap(p.join('build', 'app', 'outputs', 'bundle', 'release', 'app-rel
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
'flutter',
@@ -247,12 +233,8 @@ ${lightCyan.wrap(p.join('build', 'app', 'outputs', 'bundle', '${flavor}Release',
test('runs flutter pub get with system flutter after successful build',
() async {
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
@@ -123,14 +121,10 @@ void main() {
test('exits with code 70 when building ipa fails', () async {
when(() => buildProcessResult.exitCode).thenReturn(1);
when(() => buildProcessResult.stderr).thenReturn('oops');
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
expect(exitCode, equals(ExitCode.software.code));
verify(
() => shorebirdProcess.run(
'flutter',
@@ -146,13 +140,9 @@ void main() {
test('exits with code 0 when building ipa succeeds', () async {
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
@@ -183,12 +173,8 @@ ${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
test('runs flutter pub get with system flutter after successful build',
() async {
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -208,13 +194,9 @@ ${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
@@ -253,13 +235,9 @@ ${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
'with --no-codesign', () async {
when(() => argResults['codesign']).thenReturn(false);
when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.run(
@@ -40,6 +40,7 @@ environment:
late CodePushClientWrapper codePushClientWrapper;
late File shorebirdYamlFile;
late ShorebirdYaml shorebirdYaml;
late Directory projectRoot;
late File pubspecYamlFile;
late Logger logger;
late Platform platform;
@@ -66,6 +67,10 @@ environment:
);
}
setUpAll(() {
registerFallbackValue(MockDirectory());
});
setUp(() {
argResults = MockArgResults();
doctor = MockDoctor();
@@ -74,6 +79,7 @@ environment:
shorebirdYaml = MockShorebirdYaml();
shorebirdYamlFile = MockFile();
pubspecYamlFile = MockFile();
projectRoot = Directory.systemTemp.createTempSync();
logger = MockLogger();
platform = MockPlatform();
progress = MockProgress();
@@ -89,9 +95,12 @@ environment:
).thenAnswer((_) async => {});
when(() => doctor.allValidators).thenReturn([]);
when(
() => shorebirdEnv.getShorebirdYamlFile(),
() => shorebirdEnv.getShorebirdYamlFile(cwd: any(named: 'cwd')),
).thenReturn(shorebirdYamlFile);
when(() => shorebirdEnv.getPubspecYamlFile()).thenReturn(pubspecYamlFile);
when(
() => shorebirdEnv.getPubspecYamlFile(cwd: any(named: 'cwd')),
).thenReturn(pubspecYamlFile);
when(() => shorebirdEnv.getFlutterProjectRoot()).thenReturn(projectRoot);
when(
() => pubspecYamlFile.readAsStringSync(),
).thenReturn(pubspecYamlContent);
@@ -148,7 +157,7 @@ environment:
() => logger.err(
'''
Could not find a "pubspec.yaml".
Please make sure you are running "shorebird init" from the root of your Flutter project.
Please make sure you are running "shorebird init" from within your Flutter project.
''',
),
).called(1);
@@ -253,12 +262,8 @@ Please make sure you are running "shorebird init" from the root of your Flutter
});
test('throws software error when unable to detect schemes', () async {
final tempDir = Directory.systemTemp.createTempSync();
Directory(p.join(tempDir.path, 'ios')).createSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
Directory(p.join(projectRoot.path, 'ios')).createSync(recursive: true);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err(
@@ -269,11 +274,7 @@ Please make sure you are running "shorebird init" from the root of your Flutter
});
test('creates shorebird for an android-only app', () async {
final tempDir = Directory.systemTemp.createTempSync();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdYamlFile.writeAsStringSync(
@@ -284,13 +285,12 @@ Please make sure you are running "shorebird init" from the root of your Flutter
});
test('creates shorebird for an app without flavors', () async {
final tempDir = Directory.systemTemp.createTempSync();
when(
() => gradlew.productFlavors(any()),
).thenThrow(MissingAndroidProjectException(tempDir.path));
).thenThrow(MissingAndroidProjectException(projectRoot.path));
File(
p.join(
tempDir.path,
projectRoot.path,
'ios',
'Runner.xcodeproj',
'xcshareddata',
@@ -298,10 +298,7 @@ Please make sure you are running "shorebird init" from the root of your Flutter
'Runner.xcscheme',
),
).createSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdYamlFile.writeAsStringSync(
@@ -322,12 +319,11 @@ Please make sure you are running "shorebird init" from the root of your Flutter
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '-');
});
final tempDir = Directory.systemTemp.createTempSync();
when(
() => gradlew.productFlavors(any()),
).thenThrow(MissingAndroidProjectException(tempDir.path));
).thenThrow(MissingAndroidProjectException(projectRoot.path));
final schemesPath = p.join(
tempDir.path,
projectRoot.path,
'ios',
'Runner.xcodeproj',
'xcshareddata',
@@ -342,10 +338,7 @@ Please make sure you are running "shorebird init" from the root of your Flutter
File(
p.join(schemesPath, 'stable.xcscheme'),
).createSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(() => progress.complete('2 product flavors detected:'))
.called(1);
@@ -85,6 +85,7 @@ void main() {
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory projectRoot;
late Directory flutterDirectory;
late OperatingSystemInterface operatingSystemInterface;
late PatchDiffChecker patchDiffChecker;
@@ -122,11 +123,9 @@ void main() {
);
}
Directory setUpTempDir() => Directory.systemTemp.createTempSync();
void setUpTempArtifacts(Directory dir) {
void setUpProjectRootArtifacts() {
final aarDir = p.join(
dir.path,
projectRoot.path,
'build',
'host',
'outputs',
@@ -170,6 +169,7 @@ void main() {
auth = MockAuth();
codePushClientWrapper = MockCodePushClientWrapper();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
@@ -191,6 +191,9 @@ void main() {
.thenReturn('/path/to/flutter');
when(() => platform.environment).thenReturn({});
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(
() => shorebirdEnv.androidPackageName,
@@ -458,12 +461,8 @@ ${release.version}'''),
),
],
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(
() => logger.err('''
@@ -490,12 +489,8 @@ Please re-run the release command for this version or create a new release.'''),
),
],
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
});
@@ -509,12 +504,8 @@ Please re-run the release command for this version or create a new release.'''),
outputPath: any(named: 'outputPath'),
),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(() => progress.fail('$exception')).called(1);
expect(exitCode, ExitCode.software.code);
});
@@ -524,13 +515,9 @@ Please re-run the release command for this version or create a new release.'''),
'when release flutter revision differs', () async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => logger.progress(
@@ -564,20 +551,16 @@ Please re-run the release command for this version or create a new release.'''),
environment: any(named: 'environment'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(
() => runScoped(
() => command.run(),
values: {
processRef.overrideWith(
() => ShorebirdProcess(processWrapper: processWrapper),
),
},
),
setUpProjectRootArtifacts();
await runWithOverrides(
() => runScoped(
() => command.run(),
values: {
processRef.overrideWith(
() => ShorebirdProcess(processWrapper: processWrapper),
),
},
),
getCurrentDirectory: () => tempDir,
);
verify(
() => processWrapper.run(
@@ -609,13 +592,9 @@ Please re-run the release command for this version or create a new release.'''),
revision: any(named: 'revision'),
),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.progress(
@@ -649,13 +628,9 @@ Please re-run the release command for this version or create a new release.'''),
force: any(named: 'force'),
),
).thenThrow(UserCancelledException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
@@ -690,13 +665,9 @@ Please re-run the release command for this version or create a new release.'''),
),
).thenThrow(UnpatchableChangeException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -727,24 +698,16 @@ Please re-run the release command for this version or create a new release.'''),
patchArtifactPath: any(named: 'patchArtifactPath'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(() => progress.fail('$error')).called(1);
expect(exitCode, ExitCode.software.code);
});
test('does not create patch on --dry-run', () async {
when(() => argResults['dry-run']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(
() => codePushClientWrapper.publishPatch(
@@ -760,25 +723,17 @@ Please re-run the release command for this version or create a new release.'''),
test('aborts when user opts out', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
});
test('does not prompt on --force', () async {
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -794,13 +749,9 @@ Please re-run the release command for this version or create a new release.'''),
});
test('succeeds when patch is successful', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(
@@ -846,13 +797,9 @@ Please re-run the release command for this version or create a new release.'''),
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -866,13 +813,9 @@ Please re-run the release command for this version or create a new release.'''),
test('does not prompt if running on CI', () async {
when(() => shorebirdEnv.isRunningOnCI).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -97,6 +97,7 @@ flutter:
late CodePushClientWrapper codePushClientWrapper;
late Directory flutterDirectory;
late Directory shorebirdRoot;
late Directory projectRoot;
late Doctor doctor;
late Java java;
late OperatingSystemInterface operatingSystemInterface;
@@ -139,22 +140,20 @@ flutter:
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
void setUpProjectRoot() {
File(
p.join(tempDir.path, 'pubspec.yaml'),
p.join(projectRoot.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
void setUpTempArtifacts(Directory dir, {String? flavor}) {
void setUpProjectRootArtifacts({String? flavor}) {
for (final archMetadata
in ShorebirdBuildMixin.allAndroidArchitectures.values) {
final artifactPath = p.join(
dir.path,
projectRoot.path,
'build',
'app',
'intermediates',
@@ -190,6 +189,7 @@ flutter:
doctor = MockDoctor();
java = MockJava();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
@@ -216,6 +216,9 @@ flutter:
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(() => shorebirdFlutter.useRevision(revision: any(named: 'revision')))
@@ -395,11 +398,8 @@ flutter:
'both --dry-run and --force are specified', () async {
when(() => argResults['dry-run']).thenReturn(true);
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.usage.code));
});
@@ -407,11 +407,8 @@ flutter:
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
});
@@ -436,12 +433,9 @@ flutter:
updatedAt: DateTime(2023),
),
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(
() => logger.err('''
@@ -470,12 +464,9 @@ Please re-run the release command for this version or create a new release.'''),
),
],
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
});
@@ -484,13 +475,10 @@ Please re-run the release command for this version or create a new release.'''),
() async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
// Verify that we switch back to the original revision once we're done.
@@ -523,13 +511,10 @@ Please re-run the release command for this version or create a new release.'''),
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
});
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
},
@@ -541,12 +526,9 @@ Please re-run the release command for this version or create a new release.'''),
});
test('does not extract release version from app bundle', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verifyNever(() => bundletool.getVersionName(any()));
verifyNever(() => bundletool.getVersionCode(any()));
verifyNever(() => logger.progress('Detecting release version'));
@@ -555,12 +537,9 @@ Please re-run the release command for this version or create a new release.'''),
group('when release-version option is not provided', () {
test('extracts release version from app bundle', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verify(() => bundletool.getVersionName(any())).called(1);
verify(() => bundletool.getVersionCode(any())).called(1);
verify(() => logger.progress('Detecting release version')).called(1);
@@ -572,12 +551,9 @@ Please re-run the release command for this version or create a new release.'''),
'Failed to extract version name from app bundle: oops',
);
when(() => bundletool.getVersionName(any())).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('$exception')).called(1);
});
@@ -587,23 +563,17 @@ Please re-run the release command for this version or create a new release.'''),
'Failed to extract version code from app bundle: oops',
);
when(() => bundletool.getVersionCode(any())).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('$exception')).called(1);
});
test('prints release version when detected', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
@@ -613,12 +583,9 @@ Please re-run the release command for this version or create a new release.'''),
test('aborts when user opts out', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
});
@@ -633,12 +600,9 @@ Please re-run the release command for this version or create a new release.'''),
outputPath: any(named: 'outputPath'),
),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(() => progress.fail('$exception')).called(1);
expect(exitCode, ExitCode.software.code);
});
@@ -648,12 +612,9 @@ Please re-run the release command for this version or create a new release.'''),
'Failed to extract version name from app bundle: oops',
);
when(() => bundletool.getVersionName(any())).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('$exception')).called(1);
});
@@ -663,23 +624,17 @@ Please re-run the release command for this version or create a new release.'''),
'Failed to extract version code from app bundle: oops',
);
when(() => bundletool.getVersionCode(any())).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('$exception')).called(1);
});
test('prints release version when detected', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
@@ -699,13 +654,10 @@ Please re-run the release command for this version or create a new release.'''),
force: any(named: 'force'),
),
).thenThrow(UserCancelledException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
@@ -740,13 +692,10 @@ Please re-run the release command for this version or create a new release.'''),
),
).thenThrow(UnpatchableChangeException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -777,24 +726,18 @@ Please re-run the release command for this version or create a new release.'''),
patchArtifactPath: any(named: 'patchArtifactPath'),
),
).thenThrow(error);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(() => progress.fail('$error')).called(1);
expect(exitCode, ExitCode.software.code);
});
test('does not create patch on --dry-run', () async {
when(() => argResults['dry-run']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(
() => codePushClientWrapper.publishPatch(
@@ -812,13 +755,10 @@ Please re-run the release command for this version or create a new release.'''),
when(() => argResults['force']).thenReturn(true);
when(() => archiveDiffer.containsPotentiallyBreakingAssetDiffs(any()))
.thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -834,12 +774,9 @@ Please re-run the release command for this version or create a new release.'''),
});
test('succeeds when patch is successful (production)', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.info(
any(
@@ -865,12 +802,9 @@ Please re-run the release command for this version or create a new release.'''),
test('succeeds when patch is successful (staging)', () async {
when(() => argResults['staging']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.info(
any(
@@ -896,12 +830,9 @@ Please re-run the release command for this version or create a new release.'''),
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -920,18 +851,15 @@ Please re-run the release command for this version or create a new release.'''),
const target = './lib/main_development.dart';
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final tempDir = setUpTempDir();
setUpProjectRoot();
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: productionAppId
flavors:
development: $appId''');
setUpTempArtifacts(tempDir, flavor: flavor);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts(flavor: flavor);
final exitCode = await runWithOverrides(command.run);
verify(
() => codePushClientWrapper.publishPatch(
appId: appId,
@@ -946,13 +874,10 @@ flavors:
test('does not prompt if running on CI', () async {
when(() => shorebirdEnv.isRunningOnCI).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -126,6 +126,7 @@ flutter:
late CodePushClientWrapper codePushClientWrapper;
late Directory flutterDirectory;
late Directory shorebirdRoot;
late Directory projectRoot;
late File genSnapshotFile;
late Doctor doctor;
late IosArchiveDiffer archiveDiffer;
@@ -164,17 +165,16 @@ flutter:
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
void setUpProjectRoot() {
File(
p.join(tempDir.path, 'pubspec.yaml'),
p.join(projectRoot.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
File(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -184,15 +184,14 @@ flutter:
)
..createSync(recursive: true)
..writeAsStringSync(infoPlistContent);
File(p.join(tempDir.path, ipaPath)).createSync(recursive: true);
return tempDir;
File(p.join(projectRoot.path, ipaPath)).createSync(recursive: true);
}
void setUpTempArtifacts(Directory dir) {
void setUpProjectRootArtifacts() {
// Create a second app.dill for coverage of newestAppDill file.
File(
p.join(
dir.path,
projectRoot.path,
'.dart_tool',
'flutter_build',
'subdir',
@@ -200,11 +199,11 @@ flutter:
),
).createSync(recursive: true);
File(
p.join(dir.path, '.dart_tool', 'flutter_build', 'app.dill'),
p.join(projectRoot.path, '.dart_tool', 'flutter_build', 'app.dill'),
).createSync(recursive: true);
File(
p.join(projectRoot.path, 'build', elfAotSnapshotFileName),
).createSync(recursive: true);
File(p.join(dir.path, 'build', elfAotSnapshotFileName)).createSync(
recursive: true,
);
}
setUpAll(() {
@@ -224,6 +223,7 @@ flutter:
codePushClientWrapper = MockCodePushClientWrapper();
doctor = MockDoctor();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
@@ -299,6 +299,9 @@ flutter:
when(() => platform.script).thenReturn(shorebirdRoot.uri);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(() => shorebirdEnv.genSnapshotFile).thenReturn(genSnapshotFile);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
@@ -390,11 +393,8 @@ flutter:
'both --dry-run and --force are specified', () async {
when(() => argResults['dry-run']).thenReturn(true);
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.usage.code));
});
@@ -402,11 +402,8 @@ flutter:
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
});
@@ -430,11 +427,8 @@ error: exportArchive: Communication with Apple failed
error: exportArchive: No signing certificate "iOS Distribution" found
''');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -451,28 +445,28 @@ error: exportArchive: No signing certificate "iOS Distribution" found
group('when build directory has non-default structure', () {
test('exits with code 70 if xcarchive is not found', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
Directory(p.join(tempDir.path, 'build')).deleteSync(recursive: true);
setUpProjectRoot();
setUpProjectRootArtifacts();
Directory(
p.join(projectRoot.path, 'build'),
).deleteSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger
.err(any(that: contains('Unable to find .xcarchive directory'))),
() => logger.err(
any(that: contains('Unable to find .xcarchive directory')),
),
).called(1);
});
test('prints error and exits with code 70 if Info.plist does not exist',
() async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final plistPath = p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -481,22 +475,20 @@ error: exportArchive: No signing certificate "iOS Distribution" found
);
File(plistPath).deleteSync();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(() => logger.err('No Info.plist file found at $plistPath.'))
.called(1);
verify(
() => logger.err('No Info.plist file found at $plistPath.'),
).called(1);
});
test('finds xcarchive that has been renamed from Runner', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
Directory(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -504,7 +496,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found
),
).renameSync(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -512,10 +504,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found
),
);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
});
@@ -541,12 +530,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found
updatedAt: DateTime(2023),
),
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => logger.err('No iOS release found for 1.2.3+1.')).called(1);
});
@@ -571,12 +557,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found
updatedAt: DateTime(2023),
),
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(
() => logger.err('''
@@ -607,12 +590,9 @@ Please re-run the release command for this version or create a new release.'''),
updatedAt: DateTime(2023),
),
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
});
@@ -621,13 +601,10 @@ Please re-run the release command for this version or create a new release.'''),
() async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
// Verify that we switch back to the original revision once we're done.
@@ -661,13 +638,10 @@ Please re-run the release command for this version or create a new release.'''),
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
});
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
},
@@ -677,17 +651,15 @@ Please re-run the release command for this version or create a new release.'''),
const customReleaseVersion = 'custom-release-version';
setUp(() {
when(() => argResults['release-version'])
.thenReturn(customReleaseVersion);
when(
() => argResults['release-version'],
).thenReturn(customReleaseVersion);
});
test('does not extract release version from archive', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verify(
() => codePushClientWrapper.getRelease(
@@ -700,12 +672,9 @@ Please re-run the release command for this version or create a new release.'''),
group('when release-version option is not provided', () {
test('extracts release version from app bundle', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verify(
() => codePushClientWrapper.getRelease(
@@ -718,11 +687,11 @@ Please re-run the release command for this version or create a new release.'''),
test('exits with code 70 when release version cannot be determiend',
() async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final file = File(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -733,10 +702,7 @@ Please re-run the release command for this version or create a new release.'''),
..createSync(recursive: true)
..writeAsStringSync(emptyPlistContent);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -748,13 +714,10 @@ Please re-run the release command for this version or create a new release.'''),
});
test('prints release version when detected', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(() => logger.info('Detected release version 1.2.3+1')).called(1);
@@ -762,12 +725,9 @@ Please re-run the release command for this version or create a new release.'''),
test('aborts when user opts out', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
});
@@ -776,12 +736,9 @@ Please re-run the release command for this version or create a new release.'''),
const error = 'oops something went wrong';
when(() => aotBuildProcessResult.exitCode).thenReturn(1);
when(() => aotBuildProcessResult.stderr).thenReturn(error);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => progress.fail('Exception: Failed to create snapshot: $error'),
).called(1);
@@ -800,13 +757,10 @@ Please re-run the release command for this version or create a new release.'''),
force: any(named: 'force'),
),
).thenThrow(UserCancelledException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
@@ -840,13 +794,10 @@ Please re-run the release command for this version or create a new release.'''),
force: any(named: 'force'),
),
).thenThrow(UnpatchableChangeException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -870,12 +821,9 @@ Please re-run the release command for this version or create a new release.'''),
test('does not create patch on --dry-run', () async {
when(() => argResults['dry-run']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(
() => codePushClientWrapper.createPatch(
@@ -888,12 +836,9 @@ Please re-run the release command for this version or create a new release.'''),
test('does not prompt on --force', () async {
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
verify(
@@ -908,12 +853,9 @@ Please re-run the release command for this version or create a new release.'''),
});
test('succeeds when patch is successful (production)', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.info(
any(
@@ -939,12 +881,9 @@ Please re-run the release command for this version or create a new release.'''),
test('succeeds when patch is successful (staging)', () async {
when(() => argResults['staging']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.info(
any(
@@ -970,13 +909,10 @@ Please re-run the release command for this version or create a new release.'''),
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -990,12 +926,9 @@ Please re-run the release command for this version or create a new release.'''),
test('forwards codesign to flutter build', () async {
when(() => argResults['codesign']).thenReturn(false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -1017,13 +950,10 @@ Please re-run the release command for this version or create a new release.'''),
test('does not provide export options when codesign is false', () async {
when(() => argResults['codesign']).thenReturn(false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
final capturedArgs = verify(
() => shorebirdProcess.run(
@@ -1047,18 +977,15 @@ Please re-run the release command for this version or create a new release.'''),
const target = './lib/main_development.dart';
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final tempDir = setUpTempDir();
setUpProjectRoot();
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: productionAppId
flavors:
development: $appId''');
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(
() => codePushClientWrapper.publishPatch(
@@ -1072,31 +999,25 @@ flavors:
});
test('succeeds when patch is successful using custom base_url', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
const baseUrl = 'https://example.com';
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync(
'''
app_id: $appId
base_url: $baseUrl''',
);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
});
test('does not prompt if running on CI', () async {
when(() => shorebirdEnv.isRunningOnCI).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -77,6 +77,7 @@ flutter:
late ArgResults argResults;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory projectRoot;
late Directory flutterDirectory;
late File genSnapshotFile;
late Doctor doctor;
@@ -116,11 +117,11 @@ flutter:
);
}
void setUpTempArtifacts(Directory dir) {
void setUpProjectRootArtifacts() {
// Create a second app.dill for coverage of newestAppDill file.
File(
p.join(
dir.path,
projectRoot.path,
'.dart_tool',
'flutter_build',
'subdir',
@@ -128,14 +129,15 @@ flutter:
),
).createSync(recursive: true);
File(
p.join(dir.path, '.dart_tool', 'flutter_build', 'app.dill'),
p.join(projectRoot.path, '.dart_tool', 'flutter_build', 'app.dill'),
).createSync(recursive: true);
File(p.join(dir.path, 'build', elfAotSnapshotFileName)).createSync(
File(p.join(projectRoot.path, 'build', elfAotSnapshotFileName))
.createSync(
recursive: true,
);
Directory(
p.join(
dir.path,
projectRoot.path,
'build',
'ios',
'framework',
@@ -147,15 +149,13 @@ flutter:
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
void setUpProjectRoot() {
File(
p.join(tempDir.path, 'pubspec.yaml'),
p.join(projectRoot.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
setUpAll(() {
@@ -174,6 +174,7 @@ flutter:
patchDiffChecker = MockPatchDiffChecker();
platform = MockPlatform();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
@@ -232,11 +233,15 @@ flutter:
when(() => logger.level).thenReturn(Level.info);
when(() => logger.progress(any())).thenReturn(progress);
when(() => logger.confirm(any())).thenReturn(true);
when(() => operatingSystemInterface.which('flutter'))
.thenReturn('/path/to/flutter');
when(
() => operatingSystemInterface.which('flutter'),
).thenReturn('/path/to/flutter');
when(() => platform.operatingSystem).thenReturn(Platform.macOS);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(() => shorebirdEnv.genSnapshotFile).thenReturn(genSnapshotFile);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
@@ -425,12 +430,9 @@ ${release.version}'''),
),
],
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(
() => logger.err('''
@@ -457,12 +459,9 @@ Please re-run the release command for this version or create a new release.'''),
),
],
);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
});
@@ -471,13 +470,10 @@ Please re-run the release command for this version or create a new release.'''),
'when release flutter revision differs', () async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => logger.progress(
@@ -511,20 +507,17 @@ Please re-run the release command for this version or create a new release.'''),
environment: any(named: 'environment'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
await IOOverrides.runZoned(
() => runWithOverrides(
() => runScoped(
() => command.run(),
values: {
processRef.overrideWith(
() => ShorebirdProcess(processWrapper: processWrapper),
),
},
),
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(
() => runScoped(
() => command.run(),
values: {
processRef.overrideWith(
() => ShorebirdProcess(processWrapper: processWrapper),
),
},
),
getCurrentDirectory: () => tempDir,
);
verify(
() => processWrapper.run(
@@ -556,13 +549,10 @@ Please re-run the release command for this version or create a new release.'''),
revision: any(named: 'revision'),
),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.progress(
@@ -579,12 +569,9 @@ Please re-run the release command for this version or create a new release.'''),
test('aborts when user opts out', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
});
@@ -593,12 +580,9 @@ Please re-run the release command for this version or create a new release.'''),
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oh no');
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('Failed to build: oh no')).called(1);
});
@@ -607,12 +591,9 @@ Please re-run the release command for this version or create a new release.'''),
const error = 'oops something went wrong';
when(() => aotBuildProcessResult.exitCode).thenReturn(1);
when(() => aotBuildProcessResult.stderr).thenReturn(error);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => progress.fail('Exception: Failed to create snapshot: $error'),
).called(1);
@@ -631,13 +612,10 @@ Please re-run the release command for this version or create a new release.'''),
force: any(named: 'force'),
),
).thenThrow(UserCancelledException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
@@ -671,13 +649,10 @@ Please re-run the release command for this version or create a new release.'''),
force: any(named: 'force'),
),
).thenThrow(UnpatchableChangeException());
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -701,12 +676,9 @@ Please re-run the release command for this version or create a new release.'''),
test('does not create patch on --dry-run', () async {
when(() => argResults['dry-run']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(
() => codePushClientWrapper.createPatch(
@@ -719,12 +691,9 @@ Please re-run the release command for this version or create a new release.'''),
test('does not prompt on --force', () async {
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
verify(
@@ -739,12 +708,9 @@ Please re-run the release command for this version or create a new release.'''),
});
test('succeeds when patch is successful', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.info(
any(
@@ -770,13 +736,10 @@ Please re-run the release command for this version or create a new release.'''),
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -790,13 +753,10 @@ Please re-run the release command for this version or create a new release.'''),
test('does not prompt if running on CI', () async {
when(() => shorebirdEnv.isRunningOnCI).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -59,6 +59,7 @@ void main() {
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory projectRoot;
late Java java;
late OperatingSystemInterface operatingSystemInterface;
late Platform platform;
@@ -89,10 +90,9 @@ void main() {
);
}
Directory setUpTempArtifacts() {
final dir = Directory.systemTemp.createTempSync();
void setUpProjectRootArtifacts() {
final aarDir = p.join(
dir.path,
projectRoot.path,
'build',
'host',
'outputs',
@@ -116,7 +116,6 @@ void main() {
File(artifactPath).createSync(recursive: true);
}
File(aarPath).createSync(recursive: true);
return dir;
}
setUpAll(() {
@@ -140,6 +139,7 @@ void main() {
flutterPubGetProcessResult = MockProcessResult();
shorebirdProcess = MockShorebirdProcess();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
shorebirdEnv = MockShorebirdEnv();
shorebirdValidator = MockShorebirdValidator();
@@ -155,6 +155,9 @@ void main() {
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(
() => shorebirdEnv.androidPackageName,
).thenReturn(androidPackageName);
@@ -302,22 +305,16 @@ void main() {
defaultValue: any(named: 'defaultValue'),
),
).thenAnswer((_) => '1.0.0');
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
});
test('does not prompt for confirmation when --force is used', () async {
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verifyNever(
@@ -326,11 +323,8 @@ void main() {
});
test('succeeds when release is successful', () async {
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
@@ -386,23 +380,20 @@ void main() {
});
test('copies aar library to a releases folder', () async {
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
expect(Directory(p.join(tempDir.path, 'release')).existsSync(), isTrue);
expect(
Directory(p.join(projectRoot.path, 'release')).existsSync(),
isTrue,
);
});
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempArtifacts();
setUpProjectRootArtifacts();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -422,11 +413,8 @@ void main() {
releaseVersion: any(named: 'releaseVersion'),
),
).thenAnswer((_) async => release);
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verifyNever(
() => codePushClientWrapper.createRelease(
@@ -62,6 +62,7 @@ void main() {
late http.Client httpClient;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory projectRoot;
late Doctor doctor;
late Platform platform;
late Auth auth;
@@ -115,6 +116,7 @@ void main() {
operatingSystemInterface = MockOperatingSystemInterface();
platform = MockPlatform();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
auth = MockAuth();
cache = MockCache();
java = MockJava();
@@ -129,6 +131,9 @@ void main() {
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(() => shorebirdEnv.isRunningOnCI).thenReturn(false);
@@ -195,6 +200,7 @@ void main() {
() => codePushClientWrapper.createAndroidReleaseArtifacts(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
projectRoot: any(named: 'projectRoot'),
aabPath: any(named: 'aabPath'),
platform: any(named: 'platform'),
architectures: any(named: 'architectures'),
@@ -335,6 +341,7 @@ void main() {
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
final aabPath = p.join(
projectRoot.path,
'build',
'app',
'outputs',
@@ -358,6 +365,7 @@ ${link(uri: Uri.parse('https://support.google.com/googleplay/android-developer/a
appId: appId,
releaseId: release.id,
platform: releasePlatform,
projectRoot: any(named: 'projectRoot'),
aabPath: any(named: 'aabPath'),
architectures: any(named: 'architectures'),
),
@@ -379,6 +387,7 @@ ${link(uri: Uri.parse('https://support.google.com/googleplay/android-developer/a
verify(() => logger.success('\n✅ Published Release!')).called(1);
// Verify info message does include apk instructions.
final aabPath = p.join(
projectRoot.path,
'build',
'app',
'outputs',
@@ -387,6 +396,7 @@ ${link(uri: Uri.parse('https://support.google.com/googleplay/android-developer/a
'app-release.aab',
);
final apkPath = p.join(
projectRoot.path,
'build',
'app',
'outputs',
@@ -412,6 +422,7 @@ ${link(uri: Uri.parse('https://support.google.com/googleplay/android-developer/a
appId: appId,
releaseId: release.id,
platform: releasePlatform,
projectRoot: any(named: 'projectRoot'),
aabPath: any(named: 'aabPath'),
architectures: any(named: 'architectures'),
),
@@ -568,6 +579,7 @@ Either run `flutter pub get` manually, or follow the steps in ${link(uri: Uri.pa
appId: appId,
releaseId: release.id,
platform: releasePlatform,
projectRoot: any(named: 'projectRoot'),
aabPath: any(named: 'aabPath'),
architectures: any(named: 'architectures'),
flavor: flavor,
@@ -107,6 +107,7 @@ flutter:
late ArgResults argResults;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory projectRoot;
late Doctor doctor;
late Platform platform;
late Auth auth;
@@ -138,17 +139,16 @@ flutter:
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
void setUpProjectRoot() {
File(
p.join(tempDir.path, 'pubspec.yaml'),
p.join(projectRoot.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
File(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -160,7 +160,7 @@ flutter:
..writeAsStringSync(infoPlistContent);
Directory(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -170,8 +170,7 @@ flutter:
'Runner.app',
),
).createSync(recursive: true);
File(p.join(tempDir.path, ipaPath)).createSync(recursive: true);
return tempDir;
File(p.join(projectRoot.path, ipaPath)).createSync(recursive: true);
}
setUpAll(() {
@@ -187,6 +186,7 @@ flutter:
doctor = MockDoctor();
platform = MockPlatform();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
auth = MockAuth();
operatingSystemInterface = MockOperatingSystemInterface();
progress = MockProgress();
@@ -200,6 +200,9 @@ flutter:
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(() => shorebirdEnv.isRunningOnCI).thenReturn(false);
when(
@@ -233,14 +236,16 @@ flutter:
when(
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
).thenReturn(version);
when(() => operatingSystemInterface.which('flutter'))
.thenReturn('/path/to/flutter');
when(
() => operatingSystemInterface.which('flutter'),
).thenReturn('/path/to/flutter');
when(() => platform.operatingSystem).thenReturn(Platform.macOS);
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(() => flutterPubGetProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(
() => flutterPubGetProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => codePushClientWrapper.getApp(appId: any(named: 'appId')),
).thenAnswer((_) async => appMetadata);
@@ -330,11 +335,8 @@ flutter:
});
test('prints instructions to manually codesign', () async {
final tempDir = setUpTempDir();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
await runWithOverrides(command.run);
verify(
() => logger.info(
@@ -344,11 +346,8 @@ flutter:
});
test('builds without codesigning', () async {
final tempDir = setUpTempDir();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -371,10 +370,10 @@ flutter:
group('when build directory has non-default structure', () {
test('prints error and exits with code 70 if xcarchive does not exist',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
Directory(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -382,23 +381,21 @@ flutter:
),
).deleteSync(recursive: true);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
verify(() => logger.err('Unable to find .xcarchive directory'))
.called(1);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find .xcarchive directory'),
).called(1);
});
test(
'''prints error and exits with code 70 if .app directory does not exist''',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
Directory(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -408,21 +405,18 @@ flutter:
),
).deleteSync(recursive: true);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
expect(exitCode, equals(ExitCode.software.code));
verify(() => logger.err('Unable to find .app directory')).called(1);
});
test(
'''finds .xcarchive and .app when they do not have the default "Runner" name''',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final archivePath = p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -446,24 +440,19 @@ flutter:
),
);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
});
});
test('prints archive upload instructions on success', () async {
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
final archivePath = p.join(
projectRoot.path,
'build',
'ios',
'archive',
@@ -475,10 +464,10 @@ flutter:
that: stringContainsInOrder(
[
'Your next step is to submit the archive',
archivePath,
p.relative(archivePath),
'to the App Store using Xcode.',
'You can open the archive in Xcode by running',
'open $archivePath',
'open ${p.relative(archivePath)}',
'''Make sure to uncheck "Manage Version and Build Number", or else shorebird will not work.''',
],
),
@@ -488,20 +477,19 @@ flutter:
});
test('creates unsigned release artifacts', () async {
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
expect(exitCode, equals(ExitCode.success.code));
verify(
() => codePushClientWrapper.createIosReleaseArtifacts(
appId: appId,
releaseId: release.id,
xcarchivePath:
any(named: 'xcarchivePath', that: endsWith('.xcarchive')),
xcarchivePath: any(
named: 'xcarchivePath',
that: endsWith('.xcarchive'),
),
runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')),
isCodesigned: false,
),
@@ -512,18 +500,16 @@ flutter:
group('when both export-method and export-options-plist are provided', () {
setUp(() {
when(() => argResults.wasParsed(exportMethodArgName)).thenReturn(true);
when(() => argResults[exportOptionsPlistArgName])
.thenReturn('/path/to/export.plist');
when(
() => argResults[exportOptionsPlistArgName],
).thenReturn('/path/to/export.plist');
});
test('logs error and exits with usage code', () async {
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.usage.code));
expect(exitCode, equals(ExitCode.usage.code));
verify(
() => logger.err(
'Cannot specify both --export-method and --export-options-plist.',
@@ -542,11 +528,8 @@ flutter:
test('generates an export options plist with that export method',
() async {
final tempDir = setUpTempDir();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
await runWithOverrides(command.run);
final capturedArgs = verify(
() => shorebirdProcess.run(
@@ -578,13 +561,10 @@ flutter:
});
test('exits with usage code', () async {
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.usage.code));
expect(exitCode, equals(ExitCode.usage.code));
verify(
() => logger.err(
'Exception: Export options plist file /does/not/exist does not exist',
@@ -604,18 +584,16 @@ flutter:
''';
test('exits with usage code', () async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final exportPlistFile = File(
p.join(tempDir.path, 'export.plist'),
p.join(projectRoot.path, 'export.plist'),
)..writeAsStringSync(exportPlistContent);
when(() => argResults[exportOptionsPlistArgName])
.thenReturn(exportPlistFile.path);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
when(
() => argResults[exportOptionsPlistArgName],
).thenReturn(exportPlistFile.path);
final exitCode = await runWithOverrides(command.run);
expect(result, equals(ExitCode.usage.code));
expect(exitCode, equals(ExitCode.usage.code));
verify(
() => logger.err(
'''Exception: Export options plist ${exportPlistFile.path} does not set manageAppVersionAndBuildNumber to false. This is required for shorebird to work.''',
@@ -634,11 +612,8 @@ flutter:
test('generates an export options plist with app-store export method',
() async {
final tempDir = setUpTempDir();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
await runWithOverrides(command.run);
final capturedArgs = verify(
() => shorebirdProcess.run(
@@ -667,11 +642,8 @@ flutter:
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -697,11 +669,8 @@ error: exportArchive: Communication with Apple failed
error: exportArchive: No signing certificate "iOS Distribution" found
''');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -718,10 +687,10 @@ error: exportArchive: No signing certificate "iOS Distribution" found
test('exits with code 70 when release version cannot be determined',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final file = File(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -731,10 +700,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found
)
..createSync(recursive: true)
..writeAsStringSync(emptyPlistContent);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -747,12 +713,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found
test('aborts when user opts out', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
@@ -769,10 +732,10 @@ error: exportArchive: No signing certificate "iOS Distribution" found
});
test('exits with code 70 if Info.plist does not exist', () async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final infoPlistFile = File(
p.join(
tempDir.path,
projectRoot.path,
'build',
'ios',
'archive',
@@ -781,10 +744,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found
),
)..deleteSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -793,28 +753,22 @@ error: exportArchive: No signing certificate "iOS Distribution" found
});
test('exits with code 70 if build directory does not exist', () async {
final tempDir = setUpTempDir();
Directory(p.join(tempDir.path, 'build')).deleteSync(recursive: true);
setUpProjectRoot();
Directory(p.join(projectRoot.path, 'build')).deleteSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(() => logger.err('Unable to find .xcarchive directory')).called(1);
});
test('exits with code 70 if ipa build directory does not exist', () async {
final tempDir = setUpTempDir();
final ipaDirectory =
Directory(p.join(tempDir.path, 'build', 'ios', 'ipa'))
..deleteSync(recursive: true);
setUpProjectRoot();
final ipaDirectory = Directory(
p.join(projectRoot.path, 'build', 'ios', 'ipa'),
)..deleteSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -832,13 +786,10 @@ error: exportArchive: No signing certificate "iOS Distribution" found
});
test('exits with code 70 if ipa file does not exist', () async {
final tempDir = setUpTempDir();
File(p.join(tempDir.path, ipaPath)).deleteSync(recursive: true);
setUpProjectRoot();
File(p.join(projectRoot.path, ipaPath)).deleteSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -855,14 +806,12 @@ error: exportArchive: No signing certificate "iOS Distribution" found
});
test('exits with code 70 if more than one ipa file is found', () async {
final tempDir = setUpTempDir();
File(p.join(tempDir.path, 'build/ios/ipa/Runner2.ipa'))
.createSync(recursive: true);
setUpProjectRoot();
File(
p.join(projectRoot.path, 'build/ios/ipa/Runner2.ipa'),
).createSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -883,12 +832,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found
'when --release-version and --force are used', () async {
when(() => argResults['force']).thenReturn(true);
when(() => argResults['release-version']).thenReturn(version);
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
expect(exitCode, ExitCode.success.code);
@@ -906,12 +852,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found
});
test('succeeds when release is successful', () async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
@@ -949,12 +892,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -973,18 +913,15 @@ error: exportArchive: No signing certificate "iOS Distribution" found
final target = p.join('lib', 'main_development.dart');
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final tempDir = setUpTempDir();
setUpProjectRoot();
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: productionAppId
flavors:
development: $appId''');
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
@@ -1020,12 +957,9 @@ flavors:
releaseVersion: any(named: 'releaseVersion'),
),
).thenAnswer((_) async => release);
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verifyNever(
@@ -1058,12 +992,9 @@ flavors:
test('provides appropriate ExportOptions.plist to build ipa command',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
final capturedArgs = verify(
@@ -1093,12 +1024,9 @@ flavors:
test('does not provide export options when codesign is false', () async {
when(() => argResults['codesign']).thenReturn(false);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
final capturedArgs = verify(
() => shorebirdProcess.run(
@@ -1117,12 +1045,9 @@ flavors:
test('does not prompt if running on CI', () async {
when(() => shorebirdEnv.isRunningOnCI).thenReturn(true);
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => logger.confirm(any()));
@@ -63,6 +63,7 @@ flutter:
late ArgResults argResults;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory projectRoot;
late Doctor doctor;
late Platform platform;
late Auth auth;
@@ -94,15 +95,13 @@ flutter:
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
void setUpProjectRoot() {
File(
p.join(tempDir.path, 'pubspec.yaml'),
p.join(projectRoot.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
p.join(projectRoot.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
setUpAll(() {
@@ -119,6 +118,7 @@ flutter:
doctor = MockDoctor();
platform = MockPlatform();
shorebirdRoot = Directory.systemTemp.createTempSync();
projectRoot = Directory.systemTemp.createTempSync();
auth = MockAuth();
progress = MockProgress();
logger = MockLogger();
@@ -132,6 +132,9 @@ flutter:
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.getShorebirdProjectRoot(),
).thenReturn(projectRoot);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(
() => shorebirdProcess.run(
@@ -247,11 +250,8 @@ flutter:
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
setUpProjectRoot();
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
@@ -266,12 +266,9 @@ flutter:
releaseVersion: any(named: 'releaseVersion'),
),
).thenAnswer((_) async => release);
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => codePushClientWrapper.ensureReleaseIsNotActive(
@@ -283,12 +280,9 @@ flutter:
test('aborts when user opts out', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
@@ -296,8 +290,10 @@ flutter:
() => codePushClientWrapper.createIosReleaseArtifacts(
appId: appId,
releaseId: release.id,
xcarchivePath:
any(named: 'xcarchivePath', that: endsWith('.xcarchive')),
xcarchivePath: any(
named: 'xcarchivePath',
that: endsWith('.xcarchive'),
),
runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')),
isCodesigned: any(named: 'isCodesigned'),
),
@@ -309,12 +305,9 @@ flutter:
'when --release-version and --force are used', () async {
when(() => argResults['force']).thenReturn(true);
when(() => argResults['release-version']).thenReturn(version);
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
expect(exitCode, ExitCode.success.code);
@@ -332,12 +325,9 @@ flutter:
});
test('succeeds when release is successful', () async {
final tempDir = setUpTempDir();
setUpProjectRoot();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
@@ -378,12 +368,9 @@ flutter:
test('runs flutter pub get with system flutter after successful build',
() async {
final tempDir = setUpTempDir();
setUpProjectRoot();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => shorebirdProcess.run(
@@ -114,13 +114,11 @@ Please use "shorebird preview" instead.'''),
});
test('exits with code when running the app fails', () async {
final tempDir = Directory.systemTemp.createTempSync();
final progress = MockProgress();
when(() => logger.progress(any())).thenReturn(progress);
const error = 'oops something went wrong';
const exitCode = 1;
const expectedExitCode = 1;
when(
() => process.stdout,
).thenAnswer((_) => const Stream.empty());
@@ -128,20 +126,15 @@ Please use "shorebird preview" instead.'''),
when(() => process.stderr).thenAnswer(
(_) => Stream.value(utf8.encode(error)),
);
when(() => process.exitCode).thenAnswer((_) async => exitCode);
when(() => process.exitCode).thenAnswer((_) async => expectedExitCode);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
await expectLater(result, equals(exitCode));
await expectLater(exitCode, equals(expectedExitCode));
verify(() => logger.err(error)).called(1);
});
test('exits with code 0 when running the app succeeds', () async {
final tempDir = Directory.systemTemp.createTempSync();
final progress = MockProgress();
when(() => logger.progress(any())).thenReturn(progress);
@@ -155,19 +148,14 @@ Please use "shorebird preview" instead.'''),
() => process.exitCode,
).thenAnswer((_) async => ExitCode.success.code);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
await expectLater(result, equals(ExitCode.success.code));
await expectLater(exitCode, equals(ExitCode.success.code));
verify(() => logger.info(output)).called(1);
verify(() => ioSink.addStream(any())).called(1);
});
test('passes additional args when specified', () async {
final tempDir = Directory.systemTemp.createTempSync();
final progress = MockProgress();
when(() => logger.progress(any())).thenReturn(progress);
@@ -187,10 +175,7 @@ Please use "shorebird preview" instead.'''),
() => process.exitCode,
).thenAnswer((_) async => ExitCode.success.code);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
final args = verify(
() => shorebirdProcess.start(
@@ -212,7 +197,7 @@ Please use "shorebird preview" instead.'''),
]),
);
await expectLater(result, equals(ExitCode.success.code));
await expectLater(exitCode, equals(ExitCode.success.code));
});
});
}
@@ -60,6 +60,8 @@ class MockCodePushClientWrapper extends Mock implements CodePushClientWrapper {}
class MockDevicectl extends Mock implements Devicectl {}
class MockDirectory extends Mock implements Directory {}
class MockDoctor extends Mock implements Doctor {}
class MockFile extends Mock implements File {}
@@ -45,6 +45,103 @@ void main() {
when(() => platform.script).thenReturn(platformScript);
});
group('getShorebirdYamlFile', () {
test('returns correct file', () {
final tempDir = Directory.systemTemp.createTempSync();
expect(
runWithOverrides(
() => shorebirdEnv.getShorebirdYamlFile(cwd: tempDir).path,
),
equals(p.join(tempDir.path, 'shorebird.yaml')),
);
});
});
group('getFlutterProjectRoot', () {
test('returns null when no Flutter project exists', () {
final tempDir = Directory.systemTemp.createTempSync();
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.getFlutterProjectRoot()),
getCurrentDirectory: () => tempDir,
),
isNull,
);
});
test('returns correct directory when Flutter project exists (root)', () {
final tempDir = Directory.systemTemp.createTempSync();
File(p.join(tempDir.path, 'pubspec.yaml')).createSync(recursive: true);
final projectRoot = IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.getFlutterProjectRoot(),
),
getCurrentDirectory: () => tempDir,
);
expect(projectRoot!.path, equals(tempDir.path));
});
test('returns correct directory when Flutter project exists (nested)',
() {
final tempDir = Directory.systemTemp.createTempSync();
final nestedDir = Directory(p.join(tempDir.path, 'nested'));
File(p.join(tempDir.path, 'pubspec.yaml')).createSync(recursive: true);
final projectRoot = IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.getFlutterProjectRoot(),
),
getCurrentDirectory: () => nestedDir,
);
expect(projectRoot!.path, equals(tempDir.path));
});
});
group('getShorebirdProjectRoot', () {
test('returns null when no Shorebird project exists', () {
final tempDir = Directory.systemTemp.createTempSync();
expect(
IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.getShorebirdProjectRoot(),
),
getCurrentDirectory: () => tempDir,
),
isNull,
);
});
test('returns correct directory when Shorebird project exists (root)',
() {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).createSync(recursive: true);
final projectRoot = IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.getShorebirdProjectRoot(),
),
getCurrentDirectory: () => tempDir,
);
expect(projectRoot!.path, equals(tempDir.path));
});
test('returns correct directory when Flutter project exists (nested)',
() {
final tempDir = Directory.systemTemp.createTempSync();
final nestedDir = Directory(p.join(tempDir.path, 'nested'));
File(
p.join(tempDir.path, 'shorebird.yaml'),
).createSync(recursive: true);
final projectRoot = IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.getShorebirdProjectRoot(),
),
getCurrentDirectory: () => nestedDir,
);
expect(projectRoot!.path, equals(tempDir.path));
});
});
group('flutterBinaryFile', () {
test('returns correct path', () {
expect(
@@ -89,15 +186,10 @@ void main() {
group('getPubspecYamlFile', () {
test('returns correct file', () {
final tempDir = Directory('temp');
final tempDir = Directory.systemTemp.createTempSync();
expect(
IOOverrides.runZoned(
() {
return runWithOverrides(
() => shorebirdEnv.getPubspecYamlFile().path,
);
},
getCurrentDirectory: () => tempDir,
runWithOverrides(
() => shorebirdEnv.getPubspecYamlFile(cwd: tempDir).path,
),
equals(p.join(tempDir.path, 'pubspec.yaml')),
);
@@ -106,7 +198,7 @@ void main() {
group('getPubspecYaml', () {
test('returns null when pubspec.yaml does not exist', () {
final tempDir = Directory('temp');
final tempDir = Directory.systemTemp.createTempSync();
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.getPubspecYaml()),
@@ -133,7 +225,7 @@ void main() {
group('hasPubspecYaml', () {
test('returns false when pubspec.yaml does not exist', () {
final tempDir = Directory('temp');
final tempDir = Directory.systemTemp.createTempSync();
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.hasPubspecYaml),
@@ -1,9 +1,14 @@
import 'dart:io';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:test/test.dart';
import '../mocks.dart';
void main() {
const manifestWithInternetPermission = '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -33,40 +38,53 @@ void main() {
''';
group(AndroidInternetPermissionValidator, () {
Directory createTempDir() => Directory.systemTemp.createTempSync();
late Directory projectRoot;
late ShorebirdEnv shorebirdEnv;
void writeManifestToPath(String manifestContents, String path) {
Directory(path).createSync(recursive: true);
File(p.join(path, 'AndroidManifest.xml'))
.writeAsStringSync(manifestContents);
File(
p.join(path, 'AndroidManifest.xml'),
).writeAsStringSync(manifestContents);
}
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
setUp(() {
projectRoot = Directory.systemTemp.createTempSync();
shorebirdEnv = MockShorebirdEnv();
when(() => shorebirdEnv.getFlutterProjectRoot()).thenReturn(projectRoot);
});
test('has a non-empty description', () {
expect(AndroidInternetPermissionValidator().description, isNotEmpty);
});
group('canRunInContext', () {
test('returns false if no android src directory exists', () {
final tempDirectory = createTempDir();
final result = IOOverrides.runZoned(
final result = runWithOverrides(
() => AndroidInternetPermissionValidator().canRunInCurrentContext(),
getCurrentDirectory: () => tempDirectory,
);
expect(result, isFalse);
});
test('returns true if an android src directory exists', () {
final tempDirectory = createTempDir();
writeManifestToPath(
manifestWithInternetPermission,
p.join(tempDirectory.path, 'android', 'app', 'src', 'main'),
p.join(projectRoot.path, 'android', 'app', 'src', 'main'),
);
final result = IOOverrides.runZoned(
final result = runWithOverrides(
() => AndroidInternetPermissionValidator().canRunInCurrentContext(),
getCurrentDirectory: () => tempDirectory,
);
expect(result, isTrue);
@@ -76,15 +94,13 @@ void main() {
test(
'''returns successful result if the main AndroidManifest.xml file has the INTERNET permission''',
() async {
final tempDirectory = createTempDir();
writeManifestToPath(
manifestWithInternetPermission,
p.join(tempDirectory.path, 'android', 'app', 'src', 'main'),
p.join(projectRoot.path, 'android', 'app', 'src', 'main'),
);
final results = await IOOverrides.runZoned(
final results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results.map((res) => res.severity), isEmpty);
@@ -93,13 +109,12 @@ void main() {
test('returns an error if AndroidManifest.xml file does not exist',
() async {
final tempDirectory = createTempDir();
Directory(p.join(tempDirectory.path, 'android', 'app', 'src', 'main'))
.createSync(recursive: true);
Directory(
p.join(projectRoot.path, 'android', 'app', 'src', 'main'),
).createSync(recursive: true);
final results = await IOOverrides.runZoned(
final results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results, hasLength(1));
@@ -113,18 +128,21 @@ void main() {
group('when the INTERNET permission is commented out', () {
test('returns error', () async {
final tempDirectory = createTempDir();
final manifestPath =
p.join(tempDirectory.path, 'android', 'app', 'src', 'main');
final manifestPath = p.join(
projectRoot.path,
'android',
'app',
'src',
'main',
);
writeManifestToPath(
manifestWithCommentedOutInternetPermission,
manifestPath,
);
final results = await IOOverrides.runZoned(
final results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results, hasLength(1));
@@ -143,15 +161,18 @@ void main() {
group('when the INTERNET permission is missing', () {
test('returns error', () async {
final tempDirectory = createTempDir();
final manifestPath =
p.join(tempDirectory.path, 'android', 'app', 'src', 'main');
final manifestPath = p.join(
projectRoot.path,
'android',
'app',
'src',
'main',
);
writeManifestToPath(manifestWithNoPermissions, manifestPath);
final results = await IOOverrides.runZoned(
final results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results, hasLength(1));
@@ -170,18 +191,21 @@ void main() {
group('when manifest has non-INTERNET permissions', () {
test('returns error', () async {
final tempDirectory = createTempDir();
final manifestPath =
p.join(tempDirectory.path, 'android', 'app', 'src', 'main');
final manifestPath = p.join(
projectRoot.path,
'android',
'app',
'src',
'main',
);
writeManifestToPath(
manifestWithNonInternetPermissions,
manifestPath,
);
final results = await IOOverrides.runZoned(
final results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results, hasLength(1));
@@ -200,27 +224,21 @@ void main() {
group('fix', () {
test('adds permission to manifest file', () async {
final tempDirectory = createTempDir();
writeManifestToPath(
manifestWithNonInternetPermissions,
p.join(tempDirectory.path, 'android', 'app', 'src', 'main'),
p.join(projectRoot.path, 'android', 'app', 'src', 'main'),
);
var results = await IOOverrides.runZoned(
var results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results, hasLength(1));
expect(results.first.fix, isNotNull);
await IOOverrides.runZoned(
() => results.first.fix!(),
getCurrentDirectory: () => tempDirectory,
);
await runWithOverrides(() => results.first.fix!());
results = await IOOverrides.runZoned(
results = await runWithOverrides(
AndroidInternetPermissionValidator().validate,
getCurrentDirectory: () => tempDirectory,
);
expect(results, isEmpty);
});