feat(shorebird_cli): add ShorebirdFlutterManager (#1012)

This commit is contained in:
Felix Angelov
2023-08-03 15:16:14 -05:00
committed by GitHub
parent 5dfb19a922
commit a09cc296e3
2 changed files with 233 additions and 0 deletions
@@ -0,0 +1,69 @@
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
/// {@template shorebird_flutter_manager}
/// Helps manage the Flutter installation used by Shorebird.
/// {@endtemplate}
class ShorebirdFlutterManager {
/// {@macro shorebird_flutter_manager}
const ShorebirdFlutterManager();
static const String flutterGitUrl =
'https://github.com/shorebirdtech/flutter.git';
Future<void> installRevision({required String revision}) async {
final targetDirectory = Directory(
p.join(shorebirdEnv.flutterDirectory.parent.path, revision),
);
if (targetDirectory.existsSync()) return;
const executable = 'git';
// Clone the Shorebird Flutter repo into the target directory.
final cloneArgs = [
'clone',
'--filter=tree:0',
flutterGitUrl,
'--no-checkout',
targetDirectory.path,
];
final cloneResult = await process.run(
executable,
cloneArgs,
runInShell: true,
);
if (cloneResult.exitCode != 0) {
throw ProcessException(
executable,
cloneArgs,
'${cloneResult.stderr}',
cloneResult.exitCode,
);
}
// Checkout the correct revision
final checkoutArgs = [
'-C',
targetDirectory.path,
'-c',
'advice.detachedHead=false',
'checkout',
revision,
];
final checkoutResult = await process.run(
executable,
checkoutArgs,
runInShell: true,
);
if (checkoutResult.exitCode != 0) {
throw ProcessException(
executable,
checkoutArgs,
'${checkoutResult.stderr}',
checkoutResult.exitCode,
);
}
}
}
@@ -0,0 +1,164 @@
import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_flutter_manager.dart';
import 'package:test/test.dart';
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdProcessResult extends Mock
implements ShorebirdProcessResult {}
void main() {
group(ShorebirdFlutterManager, () {
late Directory shorebirdRoot;
late Directory flutterDirectory;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcessResult cloneProcessResult;
late ShorebirdProcessResult checkoutProcessResult;
late ShorebirdProcess shorebirdProcess;
late ShorebirdFlutterManager shorebirdFlutterManager;
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
setUp(() {
shorebirdRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(p.join(shorebirdRoot.path, 'flutter'));
shorebirdEnv = _MockShorebirdEnv();
cloneProcessResult = _MockShorebirdProcessResult();
checkoutProcessResult = _MockShorebirdProcessResult();
shorebirdProcess = _MockShorebirdProcess();
shorebirdFlutterManager = runWithOverrides(ShorebirdFlutterManager.new);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(
() => shorebirdProcess.run(
any(),
any(),
runInShell: any(named: 'runInShell'),
),
).thenAnswer((invocation) async {
final executable = invocation.positionalArguments[0] as String;
final args = invocation.positionalArguments[1] as List<String>;
if (executable == 'git' && args[0] == 'clone') {
return cloneProcessResult;
} else if (executable == 'git' && args[4] == 'checkout') {
return checkoutProcessResult;
} else {
throw UnimplementedError();
}
});
when(() => cloneProcessResult.exitCode).thenReturn(ExitCode.success.code);
when(
() => checkoutProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
});
group('installRevision', () {
const revision = 'test-revision';
test('does nothing if the revision is already installed', () async {
Directory(
p.join(flutterDirectory.parent.path, revision),
).createSync(recursive: true);
await runWithOverrides(
() => shorebirdFlutterManager.installRevision(revision: revision),
);
verifyNever(() => shorebirdProcess.run(any(), any()));
});
test('throws ProcessException if unable to clone', () async {
when(
() => cloneProcessResult.exitCode,
).thenReturn(ExitCode.software.code);
await expectLater(
runWithOverrides(
() => shorebirdFlutterManager.installRevision(revision: revision),
),
throwsA(isA<ProcessException>()),
);
verify(
() => shorebirdProcess.run(
'git',
[
'clone',
'--filter=tree:0',
ShorebirdFlutterManager.flutterGitUrl,
'--no-checkout',
p.join(flutterDirectory.parent.path, revision)
],
runInShell: true,
),
).called(1);
});
test('throws ProcessException if unable to checkout revision', () async {
when(
() => checkoutProcessResult.exitCode,
).thenReturn(ExitCode.software.code);
await expectLater(
runWithOverrides(
() => shorebirdFlutterManager.installRevision(revision: revision),
),
throwsA(isA<ProcessException>()),
);
verify(
() => shorebirdProcess.run(
'git',
[
'clone',
'--filter=tree:0',
ShorebirdFlutterManager.flutterGitUrl,
'--no-checkout',
p.join(flutterDirectory.parent.path, revision)
],
runInShell: true,
),
).called(1);
verify(
() => shorebirdProcess.run(
'git',
[
'-C',
p.join(flutterDirectory.parent.path, revision),
'-c',
'advice.detachedHead=false',
'checkout',
revision,
],
runInShell: true,
),
).called(1);
});
test('completes when clone and checkout succeed', () async {
await expectLater(
runWithOverrides(
() => shorebirdFlutterManager.installRevision(revision: revision),
),
completes,
);
});
});
});
}