From cc0e7bf658a6e11183fe5396713eacfa4a5a57c2 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Tue, 11 Jul 2023 15:13:20 -0400 Subject: [PATCH] feat(shorebird_cli): add `Adb` (#825) --- packages/shorebird_cli/bin/shorebird.dart | 2 + packages/shorebird_cli/lib/src/adb.dart | 20 ++++ .../shorebird_cli/lib/src/android_sdk.dart | 7 ++ packages/shorebird_cli/test/src/adb_test.dart | 96 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 packages/shorebird_cli/lib/src/adb.dart create mode 100644 packages/shorebird_cli/test/src/adb_test.dart diff --git a/packages/shorebird_cli/bin/shorebird.dart b/packages/shorebird_cli/bin/shorebird.dart index b5d274c0..cf5be36c 100644 --- a/packages/shorebird_cli/bin/shorebird.dart +++ b/packages/shorebird_cli/bin/shorebird.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:scoped/scoped.dart'; +import 'package:shorebird_cli/src/android_sdk.dart'; import 'package:shorebird_cli/src/android_studio.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/bundletool.dart'; @@ -17,6 +18,7 @@ Future main(List args) async { await runScoped( () async => ShorebirdCliCommandRunner().run(args), values: { + androidSdkRef, androidStudioRef, authRef, bundletoolRef, diff --git a/packages/shorebird_cli/lib/src/adb.dart b/packages/shorebird_cli/lib/src/adb.dart new file mode 100644 index 00000000..546686ef --- /dev/null +++ b/packages/shorebird_cli/lib/src/adb.dart @@ -0,0 +1,20 @@ +import 'package:shorebird_cli/src/android_sdk.dart'; +import 'package:shorebird_cli/src/process.dart'; + +/// A wrapper around the `adb` command. +class Adb { + Future _exec(String command) async { + final adbPath = androidSdk.adbPath; + if (adbPath == null) throw Exception('Unable to locate adb.'); + + return process.run(adbPath, command.split(' ')); + } + + /// Starts the app with the given [package] name. + Future startApp(String package) async { + final result = await _exec('shell monkey -p $package 1'); + if (result.exitCode != 0) { + throw Exception('Unable to start app: ${result.stderr}'); + } + } +} diff --git a/packages/shorebird_cli/lib/src/android_sdk.dart b/packages/shorebird_cli/lib/src/android_sdk.dart index 02b79b33..0c54a295 100644 --- a/packages/shorebird_cli/lib/src/android_sdk.dart +++ b/packages/shorebird_cli/lib/src/android_sdk.dart @@ -2,12 +2,19 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:path/path.dart' as p; +import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/platform.dart'; // https://developer.android.com/studio/command-line/variables.html#envar const kAndroidHome = 'ANDROID_HOME'; const kAndroidSdkRoot = 'ANDROID_SDK_ROOT'; +/// A reference to a [AndroidSdk] instance. +final androidSdkRef = create(AndroidSdk.new); + +/// The [AndroidSdk] instance available in the current zone. +AndroidSdk get androidSdk => read(androidSdkRef); + /// A wrapper around Android SDK. class AndroidSdk { /// The path to the Android SDK installation. diff --git a/packages/shorebird_cli/test/src/adb_test.dart b/packages/shorebird_cli/test/src/adb_test.dart new file mode 100644 index 00000000..e85da656 --- /dev/null +++ b/packages/shorebird_cli/test/src/adb_test.dart @@ -0,0 +1,96 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:scoped/scoped.dart'; +import 'package:shorebird_cli/src/adb.dart'; +import 'package:shorebird_cli/src/android_sdk.dart'; +import 'package:shorebird_cli/src/process.dart'; +import 'package:test/test.dart'; + +class _MockAndroidSdk extends Mock implements AndroidSdk {} + +class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} + +void main() { + group(Adb, () { + const adbPath = '/path/to/adb'; + + late AndroidSdk androidSdk; + late ShorebirdProcess process; + late Adb adb; + + R runWithOverrides(R Function() body) { + return runScoped( + () => body(), + values: { + androidSdkRef.overrideWith(() => androidSdk), + processRef.overrideWith(() => process), + }, + ); + } + + setUp(() { + androidSdk = _MockAndroidSdk(); + process = _MockShorebirdProcess(); + adb = Adb(); + + when(() => androidSdk.adbPath).thenReturn(adbPath); + when( + () => process.run(any(), any()), + ).thenAnswer( + (_) async => const ShorebirdProcessResult( + exitCode: 0, + stdout: '', + stderr: '', + ), + ); + }); + + group('startApp', () { + const package = 'com.example.app'; + test('throws when unable to locate adb', () async { + when(() => androidSdk.adbPath).thenReturn(null); + await expectLater( + () => runWithOverrides(() => adb.startApp(package)), + throwsA( + isA().having( + (e) => e.toString(), + 'exception()', + contains('Unable to locate adb'), + ), + ), + ); + }); + + test('throws process exits with non-zero exit code', () async { + when( + () => process.run(any(), any()), + ).thenAnswer( + (_) async => const ShorebirdProcessResult( + exitCode: 1, + stdout: '', + stderr: 'oops', + ), + ); + await expectLater( + () => runWithOverrides(() => adb.startApp(package)), + throwsA( + isA().having( + (e) => e.toString(), + 'exception()', + contains('Unable to start app: oops'), + ), + ), + ); + }); + + test('completes when process exits with 0', () async { + await expectLater( + runWithOverrides(() => adb.startApp(package)), + completes, + ); + verify( + () => process.run(adbPath, 'shell monkey -p $package 1'.split(' ')), + ).called(1); + }); + }); + }); +}