feat: Add a workflow for local-engine dev (#309)
This commit is contained in:
@@ -16,7 +16,8 @@ https://github.com/shorebirdtech/old_repo
|
||||
|
||||
## Getting Started
|
||||
|
||||
Refer to [shorebird/install](https://github.com/shorebirdtech/install) for installation instructions.
|
||||
Refer to [shorebird/install](https://github.com/shorebirdtech/install) for
|
||||
installation instructions.
|
||||
|
||||
## Packages
|
||||
|
||||
@@ -47,17 +48,24 @@ We currently assume the Dart from the Flutter SDK on the 'stable' channel. Due
|
||||
to the way the Dart compiler works, Shorebird requires an exact version of
|
||||
Flutter/Dart to operate correctly today.
|
||||
|
||||
We currently assume Rust 1.67.0 or later, although the code is unlikely to be
|
||||
sensitive to the exact version of Rust.
|
||||
|
||||
Once both are installed, `./scripts/bootstrap.sh` will run `pub get`
|
||||
and `cargo check` for all packages in the repository.
|
||||
Once both are installed, `./scripts/bootstrap.sh` will run `pub get` all
|
||||
packages in the repository.
|
||||
|
||||
### Running tests
|
||||
|
||||
We don't yet have a script to run tests locally. For now, you can run tests
|
||||
manually by running `cargo test` in a Rust package directory or `dart test` in
|
||||
a Dart package directory.
|
||||
manually by running `dart test` in a Dart package directory.
|
||||
|
||||
### Tracking coverage
|
||||
|
||||
The following command will generate a coverage report for the Dart packages:
|
||||
|
||||
```bash
|
||||
dart test --coverage=coverage && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --check-ignore
|
||||
```
|
||||
|
||||
We don't yet have a recommended way to view the coverage report but there are
|
||||
several extensions available in VSCode.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart';
|
||||
import 'package:shorebird_cli/src/command_runner.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
@@ -25,12 +26,10 @@ typedef StartProcess = Future<Process> Function(
|
||||
bool runInShell,
|
||||
});
|
||||
|
||||
List<Validator> _defaultValidators({required RunProcess runProcess}) {
|
||||
return [
|
||||
ShorebirdFlutterValidator(runProcess: runProcess),
|
||||
AndroidInternetPermissionValidator(),
|
||||
];
|
||||
}
|
||||
List<Validator> _defaultValidators() => [
|
||||
ShorebirdFlutterValidator(),
|
||||
AndroidInternetPermissionValidator(),
|
||||
];
|
||||
|
||||
abstract class ShorebirdCommand extends Command<int> {
|
||||
ShorebirdCommand({
|
||||
@@ -38,24 +37,42 @@ abstract class ShorebirdCommand extends Command<int> {
|
||||
Auth? auth,
|
||||
Cache? cache,
|
||||
CodePushClientBuilder? buildCodePushClient,
|
||||
RunProcess? runProcess,
|
||||
StartProcess? startProcess,
|
||||
List<Validator>? validators,
|
||||
List<Validator>? validators, // For mocking.
|
||||
}) : auth = auth ?? Auth(),
|
||||
cache = cache ?? Cache(),
|
||||
buildCodePushClient = buildCodePushClient ?? CodePushClient.new,
|
||||
runProcess = runProcess ?? ShorebirdProcess.run,
|
||||
startProcess = startProcess ?? ShorebirdProcess.start {
|
||||
this.validators =
|
||||
validators ?? _defaultValidators(runProcess: this.runProcess);
|
||||
}
|
||||
validators = validators ?? _defaultValidators();
|
||||
|
||||
final Auth auth;
|
||||
final Cache cache;
|
||||
final CodePushClientBuilder buildCodePushClient;
|
||||
final Logger logger;
|
||||
final RunProcess runProcess;
|
||||
final StartProcess startProcess;
|
||||
|
||||
// We don't currently have a test involving both a CommandRunner
|
||||
// and a Command, so we can't test this getter.
|
||||
// coverage:ignore-start
|
||||
@override
|
||||
ShorebirdCliCommandRunner? get runner =>
|
||||
super.runner as ShorebirdCliCommandRunner?;
|
||||
// coverage:ignore-end
|
||||
|
||||
/// [ShorebirdProcess] used for testing purposes only.
|
||||
@visibleForTesting
|
||||
ShorebirdProcess? testProcess;
|
||||
|
||||
// If you hit a late initialization error here, it's because you're either
|
||||
// using process before runCommand has been called, or you're in a test
|
||||
// and should set testProcess instead.
|
||||
ShorebirdProcess get process => testProcess ?? runner!.process;
|
||||
|
||||
/// [EngineConfig] used for testing purposes only.
|
||||
@visibleForTesting
|
||||
EngineConfig? testEngineConfig;
|
||||
|
||||
// If you hit a late initialization error here, it's because you're either
|
||||
// using engineConfig before runCommand has been called, or you're in a test
|
||||
// and should set testEngineConfig instead.
|
||||
EngineConfig get engineConfig => testEngineConfig ?? process.engineConfig;
|
||||
|
||||
/// Checks that the Shorebird install and project are in a good state.
|
||||
late List<Validator> validators;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:cli_completion/cli_completion.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/version.dart';
|
||||
|
||||
const executableName = 'shorebird';
|
||||
@@ -33,6 +34,18 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
|
||||
..addFlag(
|
||||
'verbose',
|
||||
help: 'Noisy logging, including all shell commands executed.',
|
||||
)
|
||||
..addOption(
|
||||
'local-engine-src-path',
|
||||
hide: true,
|
||||
help: 'Path to your engine src directory, if you are building Flutter '
|
||||
'locally.',
|
||||
)
|
||||
..addOption(
|
||||
'local-engine',
|
||||
hide: true,
|
||||
help: 'Name of a build output within the engine out directory, if you '
|
||||
'are building Flutter locally.',
|
||||
);
|
||||
|
||||
addCommand(AccountCommand(logger: _logger));
|
||||
@@ -55,6 +68,9 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
|
||||
void printUsage() => _logger.info(usage);
|
||||
|
||||
final Logger _logger;
|
||||
// Currently using ShorebirdCliCommandRunner as our context object.
|
||||
late final ShorebirdProcess process;
|
||||
late final EngineConfig engineConfig;
|
||||
|
||||
@override
|
||||
Future<int> run(Iterable<String> args) async {
|
||||
@@ -64,6 +80,15 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
|
||||
_logger.level = Level.verbose;
|
||||
}
|
||||
|
||||
// Set up our context before running the command.
|
||||
engineConfig = EngineConfig(
|
||||
localEngineSrcPath: topLevelResults['local-engine-src-path'] as String?,
|
||||
localEngine: topLevelResults['local-engine'] as String?,
|
||||
);
|
||||
process = ShorebirdProcess(
|
||||
engineConfig: engineConfig,
|
||||
);
|
||||
|
||||
return await runCommand(topLevelResults) ?? ExitCode.success.code;
|
||||
} on FormatException catch (e, stackTrace) {
|
||||
// On format errors, show the commands error message, root usage and
|
||||
|
||||
@@ -17,7 +17,6 @@ class BuildApkCommand extends ShorebirdCommand
|
||||
BuildApkCommand({
|
||||
required super.logger,
|
||||
super.auth,
|
||||
super.runProcess,
|
||||
super.validators,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ class BuildAppBundleCommand extends ShorebirdCommand
|
||||
BuildAppBundleCommand({
|
||||
required super.logger,
|
||||
super.auth,
|
||||
super.runProcess,
|
||||
super.validators,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ class BuildCommand extends ShorebirdCommand
|
||||
BuildApkCommand(
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
runProcess: runProcess,
|
||||
validators: validators,
|
||||
),
|
||||
);
|
||||
@@ -25,7 +24,6 @@ class BuildCommand extends ShorebirdCommand
|
||||
BuildAppBundleCommand(
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
runProcess: runProcess,
|
||||
validators: validators,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -17,7 +17,6 @@ class DoctorCommand extends ShorebirdCommand with ShorebirdVersionMixin {
|
||||
DoctorCommand({
|
||||
required super.logger,
|
||||
super.validators,
|
||||
super.runProcess,
|
||||
}) {
|
||||
validators = _allValidators(baseValidators: validators);
|
||||
}
|
||||
@@ -26,7 +25,7 @@ class DoctorCommand extends ShorebirdCommand with ShorebirdVersionMixin {
|
||||
ShorebirdVersionValidator(
|
||||
isShorebirdVersionCurrent: isShorebirdVersionCurrent,
|
||||
),
|
||||
ShorebirdFlutterValidator(runProcess: runProcess),
|
||||
ShorebirdFlutterValidator(),
|
||||
AndroidInternetPermissionValidator(),
|
||||
];
|
||||
|
||||
@@ -46,7 +45,7 @@ Shorebird Engine • revision ${ShorebirdEnvironment.shorebirdEngineRevision}'''
|
||||
var numIssues = 0;
|
||||
for (final validator in validators) {
|
||||
final progress = logger.progress(validator.description);
|
||||
final issues = await validator.validate();
|
||||
final issues = await validator.validate(process);
|
||||
numIssues += issues.length;
|
||||
if (issues.isEmpty) {
|
||||
progress.complete();
|
||||
|
||||
@@ -46,7 +46,6 @@ class PatchCommand extends ShorebirdCommand
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.cache,
|
||||
super.runProcess,
|
||||
super.validators,
|
||||
HashFunction? hashFn,
|
||||
http.Client? httpClient,
|
||||
@@ -215,7 +214,7 @@ Please create a release using "shorebird release" and try again.
|
||||
final fetchReleaseArtifactProgress = logger.progress(
|
||||
'Fetching release artifacts',
|
||||
);
|
||||
for (final entry in ShorebirdBuildMixin.architectures.entries) {
|
||||
for (final entry in architectures.entries) {
|
||||
try {
|
||||
final releaseArtifact = await codePushClient.getReleaseArtifact(
|
||||
releaseId: release.id,
|
||||
@@ -251,8 +250,7 @@ Please create a release using "shorebird release" and try again.
|
||||
final createDiffProgress = logger.progress('Creating artifacts');
|
||||
|
||||
for (final releaseArtifactPath in releaseArtifactPaths.entries) {
|
||||
final archMetadata =
|
||||
ShorebirdBuildMixin.architectures[releaseArtifactPath.key]!;
|
||||
final archMetadata = architectures[releaseArtifactPath.key]!;
|
||||
final patchArtifactPath = p.join(
|
||||
Directory.current.path,
|
||||
'build',
|
||||
@@ -413,7 +411,7 @@ ${styleBold.wrap(lightGreen.wrap('🚀 Ready to publish a new patch!'))}
|
||||
diffPath,
|
||||
];
|
||||
|
||||
final result = await runProcess(
|
||||
final result = await process.run(
|
||||
diffExecutable,
|
||||
diffArguments,
|
||||
runInShell: true,
|
||||
|
||||
@@ -26,7 +26,6 @@ class ReleaseCommand extends ShorebirdCommand
|
||||
required super.logger,
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.runProcess,
|
||||
super.validators,
|
||||
HashFunction? hashFn,
|
||||
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) {
|
||||
@@ -126,7 +125,7 @@ Did you forget to run "shorebird init"?''',
|
||||
);
|
||||
|
||||
final platform = results['platform'] as String;
|
||||
final archNames = ShorebirdBuildMixin.architectures.keys.map(
|
||||
final archNames = architectures.keys.map(
|
||||
(arch) => arch.name,
|
||||
);
|
||||
|
||||
@@ -172,7 +171,7 @@ ${styleBold.wrap(lightGreen.wrap('🚀 Ready to create a new release!'))}
|
||||
}
|
||||
|
||||
final createArtifactProgress = logger.progress('Creating artifacts');
|
||||
for (final archMetadata in ShorebirdBuildMixin.architectures.values) {
|
||||
for (final archMetadata in architectures.values) {
|
||||
final artifactPath = p.join(
|
||||
Directory.current.path,
|
||||
'build',
|
||||
|
||||
@@ -16,7 +16,6 @@ class RunCommand extends ShorebirdCommand
|
||||
required super.logger,
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.startProcess,
|
||||
super.validators,
|
||||
});
|
||||
|
||||
@@ -38,7 +37,7 @@ class RunCommand extends ShorebirdCommand
|
||||
await logValidationIssues();
|
||||
|
||||
logger.info('Running app...');
|
||||
final process = await startProcess(
|
||||
final flutter = await process.start(
|
||||
'flutter',
|
||||
[
|
||||
'run',
|
||||
@@ -49,13 +48,13 @@ class RunCommand extends ShorebirdCommand
|
||||
runInShell: true,
|
||||
);
|
||||
|
||||
process.stdout.listen((event) {
|
||||
flutter.stdout.listen((event) {
|
||||
logger.info(utf8.decode(event));
|
||||
});
|
||||
process.stderr.listen((event) {
|
||||
flutter.stderr.listen((event) {
|
||||
logger.err(utf8.decode(event));
|
||||
});
|
||||
|
||||
return process.exitCode;
|
||||
return flutter.exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/shorebird_version_mixin.dart';
|
||||
/// {@endtemplate}
|
||||
class UpgradeCommand extends ShorebirdCommand with ShorebirdVersionMixin {
|
||||
/// {@macro upgrade_command}
|
||||
UpgradeCommand({required super.logger, super.runProcess});
|
||||
UpgradeCommand({required super.logger});
|
||||
|
||||
@override
|
||||
String get description => 'Upgrade your copy of Shorebird.';
|
||||
|
||||
@@ -6,7 +6,7 @@ mixin ShorebirdValidationMixin on ShorebirdCommand {
|
||||
/// Runs [Validator.validate] on all [validators] and writes issues to stdout.
|
||||
Future<void> logValidationIssues() async {
|
||||
final validationIssues = (await Future.wait(
|
||||
validators.map((v) => v.validate()),
|
||||
validators.map((v) => v.validate(process)),
|
||||
))
|
||||
.flattened;
|
||||
if (validationIssues.isNotEmpty) {
|
||||
|
||||
@@ -1,38 +1,71 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
|
||||
enum Arch {
|
||||
arm64,
|
||||
arm32,
|
||||
x86,
|
||||
x86_64,
|
||||
}
|
||||
|
||||
class ArchMetadata {
|
||||
const ArchMetadata({required this.path, required this.arch});
|
||||
const ArchMetadata({
|
||||
required this.path,
|
||||
required this.arch,
|
||||
required this.enginePath,
|
||||
});
|
||||
|
||||
final String path;
|
||||
final String arch;
|
||||
final String enginePath;
|
||||
}
|
||||
|
||||
mixin ShorebirdBuildMixin on ShorebirdCommand {
|
||||
// TODO(felangel): extend to other platforms.
|
||||
static const architectures = <Arch, ArchMetadata>{
|
||||
// This exists only so tests can get the full list.
|
||||
static const allAndroidArchitectures = <Arch, ArchMetadata>{
|
||||
Arch.arm64: ArchMetadata(
|
||||
path: 'arm64-v8a',
|
||||
arch: 'aarch64',
|
||||
enginePath: 'android_release_arm64',
|
||||
),
|
||||
Arch.arm32: ArchMetadata(
|
||||
path: 'armeabi-v7a',
|
||||
arch: 'arm',
|
||||
enginePath: 'android_release',
|
||||
),
|
||||
Arch.x86: ArchMetadata(
|
||||
Arch.x86_64: ArchMetadata(
|
||||
path: 'x86_64',
|
||||
arch: 'x86_64',
|
||||
enginePath: 'android_release_x64',
|
||||
),
|
||||
};
|
||||
|
||||
// TODO(felangel): extend to other platforms.
|
||||
Map<Arch, ArchMetadata> get architectures {
|
||||
// Flutter has a whole bunch of logic to parse the --local-engine flag.
|
||||
// We probably need similar.
|
||||
// It's a bit odd to grab off the shorebird process, but it's the easiest
|
||||
// way to have a single source of truth for the engine config for now.
|
||||
if (engineConfig.localEngine != null) {
|
||||
final localEngineOutName = engineConfig.localEngine;
|
||||
final metaDataEntry = allAndroidArchitectures.entries.firstWhereOrNull(
|
||||
(entry) => localEngineOutName == entry.value.enginePath,
|
||||
);
|
||||
if (metaDataEntry == null) {
|
||||
throw Exception(
|
||||
'Unknown local engine architecture for '
|
||||
'--local-engine=$localEngineOutName\n'
|
||||
'Known values: '
|
||||
'${allAndroidArchitectures.values.map((e) => e.enginePath)}',
|
||||
);
|
||||
}
|
||||
return {metaDataEntry.key: metaDataEntry.value};
|
||||
}
|
||||
return allAndroidArchitectures;
|
||||
}
|
||||
|
||||
Future<void> buildAppBundle() async {
|
||||
const executable = 'flutter';
|
||||
final arguments = [
|
||||
@@ -42,7 +75,7 @@ mixin ShorebirdBuildMixin on ShorebirdCommand {
|
||||
...results.rest,
|
||||
];
|
||||
|
||||
final result = await runProcess(
|
||||
final result = await process.run(
|
||||
executable,
|
||||
arguments,
|
||||
runInShell: true,
|
||||
@@ -67,7 +100,7 @@ mixin ShorebirdBuildMixin on ShorebirdCommand {
|
||||
...results.rest,
|
||||
];
|
||||
|
||||
final result = await runProcess(
|
||||
final result = await process.run(
|
||||
executable,
|
||||
arguments,
|
||||
runInShell: true,
|
||||
|
||||
@@ -3,30 +3,34 @@ import 'dart:io';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
|
||||
typedef RunProcess = Future<ProcessResult> Function(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter,
|
||||
});
|
||||
class EngineConfig {
|
||||
const EngineConfig({
|
||||
required this.localEngineSrcPath,
|
||||
required this.localEngine,
|
||||
});
|
||||
|
||||
typedef StartProcess = Future<Process> Function(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell,
|
||||
Map<String, String>? environment,
|
||||
bool useVendedFlutter,
|
||||
});
|
||||
const EngineConfig.empty()
|
||||
: localEngineSrcPath = null,
|
||||
localEngine = null;
|
||||
|
||||
final String? localEngineSrcPath;
|
||||
final String? localEngine;
|
||||
}
|
||||
|
||||
/// A wrapper around [Process] that replaces executables to Shorebird-vended
|
||||
/// versions.
|
||||
abstract class ShorebirdProcess {
|
||||
@visibleForTesting
|
||||
static ProcessWrapper processWrapper = ProcessWrapper();
|
||||
// This may need a better name, since it returns "Process" it's more a
|
||||
// "ProcessFactory" than a "Process".
|
||||
class ShorebirdProcess {
|
||||
ShorebirdProcess({
|
||||
required this.engineConfig,
|
||||
ProcessWrapper? processWrapper, // For mocking ShorebirdProcess.
|
||||
}) : processWrapper = processWrapper ?? ProcessWrapper();
|
||||
|
||||
static Future<ProcessResult> run(
|
||||
final ProcessWrapper processWrapper;
|
||||
final EngineConfig engineConfig;
|
||||
|
||||
Future<ProcessResult> run(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell = false,
|
||||
@@ -44,16 +48,16 @@ abstract class ShorebirdProcess {
|
||||
|
||||
return processWrapper.run(
|
||||
useVendedFlutter ? _resolveExecutable(executable) : executable,
|
||||
arguments,
|
||||
useVendedFlutter ? _resolveArguments(executable, arguments) : arguments,
|
||||
runInShell: runInShell,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: resolvedEnvironment,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Process> start(
|
||||
Future<Process> start(
|
||||
String executable,
|
||||
List<String> argument, {
|
||||
List<String> arguments, {
|
||||
Map<String, String>? environment,
|
||||
bool runInShell = false,
|
||||
bool useVendedFlutter = true,
|
||||
@@ -68,13 +72,13 @@ abstract class ShorebirdProcess {
|
||||
|
||||
return processWrapper.start(
|
||||
useVendedFlutter ? _resolveExecutable(executable) : executable,
|
||||
argument,
|
||||
useVendedFlutter ? _resolveArguments(executable, arguments) : arguments,
|
||||
runInShell: runInShell,
|
||||
environment: resolvedEnvironment,
|
||||
);
|
||||
}
|
||||
|
||||
static String _resolveExecutable(String executable) {
|
||||
String _resolveExecutable(String executable) {
|
||||
if (executable == 'flutter') {
|
||||
return ShorebirdEnvironment.flutterBinaryFile.path;
|
||||
}
|
||||
@@ -82,7 +86,21 @@ abstract class ShorebirdProcess {
|
||||
return executable;
|
||||
}
|
||||
|
||||
static Map<String, String> _environmentOverrides({
|
||||
List<String> _resolveArguments(
|
||||
String executable,
|
||||
List<String> arguments,
|
||||
) {
|
||||
if (executable == 'flutter' && engineConfig.localEngine != null) {
|
||||
return [
|
||||
'--local-engine-src-path=${engineConfig.localEngineSrcPath}',
|
||||
'--local-engine=${engineConfig.localEngine}',
|
||||
...arguments
|
||||
];
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
Map<String, String> _environmentOverrides({
|
||||
required String executable,
|
||||
}) {
|
||||
if (executable == 'flutter') {
|
||||
|
||||
@@ -23,7 +23,7 @@ mixin ShorebirdVersionMixin on ShorebirdCommand {
|
||||
/// Exits if HEAD isn't pointing to a branch, or there is no upstream.
|
||||
Future<String> fetchLatestGitHash({required String workingDirectory}) async {
|
||||
// Fetch upstream branch's commits and tags
|
||||
await runProcess(
|
||||
await process.run(
|
||||
'git',
|
||||
['fetch', '--tags'],
|
||||
workingDirectory: workingDirectory,
|
||||
@@ -47,7 +47,7 @@ mixin ShorebirdVersionMixin on ShorebirdCommand {
|
||||
String? workingDirectory,
|
||||
}) async {
|
||||
// Get the commit revision of HEAD
|
||||
final result = await runProcess(
|
||||
final result = await process.run(
|
||||
'git',
|
||||
['rev-parse', '--verify', revision],
|
||||
workingDirectory: workingDirectory,
|
||||
@@ -72,7 +72,7 @@ mixin ShorebirdVersionMixin on ShorebirdCommand {
|
||||
required String newRevision,
|
||||
required String workingDirectory,
|
||||
}) async {
|
||||
final result = await runProcess(
|
||||
final result = await process.run(
|
||||
'git',
|
||||
['reset', '--hard', newRevision],
|
||||
workingDirectory: workingDirectory,
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
@@ -16,7 +17,7 @@ class AndroidInternetPermissionValidator extends Validator {
|
||||
// coverage:ignore-end
|
||||
|
||||
@override
|
||||
Future<List<ValidationIssue>> validate() async {
|
||||
Future<List<ValidationIssue>> validate(ShorebirdProcess process) async {
|
||||
const manifestFileName = 'AndroidManifest.xml';
|
||||
final androidSrcDir = Directory(
|
||||
p.join(
|
||||
|
||||
@@ -10,9 +10,8 @@ class FlutterValidationException implements Exception {
|
||||
}
|
||||
|
||||
class ShorebirdFlutterValidator extends Validator {
|
||||
ShorebirdFlutterValidator({required this.runProcess});
|
||||
ShorebirdFlutterValidator();
|
||||
|
||||
final RunProcess runProcess;
|
||||
final _flutterVersionRegex = RegExp(r'Flutter (\d+.\d+.\d+)');
|
||||
|
||||
// coverage:ignore-start
|
||||
@@ -21,7 +20,7 @@ class ShorebirdFlutterValidator extends Validator {
|
||||
// coverage:ignore-end
|
||||
|
||||
@override
|
||||
Future<List<ValidationIssue>> validate() async {
|
||||
Future<List<ValidationIssue>> validate(ShorebirdProcess process) async {
|
||||
final issues = <ValidationIssue>[];
|
||||
|
||||
if (!ShorebirdEnvironment.flutterDirectory.existsSync()) {
|
||||
@@ -35,7 +34,7 @@ class ShorebirdFlutterValidator extends Validator {
|
||||
);
|
||||
}
|
||||
|
||||
if (!await _flutterDirectoryIsClean()) {
|
||||
if (!await _flutterDirectoryIsClean(process)) {
|
||||
issues.add(
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
@@ -45,7 +44,7 @@ class ShorebirdFlutterValidator extends Validator {
|
||||
);
|
||||
}
|
||||
|
||||
if (!await _flutterDirectoryTracksCorrectRevision()) {
|
||||
if (!await _flutterDirectoryTracksCorrectRevision(process)) {
|
||||
final message =
|
||||
'''${ShorebirdEnvironment.flutterDirectory} is not on the correct revision''';
|
||||
issues.add(
|
||||
@@ -56,8 +55,8 @@ class ShorebirdFlutterValidator extends Validator {
|
||||
);
|
||||
}
|
||||
|
||||
final shorebirdFlutterVersion = await _shorebirdFlutterVersion();
|
||||
final pathFlutterVersion = await _pathFlutterVersion();
|
||||
final shorebirdFlutterVersion = await _shorebirdFlutterVersion(process);
|
||||
final pathFlutterVersion = await _pathFlutterVersion(process);
|
||||
|
||||
if (shorebirdFlutterVersion != pathFlutterVersion) {
|
||||
final message = '''
|
||||
@@ -90,8 +89,8 @@ This can cause unexpected behavior if you are switching between the tools and th
|
||||
return issues;
|
||||
}
|
||||
|
||||
Future<bool> _flutterDirectoryIsClean() async {
|
||||
final result = await runProcess(
|
||||
Future<bool> _flutterDirectoryIsClean(ShorebirdProcess process) async {
|
||||
final result = await process.run(
|
||||
'git',
|
||||
['status'],
|
||||
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
|
||||
@@ -101,8 +100,10 @@ This can cause unexpected behavior if you are switching between the tools and th
|
||||
.contains('nothing to commit, working tree clean');
|
||||
}
|
||||
|
||||
Future<bool> _flutterDirectoryTracksCorrectRevision() async {
|
||||
final result = await runProcess(
|
||||
Future<bool> _flutterDirectoryTracksCorrectRevision(
|
||||
ShorebirdProcess process,
|
||||
) async {
|
||||
final result = await process.run(
|
||||
'git',
|
||||
['rev-parse', 'HEAD'],
|
||||
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
|
||||
@@ -112,18 +113,23 @@ This can cause unexpected behavior if you are switching between the tools and th
|
||||
.contains(ShorebirdEnvironment.flutterRevision);
|
||||
}
|
||||
|
||||
Future<String> _shorebirdFlutterVersion() => _getFlutterVersion(
|
||||
Future<String> _shorebirdFlutterVersion(ShorebirdProcess process) =>
|
||||
_getFlutterVersion(
|
||||
process: process,
|
||||
checkPathFlutter: false,
|
||||
);
|
||||
|
||||
Future<String> _pathFlutterVersion() => _getFlutterVersion(
|
||||
Future<String> _pathFlutterVersion(ShorebirdProcess process) =>
|
||||
_getFlutterVersion(
|
||||
process: process,
|
||||
checkPathFlutter: true,
|
||||
);
|
||||
|
||||
Future<String> _getFlutterVersion({
|
||||
required ShorebirdProcess process,
|
||||
required bool checkPathFlutter,
|
||||
}) async {
|
||||
final result = await runProcess(
|
||||
final result = await process.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
useVendedFlutter: !checkPathFlutter,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
|
||||
/// Verifies that the currently installed version of Shorebird is the latest.
|
||||
@@ -16,7 +17,7 @@ class ShorebirdVersionValidator extends Validator {
|
||||
// coverage:ignore-end
|
||||
|
||||
@override
|
||||
Future<List<ValidationIssue>> validate() async {
|
||||
Future<List<ValidationIssue>> validate(ShorebirdProcess process) async {
|
||||
final workingDirectory = p.dirname(Platform.script.toFilePath());
|
||||
final bool isShorebirdUpToDate;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
|
||||
export 'android_internet_permission_validator.dart';
|
||||
export 'shorebird_flutter_validator.dart';
|
||||
@@ -74,5 +75,6 @@ abstract class Validator {
|
||||
/// Checks for [ValidationIssue]s.
|
||||
///
|
||||
/// Returns an empty list if no issues are found.
|
||||
Future<List<ValidationIssue>> validate();
|
||||
/// Not all validators use [process].
|
||||
Future<List<ValidationIssue>> validate(ShorebirdProcess process);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/build/build.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -24,6 +25,8 @@ class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('build apk', () {
|
||||
late ArgResults argResults;
|
||||
@@ -33,43 +36,39 @@ void main() {
|
||||
late ProcessResult processResult;
|
||||
late BuildApkCommand command;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
String? processExecutable;
|
||||
List<String>? processArguments;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
argResults = _MockArgResults();
|
||||
httpClient = _MockHttpClient();
|
||||
auth = _MockAuth();
|
||||
logger = _MockLogger();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
processResult = _MockProcessResult();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
processExecutable = null;
|
||||
processArguments = null;
|
||||
command = BuildApkCommand(
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
processExecutable = executable;
|
||||
processArguments = arguments;
|
||||
return processResult;
|
||||
},
|
||||
validators: [flutterValidator],
|
||||
)..testArgResults = argResults;
|
||||
)
|
||||
..testArgResults = argResults
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
registerFallbackValue(shorebirdProcess);
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
any(),
|
||||
any(),
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).thenAnswer((_) async => processResult);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
when(() => logger.info(any())).thenReturn(null);
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
when(() => flutterValidator.validate(any())).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('has correct description', () {
|
||||
@@ -99,8 +98,13 @@ void main() {
|
||||
);
|
||||
|
||||
expect(result, equals(ExitCode.software.code));
|
||||
expect(processExecutable, equals('flutter'));
|
||||
expect(processArguments, equals(['build', 'apk', '--release']));
|
||||
verify(
|
||||
() => shorebirdProcess.run(
|
||||
'flutter',
|
||||
['build', 'apk', '--release'],
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('exits with code 0 when building apk succeeds', () async {
|
||||
@@ -112,12 +116,17 @@ void main() {
|
||||
);
|
||||
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
expect(processExecutable, equals('flutter'));
|
||||
expect(processArguments, equals(['build', 'apk', '--release']));
|
||||
verify(
|
||||
() => shorebirdProcess.run(
|
||||
'flutter',
|
||||
['build', 'apk', '--release'],
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
when(() => flutterValidator.validate(any())).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/build/build.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -24,6 +25,8 @@ class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('build appbundle', () {
|
||||
late ArgResults argResults;
|
||||
@@ -33,9 +36,7 @@ void main() {
|
||||
late ProcessResult processResult;
|
||||
late BuildAppBundleCommand command;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
String? processExecutable;
|
||||
List<String>? processArguments;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
argResults = _MockArgResults();
|
||||
@@ -44,32 +45,31 @@ void main() {
|
||||
logger = _MockLogger();
|
||||
processResult = _MockProcessResult();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
processExecutable = null;
|
||||
processArguments = null;
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
command = BuildAppBundleCommand(
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
processExecutable = executable;
|
||||
processArguments = arguments;
|
||||
return processResult;
|
||||
},
|
||||
validators: [flutterValidator],
|
||||
)..testArgResults = argResults;
|
||||
)
|
||||
..testArgResults = argResults
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
|
||||
registerFallbackValue(shorebirdProcess);
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
any(),
|
||||
any(),
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).thenAnswer((_) async => processResult);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
when(() => logger.info(any())).thenReturn(null);
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
when(() => flutterValidator.validate(any())).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('has correct description', () {
|
||||
@@ -99,8 +99,13 @@ void main() {
|
||||
);
|
||||
|
||||
expect(result, equals(ExitCode.software.code));
|
||||
expect(processExecutable, equals('flutter'));
|
||||
expect(processArguments, equals(['build', 'appbundle', '--release']));
|
||||
verify(
|
||||
() => shorebirdProcess.run(
|
||||
'flutter',
|
||||
['build', 'appbundle', '--release'],
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('exits with code 0 when building appbundle succeeds', () async {
|
||||
@@ -112,12 +117,34 @@ void main() {
|
||||
);
|
||||
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
expect(processExecutable, equals('flutter'));
|
||||
expect(processArguments, equals(['build', 'appbundle', '--release']));
|
||||
verify(
|
||||
() => shorebirdProcess.run(
|
||||
'flutter',
|
||||
['build', 'appbundle', '--release'],
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('local-engine and architectures', () async {
|
||||
expect(command.architectures.length, greaterThan(1));
|
||||
|
||||
command.testEngineConfig = const EngineConfig(
|
||||
localEngine: 'android_release_arm64',
|
||||
localEngineSrcPath: 'path/to/engine/src',
|
||||
);
|
||||
expect(command.architectures.length, equals(1));
|
||||
|
||||
// We only support a few release configs for now.
|
||||
command.testEngineConfig = const EngineConfig(
|
||||
localEngine: 'android_debug_unopt',
|
||||
localEngineSrcPath: 'path/to/engine/src',
|
||||
);
|
||||
expect(() => command.architectures, throwsException);
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
when(() => flutterValidator.validate(any())).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -18,6 +19,8 @@ class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockProgress extends Mock implements Progress {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('doctor', () {
|
||||
late Logger logger;
|
||||
@@ -26,6 +29,7 @@ void main() {
|
||||
late AndroidInternetPermissionValidator androidInternetPermissionValidator;
|
||||
late ShorebirdVersionValidator shorebirdVersionValidator;
|
||||
late ShorebirdFlutterValidator shorebirdFlutterValidator;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
logger = _MockLogger();
|
||||
@@ -40,26 +44,28 @@ void main() {
|
||||
_MockAndroidInternetPermissionValidator();
|
||||
shorebirdVersionValidator = _MockShorebirdVersionValidator();
|
||||
shorebirdFlutterValidator = _MockShorebirdFlutterValidator();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
registerFallbackValue(shorebirdProcess);
|
||||
|
||||
when(() => androidInternetPermissionValidator.id)
|
||||
.thenReturn('$AndroidInternetPermissionValidator');
|
||||
when(() => androidInternetPermissionValidator.description)
|
||||
.thenReturn('Android');
|
||||
when(() => androidInternetPermissionValidator.validate())
|
||||
when(() => androidInternetPermissionValidator.validate(any()))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
when(() => shorebirdVersionValidator.id)
|
||||
.thenReturn('$ShorebirdVersionValidator');
|
||||
when(() => shorebirdVersionValidator.description)
|
||||
.thenReturn('Shorebird Version');
|
||||
when(() => shorebirdVersionValidator.validate())
|
||||
when(() => shorebirdVersionValidator.validate(any()))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
when(() => shorebirdFlutterValidator.id)
|
||||
.thenReturn('$ShorebirdFlutterValidator');
|
||||
when(() => shorebirdFlutterValidator.description)
|
||||
.thenReturn('Shorebird Flutter');
|
||||
when(() => shorebirdFlutterValidator.validate())
|
||||
when(() => shorebirdFlutterValidator.validate(any()))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
command = DoctorCommand(
|
||||
@@ -69,13 +75,15 @@ void main() {
|
||||
shorebirdVersionValidator,
|
||||
shorebirdFlutterValidator,
|
||||
],
|
||||
);
|
||||
)
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
});
|
||||
|
||||
test('prints "no issues" when everything is OK', () async {
|
||||
await command.run();
|
||||
for (final validator in command.validators) {
|
||||
verify(validator.validate).called(1);
|
||||
verify(() => validator.validate(shorebirdProcess)).called(1);
|
||||
}
|
||||
verify(
|
||||
() => logger.info(any(that: contains('No issues detected'))),
|
||||
@@ -84,7 +92,7 @@ void main() {
|
||||
|
||||
test('prints messages when warnings or errors found', () async {
|
||||
when(
|
||||
() => androidInternetPermissionValidator.validate(),
|
||||
() => androidInternetPermissionValidator.validate(any()),
|
||||
).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
@@ -101,7 +109,7 @@ void main() {
|
||||
await command.run();
|
||||
|
||||
for (final validator in command.validators) {
|
||||
verify(validator.validate).called(1);
|
||||
verify(() => validator.validate(any())).called(1);
|
||||
}
|
||||
|
||||
verify(
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart' show Cache;
|
||||
import 'package:shorebird_cli/src/commands/patch_command.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
@@ -34,6 +35,8 @@ class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('patch', () {
|
||||
const appId = 'test-app-id';
|
||||
@@ -91,6 +94,7 @@ flutter:
|
||||
late PatchCommand command;
|
||||
late Uri? capturedHostedUri;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
Directory setUpTempDir() {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
@@ -104,7 +108,8 @@ flutter:
|
||||
}
|
||||
|
||||
void setUpTempArtifacts(Directory dir) {
|
||||
for (final archMetadata in ShorebirdBuildMixin.architectures.values) {
|
||||
for (final archMetadata
|
||||
in ShorebirdBuildMixin.allAndroidArchitectures.values) {
|
||||
final artifactPath = p.join(
|
||||
dir.path,
|
||||
'build',
|
||||
@@ -136,6 +141,7 @@ flutter:
|
||||
codePushClient = _MockCodePushClient();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
cache = _MockCache();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
command = PatchCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({
|
||||
@@ -146,23 +152,30 @@ flutter:
|
||||
return codePushClient;
|
||||
},
|
||||
cache: cache,
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'flutter') return flutterBuildProcessResult;
|
||||
if (executable.endsWith('patch')) return patchProcessResult;
|
||||
return _MockProcessResult();
|
||||
},
|
||||
logger: logger,
|
||||
httpClient: httpClient,
|
||||
validators: [flutterValidator],
|
||||
)..testArgResults = argResults;
|
||||
)
|
||||
..testArgResults = argResults
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
|
||||
registerFallbackValue(shorebirdProcess);
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'flutter',
|
||||
any(),
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).thenAnswer((_) async => flutterBuildProcessResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
any(that: endsWith('patch')),
|
||||
any(),
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).thenAnswer((_) async => patchProcessResult);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => argResults['arch']).thenReturn(arch);
|
||||
when(() => argResults['platform']).thenReturn(platform);
|
||||
@@ -223,7 +236,7 @@ flutter:
|
||||
channelId: any(named: 'channelId'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
when(() => flutterValidator.validate(any())).thenAnswer((_) async => []);
|
||||
when(() => cache.updateAll()).thenAnswer((_) async => {});
|
||||
when(
|
||||
() => cache.getArtifactDirectory(any()),
|
||||
@@ -580,7 +593,7 @@ base_url: $baseUrl''',
|
||||
test('prints flutter validation warnings', () async {
|
||||
final tempDir = setUpTempDir();
|
||||
setUpTempArtifacts(tempDir);
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
when(() => flutterValidator.validate(any())).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
@@ -29,6 +30,8 @@ class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('release', () {
|
||||
const appId = 'test-app-id';
|
||||
@@ -73,6 +76,7 @@ flutter:
|
||||
late ReleaseCommand command;
|
||||
late Uri? capturedHostedUri;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
Directory setUpTempDir() {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
@@ -86,7 +90,8 @@ flutter:
|
||||
}
|
||||
|
||||
void setUpTempArtifacts(Directory dir) {
|
||||
for (final archMetadata in ShorebirdBuildMixin.architectures.values) {
|
||||
for (final archMetadata
|
||||
in ShorebirdBuildMixin.allAndroidArchitectures.values) {
|
||||
final artifactPath = p.join(
|
||||
dir.path,
|
||||
'build',
|
||||
@@ -112,6 +117,7 @@ flutter:
|
||||
processResult = _MockProcessResult();
|
||||
codePushClient = _MockCodePushClient();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
command = ReleaseCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({
|
||||
@@ -121,20 +127,22 @@ flutter:
|
||||
capturedHostedUri = hostedUri;
|
||||
return codePushClient;
|
||||
},
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
return processResult;
|
||||
},
|
||||
logger: logger,
|
||||
validators: [flutterValidator],
|
||||
)..testArgResults = argResults;
|
||||
)
|
||||
..testArgResults = argResults
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
|
||||
registerFallbackValue(shorebirdProcess);
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
any(),
|
||||
any(),
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).thenAnswer((_) async => processResult);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => argResults['arch']).thenReturn(arch);
|
||||
when(() => argResults['platform']).thenReturn(platform);
|
||||
@@ -167,7 +175,7 @@ flutter:
|
||||
hash: any(named: 'hash'),
|
||||
),
|
||||
).thenAnswer((_) async => releaseArtifact);
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
when(() => flutterValidator.validate(any())).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('throws config error when shorebird is not initialized', () async {
|
||||
@@ -331,7 +339,7 @@ Did you forget to run "shorebird init"?''',
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
when(() => flutterValidator.validate(any())).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/run_command.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
@@ -32,6 +33,8 @@ class _MockAndroidInternetPermissionValidator extends Mock
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('run', () {
|
||||
late ArgResults argResults;
|
||||
@@ -43,6 +46,7 @@ void main() {
|
||||
late RunCommand runCommand;
|
||||
late AndroidInternetPermissionValidator androidInternetPermissionValidator;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
argResults = _MockArgResults();
|
||||
@@ -50,6 +54,7 @@ void main() {
|
||||
auth = _MockAuth();
|
||||
logger = _MockLogger();
|
||||
process = _MockProcess();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
codePushClient = _MockCodePushClient();
|
||||
androidInternetPermissionValidator =
|
||||
_MockAndroidInternetPermissionValidator();
|
||||
@@ -63,23 +68,32 @@ void main() {
|
||||
}) {
|
||||
return codePushClient;
|
||||
},
|
||||
startProcess: (executable, arguments, {bool runInShell = false}) async {
|
||||
return process;
|
||||
},
|
||||
validators: [
|
||||
androidInternetPermissionValidator,
|
||||
flutterValidator,
|
||||
],
|
||||
)..testArgResults = argResults;
|
||||
)
|
||||
..testArgResults = argResults
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
|
||||
registerFallbackValue(shorebirdProcess);
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.start(
|
||||
any(),
|
||||
any(),
|
||||
runInShell: any(named: 'runInShell'),
|
||||
),
|
||||
).thenAnswer((_) async => process);
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
when(
|
||||
() => androidInternetPermissionValidator.validate(),
|
||||
() => androidInternetPermissionValidator.validate(any()),
|
||||
).thenAnswer((_) async => []);
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
when(() => flutterValidator.validate(any())).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('exits with no user when not logged in', () async {
|
||||
@@ -145,7 +159,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('prints validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
when(() => flutterValidator.validate(any())).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
@@ -153,7 +167,7 @@ void main() {
|
||||
),
|
||||
],
|
||||
);
|
||||
when(() => androidInternetPermissionValidator.validate()).thenAnswer(
|
||||
when(() => androidInternetPermissionValidator.validate(any())).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.error,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
@@ -11,6 +12,8 @@ class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
|
||||
class _MockProgress extends Mock implements Progress {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
const currentShorebirdRevision = 'revision-1';
|
||||
const newerShorebirdRevision = 'revision-2';
|
||||
@@ -22,6 +25,7 @@ void main() {
|
||||
late ProcessResult fetchLatestVersionResult;
|
||||
late ProcessResult hardResetResult;
|
||||
late UpgradeCommand command;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
final progress = _MockProgress();
|
||||
@@ -32,40 +36,41 @@ void main() {
|
||||
fetchTagsResult = _MockProcessResult();
|
||||
fetchLatestVersionResult = _MockProcessResult();
|
||||
hardResetResult = _MockProcessResult();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
command = UpgradeCommand(
|
||||
logger: logger,
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'git') {
|
||||
const revParseHead = ['rev-parse', '--verify', 'HEAD'];
|
||||
if (arguments.every((arg) => revParseHead.contains(arg))) {
|
||||
return fetchCurrentVersionResult;
|
||||
}
|
||||
)
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
|
||||
const fetchTags = ['fetch', '--tags'];
|
||||
if (arguments.every((arg) => fetchTags.contains(arg))) {
|
||||
return fetchTagsResult;
|
||||
}
|
||||
|
||||
const revParseUpstream = ['rev-parse', '--verify', '@{upstream}'];
|
||||
if (arguments.every((arg) => revParseUpstream.contains(arg))) {
|
||||
return fetchLatestVersionResult;
|
||||
}
|
||||
|
||||
const resetHard = ['reset', '--hard', newerShorebirdRevision];
|
||||
if (arguments.every((arg) => resetHard.contains(arg))) {
|
||||
return hardResetResult;
|
||||
}
|
||||
}
|
||||
return _MockProcessResult();
|
||||
},
|
||||
);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['rev-parse', '--verify', 'HEAD'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => fetchCurrentVersionResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['fetch', '--tags'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => fetchTagsResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['rev-parse', '--verify', '@{upstream}'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => fetchLatestVersionResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['reset', '--hard', newerShorebirdRevision],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => hardResetResult);
|
||||
|
||||
when(
|
||||
() => fetchCurrentVersionResult.exitCode,
|
||||
|
||||
@@ -15,13 +15,16 @@ void main() {
|
||||
late ProcessWrapper processWrapper;
|
||||
late Process startProcess;
|
||||
late ProcessResult runProcessResult;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
processWrapper = _MockProcessWrapper();
|
||||
runProcessResult = _MockProcessResult();
|
||||
startProcess = _MockProcess();
|
||||
|
||||
ShorebirdProcess.processWrapper = processWrapper;
|
||||
shorebirdProcess = ShorebirdProcess(
|
||||
processWrapper: processWrapper,
|
||||
engineConfig: const EngineConfig.empty(),
|
||||
);
|
||||
|
||||
when(
|
||||
() => processWrapper.run(
|
||||
@@ -45,7 +48,7 @@ void main() {
|
||||
|
||||
group('run', () {
|
||||
test('forwards non-flutter executables to Process.run', () async {
|
||||
await ShorebirdProcess.run(
|
||||
await shorebirdProcess.run(
|
||||
'git',
|
||||
['pull'],
|
||||
runInShell: true,
|
||||
@@ -64,7 +67,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('replaces "flutter" with our local flutter', () async {
|
||||
await ShorebirdProcess.run(
|
||||
await shorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
@@ -87,7 +90,7 @@ void main() {
|
||||
test(
|
||||
'does not replace flutter with our local flutter if'
|
||||
' useVendedFlutter is false', () async {
|
||||
await ShorebirdProcess.run(
|
||||
await shorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
@@ -107,7 +110,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Updates environment if useVendedFlutter is true', () async {
|
||||
await ShorebirdProcess.run(
|
||||
await shorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
@@ -130,7 +133,7 @@ void main() {
|
||||
test(
|
||||
'Makes no changes to environment if useVendedFlutter is false',
|
||||
() async {
|
||||
await ShorebirdProcess.run(
|
||||
await shorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
@@ -152,9 +155,34 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('adds local-engine arguments if set', () async {
|
||||
shorebirdProcess = ShorebirdProcess(
|
||||
processWrapper: processWrapper,
|
||||
engineConfig: const EngineConfig(
|
||||
localEngineSrcPath: '/path/to/engine/src',
|
||||
localEngine: 'android_release_arm64',
|
||||
),
|
||||
);
|
||||
|
||||
await shorebirdProcess.run('flutter', []);
|
||||
|
||||
verify(
|
||||
() => processWrapper.run(
|
||||
any(),
|
||||
[
|
||||
'--local-engine-src-path=/path/to/engine/src',
|
||||
'--local-engine=android_release_arm64',
|
||||
],
|
||||
runInShell: any(named: 'runInShell'),
|
||||
environment: any(named: 'environment'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('start', () {
|
||||
test('forwards non-flutter executables to Process.run', () async {
|
||||
await ShorebirdProcess.start('git', ['pull'], runInShell: true);
|
||||
await shorebirdProcess.start('git', ['pull'], runInShell: true);
|
||||
|
||||
verify(
|
||||
() => processWrapper.start(
|
||||
@@ -167,7 +195,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('replaces "flutter" with our local flutter', () async {
|
||||
await ShorebirdProcess.start('flutter', ['run'], runInShell: true);
|
||||
await shorebirdProcess.start('flutter', ['run'], runInShell: true);
|
||||
|
||||
verify(
|
||||
() => processWrapper.start(
|
||||
@@ -184,7 +212,7 @@ void main() {
|
||||
test(
|
||||
'does not replace flutter with our local flutter if'
|
||||
' useVendedFlutter is false', () async {
|
||||
await ShorebirdProcess.start(
|
||||
await shorebirdProcess.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
@@ -203,7 +231,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Updates environment if useVendedFlutter is true', () async {
|
||||
await ShorebirdProcess.start(
|
||||
await shorebirdProcess.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
@@ -226,7 +254,7 @@ void main() {
|
||||
test(
|
||||
'Makes no changes to environment if useVendedFlutter is false',
|
||||
() async {
|
||||
await ShorebirdProcess.start(
|
||||
await shorebirdProcess.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
|
||||
+15
-4
@@ -1,10 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
const manifestWithInternetPermission = '''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
@@ -34,6 +38,12 @@ void main() {
|
||||
''';
|
||||
|
||||
group('AndroidInternetPermissionValidator', () {
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
});
|
||||
|
||||
Directory createTempDir() => Directory.systemTemp.createTempSync();
|
||||
|
||||
void writeManifestToPath(String manifestContents, String path) {
|
||||
@@ -57,7 +67,7 @@ void main() {
|
||||
);
|
||||
|
||||
final results = await IOOverrides.runZoned(
|
||||
() => AndroidInternetPermissionValidator().validate(),
|
||||
() => AndroidInternetPermissionValidator().validate(shorebirdProcess),
|
||||
getCurrentDirectory: () => tempDirectory,
|
||||
);
|
||||
|
||||
@@ -66,7 +76,8 @@ void main() {
|
||||
);
|
||||
|
||||
test('returns an error if no android project is found', () async {
|
||||
final results = await AndroidInternetPermissionValidator().validate();
|
||||
final results =
|
||||
await AndroidInternetPermissionValidator().validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.error);
|
||||
@@ -80,7 +91,7 @@ void main() {
|
||||
.createSync(recursive: true);
|
||||
|
||||
final results = await IOOverrides.runZoned(
|
||||
() => AndroidInternetPermissionValidator().validate(),
|
||||
() => AndroidInternetPermissionValidator().validate(shorebirdProcess),
|
||||
getCurrentDirectory: () => tempDirectory,
|
||||
);
|
||||
|
||||
@@ -133,7 +144,7 @@ void main() {
|
||||
);
|
||||
|
||||
final results = await IOOverrides.runZoned(
|
||||
() => AndroidInternetPermissionValidator().validate(),
|
||||
() => AndroidInternetPermissionValidator().validate(shorebirdProcess),
|
||||
getCurrentDirectory: () => tempDirectory,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -12,6 +12,8 @@ class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
|
||||
class _MockPlatform extends Mock implements Platform {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
group('ShorebirdFlutterValidator', () {
|
||||
const flutterRevision = '45fc514f1a9c347a3af76b02baf980a4d88b7879';
|
||||
@@ -44,6 +46,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
late ProcessResult shorebirdFlutterVersionProcessResult;
|
||||
late ProcessResult gitRevParseHeadProcessResult;
|
||||
late ProcessResult gitStatusProcessResult;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
Directory flutterDirectory(Directory root) =>
|
||||
Directory(p.join(root.path, 'bin', 'cache', 'flutter'));
|
||||
@@ -71,34 +74,32 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
shorebirdFlutterVersionProcessResult = _MockProcessResult();
|
||||
gitRevParseHeadProcessResult = _MockProcessResult();
|
||||
gitStatusProcessResult = _MockProcessResult();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
|
||||
validator = ShorebirdFlutterValidator(
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'git') {
|
||||
if (arguments.equals(['status'])) {
|
||||
return gitStatusProcessResult;
|
||||
} else if (arguments.equals(['rev-parse', 'HEAD'])) {
|
||||
return gitRevParseHeadProcessResult;
|
||||
}
|
||||
} else if (executable == 'flutter') {
|
||||
if (arguments.equals(['--version'])) {
|
||||
if (useVendedFlutter) {
|
||||
return shorebirdFlutterVersionProcessResult;
|
||||
} else {
|
||||
return pathFlutterVersionProcessResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _MockProcessResult();
|
||||
},
|
||||
);
|
||||
validator = ShorebirdFlutterValidator();
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['rev-parse', 'HEAD'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => gitRevParseHeadProcessResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['status'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => gitStatusProcessResult);
|
||||
when(() => shorebirdProcess.run('flutter', ['--version']))
|
||||
.thenAnswer((_) async => shorebirdFlutterVersionProcessResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
useVendedFlutter: false,
|
||||
),
|
||||
).thenAnswer((_) async => pathFlutterVersionProcessResult);
|
||||
|
||||
when(() => pathFlutterVersionProcessResult.stdout)
|
||||
.thenReturn(pathFlutterVersionMessage);
|
||||
@@ -114,7 +115,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
});
|
||||
|
||||
test('returns no issues when the Flutter install is good', () async {
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, isEmpty);
|
||||
});
|
||||
@@ -122,7 +123,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
test('errors when Flutter does not exist', () async {
|
||||
flutterDirectory(tempDir).deleteSync();
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.error);
|
||||
@@ -133,7 +134,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
when(() => gitStatusProcessResult.stdout)
|
||||
.thenReturn('Changes not staged for commit');
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
@@ -145,7 +146,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
62bd79521d
|
||||
''');
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
@@ -160,14 +161,15 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
pathFlutterVersionMessage.replaceAll('3.7.9', '3.7.10'),
|
||||
);
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
expect(
|
||||
results.first.message,
|
||||
contains(
|
||||
'The version of Flutter that Shorebird includes and the Flutter on your path are different',
|
||||
'The version of Flutter that Shorebird includes and the Flutter on '
|
||||
'your path are different',
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -180,7 +182,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
{'FLUTTER_STORAGE_BASE_URL': 'https://storage.flutter-io.cn'},
|
||||
);
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
@@ -198,7 +200,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
when(() => pathFlutterVersionProcessResult.stdout)
|
||||
.thenReturn('OH NO THERE IS NO FLUTTER VERSION HERE');
|
||||
|
||||
expect(() async => validator.validate(), throwsException);
|
||||
expect(() async => validator.validate(shorebirdProcess), throwsException);
|
||||
});
|
||||
|
||||
test('prints stderr output and throws if version check fails', () async {
|
||||
@@ -207,7 +209,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
.thenReturn('error getting Flutter version');
|
||||
|
||||
expect(
|
||||
() async => validator.validate(),
|
||||
() async => validator.validate(shorebirdProcess),
|
||||
throwsA(
|
||||
isA<FlutterValidationException>().having(
|
||||
(e) => e.message,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/commands/doctor_command.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -10,6 +11,8 @@ class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
void main() {
|
||||
const currentShorebirdRevision = 'revision-1';
|
||||
const newerShorebirdRevision = 'revision-2';
|
||||
@@ -20,41 +23,46 @@ void main() {
|
||||
late DoctorCommand command;
|
||||
late ProcessResult fetchCurrentVersionResult;
|
||||
late ProcessResult fetchLatestVersionResult;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
|
||||
setUp(() {
|
||||
logger = _MockLogger();
|
||||
fetchCurrentVersionResult = _MockProcessResult();
|
||||
fetchLatestVersionResult = _MockProcessResult();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
|
||||
command = DoctorCommand(
|
||||
logger: logger,
|
||||
runProcess: (
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'git') {
|
||||
const revParseHead = ['rev-parse', '--verify', 'HEAD'];
|
||||
if (arguments.every((arg) => revParseHead.contains(arg))) {
|
||||
return fetchCurrentVersionResult;
|
||||
}
|
||||
|
||||
const revParseUpstream = ['rev-parse', '--verify', '@{upstream}'];
|
||||
if (arguments.every((arg) => revParseUpstream.contains(arg))) {
|
||||
return fetchLatestVersionResult;
|
||||
}
|
||||
}
|
||||
return _MockProcessResult();
|
||||
},
|
||||
);
|
||||
)
|
||||
..testProcess = shorebirdProcess
|
||||
..testEngineConfig = const EngineConfig.empty();
|
||||
|
||||
validator = ShorebirdVersionValidator(
|
||||
isShorebirdVersionCurrent: command.isShorebirdVersionCurrent,
|
||||
);
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['rev-parse', '--verify', 'HEAD'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => fetchCurrentVersionResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['rev-parse', '--verify', '@{upstream}'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => fetchLatestVersionResult);
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['fetch', '--tags'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => _MockProcessResult());
|
||||
|
||||
when(
|
||||
() => fetchCurrentVersionResult.exitCode,
|
||||
).thenReturn(ExitCode.success.code);
|
||||
@@ -70,7 +78,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns no issues when shorebird is up-to-date', () async {
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
expect(results, isEmpty);
|
||||
});
|
||||
|
||||
@@ -79,7 +87,7 @@ void main() {
|
||||
() => fetchLatestVersionResult.stdout,
|
||||
).thenReturn(newerShorebirdRevision);
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
expect(
|
||||
@@ -101,7 +109,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final results = await validator.validate();
|
||||
final results = await validator.validate(shorebirdProcess);
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.error);
|
||||
|
||||
Reference in New Issue
Block a user