diff --git a/packages/artifact_proxy/lib/src/artifact_manifest_client.dart b/packages/artifact_proxy/lib/src/artifact_manifest_client.dart index 7fc0770f..16d3f543 100644 --- a/packages/artifact_proxy/lib/src/artifact_manifest_client.dart +++ b/packages/artifact_proxy/lib/src/artifact_manifest_client.dart @@ -12,7 +12,7 @@ import 'package:quiver/collection.dart'; class ArtifactManifestClient { /// {@macro artifact_manifest_client} ArtifactManifestClient({http.Client? httpClient}) - : _httpClient = httpClient ?? http.Client(); + : _httpClient = httpClient ?? http.Client(); final http.Client _httpClient; @@ -33,11 +33,9 @@ class ArtifactManifestClient { ); final response = await _httpClient.get(url); if (response.statusCode != HttpStatus.ok) { - throw Exception( - ''' + throw Exception(''' Failed to fetch artifacts manifest for revision $revision. -${response.statusCode} ${response.reasonPhrase}''', - ); +${response.statusCode} ${response.reasonPhrase}'''); } return checkedYamlDecode( diff --git a/packages/artifact_proxy/lib/src/artifact_proxy.dart b/packages/artifact_proxy/lib/src/artifact_proxy.dart index dc1106b4..97a3b85f 100644 --- a/packages/artifact_proxy/lib/src/artifact_proxy.dart +++ b/packages/artifact_proxy/lib/src/artifact_proxy.dart @@ -114,9 +114,10 @@ String getFlutterArtifactLocation({ required String artifactPath, String? engine, }) { - final adjustedPath = engine != null - ? artifactPath.replaceAll(r'$engine', engine) - : artifactPath; + final adjustedPath = + engine != null + ? artifactPath.replaceAll(r'$engine', engine) + : artifactPath; return 'https://storage.googleapis.com/$adjustedPath'; } diff --git a/packages/artifact_proxy/lib/src/models/artifacts_manifest.g.dart b/packages/artifact_proxy/lib/src/models/artifacts_manifest.g.dart index f751506d..8d87f174 100644 --- a/packages/artifact_proxy/lib/src/models/artifacts_manifest.g.dart +++ b/packages/artifact_proxy/lib/src/models/artifacts_manifest.g.dart @@ -9,29 +9,33 @@ part of 'artifacts_manifest.dart'; // ************************************************************************** ArtifactsManifest _$ArtifactsManifestFromJson(Map json) => $checkedCreate( - 'ArtifactsManifest', + 'ArtifactsManifest', + json, + ($checkedConvert) { + $checkKeys( json, - ($checkedConvert) { - $checkKeys( - json, - allowedKeys: const [ - 'flutter_engine_revision', - 'storage_bucket', - 'artifact_overrides' - ], - ); - final val = ArtifactsManifest( - flutterEngineRevision: - $checkedConvert('flutter_engine_revision', (v) => v as String), - storageBucket: $checkedConvert('storage_bucket', (v) => v as String), - artifactOverrides: $checkedConvert('artifact_overrides', - (v) => (v as List).map((e) => e as String).toSet()), - ); - return val; - }, - fieldKeyMap: const { - 'flutterEngineRevision': 'flutter_engine_revision', - 'storageBucket': 'storage_bucket', - 'artifactOverrides': 'artifact_overrides' - }, + allowedKeys: const [ + 'flutter_engine_revision', + 'storage_bucket', + 'artifact_overrides', + ], ); + final val = ArtifactsManifest( + flutterEngineRevision: $checkedConvert( + 'flutter_engine_revision', + (v) => v as String, + ), + storageBucket: $checkedConvert('storage_bucket', (v) => v as String), + artifactOverrides: $checkedConvert( + 'artifact_overrides', + (v) => (v as List).map((e) => e as String).toSet(), + ), + ); + return val; + }, + fieldKeyMap: const { + 'flutterEngineRevision': 'flutter_engine_revision', + 'storageBucket': 'storage_bucket', + 'artifactOverrides': 'artifact_overrides', + }, +); diff --git a/packages/artifact_proxy/pubspec.lock b/packages/artifact_proxy/pubspec.lock index 385ed944..759a5232 100644 --- a/packages/artifact_proxy/pubspec.lock +++ b/packages/artifact_proxy/pubspec.lock @@ -599,4 +599,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" diff --git a/packages/artifact_proxy/pubspec.yaml b/packages/artifact_proxy/pubspec.yaml index 6d721fb2..978083a4 100644 --- a/packages/artifact_proxy/pubspec.yaml +++ b/packages/artifact_proxy/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/shorebirdtech/shorebird/ publish_to: "none" environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ">=3.7.0 <4.0.0" dependencies: checked_yaml: ^2.0.2 @@ -17,7 +17,7 @@ dependencies: shelf_hotreload: ^1.5.0 dev_dependencies: - build_runner: ^2.4.13 + build_runner: ^2.4.13 json_serializable: ^6.9.3 mocktail: ^1.0.4 test: ^1.25.15 diff --git a/packages/artifact_proxy/test/artifact_proxy_test.dart b/packages/artifact_proxy/test/artifact_proxy_test.dart index 8f2a889f..89a621be 100644 --- a/packages/artifact_proxy/test/artifact_proxy_test.dart +++ b/packages/artifact_proxy/test/artifact_proxy_test.dart @@ -63,22 +63,17 @@ void main() { verify(() => client.getManifest(shorebirdEngineRevision)).called(1); }); - test( - 'should proxy to Flutter artifacts ' + test('should proxy to Flutter artifacts ' 'when no engine revision is detected', () async { const path = 'flutter_infra_release/flutter/fonts/3012db47f3130e62f7cc0beabff968a33cbec8d8/fonts.zip'; final request = buildRequest(path); final response = await handler(request); - expect( - response, - isRedirectTo('https://storage.googleapis.com/$path'), - ); + expect(response, isRedirectTo('https://storage.googleapis.com/$path')); verifyNever(() => client.getManifest(any())); }); - test( - 'should proxy to Shorebird artifacts ' + test('should proxy to Shorebird artifacts ' 'when an engine revision is detected with an override', () async { const path = 'flutter_infra_release/flutter/$shorebirdEngineRevision/android-x64-release/artifacts.zip'; @@ -93,8 +88,7 @@ void main() { verify(() => client.getManifest(shorebirdEngineRevision)).called(1); }); - test( - 'should proxy to Flutter artifacts ' + test('should proxy to Flutter artifacts ' 'when an engine revision is detected with no override', () async { const path = 'flutter_infra_release/flutter/$shorebirdEngineRevision/windows-x64/font-subset.zip'; @@ -109,8 +103,7 @@ void main() { verify(() => client.getManifest(shorebirdEngineRevision)).called(1); }); - test( - 'should return 404 ' + test('should return 404 ' 'when pattern is not recognized', () async { const path = 'flutter_infra_release/flutter/$shorebirdEngineRevision/unknown/artifacts.zip'; diff --git a/packages/discord_gcp_alerts/lib/src/gcp_alert_handler.dart b/packages/discord_gcp_alerts/lib/src/gcp_alert_handler.dart index 5c755461..e78951a4 100644 --- a/packages/discord_gcp_alerts/lib/src/gcp_alert_handler.dart +++ b/packages/discord_gcp_alerts/lib/src/gcp_alert_handler.dart @@ -31,7 +31,7 @@ Handler gcpAlertHandler({required String webhookUrl, http.Client? client}) { {'name': 'Resource', 'value': resource, 'inline': true}, {'name': 'Condition', 'value': conditionName, 'inline': true}, ], - } + }, ], 'attachments': const [], }; diff --git a/packages/discord_gcp_alerts/lib/src/models/gcp_alert.g.dart b/packages/discord_gcp_alerts/lib/src/models/gcp_alert.g.dart index 2bfa0dbb..3f813ed9 100644 --- a/packages/discord_gcp_alerts/lib/src/models/gcp_alert.g.dart +++ b/packages/discord_gcp_alerts/lib/src/models/gcp_alert.g.dart @@ -9,37 +9,36 @@ part of 'gcp_alert.dart'; // ************************************************************************** GCPAlert _$GCPAlertFromJson(Map json) => $checkedCreate( - 'GCPAlert', - json, - ($checkedConvert) { - final val = GCPAlert( - incident: $checkedConvert( - 'incident', - (v) => v == null - ? null - : Incident.fromJson(v as Map)), - ); - return val; - }, + 'GCPAlert', + json, + ($checkedConvert) { + final val = GCPAlert( + incident: $checkedConvert( + 'incident', + (v) => v == null ? null : Incident.fromJson(v as Map), + ), ); + return val; + }, +); Incident _$IncidentFromJson(Map json) => $checkedCreate( - 'Incident', - json, - ($checkedConvert) { - final val = Incident( - url: $checkedConvert('url', (v) => v as String?), - state: $checkedConvert('state', (v) => v as String?), - summary: $checkedConvert('summary', (v) => v as String?), - resourceName: $checkedConvert('resource_name', (v) => v as String?), - conditionName: $checkedConvert('condition_name', (v) => v as String?), - policyName: $checkedConvert('policy_name', (v) => v as String?), - ); - return val; - }, - fieldKeyMap: const { - 'resourceName': 'resource_name', - 'conditionName': 'condition_name', - 'policyName': 'policy_name' - }, + 'Incident', + json, + ($checkedConvert) { + final val = Incident( + url: $checkedConvert('url', (v) => v as String?), + state: $checkedConvert('state', (v) => v as String?), + summary: $checkedConvert('summary', (v) => v as String?), + resourceName: $checkedConvert('resource_name', (v) => v as String?), + conditionName: $checkedConvert('condition_name', (v) => v as String?), + policyName: $checkedConvert('policy_name', (v) => v as String?), ); + return val; + }, + fieldKeyMap: const { + 'resourceName': 'resource_name', + 'conditionName': 'condition_name', + 'policyName': 'policy_name', + }, +); diff --git a/packages/discord_gcp_alerts/pubspec.lock b/packages/discord_gcp_alerts/pubspec.lock index be93badb..33754cb8 100644 --- a/packages/discord_gcp_alerts/pubspec.lock +++ b/packages/discord_gcp_alerts/pubspec.lock @@ -591,4 +591,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" diff --git a/packages/discord_gcp_alerts/pubspec.yaml b/packages/discord_gcp_alerts/pubspec.yaml index 33f26403..9f062ccd 100644 --- a/packages/discord_gcp_alerts/pubspec.yaml +++ b/packages/discord_gcp_alerts/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0 publish_to: none environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ">=3.7.0 <4.0.0" dependencies: http: ^1.3.0 diff --git a/packages/discord_gcp_alerts/test/src/gcp_alert_handler_test.dart b/packages/discord_gcp_alerts/test/src/gcp_alert_handler_test.dart index 898d8258..7d1eb635 100644 --- a/packages/discord_gcp_alerts/test/src/gcp_alert_handler_test.dart +++ b/packages/discord_gcp_alerts/test/src/gcp_alert_handler_test.dart @@ -33,57 +33,55 @@ void main() { Request( 'POST', Uri.parse('http://localhost:8080/'), - body: json.encode( - { - 'version': 'test', - 'incident': { - 'incident_id': '12345', - 'scoping_project_id': '12345', - 'scoping_project_number': 12345, - 'url': 'http://www.example.com', - 'started_at': 0, - 'ended_at': 0, - 'state': 'OPEN', - 'summary': 'Test Incident', - 'apigee_url': 'http://www.example.com', - 'observed_value': '1.0', - 'resource': { - 'type': 'example_resource', - 'labels': {'example': 'label'}, - }, - 'resource_type_display_name': 'Example Resource Type', - 'resource_id': '12345', - 'resource_display_name': 'Example Resource', - 'resource_name': 'projects/12345/example_resources/12345', - 'metric': { - 'type': 'test.googleapis.com/metric', - 'displayName': 'Test Metric', - 'labels': {'example': 'label'}, - }, - 'metadata': { - 'system_labels': {'example': 'label'}, - 'user_labels': {'example': 'label'}, - }, - 'policy_name': 'projects/12345/alertPolicies/12345', - 'policy_user_labels': {'example': 'label'}, - 'documentation': 'Test documentation', - 'condition': { - 'name': 'projects/12345/alertPolicies/12345/conditions/12345', - 'displayName': 'Example condition', - 'conditionThreshold': { - 'filter': - 'metric.type="test.googleapis.com/metric" resource.type="example_resource"', - 'comparison': 'COMPARISON_GT', - 'thresholdValue': 0.5, - 'duration': '0s', - 'trigger': {'count': 1}, - }, - }, - 'condition_name': 'Example condition', - 'threshold_value': '0.5', + body: json.encode({ + 'version': 'test', + 'incident': { + 'incident_id': '12345', + 'scoping_project_id': '12345', + 'scoping_project_number': 12345, + 'url': 'http://www.example.com', + 'started_at': 0, + 'ended_at': 0, + 'state': 'OPEN', + 'summary': 'Test Incident', + 'apigee_url': 'http://www.example.com', + 'observed_value': '1.0', + 'resource': { + 'type': 'example_resource', + 'labels': {'example': 'label'}, }, + 'resource_type_display_name': 'Example Resource Type', + 'resource_id': '12345', + 'resource_display_name': 'Example Resource', + 'resource_name': 'projects/12345/example_resources/12345', + 'metric': { + 'type': 'test.googleapis.com/metric', + 'displayName': 'Test Metric', + 'labels': {'example': 'label'}, + }, + 'metadata': { + 'system_labels': {'example': 'label'}, + 'user_labels': {'example': 'label'}, + }, + 'policy_name': 'projects/12345/alertPolicies/12345', + 'policy_user_labels': {'example': 'label'}, + 'documentation': 'Test documentation', + 'condition': { + 'name': 'projects/12345/alertPolicies/12345/conditions/12345', + 'displayName': 'Example condition', + 'conditionThreshold': { + 'filter': + 'metric.type="test.googleapis.com/metric" resource.type="example_resource"', + 'comparison': 'COMPARISON_GT', + 'thresholdValue': 0.5, + 'duration': '0s', + 'trigger': {'count': 1}, + }, + }, + 'condition_name': 'Example condition', + 'threshold_value': '0.5', }, - ), + }), ), ); @@ -109,9 +107,9 @@ void main() { 'name': 'Condition', 'value': 'Example condition', 'inline': true, - } + }, ], - } + }, ], 'attachments': [], }), diff --git a/packages/jwt/lib/src/jwt.dart b/packages/jwt/lib/src/jwt.dart index 0ad2f112..24e1f5a3 100644 --- a/packages/jwt/lib/src/jwt.dart +++ b/packages/jwt/lib/src/jwt.dart @@ -40,11 +40,7 @@ Future _getPublicKeys(String url) async { return null; } - publicKeyStores.set( - url, - publicKeyStore, - ttl: Duration(seconds: maxAge), - ); + publicKeyStores.set(url, publicKeyStore, ttl: Duration(seconds: maxAge)); return publicKeyStore; } @@ -105,10 +101,7 @@ Future verify( return jwt; } -Future _verifyHeader( - JwtHeader header, - Iterable keyIds, -) async { +Future _verifyHeader(JwtHeader header, Iterable keyIds) async { if (header.typ != 'JWT') { throw const JwtVerificationFailure('Invalid token type.'); } @@ -171,10 +164,7 @@ bool _verifySignature(String jwt, String publicKey) { final public = pair.public; final signer = Signer('SHA-256/RSA'); - final key = RSAPublicKey( - public!.modulus, - BigInt.from(public.publicExponent), - ); + final key = RSAPublicKey(public!.modulus, BigInt.from(public.publicExponent)); final param = ParametersWithRandom( PublicKeyParameter(key), SecureRandom('AES/CTR/PRNG'), diff --git a/packages/jwt/lib/src/models/jwk.g.dart b/packages/jwt/lib/src/models/jwk.g.dart index cb62446d..60aff275 100644 --- a/packages/jwt/lib/src/models/jwk.g.dart +++ b/packages/jwt/lib/src/models/jwk.g.dart @@ -8,20 +8,19 @@ part of 'jwk.dart'; // JsonSerializableGenerator // ************************************************************************** -Jwk _$JwkFromJson(Map json) => $checkedCreate( - 'Jwk', - json, - ($checkedConvert) { - final val = Jwk( - kty: $checkedConvert('kty', (v) => v as String), - use: $checkedConvert('use', (v) => v as String), - kid: $checkedConvert('kid', (v) => v as String), - x5c: $checkedConvert('x5c', - (v) => (v as List).map((e) => e as String).toList()), - x5t: $checkedConvert('x5t', (v) => v as String), - n: $checkedConvert('n', (v) => v as String), - e: $checkedConvert('e', (v) => v as String), - ); - return val; - }, - ); +Jwk _$JwkFromJson(Map json) => + $checkedCreate('Jwk', json, ($checkedConvert) { + final val = Jwk( + kty: $checkedConvert('kty', (v) => v as String), + use: $checkedConvert('use', (v) => v as String), + kid: $checkedConvert('kid', (v) => v as String), + x5c: $checkedConvert( + 'x5c', + (v) => (v as List).map((e) => e as String).toList(), + ), + x5t: $checkedConvert('x5t', (v) => v as String), + n: $checkedConvert('n', (v) => v as String), + e: $checkedConvert('e', (v) => v as String), + ); + return val; + }); diff --git a/packages/jwt/lib/src/models/jwt_header.dart b/packages/jwt/lib/src/models/jwt_header.dart index 6221b045..be9a88d5 100644 --- a/packages/jwt/lib/src/models/jwt_header.dart +++ b/packages/jwt/lib/src/models/jwt_header.dart @@ -8,11 +8,7 @@ part 'jwt_header.g.dart'; @JsonSerializable(createToJson: false) class JwtHeader { /// {@macro jwt_header} - const JwtHeader({ - required this.alg, - required this.kid, - required this.typ, - }); + const JwtHeader({required this.alg, required this.kid, required this.typ}); /// Decode a [JwtHeader] from a `Map`. factory JwtHeader.fromJson(Map json) { diff --git a/packages/jwt/lib/src/models/jwt_header.g.dart b/packages/jwt/lib/src/models/jwt_header.g.dart index aafcf295..5be3010f 100644 --- a/packages/jwt/lib/src/models/jwt_header.g.dart +++ b/packages/jwt/lib/src/models/jwt_header.g.dart @@ -8,15 +8,12 @@ part of 'jwt_header.dart'; // JsonSerializableGenerator // ************************************************************************** -JwtHeader _$JwtHeaderFromJson(Map json) => $checkedCreate( - 'JwtHeader', - json, - ($checkedConvert) { - final val = JwtHeader( - alg: $checkedConvert('alg', (v) => v as String), - kid: $checkedConvert('kid', (v) => v as String), - typ: $checkedConvert('typ', (v) => v as String), - ); - return val; - }, - ); +JwtHeader _$JwtHeaderFromJson(Map json) => + $checkedCreate('JwtHeader', json, ($checkedConvert) { + final val = JwtHeader( + alg: $checkedConvert('alg', (v) => v as String), + kid: $checkedConvert('kid', (v) => v as String), + typ: $checkedConvert('typ', (v) => v as String), + ); + return val; + }); diff --git a/packages/jwt/lib/src/models/jwt_payload.g.dart b/packages/jwt/lib/src/models/jwt_payload.g.dart index d144aced..84d2f802 100644 --- a/packages/jwt/lib/src/models/jwt_payload.g.dart +++ b/packages/jwt/lib/src/models/jwt_payload.g.dart @@ -8,19 +8,15 @@ part of 'jwt_payload.dart'; // JsonSerializableGenerator // ************************************************************************** -JwtPayload _$JwtPayloadFromJson(Map json) => $checkedCreate( - 'JwtPayload', - json, - ($checkedConvert) { - final val = JwtPayload( - exp: $checkedConvert('exp', (v) => (v as num).toInt()), - iat: $checkedConvert('iat', (v) => (v as num).toInt()), - aud: $checkedConvert('aud', (v) => v as String), - iss: $checkedConvert('iss', (v) => v as String), - sub: $checkedConvert('sub', (v) => v as String), - authTime: $checkedConvert('auth_time', (v) => (v as num?)?.toInt()), - ); - return val; - }, - fieldKeyMap: const {'authTime': 'auth_time'}, - ); +JwtPayload _$JwtPayloadFromJson(Map json) => + $checkedCreate('JwtPayload', json, ($checkedConvert) { + final val = JwtPayload( + exp: $checkedConvert('exp', (v) => (v as num).toInt()), + iat: $checkedConvert('iat', (v) => (v as num).toInt()), + aud: $checkedConvert('aud', (v) => v as String), + iss: $checkedConvert('iss', (v) => v as String), + sub: $checkedConvert('sub', (v) => v as String), + authTime: $checkedConvert('auth_time', (v) => (v as num?)?.toInt()), + ); + return val; + }, fieldKeyMap: const {'authTime': 'auth_time'}); diff --git a/packages/jwt/lib/src/models/public_key_store/jwk_key_store.g.dart b/packages/jwt/lib/src/models/public_key_store/jwk_key_store.g.dart index 64d42af3..4715019e 100644 --- a/packages/jwt/lib/src/models/public_key_store/jwk_key_store.g.dart +++ b/packages/jwt/lib/src/models/public_key_store/jwk_key_store.g.dart @@ -8,17 +8,16 @@ part of 'jwk_key_store.dart'; // JsonSerializableGenerator // ************************************************************************** -JwkKeyStore _$JwkKeyStoreFromJson(Map json) => $checkedCreate( - 'JwkKeyStore', - json, - ($checkedConvert) { - final val = JwkKeyStore( - keys: $checkedConvert( - 'keys', - (v) => (v as List) +JwkKeyStore _$JwkKeyStoreFromJson(Map json) => + $checkedCreate('JwkKeyStore', json, ($checkedConvert) { + final val = JwkKeyStore( + keys: $checkedConvert( + 'keys', + (v) => + (v as List) .map((e) => Jwk.fromJson(e as Map)) - .toList()), - ); - return val; - }, - ); + .toList(), + ), + ); + return val; + }); diff --git a/packages/jwt/pubspec.yaml b/packages/jwt/pubspec.yaml index bfb3b780..2e8cf424 100644 --- a/packages/jwt/pubspec.yaml +++ b/packages/jwt/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0+1 publish_to: none environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ">=3.7.0 <4.0.0" dependencies: clock: ^1.1.0 diff --git a/packages/jwt/test/src/jwt_test.dart b/packages/jwt/test/src/jwt_test.dart index c8c06203..bf14413c 100644 --- a/packages/jwt/test/src/jwt_test.dart +++ b/packages/jwt/test/src/jwt_test.dart @@ -24,8 +24,9 @@ void main() { final jwkKeyStoreJsonString = File(p.join('test', 'fixtures', 'jwk_key_store.json')).readAsStringSync(); final keyValueKeyStoreString = - File(p.join('test', 'fixtures', 'key_value_key_store.json')) - .readAsStringSync(); + File( + p.join('test', 'fixtures', 'key_value_key_store.json'), + ).readAsStringSync(); final expiresAt = DateTime.fromMillisecondsSinceEpoch(1643687866 * 1000); final validTime = expiresAt.subtract(const Duration(minutes: 15)); @@ -69,24 +70,26 @@ void main() { ); }); - test('throws a JwtVerificationFailure if string is not valid jwt', - () async { - await expectLater( - () => verify( - 'not.a.jwt', - audience: {audience}, - issuer: issuer, - publicKeysUrl: keyValuePublicKeysUrl, - ), - throwsA( - isA().having( - (e) => e.reason, - 'reason', - 'JWT header is malformed.', + test( + 'throws a JwtVerificationFailure if string is not valid jwt', + () async { + await expectLater( + () => verify( + 'not.a.jwt', + audience: {audience}, + issuer: issuer, + publicKeysUrl: keyValuePublicKeysUrl, ), - ), - ); - }); + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'JWT header is malformed.', + ), + ), + ); + }, + ); test('throws a JwtVerificationFailure if payload is not valid', () async { await expectLater( @@ -106,26 +109,28 @@ void main() { ); }); - test('throws a JwtVerificationFailure if signature is not valid', - () async { - await withClock(Clock.fixed(validTime), () async { - await expectLater( - () => verify( - tokenInvalidSignature, - audience: {audience}, - issuer: issuer, - publicKeysUrl: keyValuePublicKeysUrl, - ), - throwsA( - isA().having( - (e) => e.reason, - 'reason', - 'JWT signature is malformed.', + test( + 'throws a JwtVerificationFailure if signature is not valid', + () async { + await withClock(Clock.fixed(validTime), () async { + await expectLater( + () => verify( + tokenInvalidSignature, + audience: {audience}, + issuer: issuer, + publicKeysUrl: keyValuePublicKeysUrl, ), - ), - ); - }); - }); + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'JWT signature is malformed.', + ), + ), + ); + }); + }, + ); test('throws exception if jwt has no matching public key id', () async { await withClock(Clock.fixed(validTime), () async { @@ -147,8 +152,7 @@ void main() { }); }); - test( - 'throws exception if invalid keys are provided ' + test('throws exception if invalid keys are provided ' 'by the publicKeysUrl (KeyValueKeyStore)', () async { getOverride = (Uri uri) async { return Response( @@ -177,8 +181,7 @@ void main() { }); }); - test( - 'throws exception if invalid keys are provided ' + test('throws exception if invalid keys are provided ' 'by the publicKeysUrl (JwkKeyStore)', () async { getOverride = (Uri uri) async { return Response( diff --git a/packages/redis_client/analysis_options.yaml b/packages/redis_client/analysis_options.yaml index c2f4cbbc..58a5c003 100644 --- a/packages/redis_client/analysis_options.yaml +++ b/packages/redis_client/analysis_options.yaml @@ -1 +1,6 @@ include: package:very_good_analysis/analysis_options.7.0.0.yaml +linter: + rules: + # Disabling in favor of the new Dart 3.7 format. + # https://github.com/VeryGoodOpenSource/very_good_analysis/issues/136 + require_trailing_commas: false \ No newline at end of file diff --git a/packages/redis_client/lib/src/redis_client.dart b/packages/redis_client/lib/src/redis_client.dart index 3a06865c..f399c938 100644 --- a/packages/redis_client/lib/src/redis_client.dart +++ b/packages/redis_client/lib/src/redis_client.dart @@ -116,9 +116,9 @@ class RedisClient { RedisSocketOptions socket = const RedisSocketOptions(), RedisCommandOptions command = const RedisCommandOptions(), RedisLogger logger = const _NoopRedisLogger(), - }) : _socketOptions = socket, - _commandOptions = command, - _logger = logger; + }) : _socketOptions = socket, + _commandOptions = command, + _logger = logger; /// The socket options for the Redis server. final RedisSocketOptions _socketOptions; @@ -160,10 +160,7 @@ class RedisClient { /// Authenticate to the Redis server. /// Equivalent to the `AUTH` command. /// https://redis.io/commands/auth - Future auth({ - required String password, - String username = 'default', - }) { + Future auth({required String password, String username = 'default'}) { return execute(['AUTH', username, password]); } @@ -205,14 +202,11 @@ class RedisClient { /// Send a command to the Redis server. Future execute(List command) async { - return _runWithRetry( - () async { - final result = await RespCommandsTier0(_client!).execute(command); - if (result.isError) throw RedisException(result.toString()); - return result.payload; - }, - command: command.join(' '), - ); + return _runWithRetry(() async { + final result = await RespCommandsTier0(_client!).execute(command); + if (result.isError) throw RedisException(result.toString()); + return result.payload; + }, command: command.join(' ')); } /// Establish a connection to the Redis server. @@ -401,10 +395,7 @@ class RedisJson { /// Returns null if the key does not exist. /// Equivalent to the `JSON.GET` command. /// https://redis.io/commands/json.get - Future get({ - required String key, - String path = r'$', - }) async { + Future get({required String key, String path = r'$'}) async { final result = await _client.execute(['JSON.GET', key, path]); if (result is String) { final parts = LineSplitter.split(result); diff --git a/packages/redis_client/pubspec.yaml b/packages/redis_client/pubspec.yaml index 34b4ecd9..b116ba7d 100644 --- a/packages/redis_client/pubspec.yaml +++ b/packages/redis_client/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/shorebirdtech/shorebird/tree/main/packages/redis_ topics: [redis, cache, shorebird] environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ">=3.7.0 <4.0.0" dependencies: resp_client: ^1.2.0 diff --git a/packages/redis_client/test/src/redis_client_test.dart b/packages/redis_client/test/src/redis_client_test.dart index 559197dd..b22eec57 100644 --- a/packages/redis_client/test/src/redis_client_test.dart +++ b/packages/redis_client/test/src/redis_client_test.dart @@ -27,32 +27,39 @@ void main() { }); group('connect', () { - test('authenticates automatically when credentials are provided', - () async { - await expectLater(client.connect(), completes); - await expectLater(client.execute(['PING']), completion(equals('PONG'))); - }); + test( + 'authenticates automatically when credentials are provided', + () async { + await expectLater(client.connect(), completes); + await expectLater( + client.execute(['PING']), + completion(equals('PONG')), + ); + }, + ); - test('throws SocketException when connection times out w/retry', - () async { - final client = RedisClient( - socket: const RedisSocketOptions( - timeout: Duration(microseconds: 1), - retryAttempts: 1, - ), - ); - await expectLater( - client.connect, - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('Connection retry limit exceeded'), + test( + 'throws SocketException when connection times out w/retry', + () async { + final client = RedisClient( + socket: const RedisSocketOptions( + timeout: Duration(microseconds: 1), + retryAttempts: 1, ), - ), - ); - await client.close(); - }); + ); + await expectLater( + client.connect, + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Connection retry limit exceeded'), + ), + ), + ); + await client.close(); + }, + ); test('throws SocketException after max connection attempts', () async { final client = RedisClient( @@ -159,10 +166,7 @@ void main() { }); test('succeeds when username/password are correct', () async { - await expectLater( - client.auth(password: 'password'), - completes, - ); + await expectLater(client.auth(password: 'password'), completes); }); }); @@ -205,8 +209,7 @@ void main() { await expectLater(client.get(key: key), completion(isNull)); }); - test( - 'throws TimeoutException ' + test('throws TimeoutException ' 'when command timeout is exceeded', () async { final client = RedisClient( command: const RedisCommandOptions(timeout: Duration.zero), @@ -243,9 +246,7 @@ void main() { 'nested': { '1.0.0+1': { 'android': { - 'arch64': { - 'url': 'http://example.com', - }, + 'arch64': {'url': 'http://example.com'}, }, }, }, @@ -268,17 +269,13 @@ void main() { await expectLater( client.json.get(key: key, path: r'$.nested'), completion( - equals( - { - '1.0.0+1': { - 'android': { - 'arch64': { - 'url': 'http://example.com', - }, - }, + equals({ + '1.0.0+1': { + 'android': { + 'arch64': {'url': 'http://example.com'}, }, }, - ), + }), ), ); await expectLater( diff --git a/packages/scoped_deps/analysis_options.yaml b/packages/scoped_deps/analysis_options.yaml index 58a08d76..285a6ae8 100644 --- a/packages/scoped_deps/analysis_options.yaml +++ b/packages/scoped_deps/analysis_options.yaml @@ -1,3 +1,8 @@ include: package:very_good_analysis/analysis_options.7.0.0.yaml +linter: + rules: + # Disabling in favor of the new Dart 3.7 format. + # https://github.com/VeryGoodOpenSource/very_good_analysis/issues/136 + require_trailing_commas: false analyzer: exclude: ["example/**"] diff --git a/packages/scoped_deps/lib/src/scoped_deps.dart b/packages/scoped_deps/lib/src/scoped_deps.dart index cf60c70f..c8072f18 100644 --- a/packages/scoped_deps/lib/src/scoped_deps.dart +++ b/packages/scoped_deps/lib/src/scoped_deps.dart @@ -10,9 +10,7 @@ class ScopedRef { /// {@macro scoped_ref} ScopedRef(this._create) : _key = Object(); - ScopedRef._(T Function() create, Object key) - : _create = create, - _key = key; + ScopedRef._(T Function() create, Object key) : _create = create, _key = key; final T Function() _create; final Object _key; @@ -47,20 +45,15 @@ T read(ScopedRef ref, {T Function()? orElse}) { final value = (Zone.current[ref._key] as ScopedRef?)?._value; if (value == null) { if (orElse != null) return orElse(); - throw StateError( - ''' + throw StateError(''' read(ScopedRef<$T>) was called in a scope which does not contain a corresponding value for the provided ref. -Did you forget to call: runScoped(() {...}, values: {value})?''', - ); +Did you forget to call: runScoped(() {...}, values: {value})?'''); } return value; } /// Runs [body] within a scope which has access to the set of refs in [values]. -R runScoped( - R Function() body, { - Set> values = const {}, -}) { +R runScoped(R Function() body, {Set> values = const {}}) { return runZoned( body, zoneValues: {for (final value in values) value._key: value}, diff --git a/packages/scoped_deps/pubspec.yaml b/packages/scoped_deps/pubspec.yaml index fa5d7e5f..bdcbf906 100644 --- a/packages/scoped_deps/pubspec.yaml +++ b/packages/scoped_deps/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://shorebird.dev repository: https://github.com/shorebirdtech/shorebird/tree/main/packages/scoped_deps environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ">=3.7.0 <4.0.0" dependencies: meta: ^1.0.0 diff --git a/packages/scoped_deps/test/src/scoped_deps_test.dart b/packages/scoped_deps/test/src/scoped_deps_test.dart index 22429949..16d411c6 100644 --- a/packages/scoped_deps/test/src/scoped_deps_test.dart +++ b/packages/scoped_deps/test/src/scoped_deps_test.dart @@ -25,10 +25,7 @@ void main() { test('read accesses the value when ref is available', () { final value = create(() => 42); - runScoped( - () => expect(read(value), equals(42)), - values: {value}, - ); + runScoped(() => expect(read(value), equals(42)), values: {value}); }); test('value is computed lazily and cached', () { @@ -40,14 +37,11 @@ void main() { expect(createCallCount, equals(0)); - runScoped( - () { - expect(read(value), equals(42)); - expect(read(value), equals(42)); - expect(read(value), equals(42)); - }, - values: {value}, - ); + runScoped(() { + expect(read(value), equals(42)); + expect(read(value), equals(42)); + expect(read(value), equals(42)); + }, values: {value}); expect(createCallCount, equals(1)); }); @@ -55,17 +49,14 @@ void main() { test('value can be overridden', () { final value = create(() => 42); - runScoped( - () { - expect(read(value), equals(42)); + runScoped(() { + expect(read(value), equals(42)); - runScoped( - () => expect(read(value), equals(0)), - values: {value.overrideWith(() => 0)}, - ); - }, - values: {value}, - ); + runScoped( + () => expect(read(value), equals(0)), + values: {value.overrideWith(() => 0)}, + ); + }, values: {value}); }); test('overrides are considered equal', () { diff --git a/packages/shorebird_cli/analysis_options.yaml b/packages/shorebird_cli/analysis_options.yaml index 4c64d26a..df87ad5e 100644 --- a/packages/shorebird_cli/analysis_options.yaml +++ b/packages/shorebird_cli/analysis_options.yaml @@ -1,4 +1,9 @@ include: package:very_good_analysis/analysis_options.7.0.0.yaml +linter: + rules: + # Disabling in favor of the new Dart 3.7 format. + # https://github.com/VeryGoodOpenSource/very_good_analysis/issues/136 + require_trailing_commas: false analyzer: exclude: - lib/**.g.dart diff --git a/packages/shorebird_cli/bin/shorebird.dart b/packages/shorebird_cli/bin/shorebird.dart index a29a4710..16e4a675 100644 --- a/packages/shorebird_cli/bin/shorebird.dart +++ b/packages/shorebird_cli/bin/shorebird.dart @@ -37,13 +37,10 @@ Future main(List args) async { ); // Write the current command to the top of the log file. - currentRunLogFile.writeAsStringSync( - ''' + currentRunLogFile.writeAsStringSync(''' Command: shorebird ${args.join(' ')} -''', - mode: FileMode.append, - ); +''', mode: FileMode.append); await IOOverrides.runZoned( () async => _flushThenExit( @@ -107,6 +104,8 @@ Command: shorebird ${args.join(' ')} /// exited already. This is useful to prevent Future chains from proceeding /// after you've decided to exit. Future _flushThenExit(int status) { - return Future.wait([stdout.close(), stderr.close()]) - .then((_) => exit(status)); + return Future.wait([ + stdout.close(), + stderr.close(), + ]).then((_) => exit(status)); } diff --git a/packages/shorebird_cli/integration_test/shorebird_cli_integration_test.dart b/packages/shorebird_cli/integration_test/shorebird_cli_integration_test.dart index 17b5e2c4..6d1854c8 100644 --- a/packages/shorebird_cli/integration_test/shorebird_cli_integration_test.dart +++ b/packages/shorebird_cli/integration_test/shorebird_cli_integration_test.dart @@ -19,13 +19,7 @@ import 'package:uuid/uuid.dart'; R runWithOverrides(R Function() body) { return runScoped( body, - values: { - authRef, - httpClientRef, - loggerRef, - platformRef, - shorebirdEnvRef, - }, + values: {authRef, httpClientRef, loggerRef, platformRef, shorebirdEnvRef}, ); } @@ -66,10 +60,7 @@ void main() { test('--version', () { final result = runCommand('shorebird --version', workingDirectory: '.'); expect(result.stderr, isEmpty); - expect( - result.stdout, - stringContainsInOrder(['Engine', 'revision']), - ); + expect(result.stdout, stringContainsInOrder(['Engine', 'revision'])); expect(result.exitCode, equals(0)); }); @@ -90,8 +81,9 @@ void main() { final uuid = const Uuid().v4().replaceAll('-', '_'); final testAppName = 'test_app_$uuid'; final tempDir = Directory.systemTemp.createTempSync(); - final subDirWithSpace = - Directory(p.join(tempDir.path, 'flutter directory'))..createSync(); + final subDirWithSpace = Directory( + p.join(tempDir.path, 'flutter directory'), + )..createSync(); var cwd = subDirWithSpace.path; // Create the default flutter counter app @@ -126,21 +118,13 @@ void main() { completion( contains( isA() - .having( - (a) => a.appId, - 'appId', - shorebirdYaml.appId, - ) + .having((a) => a.appId, 'appId', shorebirdYaml.appId) .having( (a) => a.latestReleaseVersion, 'latestReleaseVersion', null, ) - .having( - (a) => a.latestPatchNumber, - 'latestPatchNumber', - null, - ), + .having((a) => a.latestPatchNumber, 'latestPatchNumber', null), ), ), ); @@ -175,21 +159,13 @@ void main() { completion( contains( isA() - .having( - (a) => a.appId, - 'appId', - shorebirdYaml.appId, - ) + .having((a) => a.appId, 'appId', shorebirdYaml.appId) .having( (a) => a.latestReleaseVersion, 'latestReleaseVersion', releaseVersion, ) - .having( - (a) => a.latestPatchNumber, - 'latestPatchNumber', - null, - ), + .having((a) => a.latestPatchNumber, 'latestPatchNumber', null), ), ), ); @@ -221,21 +197,13 @@ void main() { completion( contains( isA() - .having( - (a) => a.appId, - 'appId', - shorebirdYaml.appId, - ) + .having((a) => a.appId, 'appId', shorebirdYaml.appId) .having( (a) => a.latestReleaseVersion, 'latestReleaseVersion', '1.0.0+1', ) - .having( - (a) => a.latestPatchNumber, - 'latestPatchNumber', - 1, - ), + .having((a) => a.latestPatchNumber, 'latestPatchNumber', 1), ), ), ); @@ -286,18 +254,16 @@ Future isPatchAvailable({ required String channel, }) async { final response = await http.post( - Uri.parse(Platform.environment['SHOREBIRD_HOSTED_URL']!).replace( - path: '/api/v1/patches/check', - ), - body: jsonEncode( - { - 'release_version': releaseVersion, - 'platform': platform, - 'arch': arch, - 'app_id': appId, - 'channel': channel, - }, - ), + Uri.parse( + Platform.environment['SHOREBIRD_HOSTED_URL']!, + ).replace(path: '/api/v1/patches/check'), + body: jsonEncode({ + 'release_version': releaseVersion, + 'platform': platform, + 'arch': arch, + 'app_id': appId, + 'channel': channel, + }), ); if (response.statusCode != HttpStatus.ok) { throw Exception('Patch Check Failure: ${response.statusCode}'); diff --git a/packages/shorebird_cli/lib/src/android_sdk.dart b/packages/shorebird_cli/lib/src/android_sdk.dart index 59a9d508..8bbe77be 100644 --- a/packages/shorebird_cli/lib/src/android_sdk.dart +++ b/packages/shorebird_cli/lib/src/android_sdk.dart @@ -69,9 +69,10 @@ class AndroidSdk { /// Returns the default path to the Android SDK if a home directory is defined /// and we're on a recognized platform, or null otherwise. String? _defaultAndroidSdkPath() { - final home = platform.isWindows - ? platform.environment['USERPROFILE'] - : platform.environment['HOME']; + final home = + platform.isWindows + ? platform.environment['USERPROFILE'] + : platform.environment['HOME']; if (home == null) { return null; } diff --git a/packages/shorebird_cli/lib/src/android_studio.dart b/packages/shorebird_cli/lib/src/android_studio.dart index 80a31388..f06f0fcb 100644 --- a/packages/shorebird_cli/lib/src/android_studio.dart +++ b/packages/shorebird_cli/lib/src/android_studio.dart @@ -52,8 +52,9 @@ class AndroidStudio { final directoryName = p.basename(directory.path); // Because we've already performed this match above, we can safely // assume that this will match. - final versionMatch = - androidStudioRegex.firstMatch(directoryName)!.group(1); + final versionMatch = androidStudioRegex + .firstMatch(directoryName)! + .group(1); final version = tryParseVersion(versionMatch!, strict: false)!; final homeFile = File(p.join(directory.path, '.home')); diff --git a/packages/shorebird_cli/lib/src/archive_analysis/apple_archive_differ.dart b/packages/shorebird_cli/lib/src/archive_analysis/apple_archive_differ.dart index 5756ca63..2446bfea 100644 --- a/packages/shorebird_cli/lib/src/archive_analysis/apple_archive_differ.dart +++ b/packages/shorebird_cli/lib/src/archive_analysis/apple_archive_differ.dart @@ -43,9 +43,7 @@ class AppleArchiveDiffer extends ArchiveDiffer { ); /// The regex pattern for identifying executable files within a macOS .app. - static final RegExp macosAppRegex = RegExp( - r'^Contents/MacOS/.+$', - ); + static final RegExp macosAppRegex = RegExp(r'^Contents/MacOS/.+$'); /// Files that have been added, removed, or that have changed between the /// archives at the two provided paths. This method will also unsign mach-o @@ -101,8 +99,9 @@ class AppleArchiveDiffer extends ArchiveDiffer { .where((file) => file.isFile) .where( (file) => - _binaryFilePatterns - .any((pattern) => pattern.hasMatch(file.name)) || + _binaryFilePatterns.any( + (pattern) => pattern.hasMatch(file.name), + ) || xcFrameworkAppRegex.hasMatch(file.name), ) .toList(); diff --git a/packages/shorebird_cli/lib/src/archive_analysis/archive_differ.dart b/packages/shorebird_cli/lib/src/archive_analysis/archive_differ.dart index 54bb9781..d8a02b9a 100644 --- a/packages/shorebird_cli/lib/src/archive_analysis/archive_differ.dart +++ b/packages/shorebird_cli/lib/src/archive_analysis/archive_differ.dart @@ -60,26 +60,26 @@ abstract class ArchiveDiffer { /// The subset of [fileSetDiff] that contains only changes that result from /// edited assets. FileSetDiff assetsFileSetDiff(FileSetDiff fileSetDiff) => FileSetDiff( - addedPaths: fileSetDiff.addedPaths.where(isAssetFilePath).toSet(), - removedPaths: fileSetDiff.removedPaths.where(isAssetFilePath).toSet(), - changedPaths: fileSetDiff.changedPaths.where(isAssetFilePath).toSet(), - ); + addedPaths: fileSetDiff.addedPaths.where(isAssetFilePath).toSet(), + removedPaths: fileSetDiff.removedPaths.where(isAssetFilePath).toSet(), + changedPaths: fileSetDiff.changedPaths.where(isAssetFilePath).toSet(), + ); /// The subset of [fileSetDiff] that contains only changes that result from /// edited Dart code. FileSetDiff dartFileSetDiff(FileSetDiff fileSetDiff) => FileSetDiff( - addedPaths: fileSetDiff.addedPaths.where(isDartFilePath).toSet(), - removedPaths: fileSetDiff.removedPaths.where(isDartFilePath).toSet(), - changedPaths: fileSetDiff.changedPaths.where(isDartFilePath).toSet(), - ); + addedPaths: fileSetDiff.addedPaths.where(isDartFilePath).toSet(), + removedPaths: fileSetDiff.removedPaths.where(isDartFilePath).toSet(), + changedPaths: fileSetDiff.changedPaths.where(isDartFilePath).toSet(), + ); /// The subset of [fileSetDiff] that contains only changes that result from /// edited native code. FileSetDiff nativeFileSetDiff(FileSetDiff fileSetDiff) => FileSetDiff( - addedPaths: fileSetDiff.addedPaths.where(isNativeFilePath).toSet(), - removedPaths: fileSetDiff.removedPaths.where(isNativeFilePath).toSet(), - changedPaths: fileSetDiff.changedPaths.where(isNativeFilePath).toSet(), - ); + addedPaths: fileSetDiff.addedPaths.where(isNativeFilePath).toSet(), + removedPaths: fileSetDiff.removedPaths.where(isNativeFilePath).toSet(), + changedPaths: fileSetDiff.changedPaths.where(isNativeFilePath).toSet(), + ); /// Files that have been added, removed, or that have changed between the /// archives at the two provided paths. diff --git a/packages/shorebird_cli/lib/src/archive_analysis/file_set_diff.dart b/packages/shorebird_cli/lib/src/archive_analysis/file_set_diff.dart index 4621aa0a..7b103d8e 100644 --- a/packages/shorebird_cli/lib/src/archive_analysis/file_set_diff.dart +++ b/packages/shorebird_cli/lib/src/archive_analysis/file_set_diff.dart @@ -23,18 +23,16 @@ class FileSetDiff extends Equatable { return FileSetDiff( addedPaths: newPaths.difference(oldPaths), removedPaths: oldPaths.difference(newPaths), - changedPaths: oldPaths - .intersection(newPaths) - .where((name) => oldPathHashes[name] != newPathHashes[name]) - .toSet(), + changedPaths: + oldPaths + .intersection(newPaths) + .where((name) => oldPathHashes[name] != newPathHashes[name]) + .toSet(), ); } /// Creates an empty FileSetDiff. - FileSetDiff.empty() - : addedPaths = {}, - removedPaths = {}, - changedPaths = {}; + FileSetDiff.empty() : addedPaths = {}, removedPaths = {}, changedPaths = {}; /// File paths that were added. final Set addedPaths; @@ -56,13 +54,13 @@ class FileSetDiff extends Equatable { /// A printable string representation of this [FileSetDiff]. String get prettyString => [ - if (addedPaths.isNotEmpty) - _prettyFileSetString(title: 'Added files', paths: addedPaths), - if (changedPaths.isNotEmpty) - _prettyFileSetString(title: 'Changed files', paths: changedPaths), - if (removedPaths.isNotEmpty) - _prettyFileSetString(title: 'Removed files', paths: removedPaths), - ].join('\n'); + if (addedPaths.isNotEmpty) + _prettyFileSetString(title: 'Added files', paths: addedPaths), + if (changedPaths.isNotEmpty) + _prettyFileSetString(title: 'Changed files', paths: changedPaths), + if (removedPaths.isNotEmpty) + _prettyFileSetString(title: 'Removed files', paths: removedPaths), + ].join('\n'); static String _prettyFileSetString({ required String title, diff --git a/packages/shorebird_cli/lib/src/archive_analysis/plist.dart b/packages/shorebird_cli/lib/src/archive_analysis/plist.dart index 01b7cde5..e8641748 100644 --- a/packages/shorebird_cli/lib/src/archive_analysis/plist.dart +++ b/packages/shorebird_cli/lib/src/archive_analysis/plist.dart @@ -8,9 +8,11 @@ import 'package:propertylistserialization/propertylistserialization.dart'; class Plist { /// Creates a new [Plist] from the contents of the provided [file]. Plist({required File file}) { - properties = PropertyListSerialization.propertyListWithString( - file.readAsStringSync(), - ) as Map; + properties = + PropertyListSerialization.propertyListWithString( + file.readAsStringSync(), + ) + as Map; } /// This key is a user-visible string for the version of the bundle. The diff --git a/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart b/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart index 384a5fee..047ec203 100644 --- a/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart +++ b/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart @@ -15,11 +15,13 @@ class ArtifactBuildException implements Exception { List? stdout, List? stderr, String? fixRecommendation, - }) : stdout = stdout ?? [], - stderr = stderr ?? [] { - flutterError = - _errorMessageFromOutput(this.stdout + this.stderr).join('\n'); - this.fixRecommendation = fixRecommendation ?? + }) : stdout = stdout ?? [], + stderr = stderr ?? [] { + flutterError = _errorMessageFromOutput( + this.stdout + this.stderr, + ).join('\n'); + this.fixRecommendation = + fixRecommendation ?? _recommendationFromOutput(this.stdout + this.stderr); } diff --git a/packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart b/packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart index 1afeac5e..9cebc950 100644 --- a/packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart +++ b/packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart @@ -46,9 +46,7 @@ class IpaBuildResult { /// {@endtemplate} class IosFrameworkBuildResult { /// {@macro ios_framework_build_result} - IosFrameworkBuildResult({ - required this.kernelFile, - }); + IosFrameworkBuildResult({required this.kernelFile}); /// The app.dill file produced by this invocation of `flutter build ipa`. final File kernelFile; @@ -126,20 +124,21 @@ class ArtifactBuilder { .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { - stdoutLines.add(line); - if (buildProgress == null) { - return; - } - final captured = gradleTaskRegex.firstMatch(line)?.group(1); - if (captured != null) { - buildProgress.updateDetailMessage(captured); - } - }); + stdoutLines.add(line); + if (buildProgress == null) { + return; + } + final captured = gradleTaskRegex.firstMatch(line)?.group(1); + if (captured != null) { + buildProgress.updateDetailMessage(captured); + } + }); - final stderrLines = await buildProcess.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .toList(); + final stderrLines = + await buildProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { throw ArtifactBuildException( @@ -257,11 +256,7 @@ class ArtifactBuilder { ...args, ]; - final result = await process.run( - executable, - arguments, - runInShell: true, - ); + final result = await process.run(executable, arguments, runInShell: true); if (result.exitCode != ExitCode.success.code) { throw ArtifactBuildException.fromProcessResult( @@ -301,14 +296,15 @@ class ArtifactBuilder { .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { - logger.detail(line); - stdoutLines.add(line); - }); + logger.detail(line); + stdoutLines.add(line); + }); - final stderrLines = await buildProcess.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .toList(); + final stderrLines = + await buildProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { throw ArtifactBuildException( @@ -364,22 +360,23 @@ class ArtifactBuilder { .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { - stdoutLines.add(line); - if (buildProgress == null) { - return; - } + stdoutLines.add(line); + if (buildProgress == null) { + return; + } - // TODO(bryanoltman): update the progress message for macOS builds. - // final update = _progressUpdateFromMacosBuildLog(line); - // if (update != null) { - // buildProgress.updateDetailMessage(update); - // } - }); + // TODO(bryanoltman): update the progress message for macOS builds. + // final update = _progressUpdateFromMacosBuildLog(line); + // if (update != null) { + // buildProgress.updateDetailMessage(update); + // } + }); - final stderrLines = await buildProcess.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .toList(); + final stderrLines = + await buildProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); final stdout = stdoutLines.join('\n'); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { @@ -439,21 +436,22 @@ class ArtifactBuilder { .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { - stdoutLines.add(line); - if (buildProgress == null) { - return; - } + stdoutLines.add(line); + if (buildProgress == null) { + return; + } - final update = _progressUpdateFromIpaBuildLog(line); - if (update != null) { - buildProgress.updateDetailMessage(update); - } - }); + final update = _progressUpdateFromIpaBuildLog(line); + if (update != null) { + buildProgress.updateDetailMessage(update); + } + }); - final stderrLines = await buildProcess.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .toList(); + final stderrLines = + await buildProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); final stderr = stderrLines.join('\n'); final stdout = stdoutLines.join('\n'); final exitCode = await buildProcess.exitCode; @@ -510,11 +508,7 @@ class ArtifactBuilder { ...args, ]; - final result = await process.run( - executable, - arguments, - runInShell: true, - ); + final result = await process.run(executable, arguments, runInShell: true); if (result.exitCode != ExitCode.success.code) { throw ArtifactBuildException('Failed to build: ${result.stderr}'); @@ -569,13 +563,11 @@ class ArtifactBuilder { ); if (result.exitCode != ExitCode.success.code) { - logger.warn( - ''' + logger.warn(''' Build was successful, but `flutter pub get` failed to run after the build completed. You may see unexpected behavior in VS Code. Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCodeUrl.toLink()}. -''', - ); +'''); } } @@ -620,12 +612,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod }) async { await _runShorebirdBuildCommand(() async { const executable = 'flutter'; - final arguments = [ - 'build', - 'windows', - '--release', - ...args, - ]; + final arguments = ['build', 'windows', '--release', ...args]; final buildProcess = await process.start( executable, @@ -639,15 +626,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { - logger.detail(line); - stdoutLines.add(line); - // TODO(bryanoltman): update build progress - }); + logger.detail(line); + stdoutLines.add(line); + // TODO(bryanoltman): update build progress + }); - final stderrLines = await buildProcess.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .toList(); + final stderrLines = + await buildProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { throw ArtifactBuildException( @@ -668,14 +656,17 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod // xcodebuild -list is a command run early in `flutter build ipa` to read // build settings and schemes. Most users aren't familiar with this command, // so we translate it to "Collecting schemes" below. - final collectingSchemesRegex = - RegExp(r'\[.*\] executing:.*xcrun xcodebuild -list$'); + final collectingSchemesRegex = RegExp( + r'\[.*\] executing:.*xcrun xcodebuild -list$', + ); final archivingRegex = RegExp(r'^\[.*\] (Archiving .+$)'); final runningXcodeBuildRegex = RegExp(r'^\[.*\] (Running Xcode build).*$'); - final compilingLinkingSigningRegex = - RegExp(r'^\[.*\]\s+└─(Compiling, linking and signing).*$'); - final buildingAppStoreIpaRegex = - RegExp(r'^\[.*\] (Building App Store IPA).*$'); + final compilingLinkingSigningRegex = RegExp( + r'^\[.*\]\s+└─(Compiling, linking and signing).*$', + ); + final buildingAppStoreIpaRegex = RegExp( + r'^\[.*\] (Building App Store IPA).*$', + ); final builtAppStoreIpaRegex = RegExp(r'^\[.*\] ✓ (Built IPA to \S+).*$'); final regexes = [ @@ -706,7 +697,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod /// command, finds the path to the app.dill file that was built. @visibleForTesting String? findAppDill({required String stdout}) { - final appDillLine = stdout.split('\n').firstWhereOrNull( + final appDillLine = stdout + .split('\n') + .firstWhereOrNull( (l) => l.contains('gen_snapshot') && l.endsWith('app.dill'), ); diff --git a/packages/shorebird_cli/lib/src/artifact_manager.dart b/packages/shorebird_cli/lib/src/artifact_manager.dart index 620e45c1..458a8693 100644 --- a/packages/shorebird_cli/lib/src/artifact_manager.dart +++ b/packages/shorebird_cli/lib/src/artifact_manager.dart @@ -77,14 +77,8 @@ class ArtifactManager { /// temporary directory if not. /// /// Returns the downloaded [File]. - Future downloadFile( - Uri uri, { - String? outputPath, - }) async { - final download = await startFileDownload( - uri, - outputPath: outputPath, - ); + Future downloadFile(Uri uri, {String? outputPath}) async { + final download = await startFileDownload(uri, outputPath: outputPath); return download.file; } @@ -94,10 +88,7 @@ class ArtifactManager { /// Returns a [FileDownload] object containing the [Future] and a /// [Stream] of download progress updates. @visibleForTesting - Future startFileDownload( - Uri uri, { - String? outputPath, - }) async { + Future startFileDownload(Uri uri, {String? outputPath}) async { final request = http.Request('GET', uri); final response = await httpClient.send(request); @@ -167,10 +158,10 @@ class ArtifactManager { final subscription = download.progress .throttle(throttleDuration, trailing: true) .listen((progress) { - downloadProgress.update( - '$message (${(progress * 100).toStringAsFixed(0)}%)', - ); - }); + downloadProgress.update( + '$message (${(progress * 100).toStringAsFixed(0)}%)', + ); + }); artifactFile = await download.file; await subscription.cancel(); @@ -235,30 +226,15 @@ class ArtifactManager { // // See https://github.com/shorebirdtech/shorebird/issues/1798 final strippedSymbolsDir = Directory( - p.join( - releasePath, - stripReleaseDebugSymbolsDirName, - ), + p.join(releasePath, stripReleaseDebugSymbolsDirName), ); final Directory archsDirectory; if (strippedSymbolsDir.existsSync()) { - archsDirectory = Directory( - p.join( - strippedSymbolsDir.path, - 'out', - 'lib', - ), - ); + archsDirectory = Directory(p.join(strippedSymbolsDir.path, 'out', 'lib')); } else { // If the new path doesn't exist, fallback to the old path. - archsDirectory = Directory( - p.join( - releasePath, - 'out', - 'lib', - ), - ); + archsDirectory = Directory(p.join(releasePath, 'out', 'lib')); } return archsDirectory.existsSync() ? archsDirectory : null; @@ -301,12 +277,7 @@ class ArtifactManager { Directory? getXcarchiveDirectory() { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; final archiveDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'archive', - ), + p.join(projectRoot.path, 'build', 'ios', 'archive'), ); if (!archiveDirectory.existsSync()) return null; @@ -330,11 +301,7 @@ class ArtifactManager { /// traditionally named `Runner.app`, but can now be renamed. Directory? getIosAppDirectory({required Directory xcarchiveDirectory}) { final applicationsDirectory = Directory( - p.join( - xcarchiveDirectory.path, - 'Products', - 'Applications', - ), + p.join(xcarchiveDirectory.path, 'Products', 'Applications'), ); if (!applicationsDirectory.existsSync()) { @@ -351,14 +318,7 @@ class ArtifactManager { Directory get linuxBundleDirectory { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; return Directory( - p.join( - projectRoot.path, - 'build', - 'linux', - 'x64', - 'release', - 'bundle', - ), + p.join(projectRoot.path, 'build', 'linux', 'x64', 'release', 'bundle'), ); } @@ -366,14 +326,7 @@ class ArtifactManager { Directory getWindowsReleaseDirectory() { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; return Directory( - p.join( - projectRoot.path, - 'build', - 'windows', - 'x64', - 'runner', - 'Release', - ), + p.join(projectRoot.path, 'build', 'windows', 'x64', 'runner', 'Release'), ); } @@ -386,12 +339,7 @@ class ArtifactManager { File? getIpa() { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; final ipaBuildDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'ipa', - ), + p.join(projectRoot.path, 'build', 'ios', 'ipa'), ); if (!ipaBuildDirectory.existsSync()) { @@ -475,13 +423,7 @@ class ArtifactManager { Directory getAppXcframeworkDirectory() { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; return Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - ), + p.join(projectRoot.path, 'build', 'ios', 'framework', 'Release'), ); } } diff --git a/packages/shorebird_cli/lib/src/auth/auth.dart b/packages/shorebird_cli/lib/src/auth/auth.dart index 58964fd6..01f92262 100644 --- a/packages/shorebird_cli/lib/src/auth/auth.dart +++ b/packages/shorebird_cli/lib/src/auth/auth.dart @@ -41,26 +41,27 @@ const microsoftJwtIssuerPrefix = 'https://login.microsoftonline.com/'; const shorebirdTokenEnvVar = 'SHOREBIRD_TOKEN'; /// Callback for obtaining access credentials. -typedef ObtainAccessCredentials = Future Function( - oauth2.ClientId clientId, - List scopes, - http.Client client, - void Function(String) userPrompt, { - oauth2.AuthEndpoints authEndpoints, -}); +typedef ObtainAccessCredentials = + Future Function( + oauth2.ClientId clientId, + List scopes, + http.Client client, + void Function(String) userPrompt, { + oauth2.AuthEndpoints authEndpoints, + }); /// Callback for refreshing access credentials. -typedef RefreshCredentials = Future Function( - oauth2.ClientId clientId, - oauth2.AccessCredentials credentials, - http.Client client, { - oauth2.AuthEndpoints authEndpoints, -}); +typedef RefreshCredentials = + Future Function( + oauth2.ClientId clientId, + oauth2.AccessCredentials credentials, + http.Client client, { + oauth2.AuthEndpoints authEndpoints, + }); /// Callback when credentials are refreshed. -typedef OnRefreshCredentials = void Function( - oauth2.AccessCredentials credentials, -); +typedef OnRefreshCredentials = + void Function(oauth2.AccessCredentials credentials); /// A client that automatically refreshes OAuth 2.0 credentials. class AuthenticatedClient extends http.BaseClient { @@ -72,11 +73,11 @@ class AuthenticatedClient extends http.BaseClient { OnRefreshCredentials? onRefreshCredentials, RefreshCredentials refreshCredentials = oauth2.refreshCredentials, }) : this._( - httpClient: httpClient, - onRefreshCredentials: onRefreshCredentials, - credentials: credentials, - refreshCredentials: refreshCredentials, - ); + httpClient: httpClient, + onRefreshCredentials: onRefreshCredentials, + credentials: credentials, + refreshCredentials: refreshCredentials, + ); /// Creates a new [AuthenticatedClient] with the given [httpClient] and /// [token]. @@ -86,11 +87,11 @@ class AuthenticatedClient extends http.BaseClient { OnRefreshCredentials? onRefreshCredentials, RefreshCredentials refreshCredentials = oauth2.refreshCredentials, }) : this._( - httpClient: httpClient, - token: token, - onRefreshCredentials: onRefreshCredentials, - refreshCredentials: refreshCredentials, - ); + httpClient: httpClient, + token: token, + onRefreshCredentials: onRefreshCredentials, + refreshCredentials: refreshCredentials, + ); AuthenticatedClient._({ required http.Client httpClient, @@ -98,11 +99,11 @@ class AuthenticatedClient extends http.BaseClient { oauth2.AccessCredentials? credentials, CiToken? token, RefreshCredentials refreshCredentials = oauth2.refreshCredentials, - }) : _baseClient = httpClient, - _credentials = credentials, - _onRefreshCredentials = onRefreshCredentials, - _refreshCredentials = refreshCredentials, - _token = token; + }) : _baseClient = httpClient, + _credentials = credentials, + _onRefreshCredentials = onRefreshCredentials, + _refreshCredentials = refreshCredentials, + _token = token; final http.Client _baseClient; final OnRefreshCredentials? _onRefreshCredentials; @@ -116,17 +117,18 @@ class AuthenticatedClient extends http.BaseClient { if (credentials == null) { final token = _token!; - credentials = _credentials = await _tryRefreshCredentials( - token.authProvider.clientId, - oauth2.AccessCredentials( - // This isn't relevant for a refresh operation. - AccessToken('Bearer', '', DateTime.timestamp()), - token.refreshToken, - token.authProvider.scopes, - ), - _baseClient, - authEndpoints: token.authProvider.authEndpoints, - ); + credentials = + _credentials = await _tryRefreshCredentials( + token.authProvider.clientId, + oauth2.AccessCredentials( + // This isn't relevant for a refresh operation. + AccessToken('Bearer', '', DateTime.timestamp()), + token.refreshToken, + token.authProvider.scopes, + ), + _baseClient, + authEndpoints: token.authProvider.authEndpoints, + ); _onRefreshCredentials?.call(credentials); } @@ -134,12 +136,13 @@ class AuthenticatedClient extends http.BaseClient { final jwt = Jwt.parse(credentials.idToken!); final authProvider = jwt.authProvider; - credentials = _credentials = await _tryRefreshCredentials( - authProvider.clientId, - credentials, - _baseClient, - authEndpoints: authProvider.authEndpoints, - ); + credentials = + _credentials = await _tryRefreshCredentials( + authProvider.clientId, + credentials, + _baseClient, + authEndpoints: authProvider.authEndpoints, + ); _onRefreshCredentials?.call(credentials); } @@ -183,12 +186,13 @@ class Auth { String? credentialsDir, ObtainAccessCredentials? obtainAccessCredentials, CodePushClientBuilder? buildCodePushClient, - }) : _httpClient = httpClient ?? _defaultHttpClient, - _credentialsDir = - credentialsDir ?? applicationConfigHome(executableName), - _obtainAccessCredentials = obtainAccessCredentials ?? - oauth2.obtainAccessCredentialsViaUserConsent, - _buildCodePushClient = buildCodePushClient ?? CodePushClient.new { + }) : _httpClient = httpClient ?? _defaultHttpClient, + _credentialsDir = + credentialsDir ?? applicationConfigHome(executableName), + _obtainAccessCredentials = + obtainAccessCredentials ?? + oauth2.obtainAccessCredentialsViaUserConsent, + _buildCodePushClient = buildCodePushClient ?? CodePushClient.new { _loadCredentials(); } @@ -212,10 +216,7 @@ class Auth { } if (_token != null) { - return AuthenticatedClient.token( - token: _token!, - httpClient: _httpClient, - ); + return AuthenticatedClient.token(token: _token!, httpClient: _httpClient); } return AuthenticatedClient.credentials( @@ -321,12 +322,10 @@ class Auth { _token = CiToken.fromBase64(envToken.trim()); } on FormatException catch (e) { logger - ..err( - ''' + ..err(''' Failed to parse CI token from environment. This likely means that your CI token is incorrectly formatted. -Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar environment variable, and try again.''', - ) +Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar environment variable, and try again.''') ..detail(e.toString()); rethrow; } @@ -430,9 +429,9 @@ extension OauthAuthProvider on Jwt { extension OauthValues on AuthProvider { /// The OAuth 2.0 endpoints for the provider. oauth2.AuthEndpoints get authEndpoints => switch (this) { - (AuthProvider.google) => const oauth2.GoogleAuthEndpoints(), - (AuthProvider.microsoft) => MicrosoftAuthEndpoints(), - }; + (AuthProvider.google) => const oauth2.GoogleAuthEndpoints(), + (AuthProvider.microsoft) => MicrosoftAuthEndpoints(), + }; /// The OAuth 2.0 client ID for the provider. oauth2.ClientId get clientId { @@ -464,15 +463,15 @@ extension OauthValues on AuthProvider { /// The OAuth 2.0 scopes for the provider. List get scopes => switch (this) { - (AuthProvider.google) => [ - 'openid', - 'https://www.googleapis.com/auth/userinfo.email', - ], - (AuthProvider.microsoft) => [ - 'openid', - 'email', - // Required to get refresh tokens. - 'offline_access', - ], - }; + (AuthProvider.google) => [ + 'openid', + 'https://www.googleapis.com/auth/userinfo.email', + ], + (AuthProvider.microsoft) => [ + 'openid', + 'email', + // Required to get refresh tokens. + 'offline_access', + ], + }; } diff --git a/packages/shorebird_cli/lib/src/auth/ci_token.g.dart b/packages/shorebird_cli/lib/src/auth/ci_token.g.dart index 849d5f88..7d86738b 100644 --- a/packages/shorebird_cli/lib/src/auth/ci_token.g.dart +++ b/packages/shorebird_cli/lib/src/auth/ci_token.g.dart @@ -9,26 +9,28 @@ part of 'ci_token.dart'; // ************************************************************************** CiToken _$CiTokenFromJson(Map json) => $checkedCreate( - 'CiToken', - json, - ($checkedConvert) { - final val = CiToken( - refreshToken: $checkedConvert('refresh_token', (v) => v as String), - authProvider: $checkedConvert( - 'auth_provider', (v) => $enumDecode(_$AuthProviderEnumMap, v)), - ); - return val; - }, - fieldKeyMap: const { - 'refreshToken': 'refresh_token', - 'authProvider': 'auth_provider' - }, + 'CiToken', + json, + ($checkedConvert) { + final val = CiToken( + refreshToken: $checkedConvert('refresh_token', (v) => v as String), + authProvider: $checkedConvert( + 'auth_provider', + (v) => $enumDecode(_$AuthProviderEnumMap, v), + ), ); + return val; + }, + fieldKeyMap: const { + 'refreshToken': 'refresh_token', + 'authProvider': 'auth_provider', + }, +); Map _$CiTokenToJson(CiToken instance) => { - 'refresh_token': instance.refreshToken, - 'auth_provider': _$AuthProviderEnumMap[instance.authProvider]!, - }; + 'refresh_token': instance.refreshToken, + 'auth_provider': _$AuthProviderEnumMap[instance.authProvider]!, +}; const _$AuthProviderEnumMap = { AuthProvider.google: 'google', diff --git a/packages/shorebird_cli/lib/src/cache.dart b/packages/shorebird_cli/lib/src/cache.dart index 1d6dbc68..0c657df9 100644 --- a/packages/shorebird_cli/lib/src/cache.dart +++ b/packages/shorebird_cli/lib/src/cache.dart @@ -98,23 +98,17 @@ class Cache { /// The Shorebird cache directory. static Directory get shorebirdCacheDirectory { - return Directory( - p.join(shorebirdEnv.shorebirdRoot.path, 'bin', 'cache'), - ); + return Directory(p.join(shorebirdEnv.shorebirdRoot.path, 'bin', 'cache')); } /// The Shorebird cached previews directory. static Directory get shorebirdPreviewsDirectory { - return Directory( - p.join(shorebirdCacheDirectory.path, 'previews'), - ); + return Directory(p.join(shorebirdCacheDirectory.path, 'previews')); } /// The Shorebird cached artifacts directory. static Directory get shorebirdArtifactsDirectory { - return Directory( - p.join(shorebirdCacheDirectory.path, 'artifacts'), - ); + return Directory(p.join(shorebirdCacheDirectory.path, 'artifacts')); } final List _artifacts = []; @@ -208,12 +202,10 @@ abstract class CachedArtifact { try { response = await httpClient.send(request); } catch (error) { - throw CacheUpdateFailure( - ''' + throw CacheUpdateFailure(''' Failed to download $fileName: $error If you're behind a firewall/proxy, please, make sure shorebird_cli is -allowed to access $storageUrl.''', - ); +allowed to access $storageUrl.'''); } if (response.statusCode != HttpStatus.ok) { @@ -304,12 +296,12 @@ class AotToolsArtifact extends CachedArtifact { @override File get file => File( - p.join( - cache.getArtifactDirectory(fileName).path, - shorebirdEnv.shorebirdEngineRevision, - fileName, - ), - ); + p.join( + cache.getArtifactDirectory(fileName).path, + shorebirdEnv.shorebirdEngineRevision, + fileName, + ), + ); @override String get storageUrl => @@ -385,11 +377,11 @@ class BundleToolArtifact extends CachedArtifact { @override String? get checksum => - // SHA-256 checksum of the bundletool.jar file. - // When updating the bundletool version, be sure to update this checksum. - // This can be done by running the following command: - // ```shell - // shasum --algorithm 256 /path/to/file - // ``` - '''45881ead13388872d82c4255b195488b7fc33f2cac5a9a977b0afc5e92367592'''; + // SHA-256 checksum of the bundletool.jar file. + // When updating the bundletool version, be sure to update this checksum. + // This can be done by running the following command: + // ```shell + // shasum --algorithm 256 /path/to/file + // ``` + '''45881ead13388872d82c4255b195488b7fc33f2cac5a9a977b0afc5e92367592'''; } diff --git a/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart b/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart index 6b99a0ff..13ec4f6e 100644 --- a/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart +++ b/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart @@ -84,10 +84,7 @@ class CodePushClientWrapper { final CodePushClient codePushClient; - Future createApp({ - required int organizationId, - String? appName, - }) async { + Future createApp({required int organizationId, String? appName}) async { late final String displayName; if (appName == null) { final defaultAppName = shorebirdEnv.getPubspecYaml()?.name; @@ -132,11 +129,9 @@ class CodePushClientWrapper { Future getApp({required String appId}) async { final app = await maybeGetApp(appId: appId); if (app == null) { - logger.err( - ''' + logger.err(''' Could not find app with id: "$appId". -This app may not exist or you may not have permission to view it.''', - ); +This app may not exist or you may not have permission to view it.'''); throw ProcessExit(ExitCode.software.code); } @@ -191,10 +186,7 @@ This app may not exist or you may not have permission to view it.''', required ReleasePlatform platform, }) { if (release.platformStatuses[platform] == ReleaseStatus.active) { - final uri = ShorebirdWebConsole.appReleaseUri( - release.appId, - release.id, - ); + final uri = ShorebirdWebConsole.appReleaseUri(release.appId, release.id); logger.err( ''' It looks like you have an existing ${platform.name} release for version ${lightCyan.wrap(release.version)}. @@ -216,14 +208,12 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' ); if (release == null) { - logger.err( - ''' + logger.err(''' Release not found: "$releaseVersion" Patches can only be published for existing releases. Please create a release using "shorebird release" and try again. -''', - ); +'''); throw ProcessExit(ExitCode.software.code); } @@ -478,11 +468,9 @@ Looked in: ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. - logger.info( - ''' + logger.info(''' -${arch.arch} artifact already exists, continuing...''', - ); +${arch.arch} artifact already exists, continuing...'''); } catch (error) { _handleErrorAndExit( error, @@ -506,11 +494,9 @@ ${arch.arch} artifact already exists, continuing...''', ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. - logger.info( - ''' + logger.info(''' -aab artifact already exists, continuing...''', - ); +aab artifact already exists, continuing...'''); } catch (error) { _handleErrorAndExit( error, @@ -545,11 +531,9 @@ aab artifact already exists, continuing...''', ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. - logger.info( - ''' + logger.info(''' -Windows release (exe) artifact already exists, continuing...''', - ); +Windows release (exe) artifact already exists, continuing...'''); } catch (error) { _handleErrorAndExit( error, @@ -594,11 +578,9 @@ Windows release (exe) artifact already exists, continuing...''', ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. - logger.info( - ''' + logger.info(''' -${arch.arch} artifact already exists, continuing...''', - ); +${arch.arch} artifact already exists, continuing...'''); } catch (error) { _handleErrorAndExit( error, @@ -622,11 +604,9 @@ ${arch.arch} artifact already exists, continuing...''', ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. - logger.info( - ''' + logger.info(''' -aar artifact already exists, continuing...''', - ); +aar artifact already exists, continuing...'''); } catch (error) { _handleErrorAndExit( error, @@ -643,8 +623,9 @@ aar artifact already exists, continuing...''', Future _thinXcarchive({required String xcarchivePath}) async { final xcarchiveDirectoryName = p.basename(xcarchivePath); final tempDir = Directory.systemTemp.createTempSync(); - final thinnedArchiveDirectory = - Directory(p.join(tempDir.path, xcarchiveDirectoryName)); + final thinnedArchiveDirectory = Directory( + p.join(tempDir.path, xcarchiveDirectoryName), + ); await io.copyPath(xcarchivePath, thinnedArchiveDirectory.path); thinnedArchiveDirectory .listSync(recursive: true) @@ -776,9 +757,9 @@ aar artifact already exists, continuing...''', } if (supplementPath != null) { - final zippedSupplement = await Directory(supplementPath).zipToTempFile( - name: 'ios_supplement', - ); + final zippedSupplement = await Directory( + supplementPath, + ).zipToTempFile(name: 'ios_supplement'); try { await codePushClient.createReleaseArtifact( appId: appId, @@ -820,9 +801,10 @@ aar artifact already exists, continuing...''', artifactPath: zippedAppFrameworkFile.path, arch: 'xcframework', platform: ReleasePlatform.ios, - hash: sha256 - .convert(await zippedAppFrameworkFile.readAsBytes()) - .toString(), + hash: + sha256 + .convert(await zippedAppFrameworkFile.readAsBytes()) + .toString(), canSideload: false, podfileLockHash: null, ); @@ -835,9 +817,9 @@ aar artifact already exists, continuing...''', } if (supplementPath != null) { - final zippedSupplement = await Directory(supplementPath).zipToTempFile( - name: 'ios_framework_supplement', - ); + final zippedSupplement = await Directory( + supplementPath, + ).zipToTempFile(name: 'ios_framework_supplement'); try { await codePushClient.createReleaseArtifact( appId: appId, @@ -948,14 +930,9 @@ aar artifact already exists, continuing...''', patchArtifactBundles: patchArtifactBundles, ); - final channel = await maybeGetChannel( - appId: appId, - name: track.channel, - ) ?? - await createChannel( - appId: appId, - name: track.channel, - ); + final channel = + await maybeGetChannel(appId: appId, name: track.channel) ?? + await createChannel(appId: appId, name: track.channel); await promotePatch(appId: appId, patchId: patch.id, channel: channel); diff --git a/packages/shorebird_cli/lib/src/code_signer.dart b/packages/shorebird_cli/lib/src/code_signer.dart index ab3ce631..f82c66f5 100644 --- a/packages/shorebird_cli/lib/src/code_signer.dart +++ b/packages/shorebird_cli/lib/src/code_signer.dart @@ -45,10 +45,11 @@ class CodeSigner { _pemBytes(pemFile: publicKeyPemFile, type: PemLabel.publicKey), ); - final publicKeySeq = ASN1Sequence() - ..add(ASN1Integer(publicKey.modulus)) - ..add(ASN1Integer(publicKey.exponent)) - ..encode(); + final publicKeySeq = + ASN1Sequence() + ..add(ASN1Integer(publicKey.modulus)) + ..add(ASN1Integer(publicKey.exponent)) + ..encode(); return base64.encode(publicKeySeq.encodedBytes!); } @@ -94,8 +95,9 @@ extension _RSAPublicKeyFromBytes on RSAPublicKey { final asn1Parser = ASN1Parser(Uint8List.fromList(bytes)); final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; final publicKeyBitString = topLevelSeq.elements![1] as ASN1BitString; - final publicKeyAsn = - ASN1Parser(publicKeyBitString.stringValues as Uint8List?); + final publicKeyAsn = ASN1Parser( + publicKeyBitString.stringValues as Uint8List?, + ); final publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence; final modulus = publicKeySeq.elements![0] as ASN1Integer; final exponent = publicKeySeq.elements![1] as ASN1Integer; diff --git a/packages/shorebird_cli/lib/src/commands/cache/clean_cache_command.dart b/packages/shorebird_cli/lib/src/commands/cache/clean_cache_command.dart index 147298db..b1c9ef51 100644 --- a/packages/shorebird_cli/lib/src/commands/cache/clean_cache_command.dart +++ b/packages/shorebird_cli/lib/src/commands/cache/clean_cache_command.dart @@ -31,9 +31,7 @@ class CleanCacheCommand extends ShorebirdCommand { await cache.clear(); } on FileSystemException catch (error) { final cachePath = Cache.shorebirdCacheDirectory.path; - progress.fail( - '''Failed to delete cache directory $cachePath: $error''', - ); + progress.fail('''Failed to delete cache directory $cachePath: $error'''); if (!platform.isWindows) { return ExitCode.software.code; @@ -45,12 +43,10 @@ class CleanCacheCommand extends ShorebirdCommand { ), ); - logger.info( - ''' + logger.info(''' This could be because a program is using a file in the cache directory. To find and stop such a program, see: ${lightCyan.wrap(superuserLink)} -''', - ); +'''); return ExitCode.software.code; } diff --git a/packages/shorebird_cli/lib/src/commands/doctor_command.dart b/packages/shorebird_cli/lib/src/commands/doctor_command.dart index 9079d300..fe973865 100644 --- a/packages/shorebird_cli/lib/src/commands/doctor_command.dart +++ b/packages/shorebird_cli/lib/src/commands/doctor_command.dart @@ -52,12 +52,10 @@ class DoctorCommand extends ShorebirdCommand { if (flutterVersion != null) { shorebirdFlutterPrefix.write(' $flutterVersion'); } - output.writeln( - ''' + output.writeln(''' Shorebird $packageVersion • git@github.com:shorebirdtech/shorebird.git $shorebirdFlutterPrefix • revision ${shorebirdEnv.flutterRevision} -Engine • revision ${shorebirdEnv.shorebirdEngineRevision}''', - ); +Engine • revision ${shorebirdEnv.shorebirdEngineRevision}'''); if (verbose) { final notDetected = red.wrap('not detected'); @@ -127,9 +125,7 @@ Android Toolchain 'GCP download speed test failed: ${error.message}', ); } on Exception catch (error) { - downloadProgress.fail( - 'GCP download speed test failed: $error', - ); + downloadProgress.fail('GCP download speed test failed: $error'); } logger.info(''); } diff --git a/packages/shorebird_cli/lib/src/commands/init_command.dart b/packages/shorebird_cli/lib/src/commands/init_command.dart index 5af9e33e..cf2bb830 100644 --- a/packages/shorebird_cli/lib/src/commands/init_command.dart +++ b/packages/shorebird_cli/lib/src/commands/init_command.dart @@ -30,10 +30,7 @@ class InitCommand extends ShorebirdCommand { help: 'Initialize the app even if a "shorebird.yaml" already exists.', negatable: false, ) - ..addOption( - 'display-name', - help: 'The display name of the app.', - ); + ..addOption('display-name', help: 'The display name of the app.'); } @override @@ -133,8 +130,9 @@ Please make sure you are running "shorebird init" from within your Flutter proje // don't care about which flavors are new. if (!force && newFlavors.isNotEmpty) { logger.info('New flavors detected: ${newFlavors.join(', ')}'); - final updateShorebirdYamlProgress = - logger.progress('Adding flavors to shorebird.yaml'); + final updateShorebirdYamlProgress = logger.progress( + 'Adding flavors to shorebird.yaml', + ); final AppMetadata existingApp; try { @@ -180,14 +178,16 @@ Please make sure you are running "shorebird init" from within your Flutter proje final needsConfirmation = !force && shorebirdEnv.canAcceptUserInput; final pubspecName = shorebirdEnv.getPubspecYaml()!.name; var displayName = results['display-name'] as String?; - displayName ??= needsConfirmation - ? logger.prompt( - '${lightGreen.wrap('?')} How should we refer to this app?', - defaultValue: pubspecName, - ) - : pubspecName; + displayName ??= + needsConfirmation + ? logger.prompt( + '${lightGreen.wrap('?')} How should we refer to this app?', + defaultValue: pubspecName, + ) + : pubspecName; final hasNoFlavors = productFlavors.isEmpty; - final hasSomeFlavors = productFlavors.isNotEmpty && + final hasSomeFlavors = + productFlavors.isNotEmpty && ((androidFlavors?.isEmpty ?? false) || (iosFlavors?.isEmpty ?? false)); diff --git a/packages/shorebird_cli/lib/src/commands/login_ci_command.dart b/packages/shorebird_cli/lib/src/commands/login_ci_command.dart index 85973daf..fdddb7fb 100644 --- a/packages/shorebird_cli/lib/src/commands/login_ci_command.dart +++ b/packages/shorebird_cli/lib/src/commands/login_ci_command.dart @@ -45,10 +45,8 @@ class LoginCiCommand extends ShorebirdCommand { ciToken = await auth.loginCI(provider, prompt: prompt); } on UserNotFoundException catch (error) { logger - ..err( - ''' -We could not find a Shorebird account for ${error.email}.''', - ) + ..err(''' +We could not find a Shorebird account for ${error.email}.''') ..info( '''If you have not yet created an account, go to "${link(uri: Uri.parse('https://console.shorebird.dev'))}" to create one. If you believe this is an error, please reach out to us via Discord, we're happy to help!''', ); diff --git a/packages/shorebird_cli/lib/src/commands/login_command.dart b/packages/shorebird_cli/lib/src/commands/login_command.dart index 1737019c..8579365f 100644 --- a/packages/shorebird_cli/lib/src/commands/login_command.dart +++ b/packages/shorebird_cli/lib/src/commands/login_command.dart @@ -53,10 +53,8 @@ class LoginCommand extends ShorebirdCommand { } on UserNotFoundException catch (error) { final consoleUri = Uri.https('console.shorebird.dev'); logger - ..err( - ''' -We could not find a Shorebird account for ${error.email}.''', - ) + ..err(''' +We could not find a Shorebird account for ${error.email}.''') ..info( """If you have not yet created an account, you can do so at "${link(uri: consoleUri)}". If you believe this is an error, please reach out to us via Discord, we're happy to help!""", ); diff --git a/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart index 5137efae..82737539 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart @@ -66,20 +66,20 @@ class AarPatcher extends Patcher { required ReleaseArtifact releaseArtifact, required File releaseArchive, required File patchArchive, - }) => - patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: patchArchive, - releaseArchive: releaseArchive, - archiveDiffer: const AndroidArchiveDiffer(), - allowAssetChanges: allowAssetDiffs, - allowNativeChanges: allowNativeDiffs, - ); + }) => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: patchArchive, + releaseArchive: releaseArchive, + archiveDiffer: const AndroidArchiveDiffer(), + allowAssetChanges: allowAssetDiffs, + allowNativeChanges: allowNativeDiffs, + ); @override Future buildPatchArtifact({String? releaseVersion}) async { final flutterVersionString = await shorebirdFlutter.getVersionAndRevision(); - final buildProgress = - logger.progress('Building patch with Flutter $flutterVersionString'); + final buildProgress = logger.progress( + 'Building patch with Flutter $flutterVersionString', + ); try { await artifactBuilder.buildAar( diff --git a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart index 35169641..7ee2db35 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart @@ -57,14 +57,13 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir required ReleaseArtifact releaseArtifact, required File releaseArchive, required File patchArchive, - }) => - patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: patchArchive, - releaseArchive: releaseArchive, - archiveDiffer: const AndroidArchiveDiffer(), - allowAssetChanges: allowAssetDiffs, - allowNativeChanges: allowNativeDiffs, - ); + }) => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: patchArchive, + releaseArchive: releaseArchive, + archiveDiffer: const AndroidArchiveDiffer(), + allowAssetChanges: allowAssetDiffs, + allowNativeChanges: allowNativeDiffs, + ); @override Future assertPreconditions() async { @@ -91,14 +90,16 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir logger.warn(updaterPatchErrorWarning); } - final buildProgress = logger - .detailProgress('Building patch with Flutter $flutterVersionString'); + final buildProgress = logger.detailProgress( + 'Building patch with Flutter $flutterVersionString', + ); try { aabFile = await artifactBuilder.buildAppBundle( flavor: flavor, target: target, - args: argResults.forwardedArgs + + args: + argResults.forwardedArgs + buildNameAndNumberArgsFromReleaseVersion(releaseVersion), base64PublicKey: argResults.encodedPublicKey, buildProgress: buildProgress, @@ -117,8 +118,7 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir if (patchArchsBuildDir == null) { logger ..err('Cannot find patch build artifacts.') - ..info( - ''' + ..info(''' Please run `shorebird cache clean` and try again. If the issue persists, please file a bug report at https://github.com/shorebirdtech/shorebird/issues/new. @@ -126,8 +126,7 @@ Looked in: - build/app/intermediates/stripped_native_libs/stripReleaseDebugSymbols/release/out/lib - build/app/intermediates/stripped_native_libs/strip{flavor}ReleaseDebugSymbols/{flavor}Release/out/lib - build/app/intermediates/stripped_native_libs/release/out/lib - - build/app/intermediates/stripped_native_libs/{flavor}Release/out/lib''', - ); + - build/app/intermediates/stripped_native_libs/{flavor}Release/out/lib'''); throw ProcessExit(ExitCode.software.code); } return aabFile; @@ -154,27 +153,25 @@ Looked in: // until we can provide a better solution. var artifactsDownloadCompleted = false; unawaited( - Future.delayed(downloadMessageTimeout).then( - (_) { - if (artifactsDownloadCompleted) { - return; - } - logger.info( - ''' + Future.delayed(downloadMessageTimeout).then((_) { + if (artifactsDownloadCompleted) { + return; + } + logger.info( + ''' It seems like your download is taking longer than expected. If you are on Windows, this is a known issue. Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/2532'))} for potential workarounds.''', - ); - }, - ), + ); + }), ); for (final (i, releaseArtifact) in releaseArtifacts.entries.indexed) { try { - final releaseArtifactFile = - await artifactManager.downloadWithProgressUpdates( - Uri.parse(releaseArtifact.value.url), - message: 'Downloading release artifact ${i + 1}/$numArtifacts', - ); + final releaseArtifactFile = await artifactManager + .downloadWithProgressUpdates( + Uri.parse(releaseArtifact.value.url), + message: 'Downloading release artifact ${i + 1}/$numArtifacts', + ); releaseArtifactPaths[releaseArtifact.key] = releaseArtifactFile.path; } on Exception { throw ProcessExit(ExitCode.software.code); @@ -208,12 +205,13 @@ Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebir final privateKeyFile = argResults.file( CommonArguments.privateKeyArg.name, ); - final hashSignature = privateKeyFile != null - ? codeSigner.sign( - message: hash, - privateKeyPemFile: privateKeyFile, - ) - : null; + final hashSignature = + privateKeyFile != null + ? codeSigner.sign( + message: hash, + privateKeyPemFile: privateKeyFile, + ) + : null; try { final diffPath = await artifactManager.createDiff( diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart index a7a94d2b..608dc091 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart @@ -43,28 +43,24 @@ class IosFrameworkPatcher extends Patcher { p.join(shorebirdEnv.buildDirectory.path, 'out.aot'); String get _patchClassTableLinkInfoFile => p.join( - shorebirdEnv.buildDirectory.path, - 'ios', - 'shorebird', - 'App.ct.link', - ); + shorebirdEnv.buildDirectory.path, + 'ios', + 'shorebird', + 'App.ct.link', + ); String get _patchClassTableLinkDebugInfoPath => p.join( - shorebirdEnv.buildDirectory.path, - 'ios', - 'shorebird', - 'App.class_table.json', - ); + shorebirdEnv.buildDirectory.path, + 'ios', + 'shorebird', + 'App.class_table.json', + ); - String get _vmcodeOutputPath => p.join( - shorebirdEnv.buildDirectory.path, - 'out.vmcode', - ); + String get _vmcodeOutputPath => + p.join(shorebirdEnv.buildDirectory.path, 'out.vmcode'); - String get _appDillCopyPath => p.join( - shorebirdEnv.buildDirectory.path, - 'app.dill', - ); + String get _appDillCopyPath => + p.join(shorebirdEnv.buildDirectory.path, 'app.dill'); @override String get primaryReleaseArtifactArch => 'xcframework'; @@ -109,14 +105,13 @@ class IosFrameworkPatcher extends Patcher { required ReleaseArtifact releaseArtifact, required File releaseArchive, required File patchArchive, - }) => - patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: patchArchive, - releaseArchive: releaseArchive, - archiveDiffer: const AppleArchiveDiffer(), - allowAssetChanges: allowAssetDiffs, - allowNativeChanges: allowNativeDiffs, - ); + }) => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: patchArchive, + releaseArchive: releaseArchive, + archiveDiffer: const AppleArchiveDiffer(), + allowAssetChanges: allowAssetDiffs, + allowNativeChanges: allowNativeDiffs, + ); @override Future buildPatchArtifact({String? releaseVersion}) async { @@ -208,20 +203,11 @@ class IosFrameworkPatcher extends Patcher { 'Extracted release artifact to $releaseXcframeworkPath', ); final releaseArtifactFile = File( - p.join( - releaseXcframeworkPath, - 'ios-arm64', - 'App.framework', - 'App', - ), + p.join(releaseXcframeworkPath, 'ios-arm64', 'App.framework', 'App'), ); final aotSnapshotFile = File( - p.join( - shorebirdEnv.getShorebirdProjectRoot()!.path, - 'build', - 'out.aot', - ), + p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'build', 'out.aot'), ); final useLinker = AotTools.usesLinker(shorebirdEnv.flutterRevision); if (useLinker) { @@ -240,9 +226,9 @@ class IosFrameworkPatcher extends Patcher { // Copy the patch's class table link info file to the build directory // so that it can be used to generate a patch. - File(_patchClassTableLinkInfoFile).copySync( - p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'), - ); + File( + _patchClassTableLinkInfoFile, + ).copySync(p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link')); File(_patchClassTableLinkDebugInfoPath).copySync( p.join(shorebirdEnv.buildDirectory.path, 'out.class_table.json'), ); @@ -311,11 +297,10 @@ class IosFrameworkPatcher extends Patcher { @override Future updatedCreatePatchMetadata( CreatePatchMetadata metadata, - ) async => - metadata.copyWith( - linkPercentage: lastBuildLinkPercentage, - environment: metadata.environment.copyWith( - xcodeVersion: await xcodeBuild.version(), - ), - ); + ) async => metadata.copyWith( + linkPercentage: lastBuildLinkPercentage, + environment: metadata.environment.copyWith( + xcodeVersion: await xcodeBuild.version(), + ), + ); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart index 769fe430..1e98f965 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart @@ -44,18 +44,18 @@ class IosPatcher extends Patcher { }); String get _patchClassTableLinkInfoPath => p.join( - shorebirdEnv.buildDirectory.path, - 'ios', - 'shorebird', - 'App.ct.link', - ); + shorebirdEnv.buildDirectory.path, + 'ios', + 'shorebird', + 'App.ct.link', + ); String get _patchClassTableLinkDebugInfoPath => p.join( - shorebirdEnv.buildDirectory.path, - 'ios', - 'shorebird', - 'App.class_table.json', - ); + shorebirdEnv.buildDirectory.path, + 'ios', + 'shorebird', + 'App.class_table.json', + ); String get _aotOutputPath => p.join(shorebirdEnv.buildDirectory.path, 'out.aot'); @@ -74,10 +74,10 @@ class IosPatcher extends Patcher { static List splitDebugInfoArgs(String? splitDebugInfoPath) { return splitDebugInfoPath != null ? [ - '--dwarf-stack-traces', - '--resolve-dwarf-paths', - '''--save-debugging-info=${saveDebuggingInfoPath(splitDebugInfoPath)}''', - ] + '--dwarf-stack-traces', + '--resolve-dwarf-paths', + '''--save-debugging-info=${saveDebuggingInfoPath(splitDebugInfoPath)}''', + ] : []; } @@ -126,15 +126,15 @@ class IosPatcher extends Patcher { // can be nondeterministic. So we still have some hope of alerting users of // unpatchable native changes, we compare the Podfile.lock hash between the // patch and the release. - final diffStatus = - await patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: patchArchive, - releaseArchive: releaseArchive, - archiveDiffer: const AppleArchiveDiffer(), - allowAssetChanges: allowAssetDiffs, - allowNativeChanges: allowNativeDiffs, - confirmNativeChanges: false, - ); + final diffStatus = await patchDiffChecker + .confirmUnpatchableDiffsIfNecessary( + localArchive: patchArchive, + releaseArchive: releaseArchive, + archiveDiffer: const AppleArchiveDiffer(), + allowAssetChanges: allowAssetDiffs, + allowNativeChanges: allowNativeDiffs, + confirmNativeChanges: false, + ); if (!diffStatus.hasNativeChanges) { return diffStatus; @@ -142,9 +142,10 @@ class IosPatcher extends Patcher { final String? podfileLockHash; if (shorebirdEnv.iosPodfileLockFile.existsSync()) { - podfileLockHash = sha256 - .convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync()) - .toString(); + podfileLockHash = + sha256 + .convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync()) + .toString(); } else { podfileLockHash = null; } @@ -175,18 +176,17 @@ This may indicate that the patch contains native changes, which cannot be applie Future buildPatchArtifact({String? releaseVersion}) async { try { final shouldCodesign = argResults['codesign'] == true; - final (flutterVersionAndRevision, flutterVersion) = await ( - shorebirdFlutter.getVersionAndRevision(), - shorebirdFlutter.getVersion(), - ).wait; + final (flutterVersionAndRevision, flutterVersion) = + await ( + shorebirdFlutter.getVersionAndRevision(), + shorebirdFlutter.getVersion(), + ).wait; if ((flutterVersion ?? minimumSupportedIosFlutterVersion) < minimumSupportedIosFlutterVersion) { - logger.err( - ''' + logger.err(''' iOS patches are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.software.code); } @@ -201,7 +201,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', codesign: shouldCodesign, flavor: flavor, target: target, - args: argResults.forwardedArgs + + args: + argResults.forwardedArgs + buildNameAndNumberArgsFromReleaseVersion(releaseVersion), base64PublicKey: argResults.encodedPublicKey, buildProgress: buildProgress, @@ -299,12 +300,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', throw ProcessExit(ExitCode.software.code); } final releaseArtifactFile = File( - p.join( - appDirectory.path, - 'Frameworks', - 'App.framework', - 'App', - ), + p.join(appDirectory.path, 'Frameworks', 'App.framework', 'App'), ); final useLinker = AotTools.usesLinker(shorebirdEnv.flutterRevision); @@ -324,9 +320,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', // Copy the patch's class table link info file to the build directory // so that it can be used to generate a patch. - File(_patchClassTableLinkInfoPath).copySync( - p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'), - ); + File( + _patchClassTableLinkInfoPath, + ).copySync(p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link')); File(_patchClassTableLinkDebugInfoPath).copySync( p.join(shorebirdEnv.buildDirectory.path, 'out.class_table.json'), ); @@ -383,12 +379,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', final patchFileSize = patchFile.statSync().size; final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name); final hash = sha256.convert(patchBuildFile.readAsBytesSync()).toString(); - final hashSignature = privateKeyFile != null - ? codeSigner.sign( - message: hash, - privateKeyPemFile: privateKeyFile, - ) - : null; + final hashSignature = + privateKeyFile != null + ? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile) + : null; return { Arch.arm64: PatchArtifactBundle( @@ -429,11 +423,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', @override Future updatedCreatePatchMetadata( CreatePatchMetadata metadata, - ) async => - metadata.copyWith( - linkPercentage: lastBuildLinkPercentage, - environment: metadata.environment.copyWith( - xcodeVersion: await xcodeBuild.version(), - ), - ); + ) async => metadata.copyWith( + linkPercentage: lastBuildLinkPercentage, + environment: metadata.environment.copyWith( + xcodeVersion: await xcodeBuild.version(), + ), + ); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart index 8abd211e..0c11cdce 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart @@ -99,15 +99,11 @@ class LinuxPatcher extends Patcher { // build/linux/x64/release/bundle final appSoPath = p.join(tempDir.path, 'lib', 'libapp.so'); - final privateKeyFile = argResults.file( - CommonArguments.privateKeyArg.name, - ); - final hashSignature = privateKeyFile != null - ? codeSigner.sign( - message: hash, - privateKeyPemFile: privateKeyFile, - ) - : null; + final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name); + final hashSignature = + privateKeyFile != null + ? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile) + : null; final String diffPath; try { diff --git a/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart index bb0df8ac..3affe490 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart @@ -42,21 +42,15 @@ class MacosPatcher extends Patcher { }); // The elf snapshot built for Apple Silicon macs. - String get _arm64AotOutputPath => p.join( - shorebirdEnv.buildDirectory.path, - 'out.arm64.aot', - ); + String get _arm64AotOutputPath => + p.join(shorebirdEnv.buildDirectory.path, 'out.arm64.aot'); // The elf snapshot built for Intel macs. - String get _x64AotOutputPath => p.join( - shorebirdEnv.buildDirectory.path, - 'out.x64.aot', - ); + String get _x64AotOutputPath => + p.join(shorebirdEnv.buildDirectory.path, 'out.x64.aot'); - String get _appDillCopyPath => p.join( - shorebirdEnv.buildDirectory.path, - 'app.dill', - ); + String get _appDillCopyPath => + p.join(shorebirdEnv.buildDirectory.path, 'app.dill'); @override ReleaseType get releaseType => ReleaseType.macos; @@ -93,15 +87,15 @@ class MacosPatcher extends Patcher { // can be nondeterministic. So we still have some hope of alerting users of // unpatchable native changes, we compare the Podfile.lock hash between the // patch and the release. - final diffStatus = - await patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: patchArchive, - releaseArchive: releaseArchive, - archiveDiffer: const AppleArchiveDiffer(), - allowAssetChanges: allowAssetDiffs, - allowNativeChanges: allowNativeDiffs, - confirmNativeChanges: false, - ); + final diffStatus = await patchDiffChecker + .confirmUnpatchableDiffsIfNecessary( + localArchive: patchArchive, + releaseArchive: releaseArchive, + archiveDiffer: const AppleArchiveDiffer(), + allowAssetChanges: allowAssetDiffs, + allowNativeChanges: allowNativeDiffs, + confirmNativeChanges: false, + ); if (!diffStatus.hasNativeChanges) { return diffStatus; @@ -109,9 +103,10 @@ class MacosPatcher extends Patcher { final String? podfileLockHash; if (shorebirdEnv.macosPodfileLockFile.existsSync()) { - podfileLockHash = sha256 - .convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync()) - .toString(); + podfileLockHash = + sha256 + .convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync()) + .toString(); } else { podfileLockHash = null; } @@ -141,18 +136,17 @@ This may indicate that the patch contains native changes, which cannot be applie @override Future buildPatchArtifact({String? releaseVersion}) async { try { - final (flutterVersionAndRevision, flutterVersion) = await ( - shorebirdFlutter.getVersionAndRevision(), - shorebirdFlutter.getVersion(), - ).wait; + final (flutterVersionAndRevision, flutterVersion) = + await ( + shorebirdFlutter.getVersionAndRevision(), + shorebirdFlutter.getVersion(), + ).wait; if ((flutterVersion ?? minimumSupportedMacosFlutterVersion) < minimumSupportedMacosFlutterVersion) { - logger.err( - ''' + logger.err(''' macOS patches are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.software.code); } @@ -167,7 +161,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', codesign: codesign, flavor: flavor, target: target, - args: argResults.forwardedArgs + + args: + argResults.forwardedArgs + buildNameAndNumberArgsFromReleaseVersion(releaseVersion), base64PublicKey: argResults.encodedPublicKey, buildProgress: buildProgress, @@ -244,17 +239,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', final patchFile = File(patchFilePath); final patchFileSize = patchFile.statSync().size; final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name); - final hash = sha256 - .convert( - patchArtifact.readAsBytesSync(), - ) - .toString(); - final hashSignature = privateKeyFile != null - ? codeSigner.sign( - message: hash, - privateKeyPemFile: privateKeyFile, - ) - : null; + final hash = sha256.convert(patchArtifact.readAsBytesSync()).toString(); + final hashSignature = + privateKeyFile != null + ? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile) + : null; return PatchArtifactBundle( arch: arch.arch, @@ -303,10 +292,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', ); createDiffProgress.complete(); - return { - Arch.x86_64: x64Bundle, - Arch.arm64: arm64Bundle, - }; + return {Arch.x86_64: x64Bundle, Arch.arm64: arm64Bundle}; } @override @@ -337,10 +323,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', @override Future updatedCreatePatchMetadata( CreatePatchMetadata metadata, - ) async => - metadata.copyWith( - environment: metadata.environment.copyWith( - xcodeVersion: await xcodeBuild.version(), - ), - ); + ) async => metadata.copyWith( + environment: metadata.environment.copyWith( + xcodeVersion: await xcodeBuild.version(), + ), + ); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart index 2c24450a..ee796bf5 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart @@ -36,9 +36,7 @@ typedef ResolvePatcher = Patcher Function(ReleaseType releaseType); /// {@endtemplate} class PatchCommand extends ShorebirdCommand { /// {@macro patch_command} - PatchCommand({ - ResolvePatcher? resolvePatcher, - }) { + PatchCommand({ResolvePatcher? resolvePatcher}) { _resolvePatcher = resolvePatcher ?? getPatcher; argParser ..addMultiOption( @@ -219,8 +217,9 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl return ExitCode.usage.code; } - final patcherFutures = - results.releaseTypes.map(_resolvePatcher).map(createPatch); + final patcherFutures = results.releaseTypes + .map(_resolvePatcher) + .map(createPatch); for (final patcherFuture in patcherFutures) { await patcherFuture; @@ -360,20 +359,23 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl final supplementalArtifact = patcher.supplementaryReleaseArtifactArch != null ? await codePushClientWrapper.maybeGetReleaseArtifact( - appId: appId, - releaseId: release.id, - arch: patcher.supplementaryReleaseArtifactArch!, - platform: releasePlatform, - ) + appId: appId, + releaseId: release.id, + arch: patcher.supplementaryReleaseArtifactArch!, + platform: releasePlatform, + ) : null; final releaseArchive = await downloadReleaseArtifact( releaseArtifact: releaseArtifact, ); - final supplementArchive = supplementalArtifact != null - ? await downloadReleaseArtifact(releaseArtifact: supplementalArtifact) - : null; + final supplementArchive = + supplementalArtifact != null + ? await downloadReleaseArtifact( + releaseArtifact: supplementalArtifact, + ) + : null; final releaseFlutterShorebirdEnv = shorebirdEnv.copyWith( flutterRevisionOverride: release.flutterRevision, @@ -444,17 +446,13 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl artifacts: patchArtifactBundles, ); }, - values: { - shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv), - }, + values: {shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv)}, ); } /// Prompts the user for the specific release to patch. Future promptForRelease(ReleasePlatform platform) async { - final releases = await codePushClientWrapper.getReleases( - appId: appId, - ); + final releases = await codePushClientWrapper.getReleases(appId: appId); final releasesForPlatform = releases.where( (release) => release.platformStatuses.keys.contains(platform), @@ -537,13 +535,14 @@ Please re-run the release command for this version or create a new release.'''); final size = formatBytes(patchArtifactBundles[arch]!.size); return '${arch.name} ($size)'; }); - final trackSummary = (() { - return switch (track) { - DeploymentTrack.staging => '🟠 Track: ${lightCyan.wrap('Staging')}', - DeploymentTrack.beta => '🔵 Track: ${lightCyan.wrap('Beta')}', - DeploymentTrack.stable => '🟢 Track: ${lightCyan.wrap('Stable')}', - }; - })(); + final trackSummary = + (() { + return switch (track) { + DeploymentTrack.staging => '🟠 Track: ${lightCyan.wrap('Staging')}', + DeploymentTrack.beta => '🔵 Track: ${lightCyan.wrap('Beta')}', + DeploymentTrack.stable => '🟢 Track: ${lightCyan.wrap('Stable')}', + }; + })(); final linkPercentage = patcher.linkPercentage; final minLinkPercentage = int.parse( @@ -567,14 +566,12 @@ Please re-run the release command for this version or create a new release.'''); '''🔍 Debug Info: ${lightCyan.wrap(Patcher.debugInfoFile.path)}''', ]; - logger.info( - ''' + logger.info(''' ${styleBold.wrap(lightGreen.wrap('🚀 Ready to publish a new patch!'))} ${summary.join('\n')} -''', - ); +'''); if (shorebirdEnv.canAcceptUserInput && !noConfirm) { final confirm = logger.confirm('Would you like to continue?'); diff --git a/packages/shorebird_cli/lib/src/commands/patch/patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/patcher.dart index bf9b3a9c..1db6376d 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patcher.dart @@ -188,9 +188,6 @@ More info: ${troubleshootingUrl.toLink()}. } final parts = releaseVersion.split('+'); - return [ - '--build-name=${parts[0]}', - '--build-number=${parts[1]}', - ]; + return ['--build-name=${parts[0]}', '--build-number=${parts[1]}']; } } diff --git a/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart index 4cb4c1f1..0f23675d 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart @@ -72,9 +72,7 @@ class WindowsPatcher extends Patcher { } @override - Future buildPatchArtifact({ - String? releaseVersion, - }) async { + Future buildPatchArtifact({String? releaseVersion}) async { final flutterVersionString = await shorebirdFlutter.getVersionAndRevision(); final buildAppBundleProgress = logger.detailProgress( @@ -123,15 +121,11 @@ class WindowsPatcher extends Patcher { // build/windows/x64/runner/Release final appSoPath = p.join(tempDir.path, 'data', 'app.so'); - final privateKeyFile = argResults.file( - CommonArguments.privateKeyArg.name, - ); - final hashSignature = privateKeyFile != null - ? codeSigner.sign( - message: hash, - privateKeyPemFile: privateKeyFile, - ) - : null; + final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name); + final hashSignature = + privateKeyFile != null + ? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile) + : null; final String diffPath; try { @@ -164,10 +158,9 @@ class WindowsPatcher extends Patcher { zipFile: artifact, outputDirectory: outputDirectory, ); - final exeFile = outputDirectory - .listSync() - .whereType() - .firstWhere((file) => p.extension(file.path) == '.exe'); + final exeFile = outputDirectory.listSync().whereType().firstWhere( + (file) => p.extension(file.path) == '.exe', + ); return powershell.getExeVersionString(exeFile); } } diff --git a/packages/shorebird_cli/lib/src/commands/preview_command.dart b/packages/shorebird_cli/lib/src/commands/preview_command.dart index 2f508837..78a43c66 100644 --- a/packages/shorebird_cli/lib/src/commands/preview_command.dart +++ b/packages/shorebird_cli/lib/src/commands/preview_command.dart @@ -165,24 +165,27 @@ This is only applicable when previewing Android releases.''', // // With these two lists, we can now determine if a platform is previewable // or not, by making a difference between the two lists. - final (allReleases, sideloadableReleases) = await ( - codePushClientWrapper.getReleases(appId: appId), - codePushClientWrapper.getReleases( - appId: appId, - sideloadableOnly: true, - ) - ).wait; + final (allReleases, sideloadableReleases) = + await ( + codePushClientWrapper.getReleases(appId: appId), + codePushClientWrapper.getReleases( + appId: appId, + sideloadableOnly: true, + ), + ).wait; - final maybePlatform = results['platform'] != null - ? ReleasePlatform.values.byName(results['platform'] as String) - : null; - final platformReleases = sideloadableReleases - .where( - (r) => - maybePlatform == null || - r.activePlatforms.contains(maybePlatform), - ) - .toList(); + final maybePlatform = + results['platform'] != null + ? ReleasePlatform.values.byName(results['platform'] as String) + : null; + final platformReleases = + sideloadableReleases + .where( + (r) => + maybePlatform == null || + r.activePlatforms.contains(maybePlatform), + ) + .toList(); if (platformReleases.isEmpty) { if (maybePlatform != null) { @@ -195,7 +198,8 @@ This is only applicable when previewing Android releases.''', return ExitCode.usage.code; } - final releaseVersion = results['release-version'] as String? ?? + final releaseVersion = + results['release-version'] as String? ?? await promptForReleaseVersion(platformReleases); final release = platformReleases.firstWhereOrNull( @@ -207,13 +211,15 @@ This is only applicable when previewing Android releases.''', return ExitCode.usage.code; } - final availablePlatforms = release.activePlatforms - .where((p) => supportedReleasePlatforms.contains(p)) - .toList(); + final availablePlatforms = + release.activePlatforms + .where((p) => supportedReleasePlatforms.contains(p)) + .toList(); if (availablePlatforms.isEmpty) { - final activePlatformsString = - release.activePlatforms.map((p) => p.displayName).join(', '); + final activePlatformsString = release.activePlatforms + .map((p) => p.displayName) + .join(', '); logger.err( '''This release can only be previewed on platforms that support $activePlatformsString''', ); @@ -248,32 +254,32 @@ This is only applicable when previewing Android releases.''', return switch (releasePlatform) { ReleasePlatform.android => installAndLaunchAndroid( - appId: appId, - release: release, - deviceId: deviceId, - track: track, - ), + appId: appId, + release: release, + deviceId: deviceId, + track: track, + ), ReleasePlatform.ios => installAndLaunchIos( - appId: appId, - release: release, - deviceId: deviceId, - track: track, - ), + appId: appId, + release: release, + deviceId: deviceId, + track: track, + ), ReleasePlatform.linux => installAndLaunchLinux( - appId: appId, - release: release, - track: track, - ), + appId: appId, + release: release, + track: track, + ), ReleasePlatform.macos => installAndLaunchMacos( - appId: appId, - release: release, - track: track, - ), + appId: appId, + release: release, + track: track, + ), ReleasePlatform.windows => installAndLaunchWindows( - appId: appId, - release: release, - track: track, - ), + appId: appId, + release: release, + track: track, + ), }; } @@ -447,10 +453,9 @@ This is only applicable when previewing Android releases.''', channel: track.name, ); - final exeFile = appDirectory - .listSync() - .whereType() - .firstWhere((file) => file.path.endsWith('.exe')); + final exeFile = appDirectory.listSync().whereType().firstWhere( + (file) => file.path.endsWith('.exe'), + ); final proc = await process.start(exeFile.path, []); proc.stdout.listen((log) => logger.info(utf8.decode(log))); @@ -540,17 +545,14 @@ This is only applicable when previewing Android releases.''', return line.trim().replaceFirst(prefixRegex, '').trim(); } - logs.listen( - (log) { - final logLine = utf8.decode(log); - if (logFilters.any((filter) => filter.hasMatch(logLine))) { - return; - } + logs.listen((log) { + final logLine = utf8.decode(log); + if (logFilters.any((filter) => filter.hasMatch(logLine))) { + return; + } - logger.info(removeLogPrefix(logLine)); - }, - onDone: completer.complete, - ); + logger.info(removeLogPrefix(logLine)); + }, onDone: completer.complete); return completer.future.then((_) => ExitCode.success.code); } @@ -798,9 +800,10 @@ This is only applicable when previewing Android releases.''', } final shouldUseDeviceCtl = deviceForLaunch != null; - final progressCompleteMessage = deviceForLaunch != null - ? 'Using device ${deviceForLaunch.name}' - : '''No iOS 17+ device found, looking for devices running iOS 16 or lower'''; + final progressCompleteMessage = + deviceForLaunch != null + ? 'Using device ${deviceForLaunch.name}' + : '''No iOS 17+ device found, looking for devices running iOS 16 or lower'''; deviceLocateProgress.complete(progressCompleteMessage); final int installExitCode; @@ -850,12 +853,7 @@ This is only applicable when previewing Android releases.''', required String channel, }) async { final shorebirdYamlFile = File( - p.join( - appDirectory.path, - 'data', - 'flutter_assets', - 'shorebird.yaml', - ), + p.join(appDirectory.path, 'data', 'flutter_assets', 'shorebird.yaml'), ); await _maybeSetChannelInShorebirdYaml( @@ -979,12 +977,7 @@ This is only applicable when previewing Android releases.''', required Directory bundleDirectory, }) async { final shorebirdYamlFile = File( - p.join( - bundleDirectory.path, - 'data', - 'flutter_assets', - 'shorebird.yaml', - ), + p.join(bundleDirectory.path, 'data', 'flutter_assets', 'shorebird.yaml'), ); await _maybeSetChannelInShorebirdYaml( @@ -1027,10 +1020,11 @@ This is only applicable when previewing Android releases.''', /// that can be previewed). extension Previewable on Release { /// Returns the platforms that can be previewed. - List get activePlatforms => platformStatuses.entries - .where((e) => e.value == ReleaseStatus.active) - .map((e) => e.key) - .toList(); + List get activePlatforms => + platformStatuses.entries + .where((e) => e.value == ReleaseStatus.active) + .map((e) => e.key) + .toList(); } /// Given two [Release]s, one with all the platforms, previewable or not, diff --git a/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart index 1bd3afcb..32f9b182 100644 --- a/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart @@ -33,12 +33,14 @@ class AarReleaser extends Releaser { String get buildNumber => argResults['build-number'] as String; /// The architectures to build the aar for. - Set get architectures => (argResults['target-platform'] as List) - .map( - (platform) => AndroidArch.availableAndroidArchs - .firstWhere((arch) => arch.targetPlatformCliArg == platform), - ) - .toSet(); + Set get architectures => + (argResults['target-platform'] as List) + .map( + (platform) => AndroidArch.availableAndroidArchs.firstWhere( + (arch) => arch.targetPlatformCliArg == platform, + ), + ) + .toSet(); @override ReleaseType get releaseType => ReleaseType.aar; @@ -92,10 +94,7 @@ class AarReleaser extends Releaser { final targetLibraryDirectory = Directory( p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'release'), ); - await copyPath( - sourceLibraryDirectory.path, - targetLibraryDirectory.path, - ); + await copyPath(sourceLibraryDirectory.path, targetLibraryDirectory.path); return targetLibraryDirectory; } diff --git a/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart index 6d115530..b322b22d 100644 --- a/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart @@ -34,12 +34,14 @@ class AndroidReleaser extends Releaser { String get artifactDisplayName => 'Android app bundle'; /// The architectures to build for. - Set get architectures => (argResults['target-platform'] as List) - .map( - (platform) => AndroidArch.availableAndroidArchs - .firstWhere((arch) => arch.targetPlatformCliArg == platform), - ) - .toSet(); + Set get architectures => + (argResults['target-platform'] as List) + .map( + (platform) => AndroidArch.availableAndroidArchs.firstWhere( + (arch) => arch.targetPlatformCliArg == platform, + ), + ) + .toSet(); /// Whether to generate an APK in addition to the AAB. late bool generateApk = argResults['artifact'] as String == 'apk'; @@ -93,12 +95,14 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec Future buildReleaseArtifacts({ DetailProgress? progress, }) async { - final architectures = (argResults['target-platform'] as List) - .map( - (platform) => AndroidArch.availableAndroidArchs - .firstWhere((arch) => arch.targetPlatformCliArg == platform), - ) - .toSet(); + final architectures = + (argResults['target-platform'] as List) + .map( + (platform) => AndroidArch.availableAndroidArchs.firstWhere( + (arch) => arch.targetPlatformCliArg == platform, + ), + ) + .toSet(); final base64PublicKey = argResults.encodedPublicKey; final aab = await artifactBuilder.buildAppBundle( @@ -128,15 +132,14 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec Future getReleaseVersion({ required FileSystemEntity releaseArtifactRoot, }) async { - final releaseVersionProgress = - logger.progress('Determining release version'); + final releaseVersionProgress = logger.progress( + 'Determining release version', + ); final String releaseVersion; try { - releaseVersion = - await shorebirdAndroidArtifacts.extractReleaseVersionFromAppBundle( - releaseArtifactRoot.path, - ); + releaseVersion = await shorebirdAndroidArtifacts + .extractReleaseVersionFromAppBundle(releaseArtifactRoot.path); releaseVersionProgress.complete('Release version: $releaseVersion'); } on Exception catch (error) { releaseVersionProgress.fail('$error'); @@ -169,8 +172,7 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec @override Future updatedReleaseMetadata( UpdateReleaseMetadata metadata, - ) async => - metadata.copyWith(generatedApks: generateApk); + ) async => metadata.copyWith(generatedApks: generateApk); @override String get postReleaseInstructions { @@ -185,13 +187,14 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec project: projectRoot, flavor: flavor, ); - apkText = generateApk - ? ''' + apkText = + generateApk + ? ''' Or distribute the apk: ${lightCyan.wrap(apkFile.path)} ''' - : ''; + : ''; } else { apkText = ''; } diff --git a/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart index a3d701eb..9e1ca768 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart @@ -35,11 +35,8 @@ class IosFrameworkReleaser extends Releaser { /// The directory where the release artifacts are stored. Directory get releaseDirectory => Directory( - p.join( - shorebirdEnv.getShorebirdProjectRoot()!.path, - 'release', - ), - ); + p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'release'), + ); @override bool get requiresReleaseVersionArg => true; @@ -73,23 +70,20 @@ class IosFrameworkReleaser extends Releaser { final flutterVersionArg = argResults['flutter-version'] as String?; if (flutterVersionArg != null) { - final version = - await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg); + final version = await shorebirdFlutter.resolveFlutterVersion( + flutterVersionArg, + ); if (version != null && version < minimumSupportedIosFlutterVersion) { - logger.err( - ''' + logger.err(''' iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.usage.code); } } } @override - Future buildReleaseArtifacts({ - Progress? progress, - }) async { + Future buildReleaseArtifacts({Progress? progress}) async { // Delete the Shorebird supplement directory if it exists. // This is to ensure that we don't accidentally upload stale artifacts // when building with older versions of Flutter. @@ -110,23 +104,14 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', if (targetLibraryDirectory.existsSync()) { targetLibraryDirectory.deleteSync(recursive: true); } - await copyPath( - sourceLibraryDirectory.path, - targetLibraryDirectory.path, - ); + await copyPath(sourceLibraryDirectory.path, targetLibraryDirectory.path); // Rename Flutter.xcframework to ShorebirdFlutter.xcframework to avoid // Xcode warning users about the .xcframework signature changing. Directory( - p.join( - targetLibraryDirectory.path, - 'Flutter.xcframework', - ), + p.join(targetLibraryDirectory.path, 'Flutter.xcframework'), ).renameSync( - p.join( - targetLibraryDirectory.path, - 'ShorebirdFlutter.xcframework', - ), + p.join(targetLibraryDirectory.path, 'ShorebirdFlutter.xcframework'), ); return targetLibraryDirectory; @@ -155,12 +140,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', @override Future updatedReleaseMetadata( UpdateReleaseMetadata metadata, - ) async => - metadata.copyWith( - environment: metadata.environment.copyWith( - xcodeVersion: await xcodeBuild.version(), - ), - ); + ) async => metadata.copyWith( + environment: metadata.environment.copyWith( + xcodeVersion: await xcodeBuild.version(), + ), + ); @override String get postReleaseInstructions { diff --git a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart index 09806d4e..0245b428 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart @@ -82,14 +82,13 @@ To change the version of this release, change your app's version in your pubspec final flutterVersionArg = argResults['flutter-version'] as String?; if (flutterVersionArg != null) { - final version = - await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg); + final version = await shorebirdFlutter.resolveFlutterVersion( + flutterVersionArg, + ); if (version != null && version < minimumSupportedIosFlutterVersion) { - logger.err( - ''' + logger.err(''' iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.usage.code); } } @@ -173,9 +172,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', final xcarchiveDirectory = artifactManager.getXcarchiveDirectory()!; final String? podfileLockHash; if (shorebirdEnv.iosPodfileLockFile.existsSync()) { - podfileLockHash = sha256 - .convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync()) - .toString(); + podfileLockHash = + sha256 + .convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync()) + .toString(); } else { podfileLockHash = null; } @@ -183,9 +183,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', appId: appId, releaseId: release.id, xcarchivePath: xcarchiveDirectory.path, - runnerPath: artifactManager - .getIosAppDirectory(xcarchiveDirectory: xcarchiveDirectory)! - .path, + runnerPath: + artifactManager + .getIosAppDirectory(xcarchiveDirectory: xcarchiveDirectory)! + .path, isCodesigned: codesign, podfileLockHash: podfileLockHash, supplementPath: artifactManager.getIosReleaseSupplementDirectory()?.path, @@ -195,12 +196,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', @override Future updatedReleaseMetadata( UpdateReleaseMetadata metadata, - ) async => - metadata.copyWith( - environment: metadata.environment.copyWith( - xcodeVersion: await xcodeBuild.version(), - ), - ); + ) async => metadata.copyWith( + environment: metadata.environment.copyWith( + xcodeVersion: await xcodeBuild.version(), + ), + ); @override String get postReleaseInstructions { diff --git a/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart index 9ad8e87d..85546a67 100644 --- a/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart @@ -61,14 +61,13 @@ To change the version of this release, change your app's version in your pubspec } final flutterVersionArg = argResults['flutter-version'] as String?; if (flutterVersionArg != null) { - final version = - await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg); + final version = await shorebirdFlutter.resolveFlutterVersion( + flutterVersionArg, + ); if (version != null && version < minimumSupportedLinuxFlutterVersion) { - logger.err( - ''' + logger.err(''' Linux releases are not supported with Flutter versions older than $minimumSupportedLinuxFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.usage.code); } } @@ -88,10 +87,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', @override Future getReleaseVersion({ required FileSystemEntity releaseArtifactRoot, - }) async => - linux.versionFromLinuxBundle( - bundleRoot: releaseArtifactRoot as Directory, - ); + }) async => linux.versionFromLinuxBundle( + bundleRoot: releaseArtifactRoot as Directory, + ); @override String get postReleaseInstructions => ''' @@ -103,10 +101,9 @@ Linux release created at ${artifactManager.linuxBundleDirectory.path}. Future uploadReleaseArtifacts({ required Release release, required String appId, - }) => - codePushClientWrapper.createLinuxReleaseArtifacts( - appId: appId, - releaseId: release.id, - bundle: artifactManager.linuxBundleDirectory, - ); + }) => codePushClientWrapper.createLinuxReleaseArtifacts( + appId: appId, + releaseId: release.id, + bundle: artifactManager.linuxBundleDirectory, + ); } diff --git a/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart index 20be24f8..c9909510 100644 --- a/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart @@ -83,14 +83,13 @@ To change the version of this release, change your app's version in your pubspec final flutterVersionArg = argResults['flutter-version'] as String?; if (flutterVersionArg != null) { - final version = - await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg); + final version = await shorebirdFlutter.resolveFlutterVersion( + flutterVersionArg, + ); if (version != null && version < minimumSupportedMacosFlutterVersion) { - logger.err( - ''' + logger.err(''' macOS releases are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.usage.code); } } @@ -163,9 +162,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', final String? podfileLockHash; if (shorebirdEnv.macosPodfileLockFile.existsSync()) { - podfileLockHash = sha256 - .convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync()) - .toString(); + podfileLockHash = + sha256 + .convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync()) + .toString(); } else { podfileLockHash = null; } @@ -182,12 +182,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', @override Future updatedReleaseMetadata( UpdateReleaseMetadata metadata, - ) async => - metadata.copyWith( - environment: metadata.environment.copyWith( - xcodeVersion: await xcodeBuild.version(), - ), - ); + ) async => metadata.copyWith( + environment: metadata.environment.copyWith( + xcodeVersion: await xcodeBuild.version(), + ), + ); @override String get postReleaseInstructions => ''' diff --git a/packages/shorebird_cli/lib/src/commands/release/release_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_command.dart index 81a19218..9701d2e5 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_command.dart @@ -114,7 +114,8 @@ class ReleaseCommand extends ShorebirdCommand { ) ..addFlag( 'split-per-abi', - help: 'Whether to split the APKs per ABIs (Android only). ' + help: + 'Whether to split the APKs per ABIs (Android only). ' 'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split', hide: true, negatable: false, @@ -164,8 +165,9 @@ of the iOS app that is using this module. (aar and ios-framework only)''', return ExitCode.usage.code; } - final releaserFutures = - results.releaseTypes.map(_resolveReleaser).map(createRelease); + final releaserFutures = results.releaseTypes + .map(_resolveReleaser) + .map(createRelease); for (final future in releaserFutures) { await future; @@ -179,11 +181,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''', Releaser getReleaser(ReleaseType releaseType) { switch (releaseType) { case ReleaseType.aar: - return AarReleaser( - argResults: results, - flavor: flavor, - target: target, - ); + return AarReleaser(argResults: results, flavor: flavor, target: target); case ReleaseType.android: return AndroidReleaser( argResults: results, @@ -191,11 +189,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''', target: target, ); case ReleaseType.ios: - return IosReleaser( - argResults: results, - flavor: flavor, - target: target, - ); + return IosReleaser(argResults: results, flavor: flavor, target: target); case ReleaseType.iosFramework: return IosFrameworkReleaser( argResults: results, @@ -269,98 +263,92 @@ of the iOS app that is using this module. (aar and ios-framework only)''', final releaseFlutterShorebirdEnv = shorebirdEnv.copyWith( flutterRevisionOverride: targetFlutterRevision, ); - return await runScoped( - () async { - await cache.updateAll(); + return await runScoped(() async { + await cache.updateAll(); - final flutterVersionString = - await shorebirdFlutter.getVersionAndRevision(); - final buildProgress = logger.detailProgress( - '''Building ${releaser.artifactDisplayName} with Flutter $flutterVersionString''', + final flutterVersionString = + await shorebirdFlutter.getVersionAndRevision(); + final buildProgress = logger.detailProgress( + '''Building ${releaser.artifactDisplayName} with Flutter $flutterVersionString''', + ); + final FileSystemEntity releaseArtifact; + try { + releaseArtifact = await releaser.buildReleaseArtifacts( + progress: buildProgress, ); - final FileSystemEntity releaseArtifact; - try { - releaseArtifact = await releaser.buildReleaseArtifacts( - progress: buildProgress, - ); - buildProgress.complete(); - } on ArtifactBuildException catch (e) { - buildProgress.fail(e.message); - logger - ..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}') - ..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}'); - if (!e.flutterError.isNullOrEmpty) { - logger.err(e.flutterError); - } - if (!e.fixRecommendation.isNullOrEmpty) { - logger.info(e.fixRecommendation); - } - if (e.fixRecommendation.isNullOrEmpty && - e.flutterError.isNullOrEmpty) { - // If we have no fix recommendation or were unable to parse a - // flutter error, fall back to printing the raw stderr. - logger.info(e.stderr.join(Platform.lineTerminator)); - } - - throw ProcessExit(ExitCode.software.code); - } on Exception catch (e) { - buildProgress.fail('Failed to build release artifacts: $e'); - throw ProcessExit(ExitCode.software.code); - } - - final releaseVersion = await releaser.getReleaseVersion( - releaseArtifactRoot: releaseArtifact, - ); - - // Ensure we can create a release from what we've built. - await ensureVersionIsReleasable( - version: releaseVersion, - flutterRevision: targetFlutterRevision, - releasePlatform: releaser.releaseType.releasePlatform, - ); - - final dryRun = results['dry-run'] == true; - if (dryRun) { - logger - ..info('No issues detected.') - ..info('The server may enforce additional checks.'); - throw ProcessExit(ExitCode.success.code); - } - - // Ask the user to proceed (this is skipped when running via CI). - await confirmCreateRelease( - app: app, - releaseVersion: releaseVersion, - flutterVersion: targetFlutterRevision, - releasePlatform: releaser.releaseType.releasePlatform, - ); - - final release = await getOrCreateRelease( - version: releaseVersion, - releasePlatform: releaser.releaseType.releasePlatform, - ); - await prepareRelease(release: release, releaser: releaser); - await releaser.uploadReleaseArtifacts(release: release, appId: appId); - await finalizeRelease(release: release, releaser: releaser); - + buildProgress.complete(); + } on ArtifactBuildException catch (e) { + buildProgress.fail(e.message); logger - ..success(''' + ..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}') + ..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}'); + if (!e.flutterError.isNullOrEmpty) { + logger.err(e.flutterError); + } + if (!e.fixRecommendation.isNullOrEmpty) { + logger.info(e.fixRecommendation); + } + if (e.fixRecommendation.isNullOrEmpty && e.flutterError.isNullOrEmpty) { + // If we have no fix recommendation or were unable to parse a + // flutter error, fall back to printing the raw stderr. + logger.info(e.stderr.join(Platform.lineTerminator)); + } + + throw ProcessExit(ExitCode.software.code); + } on Exception catch (e) { + buildProgress.fail('Failed to build release artifacts: $e'); + throw ProcessExit(ExitCode.software.code); + } + + final releaseVersion = await releaser.getReleaseVersion( + releaseArtifactRoot: releaseArtifact, + ); + + // Ensure we can create a release from what we've built. + await ensureVersionIsReleasable( + version: releaseVersion, + flutterRevision: targetFlutterRevision, + releasePlatform: releaser.releaseType.releasePlatform, + ); + + final dryRun = results['dry-run'] == true; + if (dryRun) { + logger + ..info('No issues detected.') + ..info('The server may enforce additional checks.'); + throw ProcessExit(ExitCode.success.code); + } + + // Ask the user to proceed (this is skipped when running via CI). + await confirmCreateRelease( + app: app, + releaseVersion: releaseVersion, + flutterVersion: targetFlutterRevision, + releasePlatform: releaser.releaseType.releasePlatform, + ); + + final release = await getOrCreateRelease( + version: releaseVersion, + releasePlatform: releaser.releaseType.releasePlatform, + ); + await prepareRelease(release: release, releaser: releaser); + await releaser.uploadReleaseArtifacts(release: release, appId: appId); + await finalizeRelease(release: release, releaser: releaser); + + logger + ..success(''' ✅ Published Release ${release.version}!''') - ..info(releaser.postReleaseInstructions); + ..info(releaser.postReleaseInstructions); - printPatchInstructions( - releaser: releaser, - releaseVersion: releaseVersion, - releaseType: releaser.releaseType, - flavor: flavor, - target: target, - ); - }, - values: { - shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv), - }, - ); + printPatchInstructions( + releaser: releaser, + releaseVersion: releaseVersion, + releaseType: releaser.releaseType, + flavor: flavor, + target: target, + ); + }, values: {shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv)}); } /// Validates arguments that are common to all release types. @@ -383,11 +371,9 @@ of the iOS app that is using this module. (aar and ios-framework only)''', flutterVersionArg!, ); } on Exception catch (error) { - logger.err( - ''' + logger.err(''' Unable to determine revision for Flutter version: $flutterVersionArg. -$error''', - ); +$error'''); throw ProcessExit(ExitCode.software.code); } @@ -398,12 +384,10 @@ $error''', ), message: 'open an issue', ); - logger.err( - ''' + logger.err(''' Version $flutterVersionArg not found. Please $openIssueLink to request a new version. Use `shorebird flutter versions list` to list available versions. -''', - ); +'''); throw ProcessExit(ExitCode.software.code); } @@ -452,7 +436,8 @@ Use `shorebird flutter versions list` to list available versions. ..err(''' ${styleBold.wrap(lightRed.wrap('A release with version $version already exists but was built using a different Flutter revision.'))} ''') - ..info(''' + ..info( + ''' Existing release built with: ${lightCyan.wrap(formattedExistingReleaseVersion)} Current release built with: ${lightCyan.wrap(formattedCurrentReleaseVersion)} @@ -462,7 +447,8 @@ ${styleBold.wrap(lightRed.wrap('All platforms for a given release must be built To resolve this issue, you can: * Re-run the release command with "${lightCyan.wrap('--flutter-version=${existingRelease.flutterRevision}')}". * Delete the existing release and re-run the release command with the desired Flutter version. - * Bump the release version and re-run the release command with the desired Flutter version.'''); + * Bump the release version and re-run the release command with the desired Flutter version.''', + ); throw ProcessExit(ExitCode.software.code); } } @@ -583,12 +569,10 @@ ${summary.join('\n')} ); if (!releaser.requiresReleaseVersionArg) { - logger.info( - ''' + logger.info(''' Note: ${lightCyan.wrap(baseCommand)} without the --release-version option will patch the current version of the app. -''', - ); +'''); } } } diff --git a/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart index 88768ede..0a0e6d26 100644 --- a/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart @@ -66,27 +66,25 @@ To change the version of this release, change your app's version in your pubspec final flutterVersionArg = argResults['flutter-version'] as String?; if (flutterVersionArg != null) { - final version = - await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg); - final gitHash = - await shorebirdFlutter.getRevisionForVersion(flutterVersionArg); + final version = await shorebirdFlutter.resolveFlutterVersion( + flutterVersionArg, + ); + final gitHash = await shorebirdFlutter.getRevisionForVersion( + flutterVersionArg, + ); if (version != null && version < minimumSupportedWindowsFlutterVersion && !windowsFlutterGitHashesBelowMinVersion.contains(gitHash)) { - logger.err( - ''' + logger.err(''' Windows releases are not supported with Flutter versions older than $minimumSupportedWindowsFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''); throw ProcessExit(ExitCode.usage.code); } } } @override - Future buildReleaseArtifacts({ - DetailProgress? progress, - }) { + Future buildReleaseArtifacts({DetailProgress? progress}) { return artifactBuilder.buildWindowsApp( flavor: flavor, target: target, diff --git a/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart b/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart index d8f6b601..cce5d8c7 100644 --- a/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart +++ b/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart @@ -142,10 +142,7 @@ class GetApksCommand extends ShorebirdCommand { )..createSync(recursive: true); } - await extractFileToDisk( - apksZipFile.path, - outputDirectory.path, - ); + await extractFileToDisk(apksZipFile.path, outputDirectory.path); logger.info('apk(s) generated at ${lightCyan.wrap(outputDirectory.path)}'); return ExitCode.success.code; @@ -169,9 +166,7 @@ class GetApksCommand extends ShorebirdCommand { ); } - Future _downloadAab({ - required ReleaseArtifact releaseArtifact, - }) async { + Future _downloadAab({required ReleaseArtifact releaseArtifact}) async { final File artifactFile; try { artifactFile = await artifactManager.downloadWithProgressUpdates( diff --git a/packages/shorebird_cli/lib/src/commands/run_command.dart b/packages/shorebird_cli/lib/src/commands/run_command.dart index 1c0f49f0..64287829 100644 --- a/packages/shorebird_cli/lib/src/commands/run_command.dart +++ b/packages/shorebird_cli/lib/src/commands/run_command.dart @@ -16,11 +16,7 @@ class RunCommand extends ShorebirdCommand { /// {@macro run_command} RunCommand() { argParser - ..addOption( - 'device-id', - abbr: 'd', - help: 'Target device id or name.', - ) + ..addOption('device-id', abbr: 'd', help: 'Target device id or name.') ..addOption( 'target', abbr: 't', @@ -28,7 +24,8 @@ class RunCommand extends ShorebirdCommand { ) ..addMultiOption( 'dart-define', - help: 'Additional key-value pairs that will be available as constants ' + help: + 'Additional key-value pairs that will be available as constants ' '''from the String.fromEnvironment, bool.fromEnvironment, and int.fromEnvironment ''' 'constructors.\n' '''Multiple defines can be passed by repeating "--dart-define" multiple times.''', @@ -52,11 +49,9 @@ class RunCommand extends ShorebirdCommand { @override Future run() async { - logger.warn( - ''' + logger.warn(''' This command is deprecated and will be removed in a future release. -Please use "shorebird preview" instead.''', - ); +Please use "shorebird preview" instead.'''); // TODO(bryanoltman): check run target and run either // doctor.iosValidators or doctor.androidValidators as appropriate. @@ -75,20 +70,16 @@ Please use "shorebird preview" instead.''', final flavor = results['flavor'] as String?; final target = results['target'] as String?; final dartDefines = results['dart-define'] as List?; - final flutter = await process.start( - 'flutter', - [ - 'run', - // Eventually we should support running in both debug and release mode. - '--release', - if (deviceId != null) '--device-id=$deviceId', - if (flavor != null) '--flavor=$flavor', - if (target != null) '--target=$target', - if (dartDefines != null) ...dartDefines.map((e) => '--dart-define=$e'), - ...results.rest, - ], - runInShell: true, - ); + final flutter = await process.start('flutter', [ + 'run', + // Eventually we should support running in both debug and release mode. + '--release', + if (deviceId != null) '--device-id=$deviceId', + if (flavor != null) '--flavor=$flavor', + if (target != null) '--target=$target', + if (dartDefines != null) ...dartDefines.map((e) => '--dart-define=$e'), + ...results.rest, + ], runInShell: true); flutter.stdout.listen((event) { logger.info(utf8.decode(event)); diff --git a/packages/shorebird_cli/lib/src/config/shorebird_yaml.dart b/packages/shorebird_cli/lib/src/config/shorebird_yaml.dart index 2a86e52c..29aecd5f 100644 --- a/packages/shorebird_cli/lib/src/config/shorebird_yaml.dart +++ b/packages/shorebird_cli/lib/src/config/shorebird_yaml.dart @@ -5,10 +5,7 @@ part 'shorebird_yaml.g.dart'; /// {@template shorebird_yaml} /// A Shorebird configuration file which contains metadata about the app. /// {@endtemplate} -@JsonSerializable( - anyMap: true, - disallowUnrecognizedKeys: true, -) +@JsonSerializable(anyMap: true, disallowUnrecognizedKeys: true) class ShorebirdYaml { /// {@macro shorebird_yaml} const ShorebirdYaml({ diff --git a/packages/shorebird_cli/lib/src/config/shorebird_yaml.g.dart b/packages/shorebird_cli/lib/src/config/shorebird_yaml.g.dart index 0d8620c5..bbf1c90f 100644 --- a/packages/shorebird_cli/lib/src/config/shorebird_yaml.g.dart +++ b/packages/shorebird_cli/lib/src/config/shorebird_yaml.g.dart @@ -9,31 +9,30 @@ part of 'shorebird_yaml.dart'; // ************************************************************************** ShorebirdYaml _$ShorebirdYamlFromJson(Map json) => $checkedCreate( - 'ShorebirdYaml', + 'ShorebirdYaml', + json, + ($checkedConvert) { + $checkKeys( json, - ($checkedConvert) { - $checkKeys( - json, - allowedKeys: const ['app_id', 'flavors', 'base_url', 'auto_update'], - ); - final val = ShorebirdYaml( - appId: $checkedConvert('app_id', (v) => v as String), - flavors: $checkedConvert( - 'flavors', - (v) => (v as Map?)?.map( - (k, e) => MapEntry(k as String, e as String), - )), - baseUrl: $checkedConvert('base_url', (v) => v as String?), - autoUpdate: $checkedConvert('auto_update', (v) => v as bool?), - ); - return val; - }, - fieldKeyMap: const { - 'appId': 'app_id', - 'baseUrl': 'base_url', - 'autoUpdate': 'auto_update' - }, + allowedKeys: const ['app_id', 'flavors', 'base_url', 'auto_update'], ); + final val = ShorebirdYaml( + appId: $checkedConvert('app_id', (v) => v as String), + flavors: $checkedConvert( + 'flavors', + (v) => (v as Map?)?.map((k, e) => MapEntry(k as String, e as String)), + ), + baseUrl: $checkedConvert('base_url', (v) => v as String?), + autoUpdate: $checkedConvert('auto_update', (v) => v as bool?), + ); + return val; + }, + fieldKeyMap: const { + 'appId': 'app_id', + 'baseUrl': 'base_url', + 'autoUpdate': 'auto_update', + }, +); Map _$ShorebirdYamlToJson(ShorebirdYaml instance) => { diff --git a/packages/shorebird_cli/lib/src/doctor.dart b/packages/shorebird_cli/lib/src/doctor.dart index 74198549..b7eed551 100644 --- a/packages/shorebird_cli/lib/src/doctor.dart +++ b/packages/shorebird_cli/lib/src/doctor.dart @@ -28,9 +28,7 @@ class Doctor { final List linuxCommandValidators = []; /// Validators that verify shorebird will work on macOS. - final List macosCommandValidators = [ - MacosEntitlementsValidator(), - ]; + final List macosCommandValidators = [MacosEntitlementsValidator()]; /// Validators that verify shorebird will work on Windows. final List windowsCommandValidators = [ diff --git a/packages/shorebird_cli/lib/src/engine_config.dart b/packages/shorebird_cli/lib/src/engine_config.dart index 58fed0ec..c045ff3b 100644 --- a/packages/shorebird_cli/lib/src/engine_config.dart +++ b/packages/shorebird_cli/lib/src/engine_config.dart @@ -19,9 +19,9 @@ class EngineConfig { /// An empty [EngineConfig] instance. const EngineConfig.empty() - : localEngineSrcPath = null, - localEngine = null, - localEngineHost = null; + : localEngineSrcPath = null, + localEngine = null, + localEngineHost = null; /// The path to the local engine source. final String? localEngineSrcPath; diff --git a/packages/shorebird_cli/lib/src/executables/adb.dart b/packages/shorebird_cli/lib/src/executables/adb.dart index 46167e0e..96fef722 100644 --- a/packages/shorebird_cli/lib/src/executables/adb.dart +++ b/packages/shorebird_cli/lib/src/executables/adb.dart @@ -43,10 +43,7 @@ class Adb { } /// Starts the app with the given [package] name. - Future startApp({ - required String package, - String? deviceId, - }) async { + Future startApp({required String package, String? deviceId}) async { final args = [ if (deviceId != null) ...['-s', deviceId], 'shell', @@ -64,10 +61,7 @@ class Adb { } /// Runs `adb logcat`. - Future logcat({ - String? filter, - String? deviceId, - }) async { + Future logcat({String? filter, String? deviceId}) async { final args = [ if (deviceId != null) ...['-s', deviceId], 'logcat', diff --git a/packages/shorebird_cli/lib/src/executables/aot_tools.dart b/packages/shorebird_cli/lib/src/executables/aot_tools.dart index 130a52e9..cd65db9d 100644 --- a/packages/shorebird_cli/lib/src/executables/aot_tools.dart +++ b/packages/shorebird_cli/lib/src/executables/aot_tools.dart @@ -152,19 +152,19 @@ class AotTools { final stdout = StringBuffer(); final stderr = StringBuffer(); - final stdoutSubscription = subprocess.stdout.map(utf8.decode).listen( - (data) { - logger.detail(data); - stdout.write(data); - }, - ); + final stdoutSubscription = subprocess.stdout.map(utf8.decode).listen(( + data, + ) { + logger.detail(data); + stdout.write(data); + }); - final stderrSubscription = subprocess.stderr.map(utf8.decode).listen( - (data) { - logger.detail(data); - stderr.write(data); - }, - ); + final stderrSubscription = subprocess.stderr.map(utf8.decode).listen(( + data, + ) { + logger.detail(data); + stderr.write(data); + }); final exitCode = await subprocess.exitCode; @@ -189,11 +189,11 @@ class AotTools { ); } else { // local engine versions use .dart and we distribute aot-tools as a .dill - result = await execute( - shorebirdEnv.dartBinaryFile.path, - ['run', artifactPath, ...command], - workingDirectory: workingDirectory, - ); + result = await execute(shorebirdEnv.dartBinaryFile.path, [ + 'run', + artifactPath, + ...command, + ], workingDirectory: workingDirectory); } if (throwOnError && result.exitCode != 0) { @@ -249,28 +249,22 @@ class AotTools { const linkJson = 'link.jsonl'; final outputDir = p.dirname(outputPath); final linkerUsesGenSnapshot = await _linkerUsesGenSnapshot(); - await _exec( - [ - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - if (linkerUsesGenSnapshot) ...[ - '--gen-snapshot=$genSnapshot', - '--kernel=$kernel', - '--reporter=json', - '--redirect-to=${p.join(outputDir, linkJson)}', - ], - if (dumpDebugInfoPath != null) '--dump-debug-info=$dumpDebugInfoPath', - if (additionalArgs.isNotEmpty) ...[ - '--', - ...additionalArgs, - ], + await _exec([ + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + if (linkerUsesGenSnapshot) ...[ + '--gen-snapshot=$genSnapshot', + '--kernel=$kernel', + '--reporter=json', + '--redirect-to=${p.join(outputDir, linkJson)}', ], - workingDirectory: workingDirectory, - ); + if (dumpDebugInfoPath != null) '--dump-debug-info=$dumpDebugInfoPath', + if (additionalArgs.isNotEmpty) ...['--', ...additionalArgs], + ], workingDirectory: workingDirectory); return linkerUsesGenSnapshot ? _extractLinkPercentage(File(p.join(workingDirectory!, linkJson))) @@ -279,11 +273,12 @@ class AotTools { double? _extractLinkPercentage(File file) { if (!file.existsSync()) return null; - final status = const LineSplitter() - .convert(file.readAsStringSync()) - .map(json.decode) - .cast>() - .toList(); + final status = + const LineSplitter() + .convert(file.readAsStringSync()) + .map(json.decode) + .cast>() + .toList(); final linkSuccess = status.firstWhereOrNull( (line) => line['type'] == 'link_success', @@ -312,14 +307,12 @@ class AotTools { }) async { final tmpDir = Directory.systemTemp.createTempSync(); final outFile = File(p.join(tmpDir.path, 'diff_base')); - await _exec( - [ - 'dump_blobs', - '--analyze-snapshot=$analyzeSnapshotPath', - '--output=${outFile.path}', - '--snapshot=${releaseSnapshot.path}', - ], - ); + await _exec([ + 'dump_blobs', + '--analyze-snapshot=$analyzeSnapshotPath', + '--output=${outFile.path}', + '--snapshot=${releaseSnapshot.path}', + ]); if (!outFile.existsSync()) { throw Exception( diff --git a/packages/shorebird_cli/lib/src/executables/bundletool.dart b/packages/shorebird_cli/lib/src/executables/bundletool.dart index e125510a..6ea53d05 100644 --- a/packages/shorebird_cli/lib/src/executables/bundletool.dart +++ b/packages/shorebird_cli/lib/src/executables/bundletool.dart @@ -50,19 +50,17 @@ class Bundletool { String? keyPassword, String? keyAlias, }) async { - final result = await _exec( - [ - 'build-apks', - '--overwrite', - '--bundle=$bundle', - '--output=$output', - if (universal) '--mode=universal', - if (keystore != null) '--ks=$keystore', - if (keystorePassword != null) '--ks-pass=$keystorePassword', - if (keyPassword != null) '--key-pass=$keyPassword', - if (keyAlias != null) '--ks-key-alias=$keyAlias', - ], - ); + final result = await _exec([ + 'build-apks', + '--overwrite', + '--bundle=$bundle', + '--output=$output', + if (universal) '--mode=universal', + if (keystore != null) '--ks=$keystore', + if (keystorePassword != null) '--ks-pass=$keystorePassword', + if (keyPassword != null) '--key-pass=$keyPassword', + if (keyAlias != null) '--ks-key-alias=$keyAlias', + ]); if (result.exitCode != 0) { throw Exception('Failed to build apks: ${result.stderr}'); } @@ -73,10 +71,7 @@ class Bundletool { /// e.g. `bundletool install-apks --apks=/MyApp/my_app.apks --allow-downgrade` /// /// https://developer.android.com/tools/bundletool#deploy_with_bundletool - Future installApks({ - required String apks, - String? deviceId, - }) async { + Future installApks({required String apks, String? deviceId}) async { final args = [ 'install-apks', '--apks=$apks', @@ -91,15 +86,13 @@ class Bundletool { /// Extract the package name from an app bundle. Future getPackageName(String appBundlePath) async { - final result = await _exec( - [ - 'dump', - 'manifest', - '--bundle=$appBundlePath', - '--xpath', - '/manifest/@package', - ], - ); + final result = await _exec([ + 'dump', + 'manifest', + '--bundle=$appBundlePath', + '--xpath', + '/manifest/@package', + ]); if (result.exitCode != 0) { throw Exception( @@ -112,15 +105,13 @@ class Bundletool { /// Extract the version name from an app bundle. Future getVersionName(String appBundlePath) async { - final result = await _exec( - [ - 'dump', - 'manifest', - '--bundle=$appBundlePath', - '--xpath', - '/manifest/@android:versionName', - ], - ); + final result = await _exec([ + 'dump', + 'manifest', + '--bundle=$appBundlePath', + '--xpath', + '/manifest/@android:versionName', + ]); if (result.exitCode != 0) { throw Exception( @@ -133,15 +124,13 @@ class Bundletool { /// Extract the version code from an app bundle. Future getVersionCode(String appBundlePath) async { - final result = await _exec( - [ - 'dump', - 'manifest', - '--bundle=$appBundlePath', - '--xpath', - '/manifest/@android:versionCode', - ], - ); + final result = await _exec([ + 'dump', + 'manifest', + '--bundle=$appBundlePath', + '--xpath', + '/manifest/@android:versionCode', + ]); if (result.exitCode != 0) { throw Exception( diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart b/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart index 94bbcf63..66695375 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart @@ -10,7 +10,6 @@ part 'apple_device.g.dart'; /// `xcrun devicectl list devices`. /// {@endtemplate} @JsonSerializable(createToJson: false, fieldRename: FieldRename.none) - /// {@macro apple_device} class AppleDevice { /// {@macro apple_device} diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.g.dart b/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.g.dart index d27086a3..087f2d6d 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.g.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.g.dart @@ -8,59 +8,52 @@ part of 'apple_device.dart'; // JsonSerializableGenerator // ************************************************************************** -AppleDevice _$AppleDeviceFromJson(Map json) => $checkedCreate( - 'AppleDevice', - json, - ($checkedConvert) { - final val = AppleDevice( - deviceProperties: $checkedConvert('deviceProperties', - (v) => DeviceProperties.fromJson(v as Map)), - hardwareProperties: $checkedConvert('hardwareProperties', - (v) => HardwareProperties.fromJson(v as Map)), - connectionProperties: $checkedConvert('connectionProperties', - (v) => ConnectionProperties.fromJson(v as Map)), - ); - return val; - }, - ); +AppleDevice _$AppleDeviceFromJson(Map json) => + $checkedCreate('AppleDevice', json, ($checkedConvert) { + final val = AppleDevice( + deviceProperties: $checkedConvert( + 'deviceProperties', + (v) => DeviceProperties.fromJson(v as Map), + ), + hardwareProperties: $checkedConvert( + 'hardwareProperties', + (v) => HardwareProperties.fromJson(v as Map), + ), + connectionProperties: $checkedConvert( + 'connectionProperties', + (v) => ConnectionProperties.fromJson(v as Map), + ), + ); + return val; + }); HardwareProperties _$HardwarePropertiesFromJson(Map json) => - $checkedCreate( - 'HardwareProperties', - json, - ($checkedConvert) { - final val = HardwareProperties( - platform: $checkedConvert('platform', (v) => v as String), - udid: $checkedConvert('udid', (v) => v as String), - ); - return val; - }, - ); + $checkedCreate('HardwareProperties', json, ($checkedConvert) { + final val = HardwareProperties( + platform: $checkedConvert('platform', (v) => v as String), + udid: $checkedConvert('udid', (v) => v as String), + ); + return val; + }); DeviceProperties _$DevicePropertiesFromJson(Map json) => - $checkedCreate( - 'DeviceProperties', - json, - ($checkedConvert) { - final val = DeviceProperties( - name: $checkedConvert('name', (v) => v as String), - osVersionNumber: - $checkedConvert('osVersionNumber', (v) => v as String?), - ); - return val; - }, - ); + $checkedCreate('DeviceProperties', json, ($checkedConvert) { + final val = DeviceProperties( + name: $checkedConvert('name', (v) => v as String), + osVersionNumber: $checkedConvert( + 'osVersionNumber', + (v) => v as String?, + ), + ); + return val; + }); ConnectionProperties _$ConnectionPropertiesFromJson( - Map json) => - $checkedCreate( - 'ConnectionProperties', - json, - ($checkedConvert) { - final val = ConnectionProperties( - tunnelState: $checkedConvert('tunnelState', (v) => v as String), - transportType: $checkedConvert('transportType', (v) => v as String?), - ); - return val; - }, - ); + Map json, +) => $checkedCreate('ConnectionProperties', json, ($checkedConvert) { + final val = ConnectionProperties( + tunnelState: $checkedConvert('tunnelState', (v) => v as String), + transportType: $checkedConvert('transportType', (v) => v as String?), + ); + return val; +}); diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart b/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart index f51c6765..42fa19ea 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart @@ -22,10 +22,7 @@ typedef BundleId = String; /// {@endtemplate} class DevicectlException implements Exception { /// {@macro devicectl_exception} - DevicectlException({ - required this.message, - this.underlyingException, - }); + DevicectlException({required this.message, this.underlyingException}); /// A message describing this exception. final String message; @@ -116,10 +113,10 @@ class Devicectl { final String bundleId; try { final maybeBundleId = - JsonPath(r'$.result.installedApplications[0].bundleID') - .read(jsonResult) - .firstOrNull - ?.value as String?; + JsonPath( + r'$.result.installedApplications[0].bundleID', + ).read(jsonResult).firstOrNull?.value + as String?; if (maybeBundleId == null) { throw Exception( 'Unable to find installed app bundleID in devicectl output', diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart b/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart index 3f3e8f67..4c4d719a 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart @@ -42,11 +42,7 @@ NSError( )'''; @override - List get props => [ - code, - domain, - userInfo, - ]; + List get props => [code, domain, userInfo]; } /// {@template user_info} @@ -98,11 +94,11 @@ UserInfo( @override List get props => [ - description, - localizedDescription, - localizedFailureReason, - underlyingError, - ]; + description, + localizedDescription, + localizedFailureReason, + underlyingError, + ]; } /// {@template string_container} diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/nserror.g.dart b/packages/shorebird_cli/lib/src/executables/devicectl/nserror.g.dart index c018a897..4fcd402d 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/nserror.g.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/nserror.g.dart @@ -8,103 +8,97 @@ part of 'nserror.dart'; // JsonSerializableGenerator // ************************************************************************** -NSError _$NSErrorFromJson(Map json) => $checkedCreate( - 'NSError', - json, - ($checkedConvert) { - final val = NSError( - code: $checkedConvert('code', (v) => (v as num).toInt()), - domain: $checkedConvert('domain', (v) => v as String), - userInfo: $checkedConvert( - 'userInfo', (v) => UserInfo.fromJson(v as Map)), - ); - return val; - }, - ); +NSError _$NSErrorFromJson(Map json) => + $checkedCreate('NSError', json, ($checkedConvert) { + final val = NSError( + code: $checkedConvert('code', (v) => (v as num).toInt()), + domain: $checkedConvert('domain', (v) => v as String), + userInfo: $checkedConvert( + 'userInfo', + (v) => UserInfo.fromJson(v as Map), + ), + ); + return val; + }); Map _$NSErrorToJson(NSError instance) => { - 'code': instance.code, - 'domain': instance.domain, - 'userInfo': instance.userInfo.toJson(), - }; + 'code': instance.code, + 'domain': instance.domain, + 'userInfo': instance.userInfo.toJson(), +}; UserInfo _$UserInfoFromJson(Map json) => $checkedCreate( - 'UserInfo', - json, - ($checkedConvert) { - final val = UserInfo( - description: $checkedConvert( - 'NSDescription', - (v) => v == null - ? null - : StringContainer.fromJson(v as Map)), - localizedDescription: $checkedConvert( - 'NSLocalizedDescription', - (v) => v == null - ? null - : StringContainer.fromJson(v as Map)), - localizedFailureReason: $checkedConvert( - 'NSLocalizedFailureReason', - (v) => v == null - ? null - : StringContainer.fromJson(v as Map)), - underlyingError: $checkedConvert( - 'NSUnderlyingError', - (v) => v == null - ? null - : NSUnderlyingError.fromJson(v as Map)), - ); - return val; - }, - fieldKeyMap: const { - 'description': 'NSDescription', - 'localizedDescription': 'NSLocalizedDescription', - 'localizedFailureReason': 'NSLocalizedFailureReason', - 'underlyingError': 'NSUnderlyingError' - }, + 'UserInfo', + json, + ($checkedConvert) { + final val = UserInfo( + description: $checkedConvert( + 'NSDescription', + (v) => + v == null + ? null + : StringContainer.fromJson(v as Map), + ), + localizedDescription: $checkedConvert( + 'NSLocalizedDescription', + (v) => + v == null + ? null + : StringContainer.fromJson(v as Map), + ), + localizedFailureReason: $checkedConvert( + 'NSLocalizedFailureReason', + (v) => + v == null + ? null + : StringContainer.fromJson(v as Map), + ), + underlyingError: $checkedConvert( + 'NSUnderlyingError', + (v) => + v == null + ? null + : NSUnderlyingError.fromJson(v as Map), + ), ); + return val; + }, + fieldKeyMap: const { + 'description': 'NSDescription', + 'localizedDescription': 'NSLocalizedDescription', + 'localizedFailureReason': 'NSLocalizedFailureReason', + 'underlyingError': 'NSUnderlyingError', + }, +); Map _$UserInfoToJson(UserInfo instance) => { - 'NSDescription': instance.description?.toJson(), - 'NSLocalizedDescription': instance.localizedDescription?.toJson(), - 'NSLocalizedFailureReason': instance.localizedFailureReason?.toJson(), - 'NSUnderlyingError': instance.underlyingError?.toJson(), - }; + 'NSDescription': instance.description?.toJson(), + 'NSLocalizedDescription': instance.localizedDescription?.toJson(), + 'NSLocalizedFailureReason': instance.localizedFailureReason?.toJson(), + 'NSUnderlyingError': instance.underlyingError?.toJson(), +}; StringContainer _$StringContainerFromJson(Map json) => - $checkedCreate( - 'StringContainer', - json, - ($checkedConvert) { - final val = StringContainer( - $checkedConvert('string', (v) => v as String), - ); - return val; - }, - ); + $checkedCreate('StringContainer', json, ($checkedConvert) { + final val = StringContainer( + $checkedConvert('string', (v) => v as String), + ); + return val; + }); Map _$StringContainerToJson(StringContainer instance) => - { - 'string': instance.string, - }; + {'string': instance.string}; NSUnderlyingError _$NSUnderlyingErrorFromJson(Map json) => - $checkedCreate( - 'NSUnderlyingError', - json, - ($checkedConvert) { - final val = NSUnderlyingError( - error: $checkedConvert( - 'error', - (v) => v == null - ? null - : NSError.fromJson(v as Map)), - ); - return val; - }, - ); + $checkedCreate('NSUnderlyingError', json, ($checkedConvert) { + final val = NSUnderlyingError( + error: $checkedConvert( + 'error', + (v) => v == null ? null : NSError.fromJson(v as Map), + ), + ); + return val; + }); Map _$NSUnderlyingErrorToJson(NSUnderlyingError instance) => - { - 'error': instance.error?.toJson(), - }; + {'error': instance.error?.toJson()}; diff --git a/packages/shorebird_cli/lib/src/executables/git.dart b/packages/shorebird_cli/lib/src/executables/git.dart index c9b53d0c..f352f8ca 100644 --- a/packages/shorebird_cli/lib/src/executables/git.dart +++ b/packages/shorebird_cli/lib/src/executables/git.dart @@ -45,15 +45,7 @@ class Git { required String outputDirectory, List? args, }) async { - await git( - [ - 'clone', - url, - ...?args, - outputDirectory, - ], - runInShell: true, - ); + await git(['clone', url, ...?args, outputDirectory], runInShell: true); } /// Checks out the git repository located at [directory] to the [revision]. @@ -61,17 +53,14 @@ class Git { required String directory, required String revision, }) async { - await git( - [ - '-C', - directory, - '-c', - 'advice.detachedHead=false', - 'checkout', - revision, - ], - runInShell: true, - ); + await git([ + '-C', + directory, + '-c', + 'advice.detachedHead=false', + 'checkout', + revision, + ], runInShell: true); } /// Fetch branches/tags from the repository at [directory]. @@ -80,10 +69,7 @@ class Git { } /// Run `git remote` at [directory]. - Future remote({ - required String directory, - List? args, - }) async { + Future remote({required String directory, List? args}) async { await git(['remote', ...?args], workingDirectory: directory); } @@ -95,16 +81,13 @@ class Git { required String pattern, String? contains, }) async { - final result = await git( - [ - 'for-each-ref', - if (contains != null) ...['--contains', contains], - '--format', - format, - pattern, - ], - workingDirectory: directory, - ); + final result = await git([ + 'for-each-ref', + if (contains != null) ...['--contains', contains], + '--format', + format, + pattern, + ], workingDirectory: directory); return '${result.stdout}'.trim(); } @@ -122,10 +105,11 @@ class Git { required String revision, required String directory, }) async { - final result = await git( - ['rev-parse', '--verify', revision], - workingDirectory: directory, - ); + final result = await git([ + 'rev-parse', + '--verify', + revision, + ], workingDirectory: directory); return '${result.stdout}'.trim(); } @@ -141,18 +125,19 @@ class Git { required Directory directory, String revision = 'HEAD', }) async { - final result = await git( - ['symbolic-ref', revision], - workingDirectory: directory.path, - ); + final result = await git([ + 'symbolic-ref', + revision, + ], workingDirectory: directory.path); return '${result.stdout}'.trim(); } /// Returns the name of the branch the git repository located at [directory] /// is currently on. Future currentBranch({required Directory directory}) async { - return (await symbolicRef(directory: directory)) - .replaceAll('refs/heads/', ''); + return (await symbolicRef( + directory: directory, + )).replaceAll('refs/heads/', ''); } /// Whether [directory] is part of a git repository. @@ -160,10 +145,7 @@ class Git { try { // [git] throws if the command's exit code is nonzero, which is what we're // checking for here. - await git( - ['status'], - workingDirectory: directory.path, - ); + await git(['status'], workingDirectory: directory.path); } on Exception { return false; } @@ -176,14 +158,11 @@ class Git { try { // [git] throws if the command's exit code is nonzero, which is what we're // checking for here. - await git( - [ - 'ls-files', - '--error-unmatch', - file.absolute.path, - ], - workingDirectory: file.parent.path, - ); + await git([ + 'ls-files', + '--error-unmatch', + file.absolute.path, + ], workingDirectory: file.parent.path); } on Exception { return false; } diff --git a/packages/shorebird_cli/lib/src/executables/gradlew.dart b/packages/shorebird_cli/lib/src/executables/gradlew.dart index 416be250..96a9b8a6 100644 --- a/packages/shorebird_cli/lib/src/executables/gradlew.dart +++ b/packages/shorebird_cli/lib/src/executables/gradlew.dart @@ -107,15 +107,13 @@ class Gradlew { args, runInShell: true, workingDirectory: p.dirname(executablePath), - environment: { - if (!javaHome.isNullOrEmpty) 'JAVA_HOME': javaHome!, - }, + environment: {if (!javaHome.isNullOrEmpty) 'JAVA_HOME': javaHome!}, ); if (result.exitCode != ExitCode.success.code) { - if (result.stderr - .toString() - .contains(IncompatibleGradleException.errorPattern)) { + if (result.stderr.toString().contains( + IncompatibleGradleException.errorPattern, + )) { throw IncompatibleGradleException(); } } @@ -142,10 +140,11 @@ class Gradlew { /// Return the set of product flavors configured for the app at [projectRoot]. /// Returns an empty set for apps that do not use product flavors. Future> productFlavors(String projectRoot) async { - final result = await _run( - ['app:tasks', '--all', '--console=auto'], - projectRoot, - ); + final result = await _run([ + 'app:tasks', + '--all', + '--console=auto', + ], projectRoot); if (result.exitCode != 0) { throw Exception('${result.stdout}\n${result.stderr}'); diff --git a/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart b/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart index 9d4702ba..0e3790e1 100644 --- a/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart +++ b/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart @@ -22,19 +22,18 @@ class IDeviceSysLog { /// The location of the libimobiledevice library, which contains /// idevicesyslog. static Directory get libimobiledeviceDirectory => Directory( - p.join( - shorebirdEnv.flutterDirectory.path, - 'bin', - 'cache', - 'artifacts', - 'libimobiledevice', - ), - ); + p.join( + shorebirdEnv.flutterDirectory.path, + 'bin', + 'cache', + 'artifacts', + 'libimobiledevice', + ), + ); /// The location of the idevicesyslog executable. - static File get idevicesyslogExecutable => File( - p.join(libimobiledeviceDirectory.path, 'idevicesyslog'), - ); + static File get idevicesyslogExecutable => + File(p.join(libimobiledeviceDirectory.path, 'idevicesyslog')); /// The libraries that idevicesyslog depends on. @visibleForTesting @@ -86,9 +85,7 @@ class IDeviceSysLog { // network flag. if (!device.isWired) '--network', ], - environment: { - 'DYLD_LIBRARY_PATH': _dyldPathEntry, - }, + environment: {'DYLD_LIBRARY_PATH': _dyldPathEntry}, ); loggerProcess.stdout diff --git a/packages/shorebird_cli/lib/src/executables/ios_deploy.dart b/packages/shorebird_cli/lib/src/executables/ios_deploy.dart index 3905fb75..0eb889f2 100644 --- a/packages/shorebird_cli/lib/src/executables/ios_deploy.dart +++ b/packages/shorebird_cli/lib/src/executables/ios_deploy.dart @@ -17,11 +17,7 @@ final iosDeployRef = create(IOSDeploy.new); IOSDeploy get iosDeploy => read(iosDeployRef); /// lldb debugger state. -enum _DebuggerState { - detached, - launching, - attached, -} +enum _DebuggerState { detached, launching, attached } /// {@template ios_deploy} /// Wrapper around the `ios-deploy` command cached by the Flutter tool. @@ -30,30 +26,31 @@ enum _DebuggerState { class IOSDeploy { /// {@macro ios_deploy} const IOSDeploy({ProcessSignal? sigint}) - : _sigint = sigint ?? ProcessSignal.sigint; + : _sigint = sigint ?? ProcessSignal.sigint; final ProcessSignal _sigint; /// The location of the ios-deploy executable. @visibleForTesting static File get iosDeployExecutable => File( - p.join( - shorebirdEnv.flutterDirectory.path, - 'bin', - 'cache', - 'artifacts', - 'ios-deploy', - 'ios-deploy', - ), - ); + p.join( + shorebirdEnv.flutterDirectory.path, + 'bin', + 'cache', + 'artifacts', + 'ios-deploy', + 'ios-deploy', + ), + ); static bool get _isInstalled => iosDeployExecutable.existsSync(); // (lldb) platform select remote-'ios' --sysroot // This regex is to get the configurable lldb prompt. // By default this prompt will be "lldb". - static final _lldbPlatformSelect = - RegExp(r"\s*platform select remote-'ios' --sysroot"); + static final _lldbPlatformSelect = RegExp( + r"\s*platform select remote-'ios' --sysroot", + ); // (lldb) run static final _lldbProcessExit = RegExp(r'Process \d* exited with status ='); @@ -172,16 +169,13 @@ Or run on an iOS simulator without code signing }); try { - launchProcess = await process.start( - iosDeployExecutable.path, - [ - '--debug', - if (deviceId != null) ...['--id', deviceId], - '-r', // uninstall the app before reinstalling and clear app data - '--bundle', - bundlePath, - ], - ); + launchProcess = await process.start(iosDeployExecutable.path, [ + '--debug', + if (deviceId != null) ...['--id', deviceId], + '-r', // uninstall the app before reinstalling and clear app data + '--bundle', + bundlePath, + ]); void detach() { if (debuggerState.isNotAttached) return; @@ -294,11 +288,13 @@ Or run on an iOS simulator without code signing logger.detail(line); } - final stdoutSubscription = - launchProcess.stdout.asLines().listen(onStdout); + final stdoutSubscription = launchProcess.stdout.asLines().listen( + onStdout, + ); - final stderrSubscription = - launchProcess.stderr.asLines().listen(onStderr); + final stderrSubscription = launchProcess.stderr.asLines().listen( + onStderr, + ); final status = await launchProcess.exitCode; logger.detail('[ios-deploy] exited with code: $exitCode'); @@ -343,7 +339,7 @@ Or run on an iOS simulator without code signing String detectFailures(String line, Logger logger) { final isMissingProvisioningProfile = line.contains(IOSDeploy.noProvisioningProfileErrorOne) || - line.contains(IOSDeploy.noProvisioningProfileErrorTwo); + line.contains(IOSDeploy.noProvisioningProfileErrorTwo); // No provisioning profile. if (isMissingProvisioningProfile) { @@ -351,7 +347,8 @@ String detectFailures(String line, Logger logger) { return line; } - final isDeviceLocked = line.contains(IOSDeploy.deviceLockedError) || + final isDeviceLocked = + line.contains(IOSDeploy.deviceLockedError) || line.contains(IOSDeploy.deviceLockedErrorMessage); if (isDeviceLocked) { @@ -378,7 +375,8 @@ extension on _DebuggerState { extension on Stream> { Stream asLines() { - return transform(utf8.decoder) - .transform(const LineSplitter()); + return transform( + utf8.decoder, + ).transform(const LineSplitter()); } } diff --git a/packages/shorebird_cli/lib/src/executables/open.dart b/packages/shorebird_cli/lib/src/executables/open.dart index 8e5c91f6..99d7cfd1 100644 --- a/packages/shorebird_cli/lib/src/executables/open.dart +++ b/packages/shorebird_cli/lib/src/executables/open.dart @@ -17,21 +17,18 @@ class Open { /// Opens a new application at the provided [path] and streams the stdout and /// stderr. Future>> newApplication({required String path}) async { - final app = Directory(p.join(path, 'Contents', 'MacOS')) - .listSync() - .firstWhere((f) => f is File); + final app = Directory( + p.join(path, 'Contents', 'MacOS'), + ).listSync().firstWhere((f) => f is File); await process.start('open', ['-n', path]); - final logStreamProcess = await process.start( - 'log', - [ - 'stream', - '--style=compact', - '--process', - p.basenameWithoutExtension(app.path), - ], - ); + final logStreamProcess = await process.start('log', [ + 'stream', + '--style=compact', + '--process', + p.basenameWithoutExtension(app.path), + ]); return logStreamProcess.stdout; } diff --git a/packages/shorebird_cli/lib/src/executables/patch_executable.dart b/packages/shorebird_cli/lib/src/executables/patch_executable.dart index cf81db93..56ee9f73 100644 --- a/packages/shorebird_cli/lib/src/executables/patch_executable.dart +++ b/packages/shorebird_cli/lib/src/executables/patch_executable.dart @@ -41,11 +41,7 @@ class PatchExecutable { cache.getArtifactDirectory('patch').path, 'patch', ); - final diffArguments = [ - releaseArtifactPath, - patchArtifactPath, - diffPath, - ]; + final diffArguments = [releaseArtifactPath, patchArtifactPath, diffPath]; final result = await process.run(diffExecutable, diffArguments); @@ -67,12 +63,10 @@ Please try again once you have installed this software. } if (result.exitCode != ExitCode.success.code) { - throw PatchFailedException( - ''' + throw PatchFailedException(''' Failed to create diff (exit code ${result.exitCode}). $messageDetails stdout: ${result.stdout} - stderr: ${result.stderr}''', - ); + stderr: ${result.stderr}'''); } } } diff --git a/packages/shorebird_cli/lib/src/executables/powershell.dart b/packages/shorebird_cli/lib/src/executables/powershell.dart index fc03e97a..8cbc689a 100644 --- a/packages/shorebird_cli/lib/src/executables/powershell.dart +++ b/packages/shorebird_cli/lib/src/executables/powershell.dart @@ -21,11 +21,7 @@ class Powershell { String? workingDirectory, bool runInShell = false, }) async { - final result = await process.run( - executable, - arguments, - runInShell: true, - ); + final result = await process.run(executable, arguments, runInShell: true); if (result.exitCode != ExitCode.success.code) { throw ProcessException( executable, @@ -42,10 +38,7 @@ class Powershell { final exePath = exeFile.path; final pwshCommand = '(Get-Item -Path $exePath).VersionInfo.ProductVersion'; - final result = await pwsh( - ['-Command', pwshCommand], - runInShell: true, - ); + final result = await pwsh(['-Command', pwshCommand], runInShell: true); var versionString = (result.stdout as String).trim(); if (!versionString.contains('+')) { diff --git a/packages/shorebird_cli/lib/src/executables/shorebird_tools.dart b/packages/shorebird_cli/lib/src/executables/shorebird_tools.dart index 510fe129..e651015c 100644 --- a/packages/shorebird_cli/lib/src/executables/shorebird_tools.dart +++ b/packages/shorebird_cli/lib/src/executables/shorebird_tools.dart @@ -41,11 +41,7 @@ class ShorebirdTools { /// The directory containing the `shorebird_tools` package. Directory get shorebirdToolsDirectory { final dir = Directory( - p.join( - shorebirdEnv.flutterDirectory.path, - 'packages', - 'shorebird_tools', - ), + p.join(shorebirdEnv.flutterDirectory.path, 'packages', 'shorebird_tools'), ); return dir; } @@ -53,12 +49,7 @@ class ShorebirdTools { Future _run(List args) { return process.run( shorebirdEnv.dartBinaryFile.path, - [ - 'run', - 'shorebird_tools', - 'package', - ...args, - ], + ['run', 'shorebird_tools', 'package', ...args], workingDirectory: shorebirdToolsDirectory.path, ); } @@ -70,22 +61,15 @@ class ShorebirdTools { required String patchPath, required String outputPath, }) async { - final packageArguments = [ - '-p', - patchPath, - '-o', - outputPath, - ]; + final packageArguments = ['-p', patchPath, '-o', outputPath]; final result = await _run(packageArguments); if (result.exitCode != ExitCode.success.code) { - throw PackageFailedException( - ''' + throw PackageFailedException(''' Failed to create package (exit code ${result.exitCode}). stdout: ${result.stdout} - stderr: ${result.stderr}''', - ); + stderr: ${result.stderr}'''); } } } diff --git a/packages/shorebird_cli/lib/src/extensions/arg_results.dart b/packages/shorebird_cli/lib/src/extensions/arg_results.dart index 40a7aa44..d6b91f74 100644 --- a/packages/shorebird_cli/lib/src/extensions/arg_results.dart +++ b/packages/shorebird_cli/lib/src/extensions/arg_results.dart @@ -14,10 +14,7 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da extension OptionFinder on ArgResults { /// Detects flags even when passed to underlying commands via a `--` /// separator. - String? findOption( - String name, { - required ArgParser argParser, - }) { + String? findOption(String name, {required ArgParser argParser}) { if (wasParsed(name)) { return this[name] as String?; } @@ -25,14 +22,12 @@ extension OptionFinder on ArgResults { // We would ideally check for abbreviations here as well, but ArgResults // doesn't expose its parser (which we could use to get the list of // [Options] being parsed) or an abbreviations map. - final abbr = argParser.options.values - .firstWhereOrNull((option) => option.name == name) - ?.abbr; + final abbr = + argParser.options.values + .firstWhereOrNull((option) => option.name == name) + ?.abbr; - final flagsToCheck = [ - '--$name', - if (abbr != null) '-$abbr', - ]; + final flagsToCheck = ['--$name', if (abbr != null) '-$abbr']; for (var i = 0; i < rest.length; i++) { for (final flag in flagsToCheck) { @@ -133,17 +128,15 @@ extension ForwardedArgs on ArgResults { forwarded = rest.toList(); } - forwarded.addAll( - [ - ..._argsNamed(CommonArguments.dartDefineArg.name), - ..._argsNamed(CommonArguments.dartDefineFromFileArg.name), - ..._argsNamed(CommonArguments.buildNameArg.name), - ..._argsNamed(CommonArguments.buildNumberArg.name), - ..._argsNamed(CommonArguments.splitDebugInfoArg.name), - ..._argsNamed(CommonArguments.exportMethodArg.name), - ..._argsNamed(CommonArguments.exportOptionsPlistArg.name), - ], - ); + forwarded.addAll([ + ..._argsNamed(CommonArguments.dartDefineArg.name), + ..._argsNamed(CommonArguments.dartDefineFromFileArg.name), + ..._argsNamed(CommonArguments.buildNameArg.name), + ..._argsNamed(CommonArguments.buildNumberArg.name), + ..._argsNamed(CommonArguments.splitDebugInfoArg.name), + ..._argsNamed(CommonArguments.exportMethodArg.name), + ..._argsNamed(CommonArguments.exportOptionsPlistArg.name), + ]); return forwarded; } diff --git a/packages/shorebird_cli/lib/src/extensions/file.dart b/packages/shorebird_cli/lib/src/extensions/file.dart index 701d703c..d2970ca2 100644 --- a/packages/shorebird_cli/lib/src/extensions/file.dart +++ b/packages/shorebird_cli/lib/src/extensions/file.dart @@ -9,9 +9,7 @@ extension FileValidations on File { /// Logs an error and exits with [ExitCode.usage] if this file does not exist. void assertExists() { if (!existsSync()) { - logger.err( - 'No file found at $path', - ); + logger.err('No file found at $path'); throw ProcessExit(ExitCode.usage.code); } } diff --git a/packages/shorebird_cli/lib/src/formatters/file_size_formatter.dart b/packages/shorebird_cli/lib/src/formatters/file_size_formatter.dart index 03ffb294..f0fa9233 100644 --- a/packages/shorebird_cli/lib/src/formatters/file_size_formatter.dart +++ b/packages/shorebird_cli/lib/src/formatters/file_size_formatter.dart @@ -7,8 +7,9 @@ String formatBytes(int bytes, {int decimals = 2}) { final i = (log(bytes) / log(1024)).floor(); final value = bytes / pow(1024, i); final suffix = suffixes[i]; - final formattedValue = value % 1 == 0 || decimals <= 0 - ? '${value.toInt()} $suffix' - : '${value.toStringAsFixed(decimals)} $suffix'; + final formattedValue = + value % 1 == 0 || decimals <= 0 + ? '${value.toInt()} $suffix' + : '${value.toStringAsFixed(decimals)} $suffix'; return formattedValue; } diff --git a/packages/shorebird_cli/lib/src/http_client/http_client.dart b/packages/shorebird_cli/lib/src/http_client/http_client.dart index 80aa916d..52e7d5b4 100644 --- a/packages/shorebird_cli/lib/src/http_client/http_client.dart +++ b/packages/shorebird_cli/lib/src/http_client/http_client.dart @@ -8,9 +8,7 @@ export 'retrying_client.dart'; /// A reference to a [http.Client] instance. final httpClientRef = create( - () => retryingHttpClient( - LoggingClient(httpClient: http.Client()), - ), + () => retryingHttpClient(LoggingClient(httpClient: http.Client())), ); /// The [http.Client] instance available in the current zone. diff --git a/packages/shorebird_cli/lib/src/http_client/retrying_client.dart b/packages/shorebird_cli/lib/src/http_client/retrying_client.dart index f745f1bd..49b7e07c 100644 --- a/packages/shorebird_cli/lib/src/http_client/retrying_client.dart +++ b/packages/shorebird_cli/lib/src/http_client/retrying_client.dart @@ -5,10 +5,10 @@ import 'package:http/retry.dart'; /// An http client that retries requests on connection failures. http.Client retryingHttpClient(http.Client client) => RetryClient( - client, - when: isRetryableResponse, - whenError: isRetryableException, - ); + client, + when: isRetryableResponse, + whenError: isRetryableException, +); /// Returns `true` if the [exception] is a retryable exception. bool isRetryableException(Object exception, StackTrace _) { diff --git a/packages/shorebird_cli/lib/src/logging/detail_progress.dart b/packages/shorebird_cli/lib/src/logging/detail_progress.dart index 3f1965a5..71e9947f 100644 --- a/packages/shorebird_cli/lib/src/logging/detail_progress.dart +++ b/packages/shorebird_cli/lib/src/logging/detail_progress.dart @@ -7,11 +7,9 @@ import 'package:mason_logger/mason_logger.dart'; /// {@endtemplate} class DetailProgress implements Progress { /// {@macro detail_progress} - DetailProgress._({ - required Progress progress, - required String primaryMessage, - }) : _progress = progress, - _primaryMessage = primaryMessage; + DetailProgress._({required Progress progress, required String primaryMessage}) + : _progress = progress, + _primaryMessage = primaryMessage; String _primaryMessage; String? _detailMessage; diff --git a/packages/shorebird_cli/lib/src/logging/shorebird_logger.dart b/packages/shorebird_cli/lib/src/logging/shorebird_logger.dart index 7cf57e62..341de927 100644 --- a/packages/shorebird_cli/lib/src/logging/shorebird_logger.dart +++ b/packages/shorebird_cli/lib/src/logging/shorebird_logger.dart @@ -16,22 +16,20 @@ const _logFileName = 'shorebird.log'; /// Where logs are written for the current Shorebird CLI run. A new file will /// be created for every run of the Shorebird CLI, and will have the name /// `timestamp_shorebird.log`. -final File currentRunLogFile = (() { - // TODO(bryanoltman): use package:clock to test for the correct timestamp - final timestamp = DateTime.now().millisecondsSinceEpoch; - final file = File( - p.join( - shorebirdEnv.logsDirectory.path, - '${timestamp}_$_logFileName', - ), - ); +final File currentRunLogFile = + (() { + // TODO(bryanoltman): use package:clock to test for the correct timestamp + final timestamp = DateTime.now().millisecondsSinceEpoch; + final file = File( + p.join(shorebirdEnv.logsDirectory.path, '${timestamp}_$_logFileName'), + ); - if (!file.existsSync()) { - file.createSync(recursive: true); - } + if (!file.existsSync()) { + file.createSync(recursive: true); + } - return file; -})(); + return file; + })(); /// {@template shorebird_logger} /// A [Logger] that diff --git a/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart b/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart index 179740ea..e0d07f5b 100644 --- a/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart +++ b/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart @@ -36,15 +36,14 @@ class BuildEnvironmentMetadata extends Equatable { String operatingSystemVersion = '1.2.3', ShorebirdYaml shorebirdYaml = const ShorebirdYaml(appId: '123'), String? xcodeVersion = '15.0', - }) => - BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - shorebirdVersion: shorebirdVersion, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdYaml: shorebirdYaml, - xcodeVersion: xcodeVersion, - ); + }) => BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + shorebirdVersion: shorebirdVersion, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdYaml: shorebirdYaml, + xcodeVersion: xcodeVersion, + ); // coverage:ignore-end /// Converts a `Map` to a [BuildEnvironmentMetadata] @@ -63,16 +62,15 @@ class BuildEnvironmentMetadata extends Equatable { String? operatingSystemVersion, ShorebirdYaml? shorebirdYaml, String? xcodeVersion, - }) => - BuildEnvironmentMetadata( - flutterRevision: flutterRevision ?? this.flutterRevision, - shorebirdVersion: shorebirdVersion ?? this.shorebirdVersion, - operatingSystem: operatingSystem ?? this.operatingSystem, - operatingSystemVersion: - operatingSystemVersion ?? this.operatingSystemVersion, - shorebirdYaml: shorebirdYaml ?? this.shorebirdYaml, - xcodeVersion: xcodeVersion ?? this.xcodeVersion, - ); + }) => BuildEnvironmentMetadata( + flutterRevision: flutterRevision ?? this.flutterRevision, + shorebirdVersion: shorebirdVersion ?? this.shorebirdVersion, + operatingSystem: operatingSystem ?? this.operatingSystem, + operatingSystemVersion: + operatingSystemVersion ?? this.operatingSystemVersion, + shorebirdYaml: shorebirdYaml ?? this.shorebirdYaml, + xcodeVersion: xcodeVersion ?? this.xcodeVersion, + ); /// The revision of Flutter used to run the command. /// @@ -110,11 +108,11 @@ class BuildEnvironmentMetadata extends Equatable { @override List get props => [ - flutterRevision, - shorebirdVersion, - operatingSystem, - operatingSystemVersion, - shorebirdYaml, - xcodeVersion, - ]; + flutterRevision, + shorebirdVersion, + operatingSystem, + operatingSystemVersion, + shorebirdYaml, + xcodeVersion, + ]; } diff --git a/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.g.dart b/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.g.dart index 9071b56d..f7e4f5e3 100644 --- a/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.g.dart +++ b/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.g.dart @@ -9,43 +9,47 @@ part of 'build_environment_metadata.dart'; // ************************************************************************** BuildEnvironmentMetadata _$BuildEnvironmentMetadataFromJson( - Map json) => - $checkedCreate( - 'BuildEnvironmentMetadata', - json, - ($checkedConvert) { - final val = BuildEnvironmentMetadata( - flutterRevision: - $checkedConvert('flutter_revision', (v) => v as String), - shorebirdVersion: - $checkedConvert('shorebird_version', (v) => v as String), - operatingSystem: - $checkedConvert('operating_system', (v) => v as String), - operatingSystemVersion: - $checkedConvert('operating_system_version', (v) => v as String), - shorebirdYaml: $checkedConvert('shorebird_yaml', - (v) => ShorebirdYaml.fromJson(v as Map)), - xcodeVersion: $checkedConvert('xcode_version', (v) => v as String?), - ); - return val; - }, - fieldKeyMap: const { - 'flutterRevision': 'flutter_revision', - 'shorebirdVersion': 'shorebird_version', - 'operatingSystem': 'operating_system', - 'operatingSystemVersion': 'operating_system_version', - 'shorebirdYaml': 'shorebird_yaml', - 'xcodeVersion': 'xcode_version' - }, + Map json, +) => $checkedCreate( + 'BuildEnvironmentMetadata', + json, + ($checkedConvert) { + final val = BuildEnvironmentMetadata( + flutterRevision: $checkedConvert('flutter_revision', (v) => v as String), + shorebirdVersion: $checkedConvert( + 'shorebird_version', + (v) => v as String, + ), + operatingSystem: $checkedConvert('operating_system', (v) => v as String), + operatingSystemVersion: $checkedConvert( + 'operating_system_version', + (v) => v as String, + ), + shorebirdYaml: $checkedConvert( + 'shorebird_yaml', + (v) => ShorebirdYaml.fromJson(v as Map), + ), + xcodeVersion: $checkedConvert('xcode_version', (v) => v as String?), ); + return val; + }, + fieldKeyMap: const { + 'flutterRevision': 'flutter_revision', + 'shorebirdVersion': 'shorebird_version', + 'operatingSystem': 'operating_system', + 'operatingSystemVersion': 'operating_system_version', + 'shorebirdYaml': 'shorebird_yaml', + 'xcodeVersion': 'xcode_version', + }, +); Map _$BuildEnvironmentMetadataToJson( - BuildEnvironmentMetadata instance) => - { - 'flutter_revision': instance.flutterRevision, - 'shorebird_version': instance.shorebirdVersion, - 'operating_system': instance.operatingSystem, - 'operating_system_version': instance.operatingSystemVersion, - 'shorebird_yaml': instance.shorebirdYaml.toJson(), - 'xcode_version': instance.xcodeVersion, - }; + BuildEnvironmentMetadata instance, +) => { + 'flutter_revision': instance.flutterRevision, + 'shorebird_version': instance.shorebirdVersion, + 'operating_system': instance.operatingSystem, + 'operating_system_version': instance.operatingSystemVersion, + 'shorebird_yaml': instance.shorebirdYaml.toJson(), + 'xcode_version': instance.xcodeVersion, +}; diff --git a/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart b/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart index f20c2fd3..fe32ea12 100644 --- a/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart +++ b/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart @@ -39,16 +39,15 @@ class CreatePatchMetadata extends Equatable { bool hasNativeChanges = false, double? linkPercentage, BuildEnvironmentMetadata? environment, - }) => - CreatePatchMetadata( - releasePlatform: releasePlatform, - usedIgnoreAssetChangesFlag: usedIgnoreAssetChangesFlag, - hasAssetChanges: hasAssetChanges, - usedIgnoreNativeChangesFlag: usedIgnoreNativeChangesFlag, - hasNativeChanges: hasNativeChanges, - linkPercentage: linkPercentage, - environment: environment ?? BuildEnvironmentMetadata.forTest(), - ); + }) => CreatePatchMetadata( + releasePlatform: releasePlatform, + usedIgnoreAssetChangesFlag: usedIgnoreAssetChangesFlag, + hasAssetChanges: hasAssetChanges, + usedIgnoreNativeChangesFlag: usedIgnoreNativeChangesFlag, + hasNativeChanges: hasNativeChanges, + linkPercentage: linkPercentage, + environment: environment ?? BuildEnvironmentMetadata.forTest(), + ); // coverage:ignore-end /// Converts a `Map` to a [CreatePatchMetadata] @@ -68,18 +67,17 @@ class CreatePatchMetadata extends Equatable { bool? hasNativeChanges, double? linkPercentage, BuildEnvironmentMetadata? environment, - }) => - CreatePatchMetadata( - releasePlatform: releasePlatform ?? this.releasePlatform, - usedIgnoreAssetChangesFlag: - usedIgnoreAssetChangesFlag ?? this.usedIgnoreAssetChangesFlag, - hasAssetChanges: hasAssetChanges ?? this.hasAssetChanges, - usedIgnoreNativeChangesFlag: - usedIgnoreNativeChangesFlag ?? this.usedIgnoreNativeChangesFlag, - hasNativeChanges: hasNativeChanges ?? this.hasNativeChanges, - linkPercentage: linkPercentage ?? this.linkPercentage, - environment: environment ?? this.environment, - ); + }) => CreatePatchMetadata( + releasePlatform: releasePlatform ?? this.releasePlatform, + usedIgnoreAssetChangesFlag: + usedIgnoreAssetChangesFlag ?? this.usedIgnoreAssetChangesFlag, + hasAssetChanges: hasAssetChanges ?? this.hasAssetChanges, + usedIgnoreNativeChangesFlag: + usedIgnoreNativeChangesFlag ?? this.usedIgnoreNativeChangesFlag, + hasNativeChanges: hasNativeChanges ?? this.hasNativeChanges, + linkPercentage: linkPercentage ?? this.linkPercentage, + environment: environment ?? this.environment, + ); /// The platform for which the patch was created. final ReleasePlatform releasePlatform; @@ -123,12 +121,12 @@ class CreatePatchMetadata extends Equatable { @override List get props => [ - releasePlatform, - usedIgnoreAssetChangesFlag, - hasAssetChanges, - usedIgnoreNativeChangesFlag, - hasNativeChanges, - linkPercentage, - environment, - ]; + releasePlatform, + usedIgnoreAssetChangesFlag, + hasAssetChanges, + usedIgnoreNativeChangesFlag, + hasNativeChanges, + linkPercentage, + environment, + ]; } diff --git a/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.g.dart b/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.g.dart index 8d4caa45..42081cfe 100644 --- a/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.g.dart +++ b/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.g.dart @@ -8,52 +8,59 @@ part of 'create_patch_metadata.dart'; // JsonSerializableGenerator // ************************************************************************** -CreatePatchMetadata _$CreatePatchMetadataFromJson(Map json) => - $checkedCreate( - 'CreatePatchMetadata', - json, - ($checkedConvert) { - final val = CreatePatchMetadata( - releasePlatform: $checkedConvert('release_platform', - (v) => $enumDecode(_$ReleasePlatformEnumMap, v)), - usedIgnoreAssetChangesFlag: $checkedConvert( - 'used_ignore_asset_changes_flag', (v) => v as bool), - hasAssetChanges: - $checkedConvert('has_asset_changes', (v) => v as bool), - usedIgnoreNativeChangesFlag: $checkedConvert( - 'used_ignore_native_changes_flag', (v) => v as bool), - hasNativeChanges: - $checkedConvert('has_native_changes', (v) => v as bool), - environment: $checkedConvert( - 'environment', - (v) => - BuildEnvironmentMetadata.fromJson(v as Map)), - linkPercentage: $checkedConvert( - 'link_percentage', (v) => (v as num?)?.toDouble()), - ); - return val; - }, - fieldKeyMap: const { - 'releasePlatform': 'release_platform', - 'usedIgnoreAssetChangesFlag': 'used_ignore_asset_changes_flag', - 'hasAssetChanges': 'has_asset_changes', - 'usedIgnoreNativeChangesFlag': 'used_ignore_native_changes_flag', - 'hasNativeChanges': 'has_native_changes', - 'linkPercentage': 'link_percentage' - }, +CreatePatchMetadata _$CreatePatchMetadataFromJson( + Map json, +) => $checkedCreate( + 'CreatePatchMetadata', + json, + ($checkedConvert) { + final val = CreatePatchMetadata( + releasePlatform: $checkedConvert( + 'release_platform', + (v) => $enumDecode(_$ReleasePlatformEnumMap, v), + ), + usedIgnoreAssetChangesFlag: $checkedConvert( + 'used_ignore_asset_changes_flag', + (v) => v as bool, + ), + hasAssetChanges: $checkedConvert('has_asset_changes', (v) => v as bool), + usedIgnoreNativeChangesFlag: $checkedConvert( + 'used_ignore_native_changes_flag', + (v) => v as bool, + ), + hasNativeChanges: $checkedConvert('has_native_changes', (v) => v as bool), + environment: $checkedConvert( + 'environment', + (v) => BuildEnvironmentMetadata.fromJson(v as Map), + ), + linkPercentage: $checkedConvert( + 'link_percentage', + (v) => (v as num?)?.toDouble(), + ), ); + return val; + }, + fieldKeyMap: const { + 'releasePlatform': 'release_platform', + 'usedIgnoreAssetChangesFlag': 'used_ignore_asset_changes_flag', + 'hasAssetChanges': 'has_asset_changes', + 'usedIgnoreNativeChangesFlag': 'used_ignore_native_changes_flag', + 'hasNativeChanges': 'has_native_changes', + 'linkPercentage': 'link_percentage', + }, +); Map _$CreatePatchMetadataToJson( - CreatePatchMetadata instance) => - { - 'release_platform': _$ReleasePlatformEnumMap[instance.releasePlatform]!, - 'used_ignore_asset_changes_flag': instance.usedIgnoreAssetChangesFlag, - 'has_asset_changes': instance.hasAssetChanges, - 'used_ignore_native_changes_flag': instance.usedIgnoreNativeChangesFlag, - 'has_native_changes': instance.hasNativeChanges, - 'link_percentage': instance.linkPercentage, - 'environment': instance.environment.toJson(), - }; + CreatePatchMetadata instance, +) => { + 'release_platform': _$ReleasePlatformEnumMap[instance.releasePlatform]!, + 'used_ignore_asset_changes_flag': instance.usedIgnoreAssetChangesFlag, + 'has_asset_changes': instance.hasAssetChanges, + 'used_ignore_native_changes_flag': instance.usedIgnoreNativeChangesFlag, + 'has_native_changes': instance.hasNativeChanges, + 'link_percentage': instance.linkPercentage, + 'environment': instance.environment.toJson(), +}; const _$ReleasePlatformEnumMap = { ReleasePlatform.android: 'android', diff --git a/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart b/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart index 58f6fcaf..b4f79566 100644 --- a/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart +++ b/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart @@ -33,13 +33,12 @@ class UpdateReleaseMetadata extends Equatable { String? flutterVersionOverride = '1.2.3', bool? generatedApks = false, BuildEnvironmentMetadata? environment, - }) => - UpdateReleaseMetadata( - releasePlatform: releasePlatform, - flutterVersionOverride: flutterVersionOverride, - generatedApks: generatedApks, - environment: environment ?? BuildEnvironmentMetadata.forTest(), - ); + }) => UpdateReleaseMetadata( + releasePlatform: releasePlatform, + flutterVersionOverride: flutterVersionOverride, + generatedApks: generatedApks, + environment: environment ?? BuildEnvironmentMetadata.forTest(), + ); // coverage:ignore-end /// Converts a `Map` to a [UpdateReleaseMetadata]. @@ -56,14 +55,13 @@ class UpdateReleaseMetadata extends Equatable { String? flutterVersionOverride, bool? generatedApks, BuildEnvironmentMetadata? environment, - }) => - UpdateReleaseMetadata( - releasePlatform: releasePlatform ?? this.releasePlatform, - flutterVersionOverride: - flutterVersionOverride ?? this.flutterVersionOverride, - generatedApks: generatedApks ?? this.generatedApks, - environment: environment ?? this.environment, - ); + }) => UpdateReleaseMetadata( + releasePlatform: releasePlatform ?? this.releasePlatform, + flutterVersionOverride: + flutterVersionOverride ?? this.flutterVersionOverride, + generatedApks: generatedApks ?? this.generatedApks, + environment: environment ?? this.environment, + ); /// The platform for which the patch was created. final ReleasePlatform releasePlatform; @@ -89,9 +87,9 @@ class UpdateReleaseMetadata extends Equatable { @override List get props => [ - releasePlatform, - flutterVersionOverride, - generatedApks, - environment, - ]; + releasePlatform, + flutterVersionOverride, + generatedApks, + environment, + ]; } diff --git a/packages/shorebird_cli/lib/src/metadata/update_release_metadata.g.dart b/packages/shorebird_cli/lib/src/metadata/update_release_metadata.g.dart index 6ea1015a..dccfd2c4 100644 --- a/packages/shorebird_cli/lib/src/metadata/update_release_metadata.g.dart +++ b/packages/shorebird_cli/lib/src/metadata/update_release_metadata.g.dart @@ -9,39 +9,43 @@ part of 'update_release_metadata.dart'; // ************************************************************************** UpdateReleaseMetadata _$UpdateReleaseMetadataFromJson( - Map json) => - $checkedCreate( - 'UpdateReleaseMetadata', - json, - ($checkedConvert) { - final val = UpdateReleaseMetadata( - releasePlatform: $checkedConvert('release_platform', - (v) => $enumDecode(_$ReleasePlatformEnumMap, v)), - flutterVersionOverride: - $checkedConvert('flutter_version_override', (v) => v as String?), - environment: $checkedConvert( - 'environment', - (v) => - BuildEnvironmentMetadata.fromJson(v as Map)), - generatedApks: $checkedConvert('generated_apks', (v) => v as bool?), - ); - return val; - }, - fieldKeyMap: const { - 'releasePlatform': 'release_platform', - 'flutterVersionOverride': 'flutter_version_override', - 'generatedApks': 'generated_apks' - }, + Map json, +) => $checkedCreate( + 'UpdateReleaseMetadata', + json, + ($checkedConvert) { + final val = UpdateReleaseMetadata( + releasePlatform: $checkedConvert( + 'release_platform', + (v) => $enumDecode(_$ReleasePlatformEnumMap, v), + ), + flutterVersionOverride: $checkedConvert( + 'flutter_version_override', + (v) => v as String?, + ), + environment: $checkedConvert( + 'environment', + (v) => BuildEnvironmentMetadata.fromJson(v as Map), + ), + generatedApks: $checkedConvert('generated_apks', (v) => v as bool?), ); + return val; + }, + fieldKeyMap: const { + 'releasePlatform': 'release_platform', + 'flutterVersionOverride': 'flutter_version_override', + 'generatedApks': 'generated_apks', + }, +); Map _$UpdateReleaseMetadataToJson( - UpdateReleaseMetadata instance) => - { - 'release_platform': _$ReleasePlatformEnumMap[instance.releasePlatform]!, - 'flutter_version_override': instance.flutterVersionOverride, - 'generated_apks': instance.generatedApks, - 'environment': instance.environment.toJson(), - }; + UpdateReleaseMetadata instance, +) => { + 'release_platform': _$ReleasePlatformEnumMap[instance.releasePlatform]!, + 'flutter_version_override': instance.flutterVersionOverride, + 'generated_apks': instance.generatedApks, + 'environment': instance.environment.toJson(), +}; const _$ReleasePlatformEnumMap = { ReleasePlatform.android: 'android', diff --git a/packages/shorebird_cli/lib/src/network_checker.dart b/packages/shorebird_cli/lib/src/network_checker.dart index 41d1366b..935a4caf 100644 --- a/packages/shorebird_cli/lib/src/network_checker.dart +++ b/packages/shorebird_cli/lib/src/network_checker.dart @@ -35,13 +35,14 @@ class NetworkCheckerException implements Exception { /// {@endtemplate} class NetworkChecker { /// The URLs to check for network reachability. - static final urlsToCheck = [ - 'https://api.shorebird.dev', - 'https://console.shorebird.dev', - 'https://oauth2.googleapis.com', - 'https://storage.googleapis.com', - 'https://cdn.shorebird.cloud', - ].map(Uri.parse).toList(); + static final urlsToCheck = + [ + 'https://api.shorebird.dev', + 'https://console.shorebird.dev', + 'https://oauth2.googleapis.com', + 'https://storage.googleapis.com', + 'https://cdn.shorebird.cloud', + ].map(Uri.parse).toList(); /// Verify that each of [urlsToCheck] responds to an HTTP GET request. Future checkReachability() async { @@ -73,20 +74,18 @@ class NetworkChecker { file = await artifactManager .downloadFile(uri, outputPath: file.path) .timeout( - timeout, - onTimeout: () { - throw const NetworkCheckerException('Download timed out'); - }, - ); + timeout, + onTimeout: () { + throw const NetworkCheckerException('Download timed out'); + }, + ); final end = clock.now(); final fileSize = file.existsSync() ? file.lengthSync() : 0; if (fileSize != 16000000) { - throw NetworkCheckerException( - ''' + throw NetworkCheckerException(''' Unexpected file size. Expected: 16MB -Actual: ${formatBytes(fileSize)}''', - ); +Actual: ${formatBytes(fileSize)}'''); } return fileSize / (end.difference(start).inMilliseconds * 1000); } finally { @@ -113,12 +112,14 @@ Actual: ${formatBytes(fileSize)}''', final start = clock.now(); final file = await http.MultipartFile.fromPath('file', testFile.path); final uploadRequest = http.MultipartRequest('POST', uri)..files.add(file); - final uploadResponse = await httpClient.send(uploadRequest).timeout( - timeout, - onTimeout: () { - throw const NetworkCheckerException('Upload timed out'); - }, - ); + final uploadResponse = await httpClient + .send(uploadRequest) + .timeout( + timeout, + onTimeout: () { + throw const NetworkCheckerException('Upload timed out'); + }, + ); if (uploadResponse.statusCode != HttpStatus.noContent) { final body = await uploadResponse.stream.bytesToString(); throw NetworkCheckerException( diff --git a/packages/shorebird_cli/lib/src/patch_diff_checker.dart b/packages/shorebird_cli/lib/src/patch_diff_checker.dart index 0b3669a4..f9b0e8fd 100644 --- a/packages/shorebird_cli/lib/src/patch_diff_checker.dart +++ b/packages/shorebird_cli/lib/src/patch_diff_checker.dart @@ -52,8 +52,9 @@ class PatchDiffChecker { required bool allowNativeChanges, bool confirmNativeChanges = true, }) async { - final progress = - logger.progress('Verifying patch can be applied to release'); + final progress = logger.progress( + 'Verifying patch can be applied to release', + ); final contentDiffs = await archiveDiffer.changedFiles( releaseArchive.path, @@ -62,10 +63,12 @@ class PatchDiffChecker { progress.complete(); final status = DiffStatus( - hasAssetChanges: - archiveDiffer.containsPotentiallyBreakingAssetDiffs(contentDiffs), - hasNativeChanges: - archiveDiffer.containsPotentiallyBreakingNativeDiffs(contentDiffs), + hasAssetChanges: archiveDiffer.containsPotentiallyBreakingAssetDiffs( + contentDiffs, + ), + hasNativeChanges: archiveDiffer.containsPotentiallyBreakingNativeDiffs( + contentDiffs, + ), ); if (status.hasNativeChanges && confirmNativeChanges) { @@ -79,9 +82,11 @@ class PatchDiffChecker { ), ) ..info( - yellow.wrap(''' + yellow.wrap( + ''' -If you don't know why you're seeing this error, visit our troubleshooting page at ${troubleshootingUrl.toLink()}'''), +If you don't know why you're seeing this error, visit our troubleshooting page at ${troubleshootingUrl.toLink()}''', + ), ); if (!allowNativeChanges) { diff --git a/packages/shorebird_cli/lib/src/platform/apple.dart b/packages/shorebird_cli/lib/src/platform/apple.dart index b24abaa3..d57bc374 100644 --- a/packages/shorebird_cli/lib/src/platform/apple.dart +++ b/packages/shorebird_cli/lib/src/platform/apple.dart @@ -56,12 +56,9 @@ enum ExportMethod { appStore('app-store', 'Upload to the App Store'), /// Ad-hoc distribution. - adHoc( - 'ad-hoc', - ''' + adHoc('ad-hoc', ''' Test on designated devices that do not need to be registered with the Apple developer account. - Requires a distribution certificate.''', - ), + Requires a distribution certificate.'''), /// Development distribution. development( @@ -142,11 +139,7 @@ class Apple { } final xcschemesDir = Directory( - p.join( - xcodeProjDirectory.path, - 'xcshareddata', - 'xcschemes', - ), + p.join(xcodeProjDirectory.path, 'xcshareddata', 'xcschemes'), ); if (!xcschemesDir.existsSync()) { throw Exception('Unable to detect schemes in $xcschemesDir'); @@ -196,9 +189,10 @@ class Apple { final linkProgress = logger.progress('Linking AOT files'); double? linkPercentage; - final dumpDebugInfoDir = await aotTools.isLinkDebugInfoSupported() - ? Directory.systemTemp.createTempSync() - : null; + final dumpDebugInfoDir = + await aotTools.isLinkDebugInfoSupported() + ? Directory.systemTemp.createTempSync() + : null; Future dumpDebugInfo() async { if (dumpDebugInfoDir == null) return; @@ -219,11 +213,9 @@ class Apple { p.join(codemagicExportDir, p.basename(Patcher.debugInfoFile.path)), ); } on Exception catch (error) { - logger.detail( - ''' + logger.detail(''' Failed to export ${Patcher.debugInfoFile.path} to $codemagicExportDir. -$error''', - ); +$error'''); } } } diff --git a/packages/shorebird_cli/lib/src/platform/linux.dart b/packages/shorebird_cli/lib/src/platform/linux.dart index 604b9993..23d521a8 100644 --- a/packages/shorebird_cli/lib/src/platform/linux.dart +++ b/packages/shorebird_cli/lib/src/platform/linux.dart @@ -28,13 +28,8 @@ class Linux { /// Linux apps track their version in a json file at /// data/flutter_assets/version.json. File linuxBundleVersionFile(Directory bundleRoot) => File( - p.join( - bundleRoot.absolute.path, - 'data', - 'flutter_assets', - 'version.json', - ), - ); + p.join(bundleRoot.absolute.path, 'data', 'flutter_assets', 'version.json'), + ); /// Reads the version from a Linux Flutter bundle. /// diff --git a/packages/shorebird_cli/lib/src/release_type.dart b/packages/shorebird_cli/lib/src/release_type.dart index 1cfa9cde..37baa22b 100644 --- a/packages/shorebird_cli/lib/src/release_type.dart +++ b/packages/shorebird_cli/lib/src/release_type.dart @@ -76,8 +76,9 @@ extension ReleaseTypeArgs on ArgResults { } else { if (arguments.isNotEmpty) { final platformCliName = arguments.first; - if (ReleaseType.values - .none((target) => target.cliName == platformCliName)) { + if (ReleaseType.values.none( + (target) => target.cliName == platformCliName, + )) { throw ArgumentError('Invalid platform: $platformCliName'); } releaseTypeCliNames = [platformCliName]; diff --git a/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart b/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart index 5a0c13b2..53dc3356 100644 --- a/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart +++ b/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart @@ -107,10 +107,11 @@ class ShorebirdAndroidArtifacts { } final allFiles = directory.listSync(); - final artifactCandidates = allFiles.whereType().where((file) { - final fileName = p.basename(file.path); - return fileName.artifactId == artifactId; - }).toList(); + final artifactCandidates = + allFiles.whereType().where((file) { + final fileName = p.basename(file.path); + return fileName.artifactId == artifactId; + }).toList(); if (artifactCandidates.isEmpty) { throw ArtifactNotFoundException( @@ -130,10 +131,7 @@ class ShorebirdAndroidArtifacts { } /// Find the app bundle in the provided [project] [Directory]. - File findAab({ - required Directory project, - required String? flavor, - }) { + File findAab({required Directory project, required String? flavor}) { final buildDir = p.join( project.path, 'build', @@ -153,10 +151,7 @@ class ShorebirdAndroidArtifacts { } /// Find the apk in the provided [project] [Directory]. - File findApk({ - required Directory project, - required String? flavor, - }) { + File findApk({required Directory project, required String? flavor}) { final buildDir = p.join( project.path, 'build', @@ -177,39 +172,28 @@ class ShorebirdAndroidArtifacts { /// The path to the aar library. static String get aarLibraryPath { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; - return p.joinAll([ - projectRoot.path, - 'build', - 'host', - 'outputs', - 'repo', - ]); + return p.joinAll([projectRoot.path, 'build', 'host', 'outputs', 'repo']); } /// The path to the aar directory. static String aarArtifactDirectory({ required String packageName, required String buildNumber, - }) => - p.joinAll([ - aarLibraryPath, - ...packageName.split('.'), - 'flutter_release', - buildNumber, - ]); + }) => p.joinAll([ + aarLibraryPath, + ...packageName.split('.'), + 'flutter_release', + buildNumber, + ]); /// The path to the aar artifact. static String aarArtifactPath({ required String packageName, required String buildNumber, - }) => - p.join( - aarArtifactDirectory( - packageName: packageName, - buildNumber: buildNumber, - ), - 'flutter_release-$buildNumber.aar', - ); + }) => p.join( + aarArtifactDirectory(packageName: packageName, buildNumber: buildNumber), + 'flutter_release-$buildNumber.aar', + ); /// Extract the release version from an appbundle. Future extractReleaseVersionFromAppBundle( diff --git a/packages/shorebird_cli/lib/src/shorebird_artifacts.dart b/packages/shorebird_cli/lib/src/shorebird_artifacts.dart index 53b54339..410b5521 100644 --- a/packages/shorebird_cli/lib/src/shorebird_artifacts.dart +++ b/packages/shorebird_cli/lib/src/shorebird_artifacts.dart @@ -54,9 +54,7 @@ class ShorebirdCachedArtifacts implements ShorebirdArtifacts { const ShorebirdCachedArtifacts(); @override - String getArtifactPath({ - required ShorebirdArtifact artifact, - }) { + String getArtifactPath({required ShorebirdArtifact artifact}) { switch (artifact) { case ShorebirdArtifact.analyzeSnapshotIos: return _analyzeSnapshotIosFile.path; diff --git a/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart b/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart index 90ca391f..ecf9e3e5 100644 --- a/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart +++ b/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart @@ -37,11 +37,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { /// {@macro shorebird_cli_command_runner} ShorebirdCliCommandRunner() : super(executableName, description) { argParser - ..addFlag( - 'version', - negatable: false, - help: 'Print the current version.', - ) + ..addFlag('version', negatable: false, help: 'Print the current version.') ..addFlag( 'verbose', abbr: 'v', @@ -55,13 +51,15 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { ..addOption( 'local-engine-src-path', hide: true, - help: 'Path to your engine src directory, if you are building Flutter ' + 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 ' + help: + 'Name of a build output within the engine out directory, if you ' 'are building Flutter locally.', ) ..addOption( @@ -104,10 +102,12 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { localEngine, localEngineHost, ]; - final localEngineArgsAreNull = - localEngineArgs.every((arg) => arg == null); - final localEngineArgsAreNotNull = - localEngineArgs.every((arg) => arg != null); + final localEngineArgsAreNull = localEngineArgs.every( + (arg) => arg == null, + ); + final localEngineArgsAreNotNull = localEngineArgs.every( + (arg) => arg != null, + ); final EngineConfig engineConfig; if (localEngineArgsAreNotNull) { engineConfig = EngineConfig( @@ -125,9 +125,10 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { } final process = ShorebirdProcess(); - final shorebirdArtifacts = engineConfig.localEngineSrcPath != null - ? const ShorebirdLocalEngineArtifacts() - : const ShorebirdCachedArtifacts(); + final shorebirdArtifacts = + engineConfig.localEngineSrcPath != null + ? const ShorebirdLocalEngineArtifacts() + : const ShorebirdCachedArtifacts(); return await runScoped>( () => runCommand(topLevelResults), values: { @@ -234,15 +235,13 @@ Engine • revision ${shorebirdEnv.shorebirdEngineRevision}'''); ), message: 'file an issue', ); - logger.info( - ''' + logger.info(''' If you aren't sure why this command failed, re-run with the ${lightCyan.wrap('--verbose')} flag to see more information. You can also $fileAnIssue if you think this is a bug. Please include the following log file in your report: ${currentRunLogFile.absolute.path} -''', - ); +'''); } if (topLevelResults.command?.name != UpgradeCommand.commandName) { diff --git a/packages/shorebird_cli/lib/src/shorebird_command.dart b/packages/shorebird_cli/lib/src/shorebird_command.dart index a165fc5a..6ffb481e 100644 --- a/packages/shorebird_cli/lib/src/shorebird_command.dart +++ b/packages/shorebird_cli/lib/src/shorebird_command.dart @@ -14,17 +14,16 @@ typedef HashFunction = String Function(List bytes); typedef UnzipFn = Future Function(String zipFilePath, String outputDir); /// Signature for a function which builds a [CodePushClient]. -typedef CodePushClientBuilder = CodePushClient Function({ - required http.Client httpClient, - Uri? hostedUri, -}); +typedef CodePushClientBuilder = + CodePushClient Function({required http.Client httpClient, Uri? hostedUri}); /// Signature for a function which starts a process (e.g. [Process.start]). -typedef StartProcess = Future Function( - String executable, - List arguments, { - bool runInShell, -}); +typedef StartProcess = + Future Function( + String executable, + List arguments, { + bool runInShell, + }); /// {@template shorebird_command} /// A command in the Shorebird CLI. diff --git a/packages/shorebird_cli/lib/src/shorebird_env.dart b/packages/shorebird_cli/lib/src/shorebird_env.dart index eb75a55d..6a03e766 100644 --- a/packages/shorebird_cli/lib/src/shorebird_env.dart +++ b/packages/shorebird_cli/lib/src/shorebird_env.dart @@ -22,13 +22,13 @@ ShorebirdEnv get shorebirdEnv => read(shorebirdEnvRef); class ShorebirdEnv { /// {@macro shorebird_env} const ShorebirdEnv({String? flutterRevisionOverride}) - : _flutterRevisionOverride = flutterRevisionOverride; + : _flutterRevisionOverride = flutterRevisionOverride; /// Copy the [ShorebirdEnv] and optionally override the flutter revision. ShorebirdEnv copyWith({String? flutterRevisionOverride}) => ShorebirdEnv( - flutterRevisionOverride: - flutterRevisionOverride ?? _flutterRevisionOverride, - ); + flutterRevisionOverride: + flutterRevisionOverride ?? _flutterRevisionOverride, + ); final String? _flutterRevisionOverride; @@ -52,12 +52,7 @@ class ShorebirdEnv { /// The Shorebird engine revision. String get shorebirdEngineRevision { return File( - p.join( - flutterDirectory.path, - 'bin', - 'internal', - 'engine.version', - ), + p.join(flutterDirectory.path, 'bin', 'internal', 'engine.version'), ).readAsStringSync().trim(); } @@ -84,13 +79,7 @@ class ShorebirdEnv { /// The root of the Shorebird-vended Flutter git checkout. Directory get flutterDirectory { return Directory( - p.join( - shorebirdRoot.path, - 'bin', - 'cache', - 'flutter', - flutterRevision, - ), + p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter', flutterRevision), ); } @@ -221,7 +210,8 @@ class ShorebirdEnv { /// its default. Uri? get hostedUri { try { - final baseUrl = platform.environment['SHOREBIRD_HOSTED_URL'] ?? + final baseUrl = + platform.environment['SHOREBIRD_HOSTED_URL'] ?? getShorebirdYaml()?.baseUrl; return baseUrl == null ? null : Uri.tryParse(baseUrl); } on Exception { @@ -238,34 +228,27 @@ class ShorebirdEnv { /// https://github.com/flutter/flutter/blob/0c10e1ca54ae74043909059e2ff56bf5dd0c3d23/packages/flutter_tools/lib/src/base/bot_detector.dart#L48-L69 bool get isRunningOnCI => platform.environment['BOT'] == 'true' - // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables || platform.environment['TRAVIS'] == 'true' || platform.environment['CONTINUOUS_INTEGRATION'] == 'true' || platform.environment.containsKey('CI') // Travis and AppVeyor - // https://www.appveyor.com/docs/environment-variables/ || platform.environment.containsKey('APPVEYOR') - // https://cirrus-ci.org/guide/writing-tasks/#environment-variables || platform.environment.containsKey('CIRRUS_CI') - // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html || (platform.environment.containsKey('AWS_REGION') && platform.environment.containsKey('CODEBUILD_INITIATOR')) - // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables || platform.environment.containsKey('JENKINS_URL') - // https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables || platform.environment.containsKey('GITHUB_ACTIONS') - // https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml || platform.environment.containsKey('TF_BUILD'); diff --git a/packages/shorebird_cli/lib/src/shorebird_flutter.dart b/packages/shorebird_cli/lib/src/shorebird_flutter.dart index ce23d059..b286b74b 100644 --- a/packages/shorebird_cli/lib/src/shorebird_flutter.dart +++ b/packages/shorebird_cli/lib/src/shorebird_flutter.dart @@ -33,10 +33,7 @@ class ShorebirdFlutter { 'https://github.com/shorebirdtech/flutter.git'; /// Arguments to pass to `flutter precache`. - List get precacheArgs => [ - '--android', - if (platform.isMacOS) '--ios', - ]; + List get precacheArgs => ['--android', if (platform.isMacOS) '--ios']; String _workingDirectory({String? revision}) { revision ??= shorebirdEnv.flutterRevision; @@ -59,10 +56,7 @@ class ShorebirdFlutter { await git.clone( url: flutterGitUrl, outputDirectory: targetDirectory.path, - args: [ - '--filter=tree:0', - '--no-checkout', - ], + args: ['--filter=tree:0', '--no-checkout'], ); // Checkout the correct revision. @@ -168,9 +162,7 @@ class ShorebirdFlutter { /// parsed. Future getVersionString() async { final flutterRevision = shorebirdEnv.flutterRevision; - return getVersionForRevision( - flutterRevision: flutterRevision, - ); + return getVersionForRevision(flutterRevision: flutterRevision); } /// The current Shorebird Flutter version as a [Version]. Returns null if the @@ -221,8 +213,9 @@ class ShorebirdFlutter { // If we were unable to parse the version, assume it's a revision hash. try { - final version = - await getVersionForRevision(flutterRevision: versionOrHash); + final version = await getVersionForRevision( + flutterRevision: versionOrHash, + ); if (version != null) { return versionOrHash; } @@ -245,8 +238,9 @@ class ShorebirdFlutter { try { // If we were unable to parse the version, assume it's a revision hash. - final versionString = - await getVersionForRevision(flutterRevision: versionOrHash); + final versionString = await getVersionForRevision( + flutterRevision: versionOrHash, + ); return versionString != null ? tryParseVersion(versionString) : null; } on Exception { return null; @@ -270,9 +264,9 @@ class ShorebirdFlutter { pattern: 'refs/remotes/origin/flutter_release/*', directory: _workingDirectory(revision: revision), ); - return LineSplitter.split(result) - .map((e) => e.replaceFirst('origin/flutter_release/', '')) - .toList(); + return LineSplitter.split( + result, + ).map((e) => e.replaceFirst('origin/flutter_release/', '')).toList(); } /// Use the provided [version] of Flutter. diff --git a/packages/shorebird_cli/lib/src/shorebird_process.dart b/packages/shorebird_cli/lib/src/shorebird_process.dart index 9d5f01fb..acaf85e6 100644 --- a/packages/shorebird_cli/lib/src/shorebird_process.dart +++ b/packages/shorebird_cli/lib/src/shorebird_process.dart @@ -117,9 +117,7 @@ class ShorebirdProcess { final resolvedEnvironment = environment ?? {}; if (useVendedFlutter) { // Note: this will overwrite existing environment values. - resolvedEnvironment.addAll( - _environmentOverrides(executable: executable), - ); + resolvedEnvironment.addAll(_environmentOverrides(executable: executable)); } final resolvedExecutable = _resolveExecutable( executable, @@ -151,9 +149,7 @@ class ShorebirdProcess { final resolvedEnvironment = baseEnvironment ?? {}; if (useVendedFlutter) { // Note: this will overwrite existing environment values. - resolvedEnvironment.addAll( - _environmentOverrides(executable: executable), - ); + resolvedEnvironment.addAll(_environmentOverrides(executable: executable)); } return resolvedEnvironment; @@ -216,9 +212,7 @@ $stderr'''); } } - Map _environmentOverrides({ - required String executable, - }) { + Map _environmentOverrides({required String executable}) { if (executable == 'flutter') { // If this ever changes we also need to update the `shorebird` shell // wrapper which downloads runs Flutter to fetch artifacts the first time. @@ -313,4 +307,5 @@ class ProcessWrapper { ); } } + // coverage:ignore-end diff --git a/packages/shorebird_cli/lib/src/shorebird_validator.dart b/packages/shorebird_cli/lib/src/shorebird_validator.dart index 547cd76d..806445c0 100644 --- a/packages/shorebird_cli/lib/src/shorebird_validator.dart +++ b/packages/shorebird_cli/lib/src/shorebird_validator.dart @@ -134,11 +134,10 @@ To fix, update your pubspec.yaml to include the following: Future> runValidators( List validators, ) async { - final validationIssues = (await Future.wait( - validators.map((v) => v.validate()), - )) - .flattened - .toList(); + final validationIssues = + (await Future.wait( + validators.map((v) => v.validate()), + )).flattened.toList(); for (final issue in validationIssues) { logger.info(issue.displayMessage); diff --git a/packages/shorebird_cli/lib/src/shorebird_web_console.dart b/packages/shorebird_cli/lib/src/shorebird_web_console.dart index d09ae49c..b649a42d 100644 --- a/packages/shorebird_cli/lib/src/shorebird_web_console.dart +++ b/packages/shorebird_cli/lib/src/shorebird_web_console.dart @@ -6,12 +6,7 @@ class ShorebirdWebConsole { } /// Returns a [Uri] for the Shorebird Web Console login page. - static Uri appReleaseUri( - String appId, - int releaseId, - ) { - return ShorebirdWebConsole.uri( - 'apps/$appId/releases/$releaseId', - ); + static Uri appReleaseUri(String appId, int releaseId) { + return ShorebirdWebConsole.uri('apps/$appId/releases/$releaseId'); } } diff --git a/packages/shorebird_cli/lib/src/validators/android_internet_permission_validator.dart b/packages/shorebird_cli/lib/src/validators/android_internet_permission_validator.dart index add88557..b3282fd4 100644 --- a/packages/shorebird_cli/lib/src/validators/android_internet_permission_validator.dart +++ b/packages/shorebird_cli/lib/src/validators/android_internet_permission_validator.dart @@ -68,20 +68,14 @@ The command you are running must be run within a Flutter app project that suppor Directory? get _androidSrcDirectory { final root = shorebirdEnv.getFlutterProjectRoot(); if (root == null) return null; - return Directory( - p.join( - root.path, - 'android', - 'app', - 'src', - ), - ); + return Directory(p.join(root.path, 'android', 'app', 'src')); } bool _androidManifestHasInternetPermission(String path) { final xmlDocument = XmlDocument.parse(File(path).readAsStringSync()); - return xmlDocument.rootElement.childElements - .any(_isInternetPermissionElement); + return xmlDocument.rootElement.childElements.any( + _isInternetPermissionElement, + ); } bool _isInternetPermissionElement(XmlElement element) { @@ -97,15 +91,9 @@ The command you are running must be run within a Flutter app project that suppor void _addInternetPermissionToFile(String path) { final xmlDocument = XmlDocument.parse(File(path).readAsStringSync()); xmlDocument.rootElement.children.add( - XmlElement( - XmlName('uses-permission'), - [ - XmlAttribute( - XmlName('android:name'), - 'android.permission.INTERNET', - ), - ], - ), + XmlElement(XmlName('uses-permission'), [ + XmlAttribute(XmlName('android:name'), 'android.permission.INTERNET'), + ]), ); File(path).writeAsStringSync(xmlDocument.toXmlString(pretty: true)); } diff --git a/packages/shorebird_cli/lib/src/validators/macos_network_entitlement_validator.dart b/packages/shorebird_cli/lib/src/validators/macos_network_entitlement_validator.dart index c3f855eb..f2a499bd 100644 --- a/packages/shorebird_cli/lib/src/validators/macos_network_entitlement_validator.dart +++ b/packages/shorebird_cli/lib/src/validators/macos_network_entitlement_validator.dart @@ -93,9 +93,10 @@ The command you are running must be run within a Flutter app project that suppor severity: ValidationIssueSeverity.error, message: '''${_releaseEntitlementsPlist!.path} is missing the Allow Unsigned Executable Memory ($allowUnsignedExecutableMemoryKey) entitlement.''', - fix: () => addAllowUnsignedExecutableMemoryEntitlementToPlist( - _releaseEntitlementsPlist!, - ), + fix: + () => addAllowUnsignedExecutableMemoryEntitlementToPlist( + _releaseEntitlementsPlist!, + ), ), ); } @@ -123,8 +124,9 @@ The command you are running must be run within a Flutter app project that suppor static bool hasAllowUnsignedExecutableMemoryEntitlement({ required File plistFile, }) { - return Plist(file: plistFile) - .properties[allowUnsignedExecutableMemoryKey] == + return Plist( + file: plistFile, + ).properties[allowUnsignedExecutableMemoryKey] == true; } diff --git a/packages/shorebird_cli/lib/src/validators/validators.dart b/packages/shorebird_cli/lib/src/validators/validators.dart index 8be09685..5d09db79 100644 --- a/packages/shorebird_cli/lib/src/validators/validators.dart +++ b/packages/shorebird_cli/lib/src/validators/validators.dart @@ -58,16 +58,16 @@ class ValidationIssue { /// Creates a new [ValidationIssue] with a severity of /// [ValidationIssueSeverity.error]. factory ValidationIssue.error({required String message}) => ValidationIssue( - severity: ValidationIssueSeverity.error, - message: message, - ); + severity: ValidationIssueSeverity.error, + message: message, + ); /// Creates a new [ValidationIssue] with a severity of /// [ValidationIssueSeverity.warning]. factory ValidationIssue.warning({required String message}) => ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: message, - ); + severity: ValidationIssueSeverity.warning, + message: message, + ); /// How important it is to fix this issue. final ValidationIssueSeverity severity; diff --git a/packages/shorebird_cli/pubspec.lock b/packages/shorebird_cli/pubspec.lock index d1a0aad1..b52cf2d9 100644 --- a/packages/shorebird_cli/pubspec.lock +++ b/packages/shorebird_cli/pubspec.lock @@ -892,4 +892,4 @@ packages: source: hosted version: "2.2.2" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" diff --git a/packages/shorebird_cli/pubspec.yaml b/packages/shorebird_cli/pubspec.yaml index d5b08b31..cd888b6c 100644 --- a/packages/shorebird_cli/pubspec.yaml +++ b/packages/shorebird_cli/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/shorebirdtech/shorebird publish_to: none environment: - sdk: ">=3.3.0 <4.0.0" + sdk: ">=3.7.0 <4.0.0" dependencies: archive: ^4.0.2 diff --git a/packages/shorebird_cli/test/src/android_sdk_test.dart b/packages/shorebird_cli/test/src/android_sdk_test.dart index 533d2265..f9d97ebd 100644 --- a/packages/shorebird_cli/test/src/android_sdk_test.dart +++ b/packages/shorebird_cli/test/src/android_sdk_test.dart @@ -31,17 +31,13 @@ void main() { String linuxAndroidHome() => p.join(homeDirectory.path, 'Android', 'Sdk'); String macAndroidHome() => p.join(homeDirectory.path, 'Library', 'Android', 'sdk'); - String windowsAndroidHome() => p.join( - homeDirectory.path, - 'AppData', - 'Local', - 'Android', - 'Sdk', - ); + String windowsAndroidHome() => + p.join(homeDirectory.path, 'AppData', 'Local', 'Android', 'Sdk'); void populateAndroidSdk({required String androidHomePath}) { - Directory(p.join(androidHomePath, 'platform-tools')) - .createSync(recursive: true); + Directory( + p.join(androidHomePath, 'platform-tools'), + ).createSync(recursive: true); } File createAapt({ @@ -85,9 +81,9 @@ void main() { group('path', () { group('when ANDROID_HOME is set', () { setUp(() { - when(() => platform.environment).thenReturn({ - kAndroidHome: homeDirectory.path, - }); + when( + () => platform.environment, + ).thenReturn({kAndroidHome: homeDirectory.path}); populateAndroidSdk(androidHomePath: homeDirectory.path); }); @@ -98,9 +94,9 @@ void main() { group('when ANDROID_SDK_ROOT is set', () { setUp(() { - when(() => platform.environment).thenReturn({ - kAndroidSdkRoot: homeDirectory.path, - }); + when( + () => platform.environment, + ).thenReturn({kAndroidSdkRoot: homeDirectory.path}); populateAndroidSdk(androidHomePath: homeDirectory.path); }); @@ -188,8 +184,9 @@ void main() { group('when aapt is not in a valid Android SDK', () { setUp(() { - when(() => osInterface.which('aapt')) - .thenReturn(homeDirectory.path); + when( + () => osInterface.which('aapt'), + ).thenReturn(homeDirectory.path); }); test('returns null', () { @@ -232,14 +229,14 @@ void main() { when(() => platform.isMacOS).thenReturn(true); // Add ANDROID_SDK_ROOT to path, but do not populate it. - when(() => platform.environment).thenReturn({ - kAndroidSdkRoot: homeDirectory.path, - }); + when( + () => platform.environment, + ).thenReturn({kAndroidSdkRoot: homeDirectory.path}); // Create a valid Android SDK in the user's home directory. - when(() => platform.environment).thenReturn( - {'HOME': homeDirectory.path}, - ); + when( + () => platform.environment, + ).thenReturn({'HOME': homeDirectory.path}); populateAndroidSdk(androidHomePath: macAndroidHome()); // Add adb to the path. This should not be returned, as the sdk in the @@ -272,20 +269,22 @@ void main() { }); test('returns correct value on Linux', () async { - when(() => platform.environment).thenReturn({ - kAndroidHome: homeDirectory.path, - }); + when( + () => platform.environment, + ).thenReturn({kAndroidHome: homeDirectory.path}); when(() => platform.isLinux).thenReturn(true); populateAndroidSdk(androidHomePath: homeDirectory.path); - final adb = - createAdb(androidHomePath: homeDirectory.path, isWindows: false); + final adb = createAdb( + androidHomePath: homeDirectory.path, + isWindows: false, + ); expect(runWithOverrides(() => androidSdk.adbPath), adb.path); }); test('returns correct value on MacOS', () async { - when(() => platform.environment).thenReturn({ - kAndroidHome: homeDirectory.path, - }); + when( + () => platform.environment, + ).thenReturn({kAndroidHome: homeDirectory.path}); when(() => platform.isMacOS).thenReturn(true); final adb = File(p.join(homeDirectory.path, 'platform-tools', 'adb')) ..createSync(recursive: true); @@ -293,9 +292,9 @@ void main() { }); test('returns correct value on Windows', () async { - when(() => platform.environment).thenReturn({ - kAndroidHome: homeDirectory.path, - }); + when( + () => platform.environment, + ).thenReturn({kAndroidHome: homeDirectory.path}); when(() => platform.isWindows).thenReturn(true); final adb = File( p.join(homeDirectory.path, 'platform-tools', 'adb.exe'), diff --git a/packages/shorebird_cli/test/src/android_studio_test.dart b/packages/shorebird_cli/test/src/android_studio_test.dart index c9fd2113..8a7c592e 100644 --- a/packages/shorebird_cli/test/src/android_studio_test.dart +++ b/packages/shorebird_cli/test/src/android_studio_test.dart @@ -18,9 +18,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( () => body(), - values: { - platformRef.overrideWith(() => platform), - }, + values: {platformRef.overrideWith(() => platform)}, ); } @@ -54,9 +52,8 @@ void main() { Directory setUpLocalAppData() { final tempDir = setUpAppTempDir(); - final googleDir = Directory( - p.join(tempDir.path, 'Google'), - )..createSync(recursive: true); + final googleDir = Directory(p.join(tempDir.path, 'Google')) + ..createSync(recursive: true); for (final version in androidStudioVersions) { final androidStudioDir = Directory( @@ -75,9 +72,7 @@ void main() { // Test the case where an Android Studio directory doesn't have // a .home file. - File( - p.join(androidStudioDir.path, '.home'), - ) + File(p.join(androidStudioDir.path, '.home')) ..createSync(recursive: true) ..writeAsStringSync(installPath); } @@ -88,9 +83,9 @@ void main() { test('returns correct path', () async { final appDataDir = setUpLocalAppData(); - when(() => platform.environment).thenReturn( - {'LOCALAPPDATA': appDataDir.path}, - ); + when( + () => platform.environment, + ).thenReturn({'LOCALAPPDATA': appDataDir.path}); await expectLater( runWithOverrides(() => androidStudio.path), @@ -151,9 +146,9 @@ void main() { when(() => platform.isLinux).thenReturn(true); userHomeDir = Directory.systemTemp.createTempSync(); - when(() => platform.environment).thenReturn({ - 'HOME': userHomeDir.path, - }); + when( + () => platform.environment, + ).thenReturn({'HOME': userHomeDir.path}); }); group('when installed at ~', () { diff --git a/packages/shorebird_cli/test/src/archive/directory_archive_test.dart b/packages/shorebird_cli/test/src/archive/directory_archive_test.dart index cb41b533..ae5ebad6 100644 --- a/packages/shorebird_cli/test/src/archive/directory_archive_test.dart +++ b/packages/shorebird_cli/test/src/archive/directory_archive_test.dart @@ -27,8 +27,8 @@ void main() { expect(extractedContents, hasLength(numFiles)); for (var i = 0; i < numFiles; i++) { final extractedFile = extractedContents.whereType().firstWhere( - (entity) => p.basename(entity.path) == '$i.txt', - ); + (entity) => p.basename(entity.path) == '$i.txt', + ); expect(extractedFile.existsSync(), isTrue); expect(extractedFile.readAsStringSync(), equals('$i')); } diff --git a/packages/shorebird_cli/test/src/archive_analysis/android_archive_differ_test.dart b/packages/shorebird_cli/test/src/archive_analysis/android_archive_differ_test.dart index fa316cf0..6106f3d1 100644 --- a/packages/shorebird_cli/test/src/archive_analysis/android_archive_differ_test.dart +++ b/packages/shorebird_cli/test/src/archive_analysis/android_archive_differ_test.dart @@ -7,21 +7,31 @@ void main() { group(AndroidArchiveDiffer, () { final aabFixturesBasePath = p.join('test', 'fixtures', 'aabs'); final baseAabPath = p.join(aabFixturesBasePath, 'base.aab'); - final changedAssetAabPath = - p.join(aabFixturesBasePath, 'changed_asset.aab'); + final changedAssetAabPath = p.join( + aabFixturesBasePath, + 'changed_asset.aab', + ); final changedDartAabPath = p.join(aabFixturesBasePath, 'changed_dart.aab'); - final changedKotlinAabPath = - p.join(aabFixturesBasePath, 'changed_kotlin.aab'); - final changedDartAndAssetAabPath = - p.join(aabFixturesBasePath, 'changed_dart_and_asset.aab'); + final changedKotlinAabPath = p.join( + aabFixturesBasePath, + 'changed_kotlin.aab', + ); + final changedDartAndAssetAabPath = p.join( + aabFixturesBasePath, + 'changed_dart_and_asset.aab', + ); final aarFixturesBasePath = p.join('test', 'fixtures', 'aars'); final baseAarPath = p.join(aarFixturesBasePath, 'base.aar'); - final changedAssetAarPath = - p.join(aarFixturesBasePath, 'changed_asset.aar'); + final changedAssetAarPath = p.join( + aarFixturesBasePath, + 'changed_asset.aar', + ); final changedDartAarPath = p.join(aarFixturesBasePath, 'changed_dart.aar'); - final changedDartAndAssetAarPath = - p.join(aarFixturesBasePath, 'changed_dart_and_asset.aar'); + final changedDartAndAssetAarPath = p.join( + aarFixturesBasePath, + 'changed_dart_and_asset.aar', + ); late AndroidArchiveDiffer differ; @@ -36,20 +46,19 @@ void main() { }); test('finds differences between two different aabs', () async { - final fileSetDiff = - await differ.changedFiles(baseAabPath, changedDartAabPath); - expect( - fileSetDiff.changedPaths, - { - 'BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb', - 'base/lib/arm64-v8a/libapp.so', - 'base/lib/armeabi-v7a/libapp.so', - 'base/lib/x86_64/libapp.so', - 'META-INF/ANDROIDD.SF', - 'META-INF/ANDROIDD.RSA', - 'META-INF/MANIFEST.MF', - }, + final fileSetDiff = await differ.changedFiles( + baseAabPath, + changedDartAabPath, ); + expect(fileSetDiff.changedPaths, { + 'BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb', + 'base/lib/arm64-v8a/libapp.so', + 'base/lib/armeabi-v7a/libapp.so', + 'base/lib/x86_64/libapp.so', + 'META-INF/ANDROIDD.SF', + 'META-INF/ANDROIDD.RSA', + 'META-INF/MANIFEST.MF', + }); }); }); @@ -59,24 +68,30 @@ void main() { }); test('detects asset changes', () async { - final fileSetDiff = - await differ.changedFiles(baseAabPath, changedAssetAabPath); + final fileSetDiff = await differ.changedFiles( + baseAabPath, + changedAssetAabPath, + ); expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty); expect(differ.dartFileSetDiff(fileSetDiff), isEmpty); expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); }); test('detects kotlin changes', () async { - final fileSetDiff = - await differ.changedFiles(baseAabPath, changedKotlinAabPath); + final fileSetDiff = await differ.changedFiles( + baseAabPath, + changedKotlinAabPath, + ); expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); expect(differ.dartFileSetDiff(fileSetDiff), isEmpty); expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty); }); test('detects dart changes', () async { - final fileSetDiff = - await differ.changedFiles(baseAabPath, changedDartAabPath); + final fileSetDiff = await differ.changedFiles( + baseAabPath, + changedDartAabPath, + ); expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty); expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); @@ -101,8 +116,10 @@ void main() { }); test('finds differences between two different aars', () async { - final fileSetDiff = - await differ.changedFiles(baseAarPath, changedDartAarPath); + final fileSetDiff = await differ.changedFiles( + baseAarPath, + changedDartAarPath, + ); expect(fileSetDiff.changedPaths, {'jni/arm64-v8a/libapp.so'}); }); }); @@ -113,16 +130,20 @@ void main() { }); test('detects asset changes', () async { - final fileSetDiff = - await differ.changedFiles(baseAarPath, changedAssetAarPath); + final fileSetDiff = await differ.changedFiles( + baseAarPath, + changedAssetAarPath, + ); expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty); expect(differ.dartFileSetDiff(fileSetDiff), isEmpty); expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); }); test('detects dart changes', () async { - final fileSetDiff = - await differ.changedFiles(baseAarPath, changedDartAarPath); + final fileSetDiff = await differ.changedFiles( + baseAarPath, + changedDartAarPath, + ); expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty); expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); diff --git a/packages/shorebird_cli/test/src/archive_analysis/apple_archive_differ_test.dart b/packages/shorebird_cli/test/src/archive_analysis/apple_archive_differ_test.dart index ac0d8af9..9a843879 100644 --- a/packages/shorebird_cli/test/src/archive_analysis/apple_archive_differ_test.dart +++ b/packages/shorebird_cli/test/src/archive_analysis/apple_archive_differ_test.dart @@ -6,10 +6,7 @@ import 'package:test/test.dart'; void main() { final xcarchiveFixturesBasePath = p.join('test', 'fixtures', 'xcarchives'); - final baseIpaPath = p.join( - xcarchiveFixturesBasePath, - 'base.xcarchive.zip', - ); + final baseIpaPath = p.join(xcarchiveFixturesBasePath, 'base.xcarchive.zip'); final baseChangedUuidPath = p.join( xcarchiveFixturesBasePath, 'base_changed_uuid.xcarchive.zip', @@ -32,181 +29,175 @@ void main() { 'fixtures', 'xcframeworks', ); - final baseXcframeworkPath = - p.join(xcframeworkFixturesBasePath, 'base.xcframework.zip'); - final changedAssetXcframeworkPath = - p.join(xcframeworkFixturesBasePath, 'changed_asset.xcframework.zip'); - final changedDartXcframeworkPath = - p.join(xcframeworkFixturesBasePath, 'changed_dart.xcframework.zip'); + final baseXcframeworkPath = p.join( + xcframeworkFixturesBasePath, + 'base.xcframework.zip', + ); + final changedAssetXcframeworkPath = p.join( + xcframeworkFixturesBasePath, + 'changed_asset.xcframework.zip', + ); + final changedDartXcframeworkPath = p.join( + xcframeworkFixturesBasePath, + 'changed_dart.xcframework.zip', + ); - group( - AppleArchiveDiffer, - () { - late AppleArchiveDiffer differ; + group(AppleArchiveDiffer, () { + late AppleArchiveDiffer differ; - setUp(() { - differ = const AppleArchiveDiffer(); + setUp(() { + differ = const AppleArchiveDiffer(); + }); + + group('appRegex', () { + test('identifies Runner.app/Runner as an app file', () { + expect( + AppleArchiveDiffer.xcFrameworkAppRegex.hasMatch( + 'Products/Applications/Runner.app/Runner', + ), + isTrue, + ); }); - group('appRegex', () { - test('identifies Runner.app/Runner as an app file', () { + test('does not identify Runner.app/Assets.car as an app file', () { + expect( + AppleArchiveDiffer.xcFrameworkAppRegex.hasMatch( + 'Products/Applications/Runner.app/Assets.car', + ), + isFalse, + ); + }); + }); + + group('xcarchive', () { + group('changedPaths', () { + test('finds no differences between the same xcarchive', () async { + expect(await differ.changedFiles(baseIpaPath, baseIpaPath), isEmpty); + }); + + test('finds no differences when only Mach-O UUID differs', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + baseChangedUuidPath, + ); + expect(fileSetDiff, isEmpty); + }); + + test('finds differences between two different xcarchives', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedAssetXcarchivePath, + ); + if (platform.isMacOS) { + expect(fileSetDiff.changedPaths, { + 'Products/Applications/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources', + 'Products/Applications/Runner.app/Frameworks/App.framework/flutter_assets/NOTICES.Z', + 'Products/Applications/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json', + 'Info.plist', + }); + } else { + expect(fileSetDiff.changedPaths, { + 'Products/Applications/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources', + 'Products/Applications/Runner.app/Frameworks/App.framework/App', + 'Products/Applications/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json', + 'Info.plist', + }); + } + }); + }); + + group('changedFiles', () { + test('detects asset changes', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedAssetXcarchivePath, + ); + expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty); expect( - AppleArchiveDiffer.xcFrameworkAppRegex.hasMatch( - 'Products/Applications/Runner.app/Runner', - ), + differ.dartFileSetDiff(fileSetDiff), + platform.isMacOS ? isEmpty : isNotEmpty, + ); + expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); + }); + + test('detects dart changes', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedDartXcarchivePath, + ); + expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); + expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty); + expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); + }); + + test('detects swift changes', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedSwiftXcarchivePath, + ); + expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); + expect(differ.dartFileSetDiff(fileSetDiff), isEmpty); + expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty); + }); + }); + + group('containsPotentiallyBreakingAssetDiffs', () { + test('returns true if a file in flutter_assets has changed', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedAssetXcarchivePath, + ); + expect( + differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff), isTrue, ); }); - test('does not identify Runner.app/Assets.car as an app file', () { + test( + 'returns false if no files in flutter_assets has changed', + () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedDartXcarchivePath, + ); + expect( + differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff), + isFalse, + ); + }, + ); + }); + + group('containsPotentiallyBreakingNativeDiffs', () { + test('returns true if Swift files have been changed', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedSwiftXcarchivePath, + ); expect( - AppleArchiveDiffer.xcFrameworkAppRegex.hasMatch( - 'Products/Applications/Runner.app/Assets.car', - ), + differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff), + isTrue, + ); + }); + + test('returns false if Swift files have not been changed', () async { + final fileSetDiff = await differ.changedFiles( + baseIpaPath, + changedAssetXcarchivePath, + ); + expect( + differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff), isFalse, ); }); }); + }); - group('xcarchive', () { - group('changedPaths', () { - test('finds no differences between the same xcarchive', () async { - expect( - await differ.changedFiles(baseIpaPath, baseIpaPath), - isEmpty, - ); - }); - - test('finds no differences when only Mach-O UUID differs', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - baseChangedUuidPath, - ); - expect( - fileSetDiff, - isEmpty, - ); - }); - - test('finds differences between two different xcarchives', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedAssetXcarchivePath, - ); - if (platform.isMacOS) { - expect( - fileSetDiff.changedPaths, - { - 'Products/Applications/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources', - 'Products/Applications/Runner.app/Frameworks/App.framework/flutter_assets/NOTICES.Z', - 'Products/Applications/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json', - 'Info.plist', - }, - ); - } else { - expect( - fileSetDiff.changedPaths, - { - 'Products/Applications/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources', - 'Products/Applications/Runner.app/Frameworks/App.framework/App', - 'Products/Applications/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json', - 'Info.plist', - }, - ); - } - }); - }); - - group('changedFiles', () { - test('detects asset changes', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedAssetXcarchivePath, - ); - expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty); - expect( - differ.dartFileSetDiff(fileSetDiff), - platform.isMacOS ? isEmpty : isNotEmpty, - ); - expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); - }); - - test('detects dart changes', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedDartXcarchivePath, - ); - expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); - expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty); - expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); - }); - - test('detects swift changes', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedSwiftXcarchivePath, - ); - expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); - expect(differ.dartFileSetDiff(fileSetDiff), isEmpty); - expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty); - }); - }); - - group('containsPotentiallyBreakingAssetDiffs', () { - test('returns true if a file in flutter_assets has changed', - () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedAssetXcarchivePath, - ); - expect( - differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff), - isTrue, - ); - }); - - test('returns false if no files in flutter_assets has changed', - () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedDartXcarchivePath, - ); - expect( - differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff), - isFalse, - ); - }); - }); - - group('containsPotentiallyBreakingNativeDiffs', () { - test('returns true if Swift files have been changed', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedSwiftXcarchivePath, - ); - expect( - differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff), - isTrue, - ); - }); - - test('returns false if Swift files have not been changed', () async { - final fileSetDiff = await differ.changedFiles( - baseIpaPath, - changedAssetXcarchivePath, - ); - expect( - differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff), - isFalse, - ); - }); - }); - }); - - group('xcframework', () { - group('changedPaths', () { - test('finds no differences between the same zipped xcframeworks', - () async { + group('xcframework', () { + group('changedPaths', () { + test( + 'finds no differences between the same zipped xcframeworks', + () async { expect( await differ.changedFiles( baseXcframeworkPath, @@ -214,66 +205,59 @@ void main() { ), isEmpty, ); - }); + }, + ); - test('finds differences between two differed zipped xcframeworks', - () async { - final fileSetDiff = await differ.changedFiles( - baseXcframeworkPath, - changedAssetXcframeworkPath, - ); - if (platform.isMacOS) { - expect( - fileSetDiff.changedPaths, - { - 'ios-arm64_x86_64-simulator/App.framework/_CodeSignature/CodeResources', - 'ios-arm64_x86_64-simulator/App.framework/flutter_assets/assets/asset.json', - 'ios-arm64/App.framework/_CodeSignature/CodeResources', - 'ios-arm64/App.framework/flutter_assets/assets/asset.json', - }, - ); - } else { - expect( - fileSetDiff.changedPaths, - { - 'ios-arm64_x86_64-simulator/App.framework/_CodeSignature/CodeResources', - 'ios-arm64_x86_64-simulator/App.framework/App', - 'ios-arm64_x86_64-simulator/App.framework/flutter_assets/assets/asset.json', - 'ios-arm64/App.framework/_CodeSignature/CodeResources', - 'ios-arm64/App.framework/App', - 'ios-arm64/App.framework/flutter_assets/assets/asset.json', - }, - ); - } - }); - }); - - group('changedFiles', () { - test('detects asset changes', () async { - final fileSetDiff = await differ.changedFiles( - baseXcframeworkPath, - changedAssetXcframeworkPath, - ); - expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty); - expect( - differ.dartFileSetDiff(fileSetDiff), - platform.isMacOS ? isEmpty : isNotEmpty, - ); - expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); - }); - - test('detects dart changes', () async { - final fileSetDiff = await differ.changedFiles( - baseXcframeworkPath, - changedDartXcframeworkPath, - ); - expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); - expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty); - expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); - }); + test('finds differences between two ' + 'differed zipped xcframeworks', () async { + final fileSetDiff = await differ.changedFiles( + baseXcframeworkPath, + changedAssetXcframeworkPath, + ); + if (platform.isMacOS) { + expect(fileSetDiff.changedPaths, { + 'ios-arm64_x86_64-simulator/App.framework/_CodeSignature/CodeResources', + 'ios-arm64_x86_64-simulator/App.framework/flutter_assets/assets/asset.json', + 'ios-arm64/App.framework/_CodeSignature/CodeResources', + 'ios-arm64/App.framework/flutter_assets/assets/asset.json', + }); + } else { + expect(fileSetDiff.changedPaths, { + 'ios-arm64_x86_64-simulator/App.framework/_CodeSignature/CodeResources', + 'ios-arm64_x86_64-simulator/App.framework/App', + 'ios-arm64_x86_64-simulator/App.framework/flutter_assets/assets/asset.json', + 'ios-arm64/App.framework/_CodeSignature/CodeResources', + 'ios-arm64/App.framework/App', + 'ios-arm64/App.framework/flutter_assets/assets/asset.json', + }); + } }); }); - }, - testOn: 'mac-os', - ); + + group('changedFiles', () { + test('detects asset changes', () async { + final fileSetDiff = await differ.changedFiles( + baseXcframeworkPath, + changedAssetXcframeworkPath, + ); + expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty); + expect( + differ.dartFileSetDiff(fileSetDiff), + platform.isMacOS ? isEmpty : isNotEmpty, + ); + expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); + }); + + test('detects dart changes', () async { + final fileSetDiff = await differ.changedFiles( + baseXcframeworkPath, + changedDartXcframeworkPath, + ); + expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty); + expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty); + expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty); + }); + }); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/archive_analysis/archive_differ_test.dart b/packages/shorebird_cli/test/src/archive_analysis/archive_differ_test.dart index ccdcf6eb..6b52ab67 100644 --- a/packages/shorebird_cli/test/src/archive_analysis/archive_differ_test.dart +++ b/packages/shorebird_cli/test/src/archive_analysis/archive_differ_test.dart @@ -65,8 +65,7 @@ class TestArchiveDiffer extends ArchiveDiffer { Future changedFiles( String oldArchivePath, String newArchivePath, - ) async => - changedFileSetDiff; + ) async => changedFileSetDiff; @override bool containsPotentiallyBreakingNativeDiffs(FileSetDiff fileSetDiff) => false; diff --git a/packages/shorebird_cli/test/src/archive_analysis/file_set_diff_test.dart b/packages/shorebird_cli/test/src/archive_analysis/file_set_diff_test.dart index 54c034aa..baaa6907 100644 --- a/packages/shorebird_cli/test/src/archive_analysis/file_set_diff_test.dart +++ b/packages/shorebird_cli/test/src/archive_analysis/file_set_diff_test.dart @@ -6,14 +6,8 @@ void main() { group(FileSetDiff, () { group('fromPathHashes', () { test('detects added, changed, and removed files', () { - final oldPathHashes = { - 'a': 'asdf', - 'b': 'qwer', - }; - final newPathHashes = { - 'a': 'qwer', - 'c': 'zxcv', - }; + final oldPathHashes = {'a': 'asdf', 'b': 'qwer'}; + final newPathHashes = {'a': 'qwer', 'c': 'zxcv'}; final fileSetDiff = FileSetDiff.fromPathHashes( oldPathHashes: oldPathHashes, @@ -32,17 +26,14 @@ void main() { changedPaths: {'c'}, removedPaths: {'d'}, ); - expect( - fileSetDiff.prettyString, - ''' + expect(fileSetDiff.prettyString, ''' Added files: a b Changed files: c Removed files: - d''', - ); + d'''); }); test('does not include empty path sets', () { @@ -51,13 +42,10 @@ void main() { changedPaths: {}, removedPaths: {}, ); - expect( - fileSetDiff.prettyString, - ''' + expect(fileSetDiff.prettyString, ''' Added files: a - b''', - ); + b'''); }); }); diff --git a/packages/shorebird_cli/test/src/archive_analysis/portable_executable_test.dart b/packages/shorebird_cli/test/src/archive_analysis/portable_executable_test.dart index eba3aac2..13dfcd26 100644 --- a/packages/shorebird_cli/test/src/archive_analysis/portable_executable_test.dart +++ b/packages/shorebird_cli/test/src/archive_analysis/portable_executable_test.dart @@ -32,12 +32,12 @@ void main() { group('when given a valid exe', () { late File file; - final winArchivesFixturesBasePath = - p.join('test', 'fixtures', 'win_archives'); - final releasePath = p.join( - winArchivesFixturesBasePath, - 'release.zip', + final winArchivesFixturesBasePath = p.join( + 'test', + 'fixtures', + 'win_archives', ); + final releasePath = p.join(winArchivesFixturesBasePath, 'release.zip'); setUp(() async { final tempDir = Directory.systemTemp.createTempSync(); diff --git a/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart b/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart index 9802227b..10efb7c3 100644 --- a/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart +++ b/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart @@ -35,64 +35,67 @@ void main() { }); group('gradle', () { - group('when an error is recognized but no fix recommendation is found', - () { - test('has a Flutter error and no fix recommendation', () { - final exception = ArtifactBuildException( - 'message', - stderr: [ - 'some stderr output', - 'FAILURE: Build failed with an exception.', - '* Exception is:', - 'some stack trace', - '* Try:', - 'some recommendation', - ], - stdout: ['some stdout output'], - ); - expect( - exception.flutterError, - equals('FAILURE: Build failed with an exception.'), - ); - expect(exception.fixRecommendation, isNull); - }); - }); + group( + 'when an error is recognized but no fix recommendation is found', + () { + test('has a Flutter error and no fix recommendation', () { + final exception = ArtifactBuildException( + 'message', + stderr: [ + 'some stderr output', + 'FAILURE: Build failed with an exception.', + '* Exception is:', + 'some stack trace', + '* Try:', + 'some recommendation', + ], + stdout: ['some stdout output'], + ); + expect( + exception.flutterError, + equals('FAILURE: Build failed with an exception.'), + ); + expect(exception.fixRecommendation, isNull); + }); + }, + ); group( - 'when a known error is recognized and a fix recommendation is found', - () { - test('has a Flutter error and a fix recommendation', () { - final exception = ArtifactBuildException( - 'message', - stderr: [ - 'some stderr output', - 'FAILURE: Build failed with an exception.', - '* What went wrong:', - "Execution failed for task ':app:signReleaseBundle'.", - r'''> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable''', - '> java.lang.NullPointerException (no error message)', - '* Exception is:', - 'some stack trace', - '* Try:', - 'some recommendation', - ], - stdout: ['some stdout output'], - ); - expect( - exception.flutterError, - equals(r''' + 'when a known error is recognized and a fix recommendation is found', + () { + test('has a Flutter error and a fix recommendation', () { + final exception = ArtifactBuildException( + 'message', + stderr: [ + 'some stderr output', + 'FAILURE: Build failed with an exception.', + '* What went wrong:', + "Execution failed for task ':app:signReleaseBundle'.", + r'''> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable''', + '> java.lang.NullPointerException (no error message)', + '* Exception is:', + 'some stack trace', + '* Try:', + 'some recommendation', + ], + stdout: ['some stdout output'], + ); + expect( + exception.flutterError, + equals(r''' FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:signReleaseBundle'. > A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable > java.lang.NullPointerException (no error message)'''), - ); - expect( - exception.fixRecommendation, - contains('This error is likely due to a missing keystore file'), - ); - }); - }); + ); + expect( + exception.fixRecommendation, + contains('This error is likely due to a missing keystore file'), + ); + }); + }, + ); group('when a fix recommendation is provided', () { test('does not read output to find fix recommendation', () { @@ -120,34 +123,33 @@ Execution failed for task ':app:signReleaseBundle'. group('xcode archiving', () { group( - 'when a known error is recognized and a fix recommendation is found', - () { - test('has a Flutter error and a fix recommendation', () { - final exception = ArtifactBuildException( - 'message', - stderr: [ - 'some stderr output', - 'Error (Xcode):', - 'some error message', - 'some stack trace', - 'some recommendation', - 'Encountered error while archiving for device', - 'not part of the reported error', - ], - stdout: ['some stdout output'], - ); - expect( - exception.flutterError, - equals( - ''' + 'when a known error is recognized and a fix recommendation is found', + () { + test('has a Flutter error and a fix recommendation', () { + final exception = ArtifactBuildException( + 'message', + stderr: [ + 'some stderr output', + 'Error (Xcode):', + 'some error message', + 'some stack trace', + 'some recommendation', + 'Encountered error while archiving for device', + 'not part of the reported error', + ], + stdout: ['some stdout output'], + ); + expect( + exception.flutterError, + equals(''' Error (Xcode): some error message some stack trace -some recommendation''', - ), - ); - }); - }); +some recommendation'''), + ); + }); + }, + ); }); }); } diff --git a/packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart b/packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart index 2535efae..69c3afd4 100644 --- a/packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart +++ b/packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart @@ -47,8 +47,9 @@ void main() { processRef.overrideWith(() => shorebirdProcess), shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdAndroidArtifactsRef - .overrideWith(() => shorebirdAndroidArtifacts), + shorebirdAndroidArtifactsRef.overrideWith( + () => shorebirdAndroidArtifacts, + ), }, ); } @@ -79,8 +80,9 @@ void main() { useVendedFlutter: false, ), ).thenAnswer((_) async => pubGetProcessResult); - when(() => pubGetProcessResult.exitCode) - .thenReturn(ExitCode.success.code); + when( + () => pubGetProcessResult.exitCode, + ).thenReturn(ExitCode.success.code); when( () => shorebirdProcess.run( any(), @@ -99,25 +101,18 @@ void main() { ), ).thenAnswer((_) async => buildProcess); when(() => buildProcess.stdout).thenAnswer( - (_) => Stream.fromIterable( - [ - 'Some build output', - ].map(utf8.encode), - ), + (_) => Stream.fromIterable(['Some build output'].map(utf8.encode)), ); when(() => buildProcess.stderr).thenAnswer( - (_) => Stream.fromIterable( - [ - 'Some build output', - ].map(utf8.encode), - ), + (_) => Stream.fromIterable(['Some build output'].map(utf8.encode)), ); when(() => buildProcess.exitCode).thenAnswer((_) async => 0); when(() => logger.progress(any())).thenReturn(MockProgress()); when(() => logger.info(any())).thenReturn(null); - when(() => operatingSystemInterface.which('flutter')) - .thenReturn('/path/to/flutter'); + when( + () => operatingSystemInterface.which('flutter'), + ).thenReturn('/path/to/flutter'); when(() => shorebirdEnv.flutterRevision).thenReturn('1234'); when(shorebirdEnv.getShorebirdProjectRoot).thenReturn(projectRoot); @@ -125,13 +120,12 @@ void main() { builder = ArtifactBuilder(); }); - void verifyCorrectFlutterPubGet( - Future Function() testCall, - ) { + void verifyCorrectFlutterPubGet(Future Function() testCall) { group('when flutter is installed', () { setUp(() { - when(() => operatingSystemInterface.which('flutter')) - .thenReturn('/path/to/flutter'); + when( + () => operatingSystemInterface.which('flutter'), + ).thenReturn('/path/to/flutter'); }); test('runs flutter pub get with system flutter', () async { @@ -153,21 +147,20 @@ void main() { await testCall(); verify( - () => logger.warn( - ''' + () => logger.warn(''' Build was successful, but `flutter pub get` failed to run after the build completed. You may see unexpected behavior in VS Code. Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCodeUrl.toLink()}. -''', - ), +'''), ).called(1); }); }); group('when flutter is not installed', () { setUp(() { - when(() => operatingSystemInterface.which('flutter')) - .thenReturn(null); + when( + () => operatingSystemInterface.which('flutter'), + ).thenReturn(null); }); test('does not attempt to run flutter pub get', () async { @@ -219,20 +212,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.start( - 'flutter', - [ - 'build', - 'appbundle', - '--release', - '--flavor=flavor', - '--target=target', - '--target-platform=android-arm64', - '--foo', - 'bar', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.start('flutter', [ + 'build', + 'appbundle', + '--release', + '--flavor=flavor', + '--target=target', + '--target-platform=android-arm64', + '--foo', + 'bar', + ], runInShell: any(named: 'runInShell')), ).called(1); }); @@ -252,9 +241,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod '--target-platform=android-arm64', ], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).thenAnswer((_) async => buildProcess); }); @@ -281,9 +268,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod '--target-platform=android-arm64', ], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).called(1); }); @@ -356,29 +341,25 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod when(() => buildProcess.stdout).thenAnswer( (_) => Stream.fromIterable( [ - 'Some build output', - '[ ] > Task :app:bundleRelease', - 'More build output', - '[ ] > Task :app:someOtherTask', - 'Even more build output', - ] + 'Some build output', + '[ ] > Task :app:bundleRelease', + 'More build output', + '[ ] > Task :app:someOtherTask', + 'Even more build output', + ] .map((line) => '$line${Platform.lineTerminator}') .map(utf8.encode), ), ); when(() => buildProcess.stderr).thenAnswer( - (_) => Stream.fromIterable( - ['Some build output'].map(utf8.encode), - ), + (_) => Stream.fromIterable(['Some build output'].map(utf8.encode)), ); }); test('updates progress with gradle task names', () async { await expectLater( runWithOverrides( - () => builder.buildAppBundle( - buildProgress: progress, - ), + () => builder.buildAppBundle(buildProgress: progress), ), completes, ); @@ -389,21 +370,20 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod // Ensure we update the progress in the correct order and with the // correct messages, and reset to the base message after the build // completes. - verifyInOrder( - [ - () => progress.updateDetailMessage('Task :app:bundleRelease'), - () => progress.updateDetailMessage('Task :app:someOtherTask'), - () => progress.updateDetailMessage(null), - ], - ); + verifyInOrder([ + () => progress.updateDetailMessage('Task :app:bundleRelease'), + () => progress.updateDetailMessage('Task :app:someOtherTask'), + () => progress.updateDetailMessage(null), + ]); }); }); group('after a build', () { group('when the build is successful', () { setUp(() { - when(() => buildProcess.exitCode) - .thenAnswer((_) async => ExitCode.success.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); }); verifyCorrectFlutterPubGet( @@ -412,8 +392,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('when the build fails', () { setUp(() { - when(() => buildProcess.exitCode) - .thenAnswer((_) async => ExitCode.software.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.software.code); }); verifyCorrectFlutterPubGet( @@ -461,20 +442,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.run( - 'flutter', - [ - 'build', - 'apk', - '--release', - '--flavor=flavor', - '--target=target', - '--target-platform=android-arm64', - '--foo', - 'bar', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.run('flutter', [ + 'build', + 'apk', + '--release', + '--flavor=flavor', + '--target=target', + '--target-platform=android-arm64', + '--foo', + 'bar', + ], runInShell: any(named: 'runInShell')), ).called(1); }); @@ -494,9 +471,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod '--target-platform=android-arm64', ], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).thenAnswer((_) async => buildProcessResult); }); @@ -523,9 +498,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod '--target-platform=android-arm64', ], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).called(1); }); @@ -592,8 +565,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('after a build', () { group('when the build is successful', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.success.code); + when( + () => buildProcessResult.exitCode, + ).thenReturn(ExitCode.success.code); }); verifyCorrectFlutterPubGet( @@ -602,8 +576,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('when the build fails', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.software.code); + when( + () => buildProcessResult.exitCode, + ).thenReturn(ExitCode.software.code); }); verifyCorrectFlutterPubGet( @@ -651,28 +626,25 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.run( - 'flutter', - [ - 'build', - 'aar', - '--no-debug', - '--no-profile', - '--build-number=1.0', - '--target-platform=android-arm64', - '--foo', - 'bar', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.run('flutter', [ + 'build', + 'aar', + '--no-debug', + '--no-profile', + '--build-number=1.0', + '--target-platform=android-arm64', + '--foo', + 'bar', + ], runInShell: any(named: 'runInShell')), ).called(1); }); group('after a build', () { group('when the build is successful', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.success.code); + when( + () => buildProcessResult.exitCode, + ).thenReturn(ExitCode.success.code); }); verifyCorrectFlutterPubGet( @@ -683,8 +655,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('when the build fails', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.software.code); + when( + () => buildProcessResult.exitCode, + ).thenReturn(ExitCode.software.code); }); verifyCorrectFlutterPubGet( @@ -718,15 +691,11 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod () => artifactManager.linuxBundleDirectory, ).thenReturn(linuxBundleDirectory); when( - () => shorebirdProcess.start( - 'flutter', - [ - 'build', - 'linux', - '--release', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.start('flutter', [ + 'build', + 'linux', + '--release', + ], runInShell: any(named: 'runInShell')), ).thenAnswer((_) async => buildProcess); }); @@ -736,11 +705,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod () => buildProcess.exitCode, ).thenAnswer((_) async => ExitCode.software.code); when(() => buildProcess.stderr).thenAnswer( - (_) => Stream.fromIterable( - [ - 'stderr contents', - ].map(utf8.encode), - ), + (_) => Stream.fromIterable(['stderr contents'].map(utf8.encode)), ); }); @@ -761,22 +726,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('when target is provided', () { test('forwards target to flutter command', () async { await runWithOverrides( - () => builder.buildLinuxApp( - target: 'target.dart', - ), + () => builder.buildLinuxApp(target: 'target.dart'), ); verify( - () => shorebirdProcess.start( - 'flutter', - [ - 'build', - 'linux', - '--release', - '--target=target.dart', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.start('flutter', [ + 'build', + 'linux', + '--release', + '--target=target.dart', + ], runInShell: any(named: 'runInShell')), ).called(1); }); }); @@ -790,9 +749,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod test('completes', () async { await expectLater( - runWithOverrides( - () => builder.buildLinuxApp(), - ), + runWithOverrides(() => builder.buildLinuxApp()), completes, ); }); @@ -815,15 +772,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod verify( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'linux', - '--release', - ], + ['build', 'linux', '--release'], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': publicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': publicKey}, ), ).called(1); }); @@ -856,9 +807,8 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod late Directory dartToolDir; setUp(() { - dartToolDir = Directory( - p.join(projectRoot.path, '.dart_tool'), - )..createSync(recursive: true); + dartToolDir = Directory(p.join(projectRoot.path, '.dart_tool')) + ..createSync(recursive: true); }); test('deletes .dart_tool directory before building', () async { @@ -875,11 +825,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod verify( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'macos', - '--release', - ], + ['build', 'macos', '--release'], runInShell: true, environment: any(named: 'environment'), ), @@ -895,38 +841,24 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod when( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'macos', - '--release', - ], + ['build', 'macos', '--release'], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).thenAnswer((_) async => buildProcess); }); test('adds the SHOREBIRD_PUBLIC_KEY to the environment', () async { await runWithOverrides( - () => builder.buildMacos( - base64PublicKey: base64PublicKey, - ), + () => builder.buildMacos(base64PublicKey: base64PublicKey), ); verify( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'macos', - '--release', - ], + ['build', 'macos', '--release'], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).called(1); }); @@ -943,35 +875,30 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.start( - 'flutter', - [ - 'build', - 'macos', - '--release', - '--flavor=flavor', - '--target=target.dart', - '--no-codesign', - '--foo', - 'bar', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.start('flutter', [ + 'build', + 'macos', + '--release', + '--flavor=flavor', + '--target=target.dart', + '--no-codesign', + '--foo', + 'bar', + ], runInShell: any(named: 'runInShell')), ).called(1); }); group('when the build fails', () { group('with non-zero exit code', () { setUp(() { - when(() => buildProcess.exitCode) - .thenAnswer((_) async => ExitCode.software.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.software.code); }); test('throws ArtifactBuildException', () { expect( - () => runWithOverrides( - () => builder.buildMacos(codesign: false), - ), + () => runWithOverrides(() => builder.buildMacos(codesign: false)), throwsA(isA()), ); }); @@ -981,11 +908,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('when an app.dill file is not found in build stdout', () { setUp(() { when(() => buildProcess.stdout).thenAnswer( - (_) => Stream.fromIterable( - [ - 'no app.dill', - ].map(utf8.encode), - ), + (_) => Stream.fromIterable(['no app.dill'].map(utf8.encode)), ); }); @@ -995,15 +918,15 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod throwsA( isA() .having( - (e) => e.message, - 'message', - 'Unable to find app.dill file.', - ) + (e) => e.message, + 'message', + 'Unable to find app.dill file.', + ) .having( - (e) => e.fixRecommendation, - 'fixRecommendation', - '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', - ), + (e) => e.fixRecommendation, + 'fixRecommendation', + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', + ), ), ); }); @@ -1018,9 +941,8 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod }); verifyCorrectFlutterPubGet( - () async => runWithOverrides( - () => builder.buildMacos(codesign: false), - ), + () async => + runWithOverrides(() => builder.buildMacos(codesign: false)), ); group('when the build fails', () { @@ -1032,9 +954,8 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod verifyCorrectFlutterPubGet( () async => expectLater( - () async => runWithOverrides( - () => builder.buildMacos(codesign: false), - ), + () async => + runWithOverrides(() => builder.buildMacos(codesign: false)), throwsA(isA()), ), ); @@ -1065,11 +986,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod verify( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'ipa', - '--release', - ], + ['build', 'ipa', '--release'], runInShell: true, environment: any(named: 'environment'), ), @@ -1085,38 +1002,24 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod when( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'ipa', - '--release', - ], + ['build', 'ipa', '--release'], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).thenAnswer((_) async => buildProcess); }); test('adds the SHOREBIRD_PUBLIC_KEY to the environment', () async { await runWithOverrides( - () => builder.buildIpa( - base64PublicKey: base64PublicKey, - ), + () => builder.buildIpa(base64PublicKey: base64PublicKey), ); verify( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'ipa', - '--release', - ], + ['build', 'ipa', '--release'], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': base64PublicKey}, ), ).called(1); }); @@ -1133,20 +1036,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.start( - 'flutter', - [ - 'build', - 'ipa', - '--release', - '--flavor=flavor', - '--target=target.dart', - '--no-codesign', - '--foo', - 'bar', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.start('flutter', [ + 'build', + 'ipa', + '--release', + '--flavor=flavor', + '--target=target.dart', + '--no-codesign', + '--foo', + 'bar', + ], runInShell: any(named: 'runInShell')), ).called(1); }); @@ -1175,25 +1074,17 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod '[+5925 ms] Command line invocation:', '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -configuration Release VERBOSE_SCRIPT_LOGGING=YES -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -destination generic/platform=iOS SCRIPT_OUTPUT_STREAM_FILE=/var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/pipe_to_stdout -resultBundlePath /var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/temporary_xcresult_bundle -resultBundleVersion 3 FLUTTER_SUPPRESS_ANALYTICS=true COMPILER_INDEX_STORE_ENABLE=NO -archivePath /Users/bryanoltman/Documents/sandbox/notification_extension/build/ios/archive/Runner archive', // cSpell:enable - ] - .map((line) => '$line${Platform.lineTerminator}') - .map(utf8.encode), + ].map((line) => '$line${Platform.lineTerminator}').map(utf8.encode), ), ); when(() => buildProcess.stderr).thenAnswer( - (_) => Stream.fromIterable( - ['Some build output'].map(utf8.encode), - ), + (_) => Stream.fromIterable(['Some build output'].map(utf8.encode)), ); }); test('updates progress with known build steps', () async { await expectLater( - runWithOverrides( - () => builder.buildIpa( - buildProgress: progress, - ), - ), + runWithOverrides(() => builder.buildIpa(buildProgress: progress)), completes, ); @@ -1203,24 +1094,22 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod // Ensure we update the progress in the correct order and with the // correct messages, and reset to the base message after the build // completes. - verifyInOrder( - [ - () => progress.updateDetailMessage('Collecting schemes'), - () => progress.updateDetailMessage('Running Xcode build'), - () => progress.updateDetailMessage('Running Xcode build'), - () => progress.updateDetailMessage( - 'Compiling, linking and signing', - ), - ], - ); + verifyInOrder([ + () => progress.updateDetailMessage('Collecting schemes'), + () => progress.updateDetailMessage('Running Xcode build'), + () => progress.updateDetailMessage('Running Xcode build'), + () => + progress.updateDetailMessage('Compiling, linking and signing'), + ]); }); }); group('when the build fails', () { group('with non-zero exit code', () { setUp(() { - when(() => buildProcess.exitCode) - .thenAnswer((_) async => ExitCode.software.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.software.code); }); test('throws ArtifactBuildException', () { @@ -1233,8 +1122,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('with error message in stderr (Xcode <= 15.x)', () { setUp(() { - when(() => buildProcess.exitCode) - .thenAnswer((_) async => ExitCode.success.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); when(() => buildProcess.stderr).thenAnswer( (_) => Stream.fromIterable( [ @@ -1266,8 +1156,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found''', .having( (e) => e.stderr, 'stderr', - const LineSplitter().convert( - ''' + const LineSplitter().convert(''' Encountered error while creating the IPA: error: exportArchive: Communication with Apple failed error: exportArchive: No signing certificate "iOS Distribution" found @@ -1280,8 +1169,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found error: exportArchive: Communication with Apple failed error: exportArchive: No signing certificate "iOS Distribution" found error: exportArchive: Communication with Apple failed -error: exportArchive: No signing certificate "iOS Distribution" found''', - ), +error: exportArchive: No signing certificate "iOS Distribution" found'''), ), ), ); @@ -1290,8 +1178,9 @@ error: exportArchive: No signing certificate "iOS Distribution" found''', group('with error message in stderr (Xcode >= 16.x)', () { setUp(() { - when(() => buildProcess.exitCode) - .thenAnswer((_) async => ExitCode.success.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); when(() => buildProcess.stderr).thenAnswer( (_) => Stream.fromIterable( [ @@ -1319,11 +1208,7 @@ error: exportArchive No signing certificate "iOS Distribution" found''', () => runWithOverrides(() => builder.buildIpa(codesign: false)), throwsA( isA() - .having( - (e) => e.message, - 'message', - 'Failed to build', - ) + .having((e) => e.message, 'message', 'Failed to build') .having( (e) => e.stderr, 'stderr', @@ -1351,11 +1236,7 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), group('when an app.dill file is not found in build stdout', () { setUp(() { when(() => buildProcess.stdout).thenAnswer( - (_) => Stream.fromIterable( - [ - 'no app.dill', - ].map(utf8.encode), - ), + (_) => Stream.fromIterable(['no app.dill'].map(utf8.encode)), ); }); @@ -1365,15 +1246,15 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), throwsA( isA() .having( - (e) => e.message, - 'message', - 'Unable to find app.dill file.', - ) + (e) => e.message, + 'message', + 'Unable to find app.dill file.', + ) .having( - (e) => e.fixRecommendation, - 'fixRecommendation', - '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', - ), + (e) => e.fixRecommendation, + 'fixRecommendation', + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', + ), ), ); }); @@ -1388,9 +1269,8 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), }); verifyCorrectFlutterPubGet( - () async => runWithOverrides( - () => builder.buildIpa(codesign: false), - ), + () async => + runWithOverrides(() => builder.buildIpa(codesign: false)), ); group('when the build fails', () { @@ -1402,9 +1282,8 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), verifyCorrectFlutterPubGet( () async => expectLater( - () async => runWithOverrides( - () => builder.buildIpa(codesign: false), - ), + () async => + runWithOverrides(() => builder.buildIpa(codesign: false)), throwsA(isA()), ), ); @@ -1415,13 +1294,11 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), group('buildIosFramework', () { setUp(() { - when(() => buildProcessResult.stdout).thenReturn( - ''' + when(() => buildProcessResult.stdout).thenReturn(''' [ ] Will strip AOT snapshot manually after build and dSYM generation. [ ] executing: /bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=snapshot_assembly.S /path/to/app.dill [+3688 ms] executing: sysctl hw.optional.arm64 -''', - ); +'''); }); test('invokes the correct flutter build command', () async { @@ -1430,12 +1307,7 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), verify( () => shorebirdProcess.run( 'flutter', - [ - 'build', - 'ios-framework', - '--no-debug', - '--no-profile', - ], + ['build', 'ios-framework', '--no-debug', '--no-profile'], runInShell: true, environment: any(named: 'environment'), ), @@ -1449,26 +1321,23 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), ); verify( - () => shorebirdProcess.run( - 'flutter', - [ - 'build', - 'ios-framework', - '--no-debug', - '--no-profile', - '--foo', - 'bar', - ], - runInShell: true, - ), + () => shorebirdProcess.run('flutter', [ + 'build', + 'ios-framework', + '--no-debug', + '--no-profile', + '--foo', + 'bar', + ], runInShell: true), ).called(1); }); group('after a build', () { group('when the build is successful', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.success.code); + when( + () => buildProcessResult.exitCode, + ).thenReturn(ExitCode.success.code); }); verifyCorrectFlutterPubGet( @@ -1486,15 +1355,15 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), throwsA( isA() .having( - (e) => e.message, - 'message', - 'Unable to find app.dill file.', - ) + (e) => e.message, + 'message', + 'Unable to find app.dill file.', + ) .having( - (e) => e.fixRecommendation, - 'fixRecommendation', - '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', - ), + (e) => e.fixRecommendation, + 'fixRecommendation', + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', + ), ), ); }); @@ -1502,8 +1371,9 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), group('when the build fails', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.software.code); + when( + () => buildProcessResult.exitCode, + ).thenReturn(ExitCode.software.code); }); verifyCorrectFlutterPubGet( @@ -1536,17 +1406,14 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), ); verify( - () => shorebirdProcess.run( - 'gen_snapshot', - [ - '--deterministic', - '--snapshot-kind=app-aot-elf', - '--elf=/path/to/out', - '--foo', - 'bar', - '/app/dill/path', - ], - ), + () => shorebirdProcess.run('gen_snapshot', [ + '--deterministic', + '--snapshot-kind=app-aot-elf', + '--elf=/path/to/out', + '--foo', + 'bar', + '/app/dill/path', + ]), ).called(1); }); @@ -1605,15 +1472,11 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), () => artifactManager.getWindowsReleaseDirectory(), ).thenReturn(windowsReleaseDirectory); when( - () => shorebirdProcess.start( - 'flutter', - [ - 'build', - 'windows', - '--release', - ], - runInShell: any(named: 'runInShell'), - ), + () => shorebirdProcess.start('flutter', [ + 'build', + 'windows', + '--release', + ], runInShell: any(named: 'runInShell')), ).thenAnswer((_) async => buildProcess); }); @@ -1623,11 +1486,7 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), () => buildProcess.exitCode, ).thenAnswer((_) async => ExitCode.software.code); when(() => buildProcess.stderr).thenAnswer( - (_) => Stream.fromIterable( - [ - 'stderr contents', - ].map(utf8.encode), - ), + (_) => Stream.fromIterable(['stderr contents'].map(utf8.encode)), ); }); @@ -1681,15 +1540,9 @@ error: exportArchive No signing certificate "iOS Distribution" found'''), verify( () => shorebirdProcess.start( 'flutter', - [ - 'build', - 'windows', - '--release', - ], + ['build', 'windows', '--release'], runInShell: any(named: 'runInShell'), - environment: { - 'SHOREBIRD_PUBLIC_KEY': publicKey, - }, + environment: {'SHOREBIRD_PUBLIC_KEY': publicKey}, ), ).called(1); }); diff --git a/packages/shorebird_cli/test/src/artifact_manager_test.dart b/packages/shorebird_cli/test/src/artifact_manager_test.dart index ecb1e880..19473fb0 100644 --- a/packages/shorebird_cli/test/src/artifact_manager_test.dart +++ b/packages/shorebird_cli/test/src/artifact_manager_test.dart @@ -57,8 +57,9 @@ void main() { shorebirdProcess = MockShorebirdProcess(); shorebirdEnv = MockShorebirdEnv(); - when(() => cache.getArtifactDirectory(any())) - .thenReturn(cacheArtifactDirectory); + when( + () => cache.getArtifactDirectory(any()), + ).thenReturn(cacheArtifactDirectory); when(() => cache.updateAll()).thenAnswer((_) async {}); when(() => httpClient.send(any())).thenAnswer( @@ -87,12 +88,10 @@ void main() { setUp(() { final tmpDir = Directory.systemTemp.createTempSync(); - releaseArtifactFile = File( - p.join(tmpDir.path, 'release_artifact'), - )..createSync(recursive: true); - patchArtifactFile = File( - p.join(tmpDir.path, 'patch_artifact'), - )..createSync(recursive: true); + releaseArtifactFile = File(p.join(tmpDir.path, 'release_artifact')) + ..createSync(recursive: true); + patchArtifactFile = File(p.join(tmpDir.path, 'patch_artifact')) + ..createSync(recursive: true); }); test('throws error when release artifact file does not exist', () async { @@ -110,11 +109,7 @@ void main() { 'message', 'Release artifact does not exist', ) - .having( - (e) => e.path, - 'path', - 'not/a/real/file', - ), + .having((e) => e.path, 'path', 'not/a/real/file'), ), ); }); @@ -134,11 +129,7 @@ void main() { 'message', 'Patch artifact does not exist', ) - .having( - (e) => e.path, - 'path', - 'not/a/real/file', - ), + .having((e) => e.path, 'path', 'not/a/real/file'), ), ); }); @@ -150,9 +141,7 @@ void main() { patchArtifactPath: patchArtifactFile.path, diffPath: any(named: 'diffPath'), ), - ).thenThrow( - PatchFailedException('error'), - ); + ).thenThrow(PatchFailedException('error')); await expectLater( () => runWithOverrides( @@ -162,11 +151,7 @@ void main() { ), ), throwsA( - isA().having( - (e) => e.toString(), - 'exception', - 'error', - ), + isA().having((e) => e.toString(), 'exception', 'error'), ), ); }); @@ -203,9 +188,8 @@ void main() { await expectLater( () => runWithOverrides( - () async => artifactManager.downloadFile( - Uri.parse('https://example.com'), - ), + () async => + artifactManager.downloadFile(Uri.parse('https://example.com')), ), throwsA( isA().having( @@ -219,9 +203,8 @@ void main() { test('returns path to file when download succeeds', () async { final result = await runWithOverrides( - () async => artifactManager.downloadFile( - Uri.parse('https://example.com'), - ), + () async => + artifactManager.downloadFile(Uri.parse('https://example.com')), ); expect(result.path, endsWith('artifact')); @@ -393,9 +376,7 @@ void main() { await responseStreamController.close(); }); }, - onPlatform: { - 'windows': const Skip('Flaky on Windows'), - }, + onPlatform: {'windows': const Skip('Flaky on Windows')}, ); }); @@ -446,57 +427,50 @@ void main() { expect(result, isNull); }); - test('returns a path containing stripReleaseDebugSymbols if it exists', - () { - final stripNativeDebugLibsDirectory = Directory( - p.join( - strippedNativeLibsDirectory.path, - 'release', - 'stripReleaseDebugSymbols', - 'out', - 'lib', - ), - )..createSync(recursive: true); + test( + 'returns a path containing stripReleaseDebugSymbols if it exists', + () { + final stripNativeDebugLibsDirectory = Directory( + p.join( + strippedNativeLibsDirectory.path, + 'release', + 'stripReleaseDebugSymbols', + 'out', + 'lib', + ), + )..createSync(recursive: true); - // Create paths with and without the stripReleaseDebugSymbols - // directory to ensure the method returns the correct path when both - // exist. - Directory( - p.join( - strippedNativeLibsDirectory.path, - 'release', - 'out', - 'lib', - ), - ).createSync(recursive: true); + // Create paths with and without the stripReleaseDebugSymbols + // directory to ensure the method returns the correct path when both + // exist. + Directory( + p.join(strippedNativeLibsDirectory.path, 'release', 'out', 'lib'), + ).createSync(recursive: true); - final result = ArtifactManager.androidArchsDirectory( - projectRoot: projectRoot, - ); + final result = ArtifactManager.androidArchsDirectory( + projectRoot: projectRoot, + ); - expect(result, isNotNull); - expect(result!.path, equals(stripNativeDebugLibsDirectory.path)); - }); + expect(result, isNotNull); + expect(result!.path, equals(stripNativeDebugLibsDirectory.path)); + }, + ); test( - '''returns a path not containing stripReleaseDebugSymbols no path containing stripReleaseDebugSymbols exists''', - () { - final noStripReleaseDebugSymbolsPath = Directory( - p.join( - strippedNativeLibsDirectory.path, - 'release', - 'out', - 'lib', - ), - )..createSync(recursive: true); + '''returns a path not containing stripReleaseDebugSymbols no path containing stripReleaseDebugSymbols exists''', + () { + final noStripReleaseDebugSymbolsPath = Directory( + p.join(strippedNativeLibsDirectory.path, 'release', 'out', 'lib'), + )..createSync(recursive: true); - final result = ArtifactManager.androidArchsDirectory( - projectRoot: projectRoot, - ); + final result = ArtifactManager.androidArchsDirectory( + projectRoot: projectRoot, + ); - expect(result, isNotNull); - expect(result!.path, equals(noStripReleaseDebugSymbolsPath.path)); - }); + expect(result, isNotNull); + expect(result!.path, equals(noStripReleaseDebugSymbolsPath.path)); + }, + ); }); group('with a flavor named "internal"', () { @@ -512,59 +486,61 @@ void main() { }); test( - '''returns a path containing stripInternalReleaseDebugSymbols if it exists''', - () { - final stripNativeDebugLibsDirectory = Directory( - p.join( - strippedNativeLibsDirectory.path, - 'internalRelease', - 'stripInternalReleaseDebugSymbols', - 'out', - 'lib', - ), - )..createSync(recursive: true); + '''returns a path containing stripInternalReleaseDebugSymbols if it exists''', + () { + final stripNativeDebugLibsDirectory = Directory( + p.join( + strippedNativeLibsDirectory.path, + 'internalRelease', + 'stripInternalReleaseDebugSymbols', + 'out', + 'lib', + ), + )..createSync(recursive: true); - // Create paths with and without the stripReleaseDebugSymbols - // directory to ensure the method returns the correct path when both - // exist. - Directory( - p.join( - strippedNativeLibsDirectory.path, - 'internalRelease', - 'out', - 'lib', - ), - ).createSync(recursive: true); + // Create paths with and without the stripReleaseDebugSymbols + // directory to ensure the method returns the correct path when both + // exist. + Directory( + p.join( + strippedNativeLibsDirectory.path, + 'internalRelease', + 'out', + 'lib', + ), + ).createSync(recursive: true); - final result = ArtifactManager.androidArchsDirectory( - projectRoot: projectRoot, - flavor: flavor, - ); + final result = ArtifactManager.androidArchsDirectory( + projectRoot: projectRoot, + flavor: flavor, + ); - expect(result, isNotNull); - expect(result!.path, equals(stripNativeDebugLibsDirectory.path)); - }); + expect(result, isNotNull); + expect(result!.path, equals(stripNativeDebugLibsDirectory.path)); + }, + ); test( - '''returns a path not containing stripInternalReleaseDebugSymbols no path containing stripInternalReleaseDebugSymbols exists''', - () { - final noStripReleaseDebugSymbolsPath = Directory( - p.join( - strippedNativeLibsDirectory.path, - 'internalRelease', - 'out', - 'lib', - ), - )..createSync(recursive: true); + '''returns a path not containing stripInternalReleaseDebugSymbols no path containing stripInternalReleaseDebugSymbols exists''', + () { + final noStripReleaseDebugSymbolsPath = Directory( + p.join( + strippedNativeLibsDirectory.path, + 'internalRelease', + 'out', + 'lib', + ), + )..createSync(recursive: true); - final result = ArtifactManager.androidArchsDirectory( - projectRoot: projectRoot, - flavor: flavor, - ); + final result = ArtifactManager.androidArchsDirectory( + projectRoot: projectRoot, + flavor: flavor, + ); - expect(result, isNotNull); - expect(result!.path, equals(noStripReleaseDebugSymbolsPath.path)); - }); + expect(result, isNotNull); + expect(result!.path, equals(noStripReleaseDebugSymbolsPath.path)); + }, + ); }); }); @@ -594,63 +570,59 @@ void main() { }); }); - group( - 'when multiple xcarchive directories exist', - () { - late Directory oldArchiveDirectory; - late Directory newArchiveDirectory; + group('when multiple xcarchive directories exist', () { + late Directory oldArchiveDirectory; + late Directory newArchiveDirectory; - setUp(() async { - oldArchiveDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'archive', - 'Runner.xcarchive', - ), - )..createSync(recursive: true); - // Wait to ensure the new archive directory is created after the old - // archive directory. - await Future.delayed(const Duration(milliseconds: 50)); - newArchiveDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'archive', - 'Runner2.xcarchive', - ), - )..createSync(recursive: true); - }); + setUp(() async { + oldArchiveDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'archive', + 'Runner.xcarchive', + ), + )..createSync(recursive: true); + // Wait to ensure the new archive directory is created after the old + // archive directory. + await Future.delayed(const Duration(milliseconds: 50)); + newArchiveDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'archive', + 'Runner2.xcarchive', + ), + )..createSync(recursive: true); + }); - test('selects the most recently updated xcarchive', () async { - final firstResult = runWithOverrides( - () => artifactManager.getXcarchiveDirectory(), - ); - // The new archive directory should be selected because it was - // created after the old archive directory. - expect(firstResult!.path, equals(newArchiveDirectory.path)); + test('selects the most recently updated xcarchive', () async { + final firstResult = runWithOverrides( + () => artifactManager.getXcarchiveDirectory(), + ); + // The new archive directory should be selected because it was + // created after the old archive directory. + expect(firstResult!.path, equals(newArchiveDirectory.path)); - // Now recreate the old archive directory and ensure it is selected. - oldArchiveDirectory.deleteSync(recursive: true); - oldArchiveDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'archive', - 'Runner.xcarchive', - ), - )..createSync(recursive: true); - final secondResult = runWithOverrides( - () => artifactManager.getXcarchiveDirectory(), - ); - expect(secondResult!.path, equals(oldArchiveDirectory.path)); - }); - }, - testOn: 'mac-os', - ); + // Now recreate the old archive directory and ensure it is selected. + oldArchiveDirectory.deleteSync(recursive: true); + oldArchiveDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'archive', + 'Runner.xcarchive', + ), + )..createSync(recursive: true); + final secondResult = runWithOverrides( + () => artifactManager.getXcarchiveDirectory(), + ); + expect(secondResult!.path, equals(oldArchiveDirectory.path)); + }); + }, testOn: 'mac-os'); group('when archive directory does not exist', () { test('returns null', () { @@ -689,69 +661,65 @@ void main() { }); }); - group( - 'when multiple .app directories exist', - () { - late Directory oldAppDirectory; - late Directory newAppDirectory; + group('when multiple .app directories exist', () { + late Directory oldAppDirectory; + late Directory newAppDirectory; - setUp(() async { - oldAppDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'macos', - 'Build', - 'Products', - 'Release', - 'Runner.app', - ), - )..createSync(recursive: true); - // Wait to ensure the new app directory is created after the old - // app directory. - await Future.delayed(const Duration(milliseconds: 50)); - newAppDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'macos', - 'Build', - 'Products', - 'Release', - 'Runner2.app', - ), - )..createSync(recursive: true); - }); + setUp(() async { + oldAppDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'macos', + 'Build', + 'Products', + 'Release', + 'Runner.app', + ), + )..createSync(recursive: true); + // Wait to ensure the new app directory is created after the old + // app directory. + await Future.delayed(const Duration(milliseconds: 50)); + newAppDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'macos', + 'Build', + 'Products', + 'Release', + 'Runner2.app', + ), + )..createSync(recursive: true); + }); - test('selects the most recently updated app', () async { - final firstResult = runWithOverrides( - artifactManager.getMacOSAppDirectory, - ); - // The new app directory should be selected because it was - // created after the old app directory. - expect(firstResult!.path, equals(newAppDirectory.path)); + test('selects the most recently updated app', () async { + final firstResult = runWithOverrides( + artifactManager.getMacOSAppDirectory, + ); + // The new app directory should be selected because it was + // created after the old app directory. + expect(firstResult!.path, equals(newAppDirectory.path)); - // Now recreate the old app directory and ensure it is selected. - oldAppDirectory.deleteSync(recursive: true); - oldAppDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'macos', - 'Build', - 'Products', - 'Release', - 'Runner.app', - ), - )..createSync(recursive: true); - final secondResult = runWithOverrides( - artifactManager.getMacOSAppDirectory, - ); - expect(secondResult!.path, equals(oldAppDirectory.path)); - }); - }, - testOn: 'mac-os', - ); + // Now recreate the old app directory and ensure it is selected. + oldAppDirectory.deleteSync(recursive: true); + oldAppDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'macos', + 'Build', + 'Products', + 'Release', + 'Runner.app', + ), + )..createSync(recursive: true); + final secondResult = runWithOverrides( + artifactManager.getMacOSAppDirectory, + ); + expect(secondResult!.path, equals(oldAppDirectory.path)); + }); + }, testOn: 'mac-os'); group('when app directory does not exist', () { test('returns null', () { @@ -873,10 +841,7 @@ void main() { group('getIpa', () { group('when ipa build directory does not exist', () { test('returns null', () { - expect( - runWithOverrides(artifactManager.getIpa), - isNull, - ); + expect(runWithOverrides(artifactManager.getIpa), isNull); }); }); @@ -886,12 +851,7 @@ void main() { setUp(() { ipaBuildDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'ipa', - ), + p.join(projectRoot.path, 'build', 'ios', 'ipa'), )..createSync(recursive: true); ipaFile = File(p.join(ipaBuildDirectory.path, 'Runner.ipa')) ..createSync(); @@ -907,10 +867,7 @@ void main() { test('returns null when multiple ipa files exist', () { File(p.join(ipaBuildDirectory.path, 'Runner2.ipa')).createSync(); - expect( - runWithOverrides(artifactManager.getIpa), - isNull, - ); + expect(runWithOverrides(artifactManager.getIpa), isNull); verify( () => logger.detail( 'More than one .ipa file found in ${ipaBuildDirectory.path}', @@ -921,10 +878,7 @@ void main() { test('returns null when no ipa files exist', () { ipaFile.deleteSync(); - expect( - runWithOverrides(artifactManager.getIpa), - isNull, - ); + expect(runWithOverrides(artifactManager.getIpa), isNull); verify( () => logger.detail( @@ -958,13 +912,7 @@ void main() { expect( runWithOverrides(artifactManager.getAppXcframeworkDirectory).path, equals( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - ), + p.join(projectRoot.path, 'build', 'ios', 'framework', 'Release'), ), ); }); @@ -983,12 +931,7 @@ void main() { group('when the directory exists', () { setUp(() { Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'shorebird', - ), + p.join(projectRoot.path, 'build', 'ios', 'shorebird'), ).createSync(recursive: true); }); @@ -997,14 +940,7 @@ void main() { runWithOverrides( artifactManager.getIosReleaseSupplementDirectory, )?.path, - equals( - p.join( - projectRoot.path, - 'build', - 'ios', - 'shorebird', - ), - ), + equals(p.join(projectRoot.path, 'build', 'ios', 'shorebird')), ); }); }); @@ -1025,12 +961,7 @@ void main() { group('when the directory exists', () { setUp(() { Directory( - p.join( - projectRoot.path, - 'build', - 'macos', - 'shorebird', - ), + p.join(projectRoot.path, 'build', 'macos', 'shorebird'), ).createSync(recursive: true); }); @@ -1039,14 +970,7 @@ void main() { runWithOverrides( artifactManager.getMacosReleaseSupplementDirectory, )?.path, - equals( - p.join( - projectRoot.path, - 'build', - 'macos', - 'shorebird', - ), - ), + equals(p.join(projectRoot.path, 'build', 'macos', 'shorebird')), ); }); }); diff --git a/packages/shorebird_cli/test/src/auth/auth_test.dart b/packages/shorebird_cli/test/src/auth/auth_test.dart index fe5663f0..dc31cd07 100644 --- a/packages/shorebird_cli/test/src/auth/auth_test.dart +++ b/packages/shorebird_cli/test/src/auth/auth_test.dart @@ -32,10 +32,7 @@ void main() { test('creates instance with default constructor', () { final instance = runScoped( () => auth, - values: { - authRef, - httpClientRef.overrideWith(MockHttpClient.new), - }, + values: {authRef, httpClientRef.overrideWith(MockHttpClient.new)}, ); expect( instance.credentialsFilePath, @@ -127,11 +124,7 @@ void main() { authProvider: AuthProvider.google, ); const email = 'test@email.com'; - const user = PrivateUser( - id: 42, - email: email, - jwtIssuer: googleJwtIssuer, - ); + const user = PrivateUser(id: 42, email: email, jwtIssuer: googleJwtIssuer); const scopes = []; final accessToken = oauth2.AccessToken( 'Bearer', @@ -230,58 +223,62 @@ void main() { () => AuthenticatedClient.token( token: ciToken, httpClient: httpClient, - refreshCredentials: ( - clientId, - credentials, - client, { - AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), - }) async => - accessCredentials, + refreshCredentials: + ( + clientId, + credentials, + client, { + AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), + }) async => accessCredentials, ), returnsNormally, ); }); - test('refreshes and uses new token when credentials are expired.', - () async { - when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), - ); + test( + 'refreshes and uses new token when credentials are expired.', + () async { + when(() => httpClient.send(any())).thenAnswer( + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.ok), + ); - final onRefreshCredentialsCalls = []; + final onRefreshCredentialsCalls = []; - final client = AuthenticatedClient.token( - token: ciToken, - httpClient: httpClient, - onRefreshCredentials: onRefreshCredentialsCalls.add, - refreshCredentials: ( - clientId, - credentials, - client, { - AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), - }) async => - accessCredentials, - ); + final client = AuthenticatedClient.token( + token: ciToken, + httpClient: httpClient, + onRefreshCredentials: onRefreshCredentialsCalls.add, + refreshCredentials: + ( + clientId, + credentials, + client, { + AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), + }) async => accessCredentials, + ); - await runWithOverrides( - () => client.get(Uri.parse('https://example.com')), - ); + await runWithOverrides( + () => client.get(Uri.parse('https://example.com')), + ); - expect( - onRefreshCredentialsCalls, - equals([ - isA() - .having((c) => c.idToken, 'token', idToken), - ]), - ); - final captured = verify(() => httpClient.send(captureAny())).captured; - expect(captured, hasLength(1)); - final request = captured.first as http.BaseRequest; - expect(request.headers['Authorization'], equals('Bearer $idToken')); - }); + expect( + onRefreshCredentialsCalls, + equals([ + isA().having( + (c) => c.idToken, + 'token', + idToken, + ), + ]), + ); + final captured = + verify(() => httpClient.send(captureAny())).captured; + expect(captured, hasLength(1)); + final request = captured.first as http.BaseRequest; + expect(request.headers['Authorization'], equals('Bearer $idToken')); + }, + ); group('when refreshing the token fails', () { late AuthenticatedClient client; @@ -299,13 +296,13 @@ void main() { token: ciToken, httpClient: httpClient, onRefreshCredentials: onRefreshCredentialsCalls.add, - refreshCredentials: ( - clientId, - credentials, - client, { - AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), - }) async => - throw Exception('error.'), + refreshCredentials: + ( + clientId, + credentials, + client, { + AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), + }) async => throw Exception('error.'), ); }); @@ -316,46 +313,41 @@ void main() { ), exitsWithCode(ExitCode.software), ); - verify(() => logger.err('Failed to refresh credentials.')) - .called(1); + verify( + () => logger.err('Failed to refresh credentials.'), + ).called(1); verify( () => logger.info( '''Try logging out with ${lightBlue.wrap('shorebird logout')} and logging in again.''', ), ).called(1); - verify( - () => logger.detail('Exception: error.'), - ).called(1); + verify(() => logger.detail('Exception: error.')).called(1); }); }); test('uses valid token when credentials valid.', () async { when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.ok), ); final onRefreshCredentialsCalls = []; final client = AuthenticatedClient.token( token: ciToken, httpClient: httpClient, onRefreshCredentials: onRefreshCredentialsCalls.add, - refreshCredentials: ( - clientId, - credentials, - client, { - AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), - }) async => - accessCredentials, + refreshCredentials: + ( + clientId, + credentials, + client, { + AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), + }) async => accessCredentials, ); - await runWithOverrides( - () async { - await client.get(Uri.parse('https://example.com')); - await client.get(Uri.parse('https://example.com')); - }, - ); + await runWithOverrides(() async { + await client.get(Uri.parse('https://example.com')); + await client.get(Uri.parse('https://example.com')); + }); expect(onRefreshCredentialsCalls.length, equals(1)); final captured = verify(() => httpClient.send(captureAny())).captured; @@ -368,58 +360,62 @@ void main() { }); group('credentials', () { - test('refreshes and uses new token when credentials are expired.', - () async { - when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), - ); + test( + 'refreshes and uses new token when credentials are expired.', + () async { + when(() => httpClient.send(any())).thenAnswer( + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.ok), + ); - const expiredIdToken = // cspell:disable-next-line - '''eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMjM0NSIsImhkIjoic2hvcmViaXJkLmRldiIsImVtYWlsIjoidGVzdEBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaWF0IjoxMjM0LCJleHAiOjY3ODl9.MYbITALvKsGYTYjw1o7AQ0ObkqRWVBSr9cFYJrvA46g'''; - final onRefreshCredentialsCalls = []; - final expiredCredentials = oauth2.AccessCredentials( - oauth2.AccessToken( - 'Bearer', - 'accessToken', - DateTime.now().subtract(const Duration(minutes: 1)).toUtc(), - ), - '', - [], - idToken: expiredIdToken, - ); + const expiredIdToken = // cspell:disable-next-line + '''eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMjM0NSIsImhkIjoic2hvcmViaXJkLmRldiIsImVtYWlsIjoidGVzdEBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaWF0IjoxMjM0LCJleHAiOjY3ODl9.MYbITALvKsGYTYjw1o7AQ0ObkqRWVBSr9cFYJrvA46g'''; + final onRefreshCredentialsCalls = []; + final expiredCredentials = oauth2.AccessCredentials( + oauth2.AccessToken( + 'Bearer', + 'accessToken', + DateTime.now().subtract(const Duration(minutes: 1)).toUtc(), + ), + '', + [], + idToken: expiredIdToken, + ); - final client = AuthenticatedClient.credentials( - credentials: expiredCredentials, - httpClient: httpClient, - onRefreshCredentials: onRefreshCredentialsCalls.add, - refreshCredentials: ( - clientId, - credentials, - client, { - AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), - }) async => - accessCredentials, - ); + final client = AuthenticatedClient.credentials( + credentials: expiredCredentials, + httpClient: httpClient, + onRefreshCredentials: onRefreshCredentialsCalls.add, + refreshCredentials: + ( + clientId, + credentials, + client, { + AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), + }) async => accessCredentials, + ); - await runWithOverrides( - () => client.get(Uri.parse('https://example.com')), - ); + await runWithOverrides( + () => client.get(Uri.parse('https://example.com')), + ); - expect( - onRefreshCredentialsCalls, - equals([ - isA() - .having((c) => c.idToken, 'token', idToken), - ]), - ); - final captured = verify(() => httpClient.send(captureAny())).captured; - expect(captured, hasLength(1)); - final request = captured.first as http.BaseRequest; - expect(request.headers['Authorization'], equals('Bearer $idToken')); - }); + expect( + onRefreshCredentialsCalls, + equals([ + isA().having( + (c) => c.idToken, + 'token', + idToken, + ), + ]), + ); + final captured = + verify(() => httpClient.send(captureAny())).captured; + expect(captured, hasLength(1)); + final request = captured.first as http.BaseRequest; + expect(request.headers['Authorization'], equals('Bearer $idToken')); + }, + ); group('when refreshing the token fails', () { late AuthenticatedClient client; @@ -449,13 +445,13 @@ void main() { credentials: expiredCredentials, httpClient: httpClient, onRefreshCredentials: onRefreshCredentialsCalls.add, - refreshCredentials: ( - clientId, - credentials, - client, { - AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), - }) async => - throw Exception('error.'), + refreshCredentials: + ( + clientId, + credentials, + client, { + AuthEndpoints authEndpoints = const GoogleAuthEndpoints(), + }) async => throw Exception('error.'), ); }); @@ -466,25 +462,22 @@ void main() { ), exitsWithCode(ExitCode.software), ); - verify(() => logger.err('Failed to refresh credentials.')) - .called(1); + verify( + () => logger.err('Failed to refresh credentials.'), + ).called(1); verify( () => logger.info( '''Try logging out with ${lightBlue.wrap('shorebird logout')} and logging in again.''', ), ).called(1); - verify( - () => logger.detail('Exception: error.'), - ).called(1); + verify(() => logger.detail('Exception: error.')).called(1); }); }); test('uses valid token when credentials valid.', () async { when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.ok), ); final onRefreshCredentialsCalls = []; final client = AuthenticatedClient.credentials( @@ -507,14 +500,11 @@ void main() { }); group('client', () { - test( - 'returns an authenticated client ' + test('returns an authenticated client ' 'when credentials are present.', () async { when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.ok), ); await auth.login(AuthProvider.google, prompt: (_) {}); final client = auth.client; @@ -533,41 +523,41 @@ void main() { group('when token is invalid', () { setUp(() { - when(() => platform.environment).thenReturn( - {shorebirdTokenEnvVar: 'not a base64 string'}, - ); + when(() => platform.environment).thenReturn({ + shorebirdTokenEnvVar: 'not a base64 string', + }); }); - test('logs and throws error when token string is not valid base64', - () async { - expect(buildAuth, throwsA(isFormatException)); - verify( - () => logger.info('$shorebirdTokenEnvVar detected'), - ).called(1); - verify( - () => logger.err( - ''' + test( + 'logs and throws error when token string is not valid base64', + () async { + expect(buildAuth, throwsA(isFormatException)); + verify( + () => logger.info('$shorebirdTokenEnvVar detected'), + ).called(1); + verify( + () => logger.err( + ''' Failed to parse CI token from environment. This likely means that your CI token is incorrectly formatted. Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar environment variable, and try again.''', - ), - ).called(1); - verifyNever( - () => logger.info('$shorebirdTokenEnvVar successfully parsed'), - ); - }); + ), + ).called(1); + verifyNever( + () => logger.info('$shorebirdTokenEnvVar successfully parsed'), + ); + }, + ); }); group('when token has leading or trailing spaces and newlines', () { setUp(() { - when(() => platform.environment).thenReturn( - { - shorebirdTokenEnvVar: ''' + when(() => platform.environment).thenReturn({ + shorebirdTokenEnvVar: ''' ${ciToken.toBase64()} ''', - }, - ); + }); }); test('trims string', () { @@ -578,18 +568,15 @@ Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar e }); }); - test( - 'returns an authenticated client ' + test('returns an authenticated client ' 'when a token and token provider is present.', () async { when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), - ); - when(() => platform.environment).thenReturn( - {shorebirdTokenEnvVar: ciToken.toBase64()}, + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.ok), ); + when(() => platform.environment).thenReturn({ + shorebirdTokenEnvVar: ciToken.toBase64(), + }); auth = buildAuth(); final client = auth.client; expect(client, isA()); @@ -600,50 +587,57 @@ Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar e ).called(1); }); - test('returns a plain http client when credentials are not present.', - () async { - final client = auth.client; - expect(client, isA()); - expect(client, isNot(isA())); - }); + test( + 'returns a plain http client when credentials are not present.', + () async { + final client = auth.client; + expect(client, isA()); + expect(client, isNot(isA())); + }, + ); }); group('login', () { - test('should set the email when claims are valid and current user exists', - () async { - await auth.login(AuthProvider.google, prompt: (_) {}); - expect(auth.email, email); - expect(auth.isAuthenticated, isTrue); - expect(buildAuth().email, email); - expect(buildAuth().isAuthenticated, isTrue); - }); - - group('with custom auth provider', () { - test( - '''should set the email when claims are valid and current user exists''', - () async { - await auth.login(AuthProvider.microsoft, prompt: (_) {}); + test( + 'should set the email when claims are valid and current user exists', + () async { + await auth.login(AuthProvider.google, prompt: (_) {}); expect(auth.email, email); expect(auth.isAuthenticated, isTrue); expect(buildAuth().email, email); expect(buildAuth().isAuthenticated, isTrue); - }); - }); + }, + ); - test('throws UserAlreadyLoggedInException if user is authenticated', + group('with custom auth provider', () { + test( + '''should set the email when claims are valid and current user exists''', () async { - writeCredentials(); - auth = buildAuth(); - - await expectLater( - auth.login(AuthProvider.google, prompt: (_) {}), - throwsA(isA()), + await auth.login(AuthProvider.microsoft, prompt: (_) {}); + expect(auth.email, email); + expect(auth.isAuthenticated, isTrue); + expect(buildAuth().email, email); + expect(buildAuth().isAuthenticated, isTrue); + }, ); - - expect(auth.email, isNotNull); - expect(auth.isAuthenticated, isTrue); }); + test( + 'throws UserAlreadyLoggedInException if user is authenticated', + () async { + writeCredentials(); + auth = buildAuth(); + + await expectLater( + auth.login(AuthProvider.google, prompt: (_) {}), + throwsA(isA()), + ); + + expect(auth.email, isNotNull); + expect(auth.isAuthenticated, isTrue); + }, + ); + group('when login credentials are corrupted', () { setUp(() { accessCredentials = oauth2.AccessCredentials( @@ -665,8 +659,9 @@ Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar e }); test('should not set the email when user does not exist', () async { - when(() => codePushClient.getCurrentUser()) - .thenAnswer((_) async => null); + when( + () => codePushClient.getCurrentUser(), + ).thenAnswer((_) async => null); await expectLater( auth.login(AuthProvider.google, prompt: (_) {}), @@ -680,24 +675,26 @@ Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar e group('loginCI', () { setUp(() { - when(() => platform.environment).thenReturn( - {shorebirdTokenEnvVar: ciToken.toBase64()}, - ); + when(() => platform.environment).thenReturn({ + shorebirdTokenEnvVar: ciToken.toBase64(), + }); auth = buildAuth(); }); - test('returns a CI token and does not set the email or cache credentials', - () async { - final token = await auth.loginCI(AuthProvider.google, prompt: (_) {}); - expect(token.authProvider, ciToken.authProvider); - expect(token.refreshToken, ciToken.refreshToken); - expect(auth.email, isNull); - expect(auth.isAuthenticated, isTrue); - expect(buildAuth().email, isNull); - expect(buildAuth().isAuthenticated, isTrue); - when(() => platform.environment).thenReturn({}); - expect(buildAuth().isAuthenticated, isFalse); - }); + test( + 'returns a CI token and does not set the email or cache credentials', + () async { + final token = await auth.loginCI(AuthProvider.google, prompt: (_) {}); + expect(token.authProvider, ciToken.authProvider); + expect(token.refreshToken, ciToken.refreshToken); + expect(auth.email, isNull); + expect(auth.isAuthenticated, isTrue); + expect(buildAuth().email, isNull); + expect(buildAuth().isAuthenticated, isTrue); + when(() => platform.environment).thenReturn({}); + expect(buildAuth().isAuthenticated, isFalse); + }, + ); test('throws when user does not exist', () async { when( diff --git a/packages/shorebird_cli/test/src/cache_test.dart b/packages/shorebird_cli/test/src/cache_test.dart index 61b6766f..e8a3159e 100644 --- a/packages/shorebird_cli/test/src/cache_test.dart +++ b/packages/shorebird_cli/test/src/cache_test.dart @@ -89,8 +89,9 @@ void main() { outputDirectory: any(named: 'outputDirectory'), ), ).thenAnswer((invocation) async { - (invocation.namedArguments[#outputDirectory] as Directory) - .createSync(recursive: true); + (invocation.namedArguments[#outputDirectory] as Directory).createSync( + recursive: true, + ); }); when(() => logger.progress(any())).thenReturn(progress); when( @@ -100,12 +101,12 @@ void main() { when(() => platform.environment).thenReturn({}); setMockPlatform(Platform.macOS); - when(() => shorebirdProcess.start(any(), any())).thenAnswer( - (_) async => chmodProcess, - ); - when(() => chmodProcess.exitCode).thenAnswer( - (_) async => ExitCode.success.code, - ); + when( + () => shorebirdProcess.start(any(), any()), + ).thenAnswer((_) async => chmodProcess); + when( + () => chmodProcess.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); when(() => httpClient.send(any())).thenAnswer( (_) async => http.StreamedResponse( Stream.value(ZipEncoder().encode(Archive())), @@ -134,14 +135,7 @@ void main() { () => cache.getArtifactDirectory('test'), ); expect( - directory.path.endsWith( - p.join( - 'bin', - 'cache', - 'artifacts', - 'test', - ), - ), + directory.path.endsWith(p.join('bin', 'cache', 'artifacts', 'test')), isTrue, ); }); @@ -153,14 +147,7 @@ void main() { () => cache.getPreviewDirectory('test'), ); expect( - directory.path.endsWith( - p.join( - 'bin', - 'cache', - 'previews', - 'test', - ), - ), + directory.path.endsWith(p.join('bin', 'cache', 'previews', 'test')), isTrue, ); }); @@ -238,13 +225,12 @@ void main() { await expectLater( runWithOverrides(() => cache.updateAll(Duration.zero)), - throwsA( - isA(), - ), + throwsA(isA()), ); - verify(() => logger.detail('Failed to update patch, retrying...')) - .called(2); + verify( + () => logger.detail('Failed to update patch, retrying...'), + ).called(2); }); }); @@ -269,24 +255,22 @@ void main() { }); test('skips optional artifacts if a 404 is returned', () async { - when(() => httpClient.send(any())).thenAnswer( - (invocation) async { - final request = - invocation.positionalArguments.first as http.BaseRequest; - final fileName = p.basename(request.url.path); - if (fileName.startsWith('aot-tools')) { - return http.StreamedResponse( - const Stream.empty(), - HttpStatus.notFound, - reasonPhrase: 'Not Found', - ); - } + when(() => httpClient.send(any())).thenAnswer((invocation) async { + final request = + invocation.positionalArguments.first as http.BaseRequest; + final fileName = p.basename(request.url.path); + if (fileName.startsWith('aot-tools')) { return http.StreamedResponse( - Stream.value(ZipEncoder().encode(Archive())), - HttpStatus.ok, + const Stream.empty(), + HttpStatus.notFound, + reasonPhrase: 'Not Found', ); - }, - ); + } + return http.StreamedResponse( + Stream.value(ZipEncoder().encode(Archive())), + HttpStatus.ok, + ); + }); await expectLater( runWithOverrides(() => cache.updateAll(Duration.zero)), completes, @@ -362,20 +346,20 @@ void main() { completes, ); - final requests = verify(() => httpClient.send(captureAny())) - .captured - .cast() - .map((r) => r.url) - .toList(); + final requests = + verify( + () => httpClient.send(captureAny()), + ).captured.cast().map((r) => r.url).toList(); String perEngine(String name) => '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/$name'; - final expected = [ - perEngine('patch-darwin-x64.zip'), - 'https://github.com/google/bundletool/releases/download/1.17.1/bundletool-all-1.17.1.jar', - perEngine('aot-tools.dill'), - ].map(Uri.parse).toList(); + final expected = + [ + perEngine('patch-darwin-x64.zip'), + 'https://github.com/google/bundletool/releases/download/1.17.1/bundletool-all-1.17.1.jar', + perEngine('aot-tools.dill'), + ].map(Uri.parse).toList(); expect(requests, equals(expected)); }); @@ -388,20 +372,20 @@ void main() { completes, ); - final requests = verify(() => httpClient.send(captureAny())) - .captured - .cast() - .map((r) => r.url) - .toList(); + final requests = + verify( + () => httpClient.send(captureAny()), + ).captured.cast().map((r) => r.url).toList(); String perEngine(String name) => '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/$name'; - final expected = [ - perEngine('patch-windows-x64.zip'), - 'https://github.com/google/bundletool/releases/download/1.17.1/bundletool-all-1.17.1.jar', - perEngine('aot-tools.dill'), - ].map(Uri.parse).toList(); + final expected = + [ + perEngine('patch-windows-x64.zip'), + 'https://github.com/google/bundletool/releases/download/1.17.1/bundletool-all-1.17.1.jar', + perEngine('aot-tools.dill'), + ].map(Uri.parse).toList(); expect(requests, equals(expected)); }); @@ -414,20 +398,20 @@ void main() { completes, ); - final requests = verify(() => httpClient.send(captureAny())) - .captured - .cast() - .map((r) => r.url) - .toList(); + final requests = + verify( + () => httpClient.send(captureAny()), + ).captured.cast().map((r) => r.url).toList(); String perEngine(String name) => '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/$name'; - final expected = [ - perEngine('patch-linux-x64.zip'), - 'https://github.com/google/bundletool/releases/download/1.17.1/bundletool-all-1.17.1.jar', - perEngine('aot-tools.dill'), - ].map(Uri.parse).toList(); + final expected = + [ + perEngine('patch-linux-x64.zip'), + 'https://github.com/google/bundletool/releases/download/1.17.1/bundletool-all-1.17.1.jar', + perEngine('aot-tools.dill'), + ].map(Uri.parse).toList(); expect(requests, equals(expected)); }); @@ -469,10 +453,8 @@ void main() { progress = MockProgress(); when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.notFound, - ), + (_) async => + http.StreamedResponse(const Stream.empty(), HttpStatus.notFound), ); when(() => logger.progress(any())).thenReturn(progress); @@ -520,8 +502,9 @@ void main() { group('when the checksum matches', () { setUp(() { - when(() => checksumChecker.checkFile(any(), any())) - .thenReturn(true); + when( + () => checksumChecker.checkFile(any(), any()), + ).thenReturn(true); }); test('returns true', () async { @@ -531,15 +514,13 @@ void main() { group('when the checksum does not match', () { setUp(() { - when(() => checksumChecker.checkFile(any(), any())) - .thenReturn(false); + when( + () => checksumChecker.checkFile(any(), any()), + ).thenReturn(false); }); test('returns false', () async { - expect( - await runWithOverrides(cachedArtifact.isValid), - isFalse, - ); + expect(await runWithOverrides(cachedArtifact.isValid), isFalse); }); }); }); @@ -554,20 +535,22 @@ void main() { cachedArtifact.stampFile.createSync(recursive: true); }); - test('deletes existing artifact and stamp file before updating', - () async { - expect(cachedArtifact.file.existsSync(), isTrue); - expect(cachedArtifact.stampFile.existsSync(), isTrue); + test( + 'deletes existing artifact and stamp file before updating', + () async { + expect(cachedArtifact.file.existsSync(), isTrue); + expect(cachedArtifact.stampFile.existsSync(), isTrue); - // This will fail due to the mock http client returning a 404. - await expectLater( - () => runWithOverrides(cachedArtifact.update), - throwsException, - ); + // This will fail due to the mock http client returning a 404. + await expectLater( + () => runWithOverrides(cachedArtifact.update), + throwsException, + ); - expect(cachedArtifact.file.existsSync(), isFalse); - expect(cachedArtifact.stampFile.existsSync(), isFalse); - }); + expect(cachedArtifact.file.existsSync(), isFalse); + expect(cachedArtifact.stampFile.existsSync(), isFalse); + }, + ); }); }); }); diff --git a/packages/shorebird_cli/test/src/checksum_checker_test.dart b/packages/shorebird_cli/test/src/checksum_checker_test.dart index e930061c..8db454e8 100644 --- a/packages/shorebird_cli/test/src/checksum_checker_test.dart +++ b/packages/shorebird_cli/test/src/checksum_checker_test.dart @@ -17,12 +17,7 @@ void main() { }); R runWithOverrides(R Function() body) { - return runScoped( - () => body(), - values: { - checksumCheckerRef, - }, - ); + return runScoped(() => body(), values: {checksumCheckerRef}); } group('checkFile', () { diff --git a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart index 191e09c7..501902ee 100644 --- a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart +++ b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart @@ -49,9 +49,9 @@ void main() { shorebirdFlutter = MockShorebirdFlutter(); when(() => auth.client).thenReturn(httpClient); - when(() => shorebirdEnv.hostedUri).thenReturn( - Uri.parse('http://example.com'), - ); + when( + () => shorebirdEnv.hostedUri, + ).thenReturn(Uri.parse('http://example.com')); when(() => logger.progress(any())).thenReturn(progress); when( @@ -65,9 +65,7 @@ void main() { when(() => httpClient.send(any())).thenAnswer( (_) async => http.StreamedResponse( Stream.value( - utf8.encode( - json.encode(const GetAppsResponse(apps: []).toJson()), - ), + utf8.encode(json.encode(const GetAppsResponse(apps: []).toJson())), ), HttpStatus.ok, ), @@ -94,13 +92,11 @@ void main() { values: {loggerRef.overrideWith(() => logger)}, ); - final request = verify(() => httpClient.send(captureAny())).captured.first - as http.BaseRequest; + final request = + verify(() => httpClient.send(captureAny())).captured.first + as http.BaseRequest; - expect( - request.headers['x-cli-version'], - equals(packageVersion), - ); + expect(request.headers['x-cli-version'], equals(packageVersion)); }); }); @@ -239,9 +235,8 @@ void main() { ).thenAnswer((_) async => app); await runWithOverrides( - () => codePushClientWrapper.createApp( - organizationId: organizationId, - ), + () => + codePushClientWrapper.createApp(organizationId: organizationId), ); verify(() => logger.prompt(any())).called(1); @@ -261,9 +256,7 @@ void main() { displayName: appName, organizationId: any(named: 'organizationId'), ), - ).thenAnswer( - (_) async => app, - ); + ).thenAnswer((_) async => app); await runWithOverrides( () => codePushClientWrapper.createApp( @@ -283,20 +276,23 @@ void main() { }); group('getOrganizationMemberships', () { - test('exits with code 70 when getting organization memberships fails', - () async { - const error = 'something went wrong'; - when(() => codePushClient.getOrganizationMemberships()) - .thenThrow(error); + test( + 'exits with code 70 when getting organization memberships fails', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.getOrganizationMemberships(), + ).thenThrow(error); - await expectLater( - () async => runWithOverrides( - codePushClientWrapper.getOrganizationMemberships, - ), - exitsWithCode(ExitCode.software), - ); - verify(() => progress.fail(error)).called(1); - }); + await expectLater( + () async => runWithOverrides( + codePushClientWrapper.getOrganizationMemberships, + ), + exitsWithCode(ExitCode.software), + ); + verify(() => progress.fail(error)).called(1); + }, + ); test('returns organization memberships on success', () async { final expectedMemberships = [ @@ -309,8 +305,9 @@ void main() { role: OrganizationRole.member, ), ]; - when(() => codePushClient.getOrganizationMemberships()) - .thenAnswer((_) async => expectedMemberships); + when( + () => codePushClient.getOrganizationMemberships(), + ).thenAnswer((_) async => expectedMemberships); final memberships = await runWithOverrides( codePushClientWrapper.getOrganizationMemberships, @@ -342,9 +339,8 @@ void main() { ), ); await expectLater( - () async => runWithOverrides( - () => codePushClientWrapper.getApps(), - ), + () async => + runWithOverrides(() => codePushClientWrapper.getApps()), exitsWithCode(ExitCode.software), ); verify(() => progress.fail()).called(1); @@ -565,9 +561,7 @@ void main() { flutterRevision: flutterRevision, flutterVersion: flutterVersion, displayName: displayName, - platformStatuses: { - releasePlatform: ReleaseStatus.active, - }, + platformStatuses: {releasePlatform: ReleaseStatus.active}, createdAt: DateTime(2023), updatedAt: DateTime(2023), ), @@ -576,10 +570,7 @@ void main() { ), exitsWithCode(ExitCode.software), ); - final uri = ShorebirdWebConsole.appReleaseUri( - appId, - releaseId, - ); + final uri = ShorebirdWebConsole.appReleaseUri(appId, releaseId); verify( () => logger.err( @@ -718,8 +709,9 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' }); test('exits with code 70 when release does not exist', () async { - when(() => codePushClient.getReleases(appId: any(named: 'appId'))) - .thenAnswer((_) async => []); + when( + () => codePushClient.getReleases(appId: any(named: 'appId')), + ).thenAnswer((_) async => []); await expectLater( () async => runWithOverrides( @@ -991,29 +983,31 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' ); }); - test('returns release artifacts when release artifacts exist', - () async { - when( - () => codePushClient.getReleaseArtifacts( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - ), - ).thenAnswer((_) async => [releaseArtifact]); + test( + 'returns release artifacts when release artifacts exist', + () async { + when( + () => codePushClient.getReleaseArtifacts( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + ), + ).thenAnswer((_) async => [releaseArtifact]); - final result = await runWithOverrides( - () => codePushClientWrapper.getReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - architectures: archs, - platform: releasePlatform, - ), - ); + final result = await runWithOverrides( + () => codePushClientWrapper.getReleaseArtifacts( + appId: app.appId, + releaseId: releaseId, + architectures: archs, + platform: releasePlatform, + ), + ); - expect(result, {arch: releaseArtifact}); - verify(() => progress.complete()).called(1); - }); + expect(result, {arch: releaseArtifact}); + verify(() => progress.complete()).called(1); + }, + ); }); group('getReleaseArtifact', () { @@ -1072,31 +1066,28 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' ).called(1); }); - test( - 'returns release artifact if release artifact exists', - () async { - when( - () => codePushClient.getReleaseArtifacts( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - ), - ).thenAnswer((_) async => [releaseArtifact]); + test('returns release artifact if release artifact exists', () async { + when( + () => codePushClient.getReleaseArtifacts( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + ), + ).thenAnswer((_) async => [releaseArtifact]); - final result = await runWithOverrides( - () => codePushClientWrapper.getReleaseArtifact( - appId: app.appId, - releaseId: releaseId, - arch: arch.arch, - platform: releasePlatform, - ), - ); + final result = await runWithOverrides( + () => codePushClientWrapper.getReleaseArtifact( + appId: app.appId, + releaseId: releaseId, + arch: arch.arch, + platform: releasePlatform, + ), + ); - expect(result, releaseArtifact); - verify(() => progress.complete()).called(1); - }, - ); + expect(result, releaseArtifact); + verify(() => progress.complete()).called(1); + }); }); group('maybeGetReleaseArtifact', () { @@ -1149,31 +1140,28 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' verify(() => progress.complete()).called(1); }); - test( - 'returns release artifact if release artifact exists', - () async { - when( - () => codePushClient.getReleaseArtifacts( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - ), - ).thenAnswer((_) async => [releaseArtifact]); + test('returns release artifact if release artifact exists', () async { + when( + () => codePushClient.getReleaseArtifacts( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + ), + ).thenAnswer((_) async => [releaseArtifact]); - final result = await runWithOverrides( - () => codePushClientWrapper.maybeGetReleaseArtifact( - appId: app.appId, - releaseId: releaseId, - arch: arch.arch, - platform: releasePlatform, - ), - ); + final result = await runWithOverrides( + () => codePushClientWrapper.maybeGetReleaseArtifact( + appId: app.appId, + releaseId: releaseId, + arch: arch.arch, + platform: releasePlatform, + ), + ); - expect(result, releaseArtifact); - verify(() => progress.complete()).called(1); - }, - ); + expect(result, releaseArtifact); + verify(() => progress.complete()).called(1); + }); }); group('createAndroidReleaseArtifacts', () { @@ -1301,41 +1289,43 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' verify(() => progress.fail(any(that: contains(error)))).called(1); }); - test('logs message when uploading release artifact that already exists', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: any(named: 'artifactPath'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(const CodePushConflictException(message: error)); - setUpProjectRoot(); + test( + 'logs message when uploading release artifact that already exists', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any(named: 'artifactPath'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), + ), + ).thenThrow(const CodePushConflictException(message: error)); + setUpProjectRoot(); - await runWithOverrides( - () => codePushClientWrapper.createAndroidReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - platform: releasePlatform, - projectRoot: projectRoot.path, - aabPath: p.join(projectRoot.path, aabPath), - architectures: Arch.values, - ), - ); + await runWithOverrides( + () => codePushClientWrapper.createAndroidReleaseArtifacts( + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + projectRoot: projectRoot.path, + aabPath: p.join(projectRoot.path, aabPath), + architectures: Arch.values, + ), + ); - // 1 for each arch, 1 for the aab - final numArtifactsUploaded = Arch.values.length + 1; - verify( - () => logger.info(any(that: contains('already exists'))), - ).called(numArtifactsUploaded); - verifyNever(() => progress.fail(error)); - }); + // 1 for each arch, 1 for the aab + final numArtifactsUploaded = Arch.values.length + 1; + verify( + () => logger.info(any(that: contains('already exists'))), + ).called(numArtifactsUploaded); + verifyNever(() => progress.fail(error)); + }, + ); test('logs message when uploading aab that already exists', () async { const error = 'something went wrong'; @@ -1453,9 +1443,9 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' group('createWindowsReleaseArtifacts', () { late File releaseZip; setUp(() { - releaseZip = - File(p.join(projectRoot.path, 'path', 'to', 'release.zip')) - ..createSync(recursive: true); + releaseZip = File( + p.join(projectRoot.path, 'path', 'to', 'release.zip'), + )..createSync(recursive: true); }); group('when release artifact already exists', () { @@ -1638,13 +1628,13 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' () 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: Arch.values, - ), + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + aarPath: p.join(projectRoot.path, aarPath), + extractedAarDir: p.join(projectRoot.path, extractedAarPath), + architectures: Arch.values, + ), ), exitsWithCode(ExitCode.software), ); @@ -1672,13 +1662,13 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' () 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: Arch.values, - ), + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + aarPath: p.join(projectRoot.path, aarPath), + extractedAarDir: p.join(projectRoot.path, extractedAarPath), + architectures: Arch.values, + ), ), exitsWithCode(ExitCode.software), ); @@ -1686,42 +1676,44 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' verify(() => progress.fail(any(that: contains(error)))).called(1); }); - test('logs message when uploading release artifact that already exists', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: any(named: 'artifactPath'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(const CodePushConflictException(message: error)); - setUpProjectRoot(); + test( + 'logs message when uploading release artifact that already exists', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any(named: 'artifactPath'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), + ), + ).thenThrow(const CodePushConflictException(message: error)); + setUpProjectRoot(); - await runWithOverrides( - () async => - codePushClientWrapper.createAndroidArchiveReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - platform: releasePlatform, - aarPath: p.join(projectRoot.path, aarPath), - extractedAarDir: p.join(projectRoot.path, extractedAarPath), - architectures: Arch.values, - ), - ); + await runWithOverrides( + () async => + codePushClientWrapper.createAndroidArchiveReleaseArtifacts( + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + aarPath: p.join(projectRoot.path, aarPath), + extractedAarDir: p.join(projectRoot.path, extractedAarPath), + architectures: Arch.values, + ), + ); - // 1 for each arch, 1 for the aab - final numArtifactsUploaded = Arch.values.length + 1; - verify( - () => logger.info(any(that: contains('already exists'))), - ).called(numArtifactsUploaded); - verifyNever(() => progress.fail(error)); - }); + // 1 for each arch, 1 for the aab + final numArtifactsUploaded = Arch.values.length + 1; + verify( + () => logger.info(any(that: contains('already exists'))), + ).called(numArtifactsUploaded); + verifyNever(() => progress.fail(error)); + }, + ); test('logs message when uploading aar that already exists', () async { const error = 'something went wrong'; @@ -1742,13 +1734,13 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' await runWithOverrides( () async => codePushClientWrapper.createAndroidArchiveReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - platform: releasePlatform, - aarPath: p.join(projectRoot.path, aarPath), - extractedAarDir: p.join(projectRoot.path, extractedAarPath), - architectures: Arch.values, - ), + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + aarPath: p.join(projectRoot.path, aarPath), + extractedAarDir: p.join(projectRoot.path, extractedAarPath), + architectures: Arch.values, + ), ); verify( @@ -1777,13 +1769,13 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' await runWithOverrides( () async => codePushClientWrapper.createAndroidArchiveReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - platform: releasePlatform, - aarPath: p.join(projectRoot.path, aarPath), - extractedAarDir: p.join(projectRoot.path, extractedAarPath), - architectures: Arch.values, - ), + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + aarPath: p.join(projectRoot.path, aarPath), + extractedAarDir: p.join(projectRoot.path, extractedAarPath), + architectures: Arch.values, + ), ); verify(() => progress.complete()).called(1); @@ -1809,13 +1801,13 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' await runWithOverrides( () async => codePushClientWrapper.createAndroidArchiveReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - platform: releasePlatform, - aarPath: p.join(projectRoot.path, aarPath), - extractedAarDir: p.join(projectRoot.path, extractedAarPath), - architectures: Arch.values, - ), + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + aarPath: p.join(projectRoot.path, aarPath), + extractedAarDir: p.join(projectRoot.path, extractedAarPath), + architectures: Arch.values, + ), ); verify( @@ -1869,155 +1861,165 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' ).thenAnswer((_) async {}); }); - test('exits with code 70 when xcarchive artifact creation fails', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: - any(named: 'artifactPath', that: endsWith('.xcarchive.zip')), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(error); - setUpProjectRoot(); - - 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, - podfileLockHash: podfileLockHash, - supplementPath: p.join(projectRoot.path, releaseSupplementPath), + test( + 'exits with code 70 when xcarchive artifact creation fails', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any( + named: 'artifactPath', + that: endsWith('.xcarchive.zip'), + ), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), ), - ), - exitsWithCode(ExitCode.software), - ); + ).thenThrow(error); + setUpProjectRoot(); - verify(() => progress.fail(any(that: contains(error)))).called(1); - }); - - test('exits with code 70 when uploading xcarchive that already exists', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: any( - named: 'artifactPath', - that: endsWith('.xcarchive.zip'), + 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, + podfileLockHash: podfileLockHash, + supplementPath: p.join(projectRoot.path, releaseSupplementPath), + ), ), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(const CodePushConflictException(message: error)); - setUpProjectRoot(); + exitsWithCode(ExitCode.software), + ); - 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, - podfileLockHash: podfileLockHash, - supplementPath: p.join(projectRoot.path, releaseSupplementPath), + verify(() => progress.fail(any(that: contains(error)))).called(1); + }, + ); + + test( + 'exits with code 70 when uploading xcarchive that already exists', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any( + named: 'artifactPath', + that: endsWith('.xcarchive.zip'), + ), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), ), - ), - exitsWithCode(ExitCode.software), - ); + ).thenThrow(const CodePushConflictException(message: error)); + setUpProjectRoot(); - verify(() => progress.fail(any(that: contains(error)))).called(1); - }); - - test('exits with code 70 when xcarchive artifact creation fails', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: any( - named: 'artifactPath', - that: endsWith('runner.app.zip'), + 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, + podfileLockHash: podfileLockHash, + supplementPath: p.join(projectRoot.path, releaseSupplementPath), + ), ), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(error); - setUpProjectRoot(); + exitsWithCode(ExitCode.software), + ); - 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, - podfileLockHash: podfileLockHash, - supplementPath: p.join(projectRoot.path, releaseSupplementPath), + verify(() => progress.fail(any(that: contains(error)))).called(1); + }, + ); + + test( + 'exits with code 70 when xcarchive artifact creation fails', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any( + named: 'artifactPath', + that: endsWith('runner.app.zip'), + ), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), ), - ), - exitsWithCode(ExitCode.software), - ); + ).thenThrow(error); + setUpProjectRoot(); - verify(() => progress.fail(any(that: contains(error)))).called(1); - }); - - test('exits with code 70 when supplement artifact creation fails', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: any( - named: 'artifactPath', - that: endsWith('ios_supplement.zip'), + 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, + podfileLockHash: podfileLockHash, + supplementPath: p.join(projectRoot.path, releaseSupplementPath), + ), ), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(error); - setUpProjectRoot(); + exitsWithCode(ExitCode.software), + ); - 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, - podfileLockHash: podfileLockHash, - supplementPath: p.join(projectRoot.path, releaseSupplementPath), + verify(() => progress.fail(any(that: contains(error)))).called(1); + }, + ); + + test( + 'exits with code 70 when supplement artifact creation fails', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any( + named: 'artifactPath', + that: endsWith('ios_supplement.zip'), + ), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), ), - ), - exitsWithCode(ExitCode.software), - ); + ).thenThrow(error); + setUpProjectRoot(); - verify(() => progress.fail(any(that: contains(error)))).called(1); - }); + 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, + podfileLockHash: podfileLockHash, + supplementPath: p.join(projectRoot.path, releaseSupplementPath), + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail(any(that: contains(error)))).called(1); + }, + ); test('completes successfully when artifact is created', () async { when( @@ -2261,68 +2263,75 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' setUpProjectRoot(); }); - test('exits with code 70 when creating xcframework artifact fails', - () async { - when( - () => codePushClient.createReleaseArtifact( - artifactPath: any(named: 'artifactPath'), - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(Exception('oh no')); - - await expectLater( - () async => runWithOverrides( - () => codePushClientWrapper.createIosFrameworkReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - appFrameworkPath: p.join(projectRoot.path, frameworkPath), - supplementPath: null, + test( + 'exits with code 70 when creating xcframework artifact fails', + () async { + when( + () => codePushClient.createReleaseArtifact( + artifactPath: any(named: 'artifactPath'), + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), ), - ), - exitsWithCode(ExitCode.software), - ); - }); + ).thenThrow(Exception('oh no')); - test('exits with code 70 when supplement artifact creation fails', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createReleaseArtifact( - appId: any(named: 'appId'), - artifactPath: any( - named: 'artifactPath', - that: endsWith('ios_framework_supplement.zip'), + await expectLater( + () async => runWithOverrides( + () => codePushClientWrapper.createIosFrameworkReleaseArtifacts( + appId: app.appId, + releaseId: releaseId, + appFrameworkPath: p.join(projectRoot.path, frameworkPath), + supplementPath: null, + ), ), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - canSideload: any(named: 'canSideload'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenThrow(error); + exitsWithCode(ExitCode.software), + ); + }, + ); - await expectLater( - () async => runWithOverrides( - () async => - codePushClientWrapper.createIosFrameworkReleaseArtifacts( - appId: app.appId, - releaseId: releaseId, - appFrameworkPath: p.join(projectRoot.path, frameworkPath), - supplementPath: p.join(projectRoot.path, releaseSupplementPath), + test( + 'exits with code 70 when supplement artifact creation fails', + () async { + const error = 'something went wrong'; + when( + () => codePushClient.createReleaseArtifact( + appId: any(named: 'appId'), + artifactPath: any( + named: 'artifactPath', + that: endsWith('ios_framework_supplement.zip'), + ), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), + podfileLockHash: any(named: 'podfileLockHash'), ), - ), - exitsWithCode(ExitCode.software), - ); + ).thenThrow(error); - verify(() => progress.fail(any(that: contains(error)))).called(1); - }); + await expectLater( + () async => runWithOverrides( + () async => + codePushClientWrapper.createIosFrameworkReleaseArtifacts( + appId: app.appId, + releaseId: releaseId, + appFrameworkPath: p.join(projectRoot.path, frameworkPath), + supplementPath: p.join( + projectRoot.path, + releaseSupplementPath, + ), + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail(any(that: contains(error)))).called(1); + }, + ); test('completes successfully when release artifact is created', () async { await expectLater( @@ -2340,31 +2349,28 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' }); group('updateReleaseStatus', () { - test( - 'exits with code 70 when updating release status fails', - () async { - when( - () => codePushClient.updateReleaseStatus( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - platform: any(named: 'platform'), - status: any(named: 'status'), - ), - ).thenThrow(Exception('oh no')); + test('exits with code 70 when updating release status fails', () async { + when( + () => codePushClient.updateReleaseStatus( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + platform: any(named: 'platform'), + status: any(named: 'status'), + ), + ).thenThrow(Exception('oh no')); - await expectLater( - () async => runWithOverrides( - () => codePushClientWrapper.updateReleaseStatus( - appId: app.appId, - releaseId: releaseId, - platform: releasePlatform, - status: ReleaseStatus.active, - ), + await expectLater( + () async => runWithOverrides( + () => codePushClientWrapper.updateReleaseStatus( + appId: app.appId, + releaseId: releaseId, + platform: releasePlatform, + status: ReleaseStatus.active, ), - exitsWithCode(ExitCode.software), - ); - }, - ); + ), + exitsWithCode(ExitCode.software), + ); + }); test('completes when updating release status succeeds', () async { when( @@ -2527,36 +2533,33 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' }); group('createPatchArtifacts', () { - test( - 'exits with code 70 when creating patch artifact fails', - () async { - const error = 'something went wrong'; - when( - () => codePushClient.createPatchArtifact( - appId: any(named: 'appId'), - patchId: any(named: 'patchId'), - artifactPath: any(named: 'artifactPath'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - ), - ).thenThrow(error); + test('exits with code 70 when creating patch artifact fails', () async { + const error = 'something went wrong'; + when( + () => codePushClient.createPatchArtifact( + appId: any(named: 'appId'), + patchId: any(named: 'patchId'), + artifactPath: any(named: 'artifactPath'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + ), + ).thenThrow(error); - await expectLater( - () async => runWithOverrides( - () => codePushClientWrapper.createPatchArtifacts( - appId: appId, - patch: patch, - platform: releasePlatform, - patchArtifactBundles: patchArtifactBundles, - ), + await expectLater( + () async => runWithOverrides( + () => codePushClientWrapper.createPatchArtifacts( + appId: appId, + patch: patch, + platform: releasePlatform, + patchArtifactBundles: patchArtifactBundles, ), - exitsWithCode(ExitCode.software), - ); + ), + exitsWithCode(ExitCode.software), + ); - verify(() => progress.fail(error)).called(1); - }, - ); + verify(() => progress.fail(error)).called(1); + }); test('creates artifacts successfully', () async { when( @@ -2748,9 +2751,9 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' final gcpSpeedTestUrl = Uri.parse('https://download.speedtest.gcp.com'); setUp(() { - when(() => codePushClient.getGCPDownloadSpeedTestUrl()).thenAnswer( - (_) async => gcpSpeedTestUrl, - ); + when( + () => codePushClient.getGCPDownloadSpeedTestUrl(), + ).thenAnswer((_) async => gcpSpeedTestUrl); }); test('calls codePushClient method', () async { @@ -2769,9 +2772,9 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console' final gcpSpeedTestUrl = Uri.parse('https://upload.speedtest.gcp.com'); setUp(() { - when(() => codePushClient.getGCPUploadSpeedTestUrl()).thenAnswer( - (_) async => gcpSpeedTestUrl, - ); + when( + () => codePushClient.getGCPUploadSpeedTestUrl(), + ).thenAnswer((_) async => gcpSpeedTestUrl); }); test('calls codePushClient method', () async { diff --git a/packages/shorebird_cli/test/src/code_signer_test.dart b/packages/shorebird_cli/test/src/code_signer_test.dart index 9ff00d75..f94c6cd6 100644 --- a/packages/shorebird_cli/test/src/code_signer_test.dart +++ b/packages/shorebird_cli/test/src/code_signer_test.dart @@ -14,9 +14,7 @@ void main() { final privateKeyFile = File( p.join(cryptoFixturesBasePath, 'private.pem'), ); - final publicKeyFile = File( - p.join(cryptoFixturesBasePath, 'public.pem'), - ); + final publicKeyFile = File(p.join(cryptoFixturesBasePath, 'public.pem')); late CodeSigner codeSigner; @@ -43,8 +41,9 @@ void main() { messageFile.path, ]); - final expectedSignature = - base64Encode(signatureFile.readAsBytesSync()); + final expectedSignature = base64Encode( + signatureFile.readAsBytesSync(), + ); final actualSignature = codeSigner.sign( message: message, privateKeyPemFile: privateKeyFile, @@ -57,22 +56,19 @@ void main() { test('output matches equivalent openssl command', () async { final tempDir = Directory.systemTemp.createTempSync(); final expectedDerFile = File(p.join(tempDir.path, 'public.der')); - await Process.run( - 'openssl', - [ - 'rsa', - '-pubin', - '-in', - publicKeyFile.path, - '-inform', - 'PEM', - '-RSAPublicKey_out', - '-outform', - 'DER', - '-out', - expectedDerFile.path, - ], - ); + await Process.run('openssl', [ + 'rsa', + '-pubin', + '-in', + publicKeyFile.path, + '-inform', + 'PEM', + '-RSAPublicKey_out', + '-outform', + 'DER', + '-out', + expectedDerFile.path, + ]); expect( codeSigner.base64PublicKey(publicKeyFile), equals(base64Encode(expectedDerFile.readAsBytesSync())), diff --git a/packages/shorebird_cli/test/src/commands/cache/clean_cache_command_test.dart b/packages/shorebird_cli/test/src/commands/cache/clean_cache_command_test.dart index 66bfd65b..84019a4e 100644 --- a/packages/shorebird_cli/test/src/commands/cache/clean_cache_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/cache/clean_cache_command_test.dart @@ -43,9 +43,9 @@ void main() { command = runWithOverrides(CleanCacheCommand.new); when(() => logger.progress(any())).thenReturn(progress); - when(() => shorebirdEnv.shorebirdRoot).thenReturn( - Directory.systemTemp.createTempSync(), - ); + when( + () => shorebirdEnv.shorebirdRoot, + ).thenReturn(Directory.systemTemp.createTempSync()); }); test('has a non-empty description', () { @@ -66,29 +66,29 @@ void main() { when(() => platform.isWindows).thenReturn(true); }); - test('tells the user how to find the issue and exits with code 70', - () async { - when(() => cache.clear()).thenThrow( - const FileSystemException('Failed to delete'), - ); + test( + 'tells the user how to find the issue and exits with code 70', + () async { + when( + () => cache.clear(), + ).thenThrow(const FileSystemException('Failed to delete')); - final result = await runWithOverrides(command.run); + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.software.code)); - verify(() => progress.fail(any())).called(1); - verify( - () => logger.info( - any( - that: stringContainsInOrder( - [ + expect(result, equals(ExitCode.software.code)); + verify(() => progress.fail(any())).called(1); + verify( + () => logger.info( + any( + that: stringContainsInOrder([ '''This could be because a program is using a file in the cache directory. To find and stop such a program, see''', 'https://superuser.com/questions/1333118/cant-delete-empty-folder-because-it-is-used', - ], + ]), ), ), - ), - ).called(1); - }); + ).called(1); + }, + ); }); group('on a non-Windows OS', () { @@ -97,9 +97,9 @@ void main() { }); test('prints error message and exits with code 70', () async { - when(() => cache.clear()).thenThrow( - const FileSystemException('Failed to delete'), - ); + when( + () => cache.clear(), + ).thenThrow(const FileSystemException('Failed to delete')); final result = await runWithOverrides(command.run); diff --git a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart index 1c8f9be5..9c24a8d0 100644 --- a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart @@ -105,8 +105,7 @@ void main() { ..testArgResults = argResults; }); - test( - 'prints shorebird version, flutter revision, ' + test('prints shorebird version, flutter revision, ' 'and engine revision ' 'when unable to determine Flutter version', () async { when( @@ -130,26 +129,27 @@ Engine • revision $shorebirdEngineRevision }); test( - '''prints shorebird version, flutter revision, flutter version, and engine revision''', - () async { - const flutterVersion = '1.2.3'; - when( - () => shorebirdFlutter.getVersionString(), - ).thenAnswer((_) async => flutterVersion); - await runWithOverrides(command.run); + '''prints shorebird version, flutter revision, flutter version, and engine revision''', + () async { + const flutterVersion = '1.2.3'; + when( + () => shorebirdFlutter.getVersionString(), + ).thenAnswer((_) async => flutterVersion); + await runWithOverrides(command.run); - verify( - () => logger.info(''' + verify( + () => logger.info(''' Shorebird v$packageVersion • git@github.com:shorebirdtech/shorebird.git Flutter $flutterVersion • revision ${shorebirdEnv.flutterRevision} Engine • revision $shorebirdEngineRevision '''), - ).called(1); - verify(() => networkChecker.checkReachability()).called(1); - verifyNever(() => networkChecker.performGCPDownloadSpeedTest()); - verifyNever(() => networkChecker.performGCPUploadSpeedTest()); - }); + ).called(1); + verify(() => networkChecker.checkReachability()).called(1); + verifyNever(() => networkChecker.performGCPDownloadSpeedTest()); + verifyNever(() => networkChecker.performGCPUploadSpeedTest()); + }, + ); group('--verbose', () { setUp(() { @@ -210,12 +210,8 @@ OpenJDK 64-Bit Server VM (build 17.0.9+0-17.0.9b1087.7-11185874, mixed mode)''' final notDetectedText = red.wrap('not detected'); expect( - msg.replaceAll( - Platform.lineTerminator, - '\n', - ), - equals( - ''' + msg.replaceAll(Platform.lineTerminator, '\n'), + equals(''' Shorebird $packageVersion • git@github.com:shorebirdtech/shorebird.git Flutter • revision ${shorebirdEnv.flutterRevision} Engine • revision $shorebirdEngineRevision @@ -231,8 +227,7 @@ Android Toolchain OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) OpenJDK 64-Bit Server VM (build 17.0.9+0-17.0.9b1087.7-11185874, mixed mode) • Gradle: $notDetectedText -''', - ), +'''), ); verify(() => networkChecker.checkReachability()).called(1); @@ -272,12 +267,8 @@ OpenJDK 64-Bit Server VM (build 17.0.9+0-17.0.9b1087.7-11185874, mixed mode)''' verify(() => logger.info(captureAny())).captured.first as String; expect( - msg.replaceAll( - Platform.lineTerminator, - '\n', - ), - equals( - ''' + msg.replaceAll(Platform.lineTerminator, '\n'), + equals(''' Shorebird $packageVersion • git@github.com:shorebirdtech/shorebird.git Flutter • revision ${shorebirdEnv.flutterRevision} Engine • revision $shorebirdEngineRevision @@ -293,8 +284,7 @@ Android Toolchain OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) OpenJDK 64-Bit Server VM (build 17.0.9+0-17.0.9b1087.7-11185874, mixed mode) • Gradle: 7.6.3 -''', - ), +'''), ); }); }); @@ -315,10 +305,7 @@ Android Toolchain }); test('logs error as detail, continues', () async { - await expectLater( - runWithOverrides(command.run), - completes, - ); + await expectLater(runWithOverrides(command.run), completes); verify( () => progress.fail('GCP upload speed test failed: oops'), @@ -334,10 +321,7 @@ Android Toolchain }); test('logs error as detail, continues', () async { - await expectLater( - runWithOverrides(command.run), - completes, - ); + await expectLater(runWithOverrides(command.run), completes); verify( () => progress.fail( @@ -364,10 +348,7 @@ Android Toolchain }); test('logs error as detail, continues', () async { - await expectLater( - runWithOverrides(command.run), - completes, - ); + await expectLater(runWithOverrides(command.run), completes); verify( () => progress.fail('GCP download speed test failed: oops'), @@ -383,10 +364,7 @@ Android Toolchain }); test('logs error as detail, continues', () async { - await expectLater( - runWithOverrides(command.run), - completes, - ); + await expectLater(runWithOverrides(command.run), completes); verify( () => progress.fail( @@ -398,14 +376,16 @@ Android Toolchain }); }); - test('runs validators without applying fixes if no fix flag exists', - () async { - when(() => argResults['fix']).thenReturn(null); - final result = await runWithOverrides(command.run); + test( + 'runs validators without applying fixes if no fix flag exists', + () async { + when(() => argResults['fix']).thenReturn(null); + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.success.code)); - verify(() => doctor.runValidators([validator])).called(1); - }); + expect(result, equals(ExitCode.success.code)); + verify(() => doctor.runValidators([validator])).called(1); + }, + ); test('runs validators and applies fixes fix flag is true', () async { when(() => argResults['fix']).thenReturn(true); diff --git a/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart b/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart index 7f11bb54..1087ce1f 100644 --- a/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart @@ -41,56 +41,56 @@ void main() { expect(command.description, equals('List available Flutter versions.')); }); - test('exits with code 70 when unable to determine Flutter versions', - () async { - when( - () => shorebirdFlutter.getVersionString(), - ).thenAnswer((_) async => '1.0.0'); - when(() => shorebirdFlutter.getVersions()).thenThrow(Exception('error')); - await expectLater( - runWithOverrides(command.run), - completion(equals(ExitCode.software.code)), - ); - verifyInOrder([ - () => logger.progress('Fetching Flutter versions'), - () => shorebirdFlutter.getVersionString(), - () => shorebirdFlutter.getVersions(), - () => progress.fail('Failed to fetch Flutter versions.'), - () => logger.err('Exception: error'), - ]); - }); + test( + 'exits with code 70 when unable to determine Flutter versions', + () async { + when( + () => shorebirdFlutter.getVersionString(), + ).thenAnswer((_) async => '1.0.0'); + when( + () => shorebirdFlutter.getVersions(), + ).thenThrow(Exception('error')); + await expectLater( + runWithOverrides(command.run), + completion(equals(ExitCode.software.code)), + ); + verifyInOrder([ + () => logger.progress('Fetching Flutter versions'), + () => shorebirdFlutter.getVersionString(), + () => shorebirdFlutter.getVersions(), + () => progress.fail('Failed to fetch Flutter versions.'), + () => logger.err('Exception: error'), + ]); + }, + ); test( - 'exits with code 0 when able to determine Flutter versions w/out current version', - () async { - const versions = ['1.0.0', '1.0.1']; - when(() => shorebirdFlutter.getVersionString()).thenThrow( - const ProcessException( - 'flutter', - ['--version'], - 'Flutter 1.0.0', - ), - ); - when( - () => shorebirdFlutter.getVersions(), - ).thenAnswer((_) async => versions); - await expectLater( - runWithOverrides(command.run), - completion(equals(ExitCode.success.code)), - ); - verifyInOrder([ - () => logger.progress('Fetching Flutter versions'), - () => shorebirdFlutter.getVersionString(), - () => shorebirdFlutter.getVersions(), - () => progress.cancel(), - () => logger.info('📦 Flutter Versions'), - () => logger.info(' 1.0.1'), - () => logger.info(' 1.0.0'), - ]); - }); + 'exits with code 0 when able to determine Flutter versions w/out current version', + () async { + const versions = ['1.0.0', '1.0.1']; + when(() => shorebirdFlutter.getVersionString()).thenThrow( + const ProcessException('flutter', ['--version'], 'Flutter 1.0.0'), + ); + when( + () => shorebirdFlutter.getVersions(), + ).thenAnswer((_) async => versions); + await expectLater( + runWithOverrides(command.run), + completion(equals(ExitCode.success.code)), + ); + verifyInOrder([ + () => logger.progress('Fetching Flutter versions'), + () => shorebirdFlutter.getVersionString(), + () => shorebirdFlutter.getVersions(), + () => progress.cancel(), + () => logger.info('📦 Flutter Versions'), + () => logger.info(' 1.0.1'), + () => logger.info(' 1.0.0'), + ]); + }, + ); - test( - 'exits with code 0 when able to determine Flutter versions ' + test('exits with code 0 when able to determine Flutter versions ' 'as well as the current version', () async { const versions = ['1.0.0', '1.0.1']; when( diff --git a/packages/shorebird_cli/test/src/commands/init_command_test.dart b/packages/shorebird_cli/test/src/commands/init_command_test.dart index d7984cea..284bb07f 100644 --- a/packages/shorebird_cli/test/src/commands/init_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/init_command_test.dart @@ -172,12 +172,10 @@ environment: when(() => shorebirdEnv.hasPubspecYaml).thenReturn(false); final exitCode = await runWithOverrides(command.run); verify( - () => logger.err( - ''' + () => logger.err(''' Could not find a "pubspec.yaml". Please make sure you are running "shorebird init" from within your Flutter project. -''', - ), +'''), ).called(1); expect(exitCode, ExitCode.noInput.code); }); @@ -303,8 +301,9 @@ Please make sure you are running "shorebird init" from within your Flutter proje group('when user has only one organization', () { setUp(() { - when(() => codePushClientWrapper.getOrganizationMemberships()) - .thenAnswer( + when( + () => codePushClientWrapper.getOrganizationMemberships(), + ).thenAnswer( (_) async => [ OrganizationMembership( role: OrganizationRole.owner, @@ -314,30 +313,29 @@ Please make sure you are running "shorebird init" from within your Flutter proje ); }); - test('does not prompt for organization, uses that org id to create app', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.success.code)); - verifyNever( - () => logger.chooseOne( - 'Which organization should this app belong to?', - choices: any(named: 'choices'), - ), - ); - verify( - () => codePushClientWrapper.createApp( - appName: appName, - organizationId: organizationId, - ), - ).called(1); - }); + test( + 'does not prompt for organization, uses that org id to create app', + () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.success.code)); + verifyNever( + () => logger.chooseOne( + 'Which organization should this app belong to?', + choices: any(named: 'choices'), + ), + ); + verify( + () => codePushClientWrapper.createApp( + appName: appName, + organizationId: organizationId, + ), + ).called(1); + }, + ); }); group('when user has multiple organizations', () { - final org1 = Organization.forTest( - name: 'org1', - id: 1, - ); + final org1 = Organization.forTest(name: 'org1', id: 1); final org2 = Organization.forTest( name: 'org2', id: 2, @@ -345,8 +343,9 @@ Please make sure you are running "shorebird init" from within your Flutter proje ); setUp(() { - when(() => codePushClientWrapper.getOrganizationMemberships()) - .thenAnswer( + when( + () => codePushClientWrapper.getOrganizationMemberships(), + ).thenAnswer( (_) async => [ OrganizationMembership( role: OrganizationRole.owner, @@ -367,26 +366,30 @@ Please make sure you are running "shorebird init" from within your Flutter proje ).thenReturn(org2); }); - test('prompts for organization and uses that org id to create app', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.success.code)); - final capturedDisplay = verify( - () => logger.chooseOne( - 'Which organization should this app belong to?', - choices: [org1, org2], - display: captureAny(named: 'display'), - ), - ).captured.single as String Function(Organization); - expect(capturedDisplay(org1), equals(org1.name)); - expect(capturedDisplay(org2), equals(org2.name)); - verify( - () => codePushClientWrapper.createApp( - appName: appName, - organizationId: org2.id, - ), - ).called(1); - }); + test( + 'prompts for organization and uses that org id to create app', + () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.success.code)); + final capturedDisplay = + verify( + () => logger.chooseOne( + 'Which organization should this app belong to?', + choices: [org1, org2], + display: captureAny(named: 'display'), + ), + ).captured.single + as String Function(Organization); + expect(capturedDisplay(org1), equals(org1.name)); + expect(capturedDisplay(org2), equals(org2.name)); + verify( + () => codePushClientWrapper.createApp( + appName: appName, + organizationId: org2.id, + ), + ).called(1); + }, + ); }); group('on non MacOS', () { @@ -396,14 +399,15 @@ Please make sure you are running "shorebird init" from within your Flutter proje group('when ios directory is empty', () { setUp(() { - when(() => apple.flavors(platform: any(named: 'platform'))).thenThrow( - MissingXcodeProjectException(projectRoot.path), - ); + when( + () => apple.flavors(platform: any(named: 'platform')), + ).thenThrow(MissingXcodeProjectException(projectRoot.path)); }); test('exits with software error code', () async { - Directory(p.join(projectRoot.path, 'ios', 'Runner.xcodeproj')) - .createSync(recursive: true); + Directory( + p.join(projectRoot.path, 'ios', 'Runner.xcodeproj'), + ).createSync(recursive: true); final exitCode = await runWithOverrides(command.run); expect(exitCode, equals(ExitCode.software.code)); verify( @@ -431,23 +435,25 @@ Please make sure you are running "shorebird init" from within your Flutter proje when(() => argResults['display-name']).thenReturn(displayName); }); - test('does not prompt for display name and uses correct display name', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.success.code)); - verify( - () => shorebirdYamlFile.writeAsStringSync( - any(that: contains('app_id: $appId')), - ), - ).called(1); - verifyNever(() => logger.prompt(any())); - verify( - () => codePushClientWrapper.createApp( - appName: displayName, - organizationId: organizationId, - ), - ).called(1); - }); + test( + 'does not prompt for display name and uses correct display name', + () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.success.code)); + verify( + () => shorebirdYamlFile.writeAsStringSync( + any(that: contains('app_id: $appId')), + ), + ).called(1); + verifyNever(() => logger.prompt(any())); + verify( + () => codePushClientWrapper.createApp( + appName: displayName, + organizationId: organizationId, + ), + ).called(1); + }, + ); }); test('creates shorebird for an app without flavors', () async { @@ -471,8 +477,9 @@ Please make sure you are running "shorebird init" from within your Flutter proje any(that: contains('app_id: $appId')), ), ).called(1); - verify(() => progress.complete('No product flavors detected.')) - .called(1); + verify( + () => progress.complete('No product flavors detected.'), + ).called(1); }); test('creates shorebird for an app with flavors', () async { @@ -495,8 +502,9 @@ Please make sure you are running "shorebird init" from within your Flutter proje ).thenReturn({'internal', 'stable'}); final exitCode = await runWithOverrides(command.run); expect(exitCode, equals(ExitCode.success.code)); - verify(() => progress.complete('2 product flavors detected:')) - .called(1); + verify( + () => progress.complete('2 product flavors detected:'), + ).called(1); verify(() => logger.info(' - internal')).called(1); verify(() => logger.info(' - stable')).called(1); verify( @@ -512,13 +520,13 @@ flavors: ).called(1); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (internal)', - organizationId: organizationId, - ), + appName: '$appName (internal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stable)', - organizationId: organizationId, - ), + appName: '$appName (stable)', + organizationId: organizationId, + ), ]); }); }); @@ -580,29 +588,29 @@ flavors: ); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (development)', - organizationId: organizationId, - ), + appName: '$appName (development)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (developmentInternal)', - organizationId: organizationId, - ), + appName: '$appName (developmentInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production)', - organizationId: organizationId, - ), + appName: '$appName (production)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (productionInternal)', - organizationId: organizationId, - ), + appName: '$appName (productionInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (staging)', - organizationId: organizationId, - ), + appName: '$appName (staging)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stagingInternal)', - organizationId: organizationId, - ), + appName: '$appName (stagingInternal)', + organizationId: organizationId, + ), ]); }); @@ -616,16 +624,14 @@ flavors: 'test-appId-6', ]; var index = 0; - when(() => apple.flavors(platform: ApplePlatform.ios)).thenReturn( - { - 'development', - 'developmentInternal', - 'production', - 'productionInternal', - 'staging', - 'stagingInternal', - }, - ); + when(() => apple.flavors(platform: ApplePlatform.ios)).thenReturn({ + 'development', + 'developmentInternal', + 'production', + 'productionInternal', + 'staging', + 'stagingInternal', + }); when( () => codePushClientWrapper.createApp( appName: any(named: 'appName'), @@ -656,29 +662,29 @@ flavors: ); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (development)', - organizationId: organizationId, - ), + appName: '$appName (development)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (developmentInternal)', - organizationId: organizationId, - ), + appName: '$appName (developmentInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production)', - organizationId: organizationId, - ), + appName: '$appName (production)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (productionInternal)', - organizationId: organizationId, - ), + appName: '$appName (productionInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (staging)', - organizationId: organizationId, - ), + appName: '$appName (staging)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stagingInternal)', - organizationId: organizationId, - ), + appName: '$appName (stagingInternal)', + organizationId: organizationId, + ), ]); }); @@ -693,16 +699,14 @@ flavors: 'test-appId-7', ]; var index = 0; - when(() => apple.flavors(platform: ApplePlatform.ios)).thenReturn( - { - 'development', - 'developmentInternal', - 'production', - 'productionInternal', - 'staging', - 'stagingInternal', - }, - ); + when(() => apple.flavors(platform: ApplePlatform.ios)).thenReturn({ + 'development', + 'developmentInternal', + 'production', + 'productionInternal', + 'staging', + 'stagingInternal', + }); when(() => gradlew.productFlavors(any())).thenAnswer((_) async => {}); when( () => codePushClientWrapper.createApp( @@ -731,29 +735,29 @@ flavors: ); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (development)', - organizationId: organizationId, - ), + appName: '$appName (development)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (developmentInternal)', - organizationId: organizationId, - ), + appName: '$appName (developmentInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production)', - organizationId: organizationId, - ), + appName: '$appName (production)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (productionInternal)', - organizationId: organizationId, - ), + appName: '$appName (productionInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (staging)', - organizationId: organizationId, - ), + appName: '$appName (staging)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stagingInternal)', - organizationId: organizationId, - ), + appName: '$appName (stagingInternal)', + organizationId: organizationId, + ), ]); }); @@ -806,29 +810,29 @@ flavors: ); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (development)', - organizationId: organizationId, - ), + appName: '$appName (development)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (developmentInternal)', - organizationId: organizationId, - ), + appName: '$appName (developmentInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production)', - organizationId: organizationId, - ), + appName: '$appName (production)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (productionInternal)', - organizationId: organizationId, - ), + appName: '$appName (productionInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (staging)', - organizationId: organizationId, - ), + appName: '$appName (staging)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stagingInternal)', - organizationId: organizationId, - ), + appName: '$appName (stagingInternal)', + organizationId: organizationId, + ), ]); }); @@ -883,29 +887,29 @@ flavors: ); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (development)', - organizationId: organizationId, - ), + appName: '$appName (development)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (developmentInternal)', - organizationId: organizationId, - ), + appName: '$appName (developmentInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production)', - organizationId: organizationId, - ), + appName: '$appName (production)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (productionInternal)', - organizationId: organizationId, - ), + appName: '$appName (productionInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (staging)', - organizationId: organizationId, - ), + appName: '$appName (staging)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stagingInternal)', - organizationId: organizationId, - ), + appName: '$appName (stagingInternal)', + organizationId: organizationId, + ), ]); }); @@ -980,49 +984,46 @@ flavors: ); verifyInOrder([ () => codePushClientWrapper.createApp( - appName: '$appName (dev)', - organizationId: organizationId, - ), + appName: '$appName (dev)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (devInternal)', - organizationId: organizationId, - ), + appName: '$appName (devInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production)', - organizationId: organizationId, - ), + appName: '$appName (production)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (productionInternal)', - organizationId: organizationId, - ), + appName: '$appName (productionInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (development)', - organizationId: organizationId, - ), + appName: '$appName (development)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (developmentInternal)', - organizationId: organizationId, - ), + appName: '$appName (developmentInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (staging)', - organizationId: organizationId, - ), + appName: '$appName (staging)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (stagingInternal)', - organizationId: organizationId, - ), + appName: '$appName (stagingInternal)', + organizationId: organizationId, + ), () => codePushClientWrapper.createApp( - appName: '$appName (production-macos)', - organizationId: organizationId, - ), + appName: '$appName (production-macos)', + organizationId: organizationId, + ), ]); }); group('with new flavors added', () { - final existingFlavors = { - 'a': 'test-appId-1', - 'b': 'test-appId-2', - }; + final existingFlavors = {'a': 'test-appId-1', 'b': 'test-appId-2'}; setUp(() { const androidVariants = {'a', 'b', 'c', 'd'}; @@ -1036,24 +1037,25 @@ flavors: when(() => shorebirdYaml.flavors).thenReturn(existingFlavors); }); - test('exits with software error if retrieving existing app fails', - () async { - when(() => codePushClientWrapper.getApp(appId: any(named: 'appId'))) - .thenThrow(Exception('oh no')); - final result = await runWithOverrides(command.run); - expect(result, ExitCode.software.code); - }); + test( + 'exits with software error if retrieving existing app fails', + () async { + when( + () => codePushClientWrapper.getApp(appId: any(named: 'appId')), + ).thenThrow(Exception('oh no')); + final result = await runWithOverrides(command.run); + expect(result, ExitCode.software.code); + }, + ); test('creates new flavor entries in shorebird.yaml', () async { - const newAppIds = [ - 'test-appId-3', - 'test-appId-4', - ]; + const newAppIds = ['test-appId-3', 'test-appId-4']; const appName = 'my-app'; var index = 0; - when(() => codePushClientWrapper.getApp(appId: any(named: 'appId'))) - .thenAnswer( + when( + () => codePushClientWrapper.getApp(appId: any(named: 'appId')), + ).thenAnswer( (_) async => AppMetadata( appId: appId, displayName: appName, @@ -1101,15 +1103,13 @@ flavors: verify( () => shorebirdYamlFile.writeAsStringSync( any( - that: contains( - ''' + that: contains(''' app_id: test_app_id flavors: a: test-appId-1 b: test-appId-2 c: test-appId-3 - d: test-appId-4''', - ), + d: test-appId-4'''), ), ), ).called(1); @@ -1137,19 +1137,17 @@ flutter: verify( () => logger.info( any( - that: stringContainsInOrder( - [ - lightGreen.wrap('🐦 Shorebird initialized successfully!')!, - '✅ A shorebird app has been created.', - '✅ A "shorebird.yaml" has been created.', - '''✅ The "pubspec.yaml" has been updated to include "shorebird.yaml" as an asset.''', - '''📦 To create a new release use: "${lightCyan.wrap('shorebird release')}".''', - '''🚀 To push an update use: "${lightCyan.wrap('shorebird patch')}".''', - '''👀 To preview a release use: "${lightCyan.wrap('shorebird preview')}".''', - '''For more information about Shorebird, visit ${link(uri: Uri.parse('https://shorebird.dev'))}''', - '', - ], - ), + that: stringContainsInOrder([ + lightGreen.wrap('🐦 Shorebird initialized successfully!')!, + '✅ A shorebird app has been created.', + '✅ A "shorebird.yaml" has been created.', + '''✅ The "pubspec.yaml" has been updated to include "shorebird.yaml" as an asset.''', + '''📦 To create a new release use: "${lightCyan.wrap('shorebird release')}".''', + '''🚀 To push an update use: "${lightCyan.wrap('shorebird patch')}".''', + '''👀 To preview a release use: "${lightCyan.wrap('shorebird preview')}".''', + '''For more information about Shorebird, visit ${link(uri: Uri.parse('https://shorebird.dev'))}''', + '', + ]), ), ), ).called(1); @@ -1165,12 +1163,12 @@ flutter: await runWithOverrides(command.run); verifyInOrder([ () => logger.info( - any( - that: contains( - lightGreen.wrap('🐦 Shorebird initialized successfully!'), - ), - ), + any( + that: contains( + lightGreen.wrap('🐦 Shorebird initialized successfully!'), ), + ), + ), () => doctor.runValidators(any(), applyFixes: true), ]); }); diff --git a/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart b/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart index 1af9c8f2..65c4cabe 100644 --- a/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart @@ -44,9 +44,7 @@ void main() { when(() => results.wasParsed('provider')).thenReturn(false); when(() => results['provider']).thenReturn(null); when(() => auth.client).thenReturn(httpClient); - when( - () => auth.loginCI(any(), prompt: any(named: 'prompt')), - ).thenAnswer( + when(() => auth.loginCI(any(), prompt: any(named: 'prompt'))).thenAnswer( (_) async => const CiToken( // "shorebird-token" in base64 refreshToken: 'c2hvcmViaXJkLXRva2Vu', // cspell:disable-line @@ -79,10 +77,7 @@ void main() { await runWithOverrides(() => command.run()); verify( - () => auth.loginCI( - provider, - prompt: any(named: 'prompt'), - ), + () => auth.loginCI(provider, prompt: any(named: 'prompt')), ).called(1); }); }); @@ -105,18 +100,17 @@ void main() { await runWithOverrides(() => command.run()); verify( - () => auth.loginCI( - provider, - prompt: any(named: 'prompt'), - ), + () => auth.loginCI(provider, prompt: any(named: 'prompt')), ).called(1); - final captured = verify( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - display: captureAny(named: 'display'), - ), - ).captured.single as String Function(AuthProvider); + final captured = + verify( + () => logger.chooseOne( + any(), + choices: any(named: 'choices'), + display: captureAny(named: 'display'), + ), + ).captured.single + as String Function(AuthProvider); expect(captured(AuthProvider.google), contains('Google')); }); }); @@ -124,10 +118,7 @@ void main() { test('exits with code 70 if no user is found', () async { when( - () => auth.loginCI( - any(), - prompt: any(named: 'prompt'), - ), + () => auth.loginCI(any(), prompt: any(named: 'prompt')), ).thenThrow(UserNotFoundException(email: email)); final result = await runWithOverrides(command.run); @@ -144,21 +135,13 @@ void main() { test('exits with code 70 when error occurs', () async { final error = Exception('oops something went wrong!'); when( - () => auth.loginCI( - any(), - prompt: any(named: 'prompt'), - ), + () => auth.loginCI(any(), prompt: any(named: 'prompt')), ).thenThrow(error); final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); - verify( - () => auth.loginCI( - any(), - prompt: any(named: 'prompt'), - ), - ).called(1); + verify(() => auth.loginCI(any(), prompt: any(named: 'prompt'))).called(1); verify(() => logger.err(error.toString())).called(1); }); @@ -169,22 +152,14 @@ void main() { authProvider: AuthProvider.google, ); when( - () => auth.loginCI( - any(), - prompt: any(named: 'prompt'), - ), + () => auth.loginCI(any(), prompt: any(named: 'prompt')), ).thenAnswer((_) async => token); when(() => auth.email).thenReturn(email); final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.success.code)); - verify( - () => auth.loginCI( - any(), - prompt: any(named: 'prompt'), - ), - ).called(1); + verify(() => auth.loginCI(any(), prompt: any(named: 'prompt'))).called(1); verify( () => logger.info( any(that: contains('${lightCyan.wrap(token.toBase64())}')), diff --git a/packages/shorebird_cli/test/src/commands/login_command_test.dart b/packages/shorebird_cli/test/src/commands/login_command_test.dart index b5a575b6..84d65c24 100644 --- a/packages/shorebird_cli/test/src/commands/login_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/login_command_test.dart @@ -50,9 +50,9 @@ void main() { when(() => results['provider']).thenReturn(null); when(() => auth.isAuthenticated).thenReturn(false); when(() => auth.client).thenReturn(httpClient); - when(() => auth.credentialsFilePath).thenReturn( - p.join(applicationConfigHome.path, 'credentials.json'), - ); + when( + () => auth.credentialsFilePath, + ).thenReturn(p.join(applicationConfigHome.path, 'credentials.json')); when( () => auth.login(any(), prompt: any(named: 'prompt')), ).thenAnswer((_) async {}); @@ -83,10 +83,7 @@ void main() { await runWithOverrides(() => command.run()); verify( - () => auth.login( - provider, - prompt: any(named: 'prompt'), - ), + () => auth.login(provider, prompt: any(named: 'prompt')), ).called(1); }); }); @@ -109,18 +106,17 @@ void main() { await runWithOverrides(() => command.run()); verify( - () => auth.login( - provider, - prompt: any(named: 'prompt'), - ), + () => auth.login(provider, prompt: any(named: 'prompt')), ).called(1); - final captured = verify( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - display: captureAny(named: 'display'), - ), - ).captured.single as String Function(AuthProvider); + final captured = + verify( + () => logger.chooseOne( + any(), + choices: any(named: 'choices'), + display: captureAny(named: 'display'), + ), + ).captured.single + as String Function(AuthProvider); expect(captured(AuthProvider.google), contains('Google')); }); }); @@ -132,29 +128,28 @@ void main() { when(() => auth.email).thenReturn(email); }); - test('prints message and exits with code 0 when already logged in', - () async { - final result = await runWithOverrides(command.run); + test( + 'prints message and exits with code 0 when already logged in', + () async { + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.success.code)); - verify( - () => logger.info('You are already logged in as <$email>.'), - ).called(1); - verify( - () => logger.info( - '''Run ${lightCyan.wrap('shorebird logout')} to log out and try again.''', - ), - ).called(1); - verifyNever(() => auth.login(any(), prompt: any(named: 'prompt'))); - }); + expect(result, equals(ExitCode.success.code)); + verify( + () => logger.info('You are already logged in as <$email>.'), + ).called(1); + verify( + () => logger.info( + '''Run ${lightCyan.wrap('shorebird logout')} to log out and try again.''', + ), + ).called(1); + verifyNever(() => auth.login(any(), prompt: any(named: 'prompt'))); + }, + ); }); test('exits with code 70 if no user is found', () async { when( - () => auth.login( - any(), - prompt: any(named: 'prompt'), - ), + () => auth.login(any(), prompt: any(named: 'prompt')), ).thenThrow(UserNotFoundException(email: email)); final result = await runWithOverrides(command.run); @@ -171,42 +166,26 @@ void main() { test('exits with code 70 when error occurs', () async { final error = Exception('oops something went wrong!'); when( - () => auth.login( - any(), - prompt: any(named: 'prompt'), - ), + () => auth.login(any(), prompt: any(named: 'prompt')), ).thenThrow(error); final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); - verify( - () => auth.login( - any(), - prompt: any(named: 'prompt'), - ), - ).called(1); + verify(() => auth.login(any(), prompt: any(named: 'prompt'))).called(1); verify(() => logger.err(error.toString())).called(1); }); test('exits with code 0 when logged in successfully', () async { when( - () => auth.login( - any(), - prompt: any(named: 'prompt'), - ), + () => auth.login(any(), prompt: any(named: 'prompt')), ).thenAnswer((_) async {}); when(() => auth.email).thenReturn(email); final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.success.code)); - verify( - () => auth.login( - any(), - prompt: any(named: 'prompt'), - ), - ).called(1); + verify(() => auth.login(any(), prompt: any(named: 'prompt'))).called(1); verify( () => logger.info( any(that: contains('You are now logged in as <$email>.')), diff --git a/packages/shorebird_cli/test/src/commands/logout_command_test.dart b/packages/shorebird_cli/test/src/commands/logout_command_test.dart index 35ecba2e..7bd583ad 100644 --- a/packages/shorebird_cli/test/src/commands/logout_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/logout_command_test.dart @@ -42,9 +42,7 @@ void main() { final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.success.code)); - verify( - () => logger.info('You are already logged out.'), - ).called(1); + verify(() => logger.info('You are already logged out.')).called(1); }); test('exits with code 0 when logged out successfully', () async { diff --git a/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart index 8fe71cac..4f2bf576 100644 --- a/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart @@ -155,11 +155,13 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); }); @@ -195,8 +197,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -207,8 +210,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -395,9 +399,9 @@ void main() { setUp(() { releaseArtifactFile = File(''); - when(() => artifactManager.downloadFile(any())).thenAnswer( - (_) async => releaseArtifactFile, - ); + when( + () => artifactManager.downloadFile(any()), + ).thenAnswer((_) async => releaseArtifactFile); when( () => codePushClientWrapper.getReleaseArtifacts( @@ -430,9 +434,9 @@ void main() { releaseArtifactPath: any(named: 'releaseArtifactPath'), ), ).thenAnswer((_) async { - final diffPath = - File(p.join(Directory.systemTemp.createTempSync().path, 'diff')) - ..createSync(); + final diffPath = File( + p.join(Directory.systemTemp.createTempSync().path, 'diff'), + )..createSync(); return diffPath.path; }); }); diff --git a/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart index 451e84ef..715817d6 100644 --- a/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart @@ -58,10 +58,7 @@ void main() { late AndroidPatcher patcher; - File patchArtifactForArch( - Arch arch, { - String? flavor, - }) { + File patchArtifactForArch(Arch arch, {String? flavor}) { return File( p.join( projectRoot.path, @@ -102,8 +99,9 @@ void main() { shorebirdEnvRef.overrideWith(() => shorebirdEnv), shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - shorebirdAndroidArtifactsRef - .overrideWith(() => shorebirdAndroidArtifacts), + shorebirdAndroidArtifactsRef.overrideWith( + () => shorebirdAndroidArtifacts, + ), }, ); } @@ -162,8 +160,9 @@ void main() { group('assertPreconditions', () { setUp(() { - when(() => doctor.androidCommandValidators) - .thenReturn([flavorValidator]); + when( + () => doctor.androidCommandValidators, + ).thenReturn([flavorValidator]); when(flavorValidator.validate).thenAnswer((_) async => []); }); @@ -172,11 +171,13 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); }); @@ -195,8 +196,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -207,8 +209,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -364,8 +367,7 @@ void main() { () => logger.err('Cannot find patch build artifacts.'), ).called(1); verify( - () => logger.info( - ''' + () => logger.info(''' Please run `shorebird cache clean` and try again. If the issue persists, please file a bug report at https://github.com/shorebirdtech/shorebird/issues/new. @@ -373,8 +375,7 @@ Looked in: - build/app/intermediates/stripped_native_libs/stripReleaseDebugSymbols/release/out/lib - build/app/intermediates/stripped_native_libs/strip{flavor}ReleaseDebugSymbols/{flavor}Release/out/lib - build/app/intermediates/stripped_native_libs/release/out/lib - - build/app/intermediates/stripped_native_libs/{flavor}Release/out/lib''', - ), + - build/app/intermediates/stripped_native_libs/{flavor}Release/out/lib'''), ).called(1); }); }); @@ -422,24 +423,26 @@ Looked in: group('when the key pair is provided', () { setUp(() { - when(() => codeSigner.base64PublicKey(any())) - .thenReturn('public_key_encoded'); + when( + () => codeSigner.base64PublicKey(any()), + ).thenReturn('public_key_encoded'); }); test('calls buildIpa with the provided key', () async { - when(() => argResults.wasParsed(CommonArguments.publicKeyArg.name)) - .thenReturn(true); + when( + () => argResults.wasParsed(CommonArguments.publicKeyArg.name), + ).thenReturn(true); final key = createTempFile('public.der') ..writeAsStringSync('public_key'); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn(key.path); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn(key.path); - await runWithOverrides( - patcher.buildPatchArtifact, - ); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(key.path); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(key.path); + await runWithOverrides(patcher.buildPatchArtifact); verify( () => artifactBuilder.buildAppBundle( @@ -608,8 +611,9 @@ Looked in: ), )..createSync(); - when(() => argResults[CommonArguments.privateKeyArg.name]) - .thenReturn(privateKey.path); + when( + () => argResults[CommonArguments.privateKeyArg.name], + ).thenReturn(privateKey.path); when( () => codeSigner.sign( @@ -622,28 +626,33 @@ Looked in: }); }); - test('returns patch artifact bundles with proper hash signatures', - () async { - final result = await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: 'appId', - releaseId: 0, - releaseArtifact: File('release.aab'), - ), - ); + test( + 'returns patch artifact bundles with proper hash signatures', + () async { + final result = await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: 'appId', + releaseId: 0, + releaseArtifact: File('release.aab'), + ), + ); - // Hash the patch artifacts and append '-signature' to get the - // expected signatures, per the mock of [codeSigner.sign] above. - final expectedSignatures = Arch.values - .map(patchArtifactForArch) - .map((f) => sha256.convert(f.readAsBytesSync()).toString()) - .map((hash) => '$hash-signature') - .toList(); + // Hash the patch artifacts and append '-signature' to get the + // expected signatures, per the mock of [codeSigner.sign] above. + final expectedSignatures = + Arch.values + .map(patchArtifactForArch) + .map( + (f) => sha256.convert(f.readAsBytesSync()).toString(), + ) + .map((hash) => '$hash-signature') + .toList(); - final signatures = - result.values.map((bundle) => bundle.hashSignature).toList(); - expect(signatures, equals(expectedSignatures)); - }); + final signatures = + result.values.map((bundle) => bundle.hashSignature).toList(); + expect(signatures, equals(expectedSignatures)); + }, + ); }); group('when artifacts download takes longer than provided timeout', () { @@ -693,15 +702,16 @@ Looked in: }); test( - '''returns value of shorebirdAndroidArtifacts.extractReleaseVersionFromAppBundle''', - () async { - expect( - await runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - equals('1.0.0'), - ); - }); + '''returns value of shorebirdAndroidArtifacts.extractReleaseVersionFromAppBundle''', + () async { + expect( + await runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + equals('1.0.0'), + ); + }, + ); }); group('patchArtifactForDiffCheck', () { diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart index 4a9c6030..f608441b 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart @@ -38,900 +38,713 @@ import '../../matchers.dart'; import '../../mocks.dart'; void main() { - group( - IosFrameworkPatcher, - () { - late AotTools aotTools; - late Apple apple; - late ArgParser argParser; - late ArgResults argResults; - late ArtifactBuilder artifactBuilder; - late ArtifactManager artifactManager; - late CodePushClientWrapper codePushClientWrapper; - late Doctor doctor; - late Directory flutterDirectory; - late Directory projectRoot; - late EngineConfig engineConfig; - late FlavorValidator flavorValidator; - late ShorebirdLogger logger; - late OperatingSystemInterface operatingSystemInterface; - late PatchDiffChecker patchDiffChecker; - late Progress progress; - late ShorebirdArtifacts shorebirdArtifacts; - late ShorebirdProcess shorebirdProcess; - late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutter; - late ShorebirdValidator shorebirdValidator; - late XcodeBuild xcodeBuild; - late IosFrameworkPatcher patcher; + group(IosFrameworkPatcher, () { + late AotTools aotTools; + late Apple apple; + late ArgParser argParser; + late ArgResults argResults; + late ArtifactBuilder artifactBuilder; + late ArtifactManager artifactManager; + late CodePushClientWrapper codePushClientWrapper; + late Doctor doctor; + late Directory flutterDirectory; + late Directory projectRoot; + late EngineConfig engineConfig; + late FlavorValidator flavorValidator; + late ShorebirdLogger logger; + late OperatingSystemInterface operatingSystemInterface; + late PatchDiffChecker patchDiffChecker; + late Progress progress; + late ShorebirdArtifacts shorebirdArtifacts; + late ShorebirdProcess shorebirdProcess; + late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutter shorebirdFlutter; + late ShorebirdValidator shorebirdValidator; + late XcodeBuild xcodeBuild; + late IosFrameworkPatcher patcher; - R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - aotToolsRef.overrideWith(() => aotTools), - appleRef.overrideWith(() => apple), - artifactBuilderRef.overrideWith(() => artifactBuilder), - artifactManagerRef.overrideWith(() => artifactManager), - codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - doctorRef.overrideWith(() => doctor), - engineConfigRef.overrideWith(() => engineConfig), - loggerRef.overrideWith(() => logger), - osInterfaceRef.overrideWith(() => operatingSystemInterface), - patchDiffCheckerRef.overrideWith(() => patchDiffChecker), - processRef.overrideWith(() => shorebirdProcess), - shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), - shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - xcodeBuildRef.overrideWith(() => xcodeBuild), - }, + R runWithOverrides(R Function() body) { + return runScoped( + body, + values: { + aotToolsRef.overrideWith(() => aotTools), + appleRef.overrideWith(() => apple), + artifactBuilderRef.overrideWith(() => artifactBuilder), + artifactManagerRef.overrideWith(() => artifactManager), + codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + doctorRef.overrideWith(() => doctor), + engineConfigRef.overrideWith(() => engineConfig), + loggerRef.overrideWith(() => logger), + osInterfaceRef.overrideWith(() => operatingSystemInterface), + patchDiffCheckerRef.overrideWith(() => patchDiffChecker), + processRef.overrideWith(() => shorebirdProcess), + shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), + xcodeBuildRef.overrideWith(() => xcodeBuild), + }, + ); + } + + setUpAll(() { + registerFallbackValue(Directory('')); + registerFallbackValue(File('')); + registerFallbackValue(const AppleArchiveDiffer()); + registerFallbackValue(ReleasePlatform.ios); + registerFallbackValue(ShorebirdArtifact.genSnapshotIos); + registerFallbackValue(Uri.parse('https://example.com')); + }); + + setUp(() { + apple = MockApple(); + aotTools = MockAotTools(); + argParser = MockArgParser(); + argResults = MockArgResults(); + artifactBuilder = MockArtifactBuilder(); + artifactManager = MockArtifactManager(); + codePushClientWrapper = MockCodePushClientWrapper(); + doctor = MockDoctor(); + engineConfig = MockEngineConfig(); + flavorValidator = MockFlavorValidator(); + operatingSystemInterface = MockOperatingSystemInterface(); + patchDiffChecker = MockPatchDiffChecker(); + progress = MockProgress(); + projectRoot = Directory.systemTemp.createTempSync(); + logger = MockShorebirdLogger(); + shorebirdArtifacts = MockShorebirdArtifacts(); + shorebirdProcess = MockShorebirdProcess(); + shorebirdEnv = MockShorebirdEnv(); + shorebirdFlutter = MockShorebirdFlutter(); + shorebirdValidator = MockShorebirdValidator(); + xcodeBuild = MockXcodeBuild(); + + when(() => argParser.options).thenReturn({}); + + when(() => argResults['build-number']).thenReturn('1.0'); + when(() => argResults.rest).thenReturn([]); + when(() => argResults.wasParsed(any())).thenReturn(false); + + when(() => logger.progress(any())).thenReturn(progress); + + when( + () => shorebirdEnv.buildDirectory, + ).thenReturn(Directory(p.join(projectRoot.path, 'build'))); + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); + + patcher = IosFrameworkPatcher( + argParser: argParser, + argResults: argResults, + flavor: null, + target: null, + ); + }); + + group('primaryReleaseArtifactArch', () { + test('is "xcframework"', () { + expect(patcher.primaryReleaseArtifactArch, 'xcframework'); + }); + }); + + group('supplementaryReleaseArtifactArch', () { + test('is "ios_framework_supplement"', () { + expect( + patcher.supplementaryReleaseArtifactArch, + 'ios_framework_supplement', ); - } + }); + }); - setUpAll(() { - registerFallbackValue(Directory('')); - registerFallbackValue(File('')); - registerFallbackValue(const AppleArchiveDiffer()); - registerFallbackValue(ReleasePlatform.ios); - registerFallbackValue(ShorebirdArtifact.genSnapshotIos); - registerFallbackValue(Uri.parse('https://example.com')); + group('releaseType', () { + test('is ReleaseType.iosFramework', () { + expect(patcher.releaseType, ReleaseType.iosFramework); + }); + }); + + group('linkPercentage', () { + group('when linking has not occurred', () { + test('returns null', () { + expect(patcher.linkPercentage, isNull); + }); }); + group('when linking has occurred', () { + const linkPercentage = 42.1337; + + setUp(() { + patcher.lastBuildLinkPercentage = linkPercentage; + }); + + test('returns correct link percentage', () { + expect(patcher.linkPercentage, equals(linkPercentage)); + }); + }); + }); + + group('assertPreconditions', () { setUp(() { - apple = MockApple(); - aotTools = MockAotTools(); - argParser = MockArgParser(); - argResults = MockArgResults(); - artifactBuilder = MockArtifactBuilder(); - artifactManager = MockArtifactManager(); - codePushClientWrapper = MockCodePushClientWrapper(); - doctor = MockDoctor(); - engineConfig = MockEngineConfig(); - flavorValidator = MockFlavorValidator(); - operatingSystemInterface = MockOperatingSystemInterface(); - patchDiffChecker = MockPatchDiffChecker(); - progress = MockProgress(); - projectRoot = Directory.systemTemp.createTempSync(); - logger = MockShorebirdLogger(); - shorebirdArtifacts = MockShorebirdArtifacts(); - shorebirdProcess = MockShorebirdProcess(); - shorebirdEnv = MockShorebirdEnv(); - shorebirdFlutter = MockShorebirdFlutter(); - shorebirdValidator = MockShorebirdValidator(); - xcodeBuild = MockXcodeBuild(); - - when(() => argParser.options).thenReturn({}); - - when(() => argResults['build-number']).thenReturn('1.0'); - when(() => argResults.rest).thenReturn([]); - when(() => argResults.wasParsed(any())).thenReturn(false); - - when(() => logger.progress(any())).thenReturn(progress); - - when(() => shorebirdEnv.buildDirectory).thenReturn( - Directory(p.join(projectRoot.path, 'build')), - ); - when( - () => shorebirdEnv.getShorebirdProjectRoot(), - ).thenReturn(projectRoot); - - patcher = IosFrameworkPatcher( - argParser: argParser, - argResults: argResults, - flavor: null, - target: null, - ); + when(() => doctor.iosCommandValidators).thenReturn([flavorValidator]); }); - group('primaryReleaseArtifactArch', () { - test('is "xcframework"', () { - expect(patcher.primaryReleaseArtifactArch, 'xcframework'); + group('when validation succeeds', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); }); - }); - group('supplementaryReleaseArtifactArch', () { - test('is "ios_framework_supplement"', () { - expect( - patcher.supplementaryReleaseArtifactArch, - 'ios_framework_supplement', + test('returns normally', () async { + await expectLater( + () => runWithOverrides(patcher.assertPreconditions), + returnsNormally, ); }); }); - group('releaseType', () { - test('is ReleaseType.iosFramework', () { - expect(patcher.releaseType, ReleaseType.iosFramework); - }); - }); - - group('linkPercentage', () { - group('when linking has not occurred', () { - test('returns null', () { - expect(patcher.linkPercentage, isNull); - }); - }); - - group('when linking has occurred', () { - const linkPercentage = 42.1337; - - setUp(() { - patcher.lastBuildLinkPercentage = linkPercentage; - }); - - test('returns correct link percentage', () { - expect(patcher.linkPercentage, equals(linkPercentage)); - }); - }); - }); - - group('assertPreconditions', () { + group('when validation fails', () { setUp(() { + final exception = ValidationFailedException(); when( - () => doctor.iosCommandValidators, - ).thenReturn([flavorValidator]); - }); - - group('when validation succeeds', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', ), - ).thenAnswer((_) async {}); - }); - - test('returns normally', () async { - await expectLater( - () => runWithOverrides(patcher.assertPreconditions), - returnsNormally, - ); - }); + validators: any(named: 'validators'), + ), + ).thenThrow(exception); }); - group('when validation fails', () { - setUp(() { - final exception = ValidationFailedException(); - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - ), - ).thenThrow(exception); - }); - - test('exits with code 70', () async { - final exception = ValidationFailedException(); - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenThrow(exception); - await expectLater( - () => runWithOverrides(patcher.assertPreconditions), - exitsWithCode(exception.exitCode), - ); - verify( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: true, - checkShorebirdInitialized: true, - validators: [flavorValidator], - supportedOperatingSystems: {Platform.macOS}, - ), - ).called(1); - }); - }); - }); - - group('assertArgsAreValid', () { - group('when release-version is not provided', () { - setUp(() { - when(() => argResults.wasParsed('release-version')) - .thenReturn(false); - }); - - test('exits with code 64', () async { - await expectLater( - () => runWithOverrides(patcher.assertArgsAreValid), - exitsWithCode(ExitCode.usage), - ); - }); - }); - - group('when arguments are valid', () { - setUp(() { - when(() => argResults.wasParsed('release-version')) - .thenReturn(true); - }); - - test('returns normally', () { - expect( - () => runWithOverrides(patcher.assertArgsAreValid), - returnsNormally, - ); - }); - }); - }); - - group('assertUnpatchableDiffs', () { - const diffStatus = DiffStatus( - hasAssetChanges: false, - hasNativeChanges: false, - ); - - setUp(() { + test('exits with code 70', () async { + final exception = ValidationFailedException(); when( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: any(named: 'localArchive'), - releaseArchive: any(named: 'releaseArchive'), - archiveDiffer: any(named: 'archiveDiffer'), - allowAssetChanges: any(named: 'allowAssetChanges'), - allowNativeChanges: any(named: 'allowNativeChanges'), - ), - ).thenAnswer((_) async => diffStatus); - }); - - test('forwards result from patchDiffChecker', () async { - final result = await runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: FakeReleaseArtifact(), - releaseArchive: File(''), - patchArchive: File(''), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), + ).thenThrow(exception); + await expectLater( + () => runWithOverrides(patcher.assertPreconditions), + exitsWithCode(exception.exitCode), ); - expect(result, equals(diffStatus)); verify( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: any(named: 'localArchive'), - releaseArchive: any(named: 'releaseArchive'), - archiveDiffer: any(named: 'archiveDiffer'), - allowAssetChanges: any(named: 'allowAssetChanges'), - allowNativeChanges: any(named: 'allowNativeChanges'), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flavorValidator], + supportedOperatingSystems: {Platform.macOS}, ), ).called(1); }); }); + }); - group('buildPatchArtifact', () { - const flutterVersionAndRevision = '3.10.6 (83305b5088)'; + group('assertArgsAreValid', () { + group('when release-version is not provided', () { + setUp(() { + when(() => argResults.wasParsed('release-version')).thenReturn(false); + }); + test('exits with code 64', () async { + await expectLater( + () => runWithOverrides(patcher.assertArgsAreValid), + exitsWithCode(ExitCode.usage), + ); + }); + }); + + group('when arguments are valid', () { + setUp(() { + when(() => argResults.wasParsed('release-version')).thenReturn(true); + }); + + test('returns normally', () { + expect( + () => runWithOverrides(patcher.assertArgsAreValid), + returnsNormally, + ); + }); + }); + }); + + group('assertUnpatchableDiffs', () { + const diffStatus = DiffStatus( + hasAssetChanges: false, + hasNativeChanges: false, + ); + + setUp(() { + when( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: any(named: 'localArchive'), + releaseArchive: any(named: 'releaseArchive'), + archiveDiffer: any(named: 'archiveDiffer'), + allowAssetChanges: any(named: 'allowAssetChanges'), + allowNativeChanges: any(named: 'allowNativeChanges'), + ), + ).thenAnswer((_) async => diffStatus); + }); + + test('forwards result from patchDiffChecker', () async { + final result = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: FakeReleaseArtifact(), + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(result, equals(diffStatus)); + verify( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: any(named: 'localArchive'), + releaseArchive: any(named: 'releaseArchive'), + archiveDiffer: any(named: 'archiveDiffer'), + allowAssetChanges: any(named: 'allowAssetChanges'), + allowNativeChanges: any(named: 'allowNativeChanges'), + ), + ).called(1); + }); + }); + + group('buildPatchArtifact', () { + const flutterVersionAndRevision = '3.10.6 (83305b5088)'; + + setUp(() { + when( + () => shorebirdFlutter.getVersionAndRevision(), + ).thenAnswer((_) async => flutterVersionAndRevision); + }); + + group('when build fails', () { setUp(() { when( - () => shorebirdFlutter.getVersionAndRevision(), - ).thenAnswer((_) async => flutterVersionAndRevision); + () => artifactBuilder.buildIosFramework(args: any(named: 'args')), + ).thenThrow(ArtifactBuildException('Build failed')); }); - group('when build fails', () { - setUp(() { - when( - () => artifactBuilder.buildIosFramework(args: any(named: 'args')), - ).thenThrow( - ArtifactBuildException('Build failed'), - ); - }); + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); + verify(() => progress.fail('Build failed')); + }); + }); - verify(() => progress.fail('Build failed')); - }); + group('when elf aot snapshot build fails', () { + setUp(() { + when( + () => artifactBuilder.buildIosFramework(args: any(named: 'args')), + ).thenAnswer( + (_) async => IosFrameworkBuildResult(kernelFile: File('app.dill')), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), + ), + ).thenThrow(const FileSystemException('error')); }); - group('when elf aot snapshot build fails', () { + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail("FileSystemException: error, path = ''")); + }); + }); + + group('when build succeeds', () { + late File kernelFile; + setUp(() { + kernelFile = File( + p.join(Directory.systemTemp.createTempSync().path, 'app.dill'), + )..createSync(recursive: true); + when( + () => artifactBuilder.buildIosFramework(args: any(named: 'args')), + ).thenAnswer( + (_) async => IosFrameworkBuildResult(kernelFile: kernelFile), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), + ), + ).thenAnswer( + (invocation) async => + File(invocation.namedArguments[#outFilePath] as String) + ..createSync(recursive: true), + ); + + Directory( + p.join(projectRoot.path, ArtifactManager.appXcframeworkName), + ).createSync(recursive: true); + when( + () => artifactManager.getAppXcframeworkDirectory(), + ).thenReturn(projectRoot); + }); + + group('when --split-debug-info is provided', () { + final tempDir = Directory.systemTemp.createTempSync(); + final splitDebugInfoPath = p.join(tempDir.path, 'symbols'); + final splitDebugInfoFile = File( + p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'), + ); setUp(() { when( - () => artifactBuilder.buildIosFramework(args: any(named: 'args')), - ).thenAnswer( - (_) async => IosFrameworkBuildResult( - kernelFile: File('app.dill'), - ), - ); + () => + argResults.wasParsed(CommonArguments.splitDebugInfoArg.name), + ).thenReturn(true); when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), - ), - ).thenThrow(const FileSystemException('error')); + () => argResults['split-debug-info'], + ).thenReturn(splitDebugInfoPath); }); - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - + test('forwards --split-debug-info to builder', () async { + try { + await runWithOverrides(patcher.buildPatchArtifact); + } on Exception { + // ignore + } verify( - () => progress.fail("FileSystemException: error, path = ''"), - ); - }); - }); - - group('when build succeeds', () { - late File kernelFile; - setUp(() { - kernelFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'app.dill', - ), - )..createSync(recursive: true); - when( - () => artifactBuilder.buildIosFramework(args: any(named: 'args')), - ).thenAnswer( - (_) async => IosFrameworkBuildResult(kernelFile: kernelFile), - ); - when( () => artifactBuilder.buildElfAotSnapshot( appDillPath: any(named: 'appDillPath'), outFilePath: any(named: 'outFilePath'), genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), + additionalArgs: [ + '--dwarf-stack-traces', + '--resolve-dwarf-paths', + '--save-debugging-info=${splitDebugInfoFile.path}', + ], ), - ).thenAnswer( - (invocation) async => - File(invocation.namedArguments[#outFilePath] as String) - ..createSync(recursive: true), - ); - - Directory( - p.join(projectRoot.path, ArtifactManager.appXcframeworkName), - ).createSync(recursive: true); - when( - () => artifactManager.getAppXcframeworkDirectory(), - ).thenReturn(projectRoot); + ).called(1); }); + }); - group('when --split-debug-info is provided', () { - final tempDir = Directory.systemTemp.createTempSync(); - final splitDebugInfoPath = p.join(tempDir.path, 'symbols'); - final splitDebugInfoFile = File( - p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'), - ); - setUp(() { - when( - () => argResults.wasParsed( - CommonArguments.splitDebugInfoArg.name, - ), - ).thenReturn(true); - when( - () => argResults['split-debug-info'], - ).thenReturn(splitDebugInfoPath); - }); - - test('forwards --split-debug-info to builder', () async { - try { - await runWithOverrides(patcher.buildPatchArtifact); - } on Exception { - // ignore - } - verify( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: [ - '--dwarf-stack-traces', - '--resolve-dwarf-paths', - '--save-debugging-info=${splitDebugInfoFile.path}', - ], - ), - ).called(1); - }); - }); - - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['ios', '--verbose']); - }); - - test('returns zipped xcframework', () async { - final artifact = await runWithOverrides( - patcher.buildPatchArtifact, - ); - expect(p.basename(artifact.path), equals('App.xcframework.zip')); - verify( - () => artifactBuilder.buildIosFramework( - args: ['--verbose'], - ), - ).called(1); - }); + group('when platform was specified via arg results rest', () { + setUp(() { + when(() => argResults.rest).thenReturn(['ios', '--verbose']); }); test('returns zipped xcframework', () async { final artifact = await runWithOverrides(patcher.buildPatchArtifact); expect(p.basename(artifact.path), equals('App.xcframework.zip')); + verify( + () => artifactBuilder.buildIosFramework(args: ['--verbose']), + ).called(1); }); + }); - test('copies app.dill to build directory', () async { - final copiedKernelFile = File( - p.join(projectRoot.path, 'build', 'app.dill'), - ); - expect(copiedKernelFile.existsSync(), isFalse); - await runWithOverrides(patcher.buildPatchArtifact); - expect(copiedKernelFile.existsSync(), isTrue); - }); + test('returns zipped xcframework', () async { + final artifact = await runWithOverrides(patcher.buildPatchArtifact); + expect(p.basename(artifact.path), equals('App.xcframework.zip')); + }); + + test('copies app.dill to build directory', () async { + final copiedKernelFile = File( + p.join(projectRoot.path, 'build', 'app.dill'), + ); + expect(copiedKernelFile.existsSync(), isFalse); + await runWithOverrides(patcher.buildPatchArtifact); + expect(copiedKernelFile.existsSync(), isTrue); }); }); + }); - group('createPatchArtifacts', () { - const postLinkerFlutterRevision = // cspell: disable-next-line - 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'; - const preLinkerFlutterRevision = // cspell: disable-next-line - '83305b5088e6fe327fb3334a73ff190828d85713'; - const appId = 'appId'; - const arch = 'aarch64'; - const releaseId = 1; - const linkFileName = 'out.vmcode'; - const elfAotSnapshotFileName = 'out.aot'; - const releaseArtifact = ReleaseArtifact( - id: 0, - releaseId: releaseId, - arch: arch, - platform: ReleasePlatform.android, - hash: '#', - size: 42, - url: 'https://example.com', - podfileLockHash: null, - canSideload: true, - ); - late File releaseArtifactFile; - late File supplementArtifactFile; + group('createPatchArtifacts', () { + const postLinkerFlutterRevision = // cspell: disable-next-line + 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'; + const preLinkerFlutterRevision = // cspell: disable-next-line + '83305b5088e6fe327fb3334a73ff190828d85713'; + const appId = 'appId'; + const arch = 'aarch64'; + const releaseId = 1; + const linkFileName = 'out.vmcode'; + const elfAotSnapshotFileName = 'out.aot'; + const releaseArtifact = ReleaseArtifact( + id: 0, + releaseId: releaseId, + arch: arch, + platform: ReleasePlatform.android, + hash: '#', + size: 42, + url: 'https://example.com', + podfileLockHash: null, + canSideload: true, + ); + late File releaseArtifactFile; + late File supplementArtifactFile; - void setUpProjectRootArtifacts() { - File(p.join(projectRoot.path, 'build', elfAotSnapshotFileName)) - .createSync( - recursive: true, - ); + void setUpProjectRootArtifacts() { + File( + p.join(projectRoot.path, 'build', elfAotSnapshotFileName), + ).createSync(recursive: true); + File( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + 'ios-arm64', + 'App.framework', + 'App', + ), + ).createSync(recursive: true); + File( + p.join(projectRoot.path, 'build', linkFileName), + ).createSync(recursive: true); + File( + p.join(projectRoot.path, 'build', 'ios', 'shorebird', 'App.ct.link'), + ).createSync(recursive: true); + File( + p.join( + projectRoot.path, + 'build', + 'ios', + 'shorebird', + 'App.class_table.json', + ), + ).createSync(recursive: true); + } + + setUp(() { + releaseArtifactFile = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'release.xcframework', + ), + )..createSync(recursive: true); + supplementArtifactFile = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'ios_framework_supplement.zip', + ), + )..createSync(recursive: true); + + when( + () => codePushClientWrapper.getReleaseArtifact( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + ), + ).thenAnswer((_) async => releaseArtifact); + when(() => artifactManager.downloadFile(any())).thenAnswer((_) async { + final tempDirectory = Directory.systemTemp.createTempSync(); + final file = File(p.join(tempDirectory.path, 'libapp.so')) + ..createSync(); + return file; + }); + when( + () => artifactManager.extractZip( + zipFile: any(named: 'zipFile'), + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async { + final zipFile = invocation.namedArguments[#zipFile] as File; + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - 'ios-arm64', - 'App.framework', - 'App', - ), - ).createSync(recursive: true); - File( - p.join(projectRoot.path, 'build', linkFileName), - ).createSync(recursive: true); - File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'shorebird', - 'App.ct.link', - ), - ).createSync(recursive: true); - File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'shorebird', - 'App.class_table.json', - ), - ).createSync(recursive: true); - } + p.join(outDir.path, '${p.basename(zipFile.path)}.zip'), + ).createSync(); + }); + when(() => engineConfig.localEngine).thenReturn(null); + }); + + group('when uses linker', () { + const linkPercentage = 50.0; + late File analyzeSnapshotFile; + late File genSnapshotFile; setUp(() { - releaseArtifactFile = File( + final shorebirdRoot = Directory.systemTemp.createTempSync(); + flutterDirectory = Directory( + p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'), + ); + genSnapshotFile = File( p.join( - Directory.systemTemp.createTempSync().path, - 'release.xcframework', + flutterDirectory.path, + 'bin', + 'cache', + 'artifacts', + 'engine', + 'ios-release', + 'gen_snapshot_arm64', ), - )..createSync(recursive: true); - supplementArtifactFile = File( + ); + analyzeSnapshotFile = File( p.join( - Directory.systemTemp.createTempSync().path, - 'ios_framework_supplement.zip', + flutterDirectory.path, + 'bin', + 'cache', + 'artifacts', + 'engine', + 'ios-release', + 'analyze_snapshot_arm64', ), )..createSync(recursive: true); when( - () => codePushClientWrapper.getReleaseArtifact( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), + () => apple.runLinker( + kernelFile: any(named: 'kernelFile'), + aotOutputFile: any(named: 'aotOutputFile'), + releaseArtifact: any(named: 'releaseArtifact'), + splitDebugInfoArgs: any(named: 'splitDebugInfoArgs'), + vmCodeFile: any(named: 'vmCodeFile'), ), - ).thenAnswer((_) async => releaseArtifact); - when(() => artifactManager.downloadFile(any())).thenAnswer((_) async { - final tempDirectory = Directory.systemTemp.createTempSync(); - final file = File(p.join(tempDirectory.path, 'libapp.so')) - ..createSync(); - return file; - }); + ).thenAnswer( + (_) async => ( + exitCode: ExitCode.success.code, + linkPercentage: linkPercentage, + ), + ); when( - () => artifactManager.extractZip( - zipFile: any(named: 'zipFile'), - outputDirectory: any(named: 'outputDirectory'), + aotTools.isGeneratePatchDiffBaseSupported, + ).thenAnswer((_) async => false); + when( + () => shorebirdEnv.flutterRevision, + ).thenReturn(postLinkerFlutterRevision); + when( + () => shorebirdArtifacts.getArtifactPath( + artifact: ShorebirdArtifact.analyzeSnapshotIos, ), - ).thenAnswer((invocation) async { - final zipFile = invocation.namedArguments[#zipFile] as File; - final outDir = - invocation.namedArguments[#outputDirectory] as Directory; - File(p.join(outDir.path, '${p.basename(zipFile.path)}.zip')) - .createSync(); - }); - when(() => engineConfig.localEngine).thenReturn(null); + ).thenReturn(analyzeSnapshotFile.path); + when( + () => shorebirdArtifacts.getArtifactPath( + artifact: ShorebirdArtifact.genSnapshotIos, + ), + ).thenReturn(genSnapshotFile.path); }); - group('when uses linker', () { - const linkPercentage = 50.0; - late File analyzeSnapshotFile; - late File genSnapshotFile; - + group('when generate patch diff base is supported', () { setUp(() { - final shorebirdRoot = Directory.systemTemp.createTempSync(); - flutterDirectory = Directory( - p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'), - ); - genSnapshotFile = File( - p.join( - flutterDirectory.path, - 'bin', - 'cache', - 'artifacts', - 'engine', - 'ios-release', - 'gen_snapshot_arm64', - ), - ); - analyzeSnapshotFile = File( - p.join( - flutterDirectory.path, - 'bin', - 'cache', - 'artifacts', - 'engine', - 'ios-release', - 'analyze_snapshot_arm64', - ), - )..createSync(recursive: true); - when( - () => apple.runLinker( - kernelFile: any(named: 'kernelFile'), - aotOutputFile: any(named: 'aotOutputFile'), - releaseArtifact: any(named: 'releaseArtifact'), - splitDebugInfoArgs: any(named: 'splitDebugInfoArgs'), - vmCodeFile: any(named: 'vmCodeFile'), - ), - ).thenAnswer( - (_) async => ( - exitCode: ExitCode.success.code, - linkPercentage: linkPercentage - ), - ); + () => aotTools.isGeneratePatchDiffBaseSupported(), + ).thenAnswer((_) async => true); when( - aotTools.isGeneratePatchDiffBaseSupported, - ).thenAnswer((_) async => false); - when( - () => shorebirdEnv.flutterRevision, - ).thenReturn(postLinkerFlutterRevision); - when( - () => shorebirdArtifacts.getArtifactPath( - artifact: ShorebirdArtifact.analyzeSnapshotIos, + () => aotTools.generatePatchDiffBase( + analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), + releaseSnapshot: any(named: 'releaseSnapshot'), ), - ).thenReturn(analyzeSnapshotFile.path); - when( - () => shorebirdArtifacts.getArtifactPath( - artifact: ShorebirdArtifact.genSnapshotIos, - ), - ).thenReturn(genSnapshotFile.path); + ).thenAnswer((_) async => File('')); }); - group('when generate patch diff base is supported', () { + group('when we fail to generate patch diff base', () { setUp(() { - when( - () => aotTools.isGeneratePatchDiffBaseSupported(), - ).thenAnswer((_) async => true); when( () => aotTools.generatePatchDiffBase( analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), releaseSnapshot: any(named: 'releaseSnapshot'), ), - ).thenAnswer((_) async => File('')); - }); + ).thenThrow(Exception('oops')); - group('when we fail to generate patch diff base', () { - setUp(() { - when( - () => aotTools.generatePatchDiffBase( - analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), - releaseSnapshot: any(named: 'releaseSnapshot'), - ), - ).thenThrow(Exception('oops')); - - setUpProjectRootArtifacts(); - }); - - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify(() => progress.fail('Exception: oops')).called(1); - }); - }); - - group('when linking and patch diff generation succeeds', () { - const diffPath = 'path/to/diff'; - - setUp(() { - when( - () => artifactManager.createDiff( - releaseArtifactPath: any(named: 'releaseArtifactPath'), - patchArtifactPath: any(named: 'patchArtifactPath'), - ), - ).thenAnswer((_) async => diffPath); - setUpProjectRootArtifacts(); - }); - - test('calls runLinker with correct arguments', () async { - await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - ), - ); - - verify( - () => apple.runLinker( - kernelFile: any( - named: 'kernelFile', - that: isA().having( - (f) => f.path, - 'path', - p.join(projectRoot.path, 'build', 'app.dill'), - ), - ), - aotOutputFile: any( - named: 'aotOutputFile', - that: isA().having( - (f) => f.path, - 'path', - p.join(projectRoot.path, 'build', 'out.aot'), - ), - ), - releaseArtifact: any( - named: 'releaseArtifact', - that: isA().having( - (f) => f.path, - 'path', - endsWith( - p.join('ios-arm64', 'App.framework', 'App'), - ), - ), - ), - vmCodeFile: any( - named: 'vmCodeFile', - that: isA().having( - (f) => f.path, - 'path', - p.join(projectRoot.path, 'build', 'out.vmcode'), - ), - ), - splitDebugInfoArgs: [], - ), - ).called(1); - }); - - test('returns linked patch artifact in patch bundle', () async { - final patchBundle = await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - ), - ); - - expect(patchBundle, hasLength(1)); - expect( - patchBundle[Arch.arm64], - isA().having( - (b) => b.path, - 'path', - endsWith(diffPath), - ), - ); - }); - - group('when class table link info is not present', () { - setUp(() { - when( - () => artifactManager.extractZip( - zipFile: supplementArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async {}); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - supplementArtifact: supplementArtifactFile, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - 'Unable to find class table link info file', - ), - ).called(1); - }); - }); - - group('when debug info is missing', () { - setUp(() { - when( - () => artifactManager.extractZip( - zipFile: supplementArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async { - final outDir = invocation.namedArguments[#outputDirectory] - as Directory; - File( - p.join(outDir.path, 'App.ct.link'), - ).createSync(recursive: true); - }); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - supplementArtifact: supplementArtifactFile, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - 'Unable to find class table link debug info file', - ), - ).called(1); - }); - }); - - group('when class table link info & debug info are present', () { - setUp(() { - when( - () => artifactManager.extractZip( - zipFile: releaseArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async { - final outDir = invocation.namedArguments[#outputDirectory] - as Directory; - File( - p.join( - outDir.path, - 'ios-arm64', - 'App.framework', - 'App', - ), - ).createSync(recursive: true); - }); - when( - () => artifactManager.extractZip( - zipFile: supplementArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async { - final outDir = invocation.namedArguments[#outputDirectory] - as Directory; - File( - p.join(outDir.path, 'App.ct.link'), - ).createSync(recursive: true); - File( - p.join(outDir.path, 'App.class_table.json'), - ).createSync(recursive: true); - }); - }); - - test('returns linked patch artifact in patch bundle', () async { - final patchBundle = await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - supplementArtifact: supplementArtifactFile, - ), - ); - - expect(patchBundle, hasLength(1)); - expect( - patchBundle[Arch.arm64], - isA().having( - (b) => b.path, - 'path', - endsWith(diffPath), - ), - ); - }); - }); - }); - }); - - group('when generate patch diff base is not supported', () { - setUp(() { - when( - aotTools.isGeneratePatchDiffBaseSupported, - ).thenAnswer((_) async => false); setUpProjectRootArtifacts(); }); - test('returns vmcode file as patch file', () async { + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail('Exception: oops')).called(1); + }); + }); + + group('when linking and patch diff generation succeeds', () { + const diffPath = 'path/to/diff'; + + setUp(() { + when( + () => artifactManager.createDiff( + releaseArtifactPath: any(named: 'releaseArtifactPath'), + patchArtifactPath: any(named: 'patchArtifactPath'), + ), + ).thenAnswer((_) async => diffPath); + setUpProjectRootArtifacts(); + }); + + test('calls runLinker with correct arguments', () async { + await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ); + + verify( + () => apple.runLinker( + kernelFile: any( + named: 'kernelFile', + that: isA().having( + (f) => f.path, + 'path', + p.join(projectRoot.path, 'build', 'app.dill'), + ), + ), + aotOutputFile: any( + named: 'aotOutputFile', + that: isA().having( + (f) => f.path, + 'path', + p.join(projectRoot.path, 'build', 'out.aot'), + ), + ), + releaseArtifact: any( + named: 'releaseArtifact', + that: isA().having( + (f) => f.path, + 'path', + endsWith(p.join('ios-arm64', 'App.framework', 'App')), + ), + ), + vmCodeFile: any( + named: 'vmCodeFile', + that: isA().having( + (f) => f.path, + 'path', + p.join(projectRoot.path, 'build', 'out.vmcode'), + ), + ), + splitDebugInfoArgs: [], + ), + ).called(1); + }); + + test('returns linked patch artifact in patch bundle', () async { final patchBundle = await runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, @@ -946,27 +759,142 @@ void main() { isA().having( (b) => b.path, 'path', - endsWith('out.vmcode'), + endsWith(diffPath), ), ); }); + + group('when class table link info is not present', () { + setUp(() { + when( + () => artifactManager.extractZip( + zipFile: supplementArtifactFile, + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async {}); + }); + + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + supplementArtifact: supplementArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err('Unable to find class table link info file'), + ).called(1); + }); + }); + + group('when debug info is missing', () { + setUp(() { + when( + () => artifactManager.extractZip( + zipFile: supplementArtifactFile, + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async { + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; + File( + p.join(outDir.path, 'App.ct.link'), + ).createSync(recursive: true); + }); + }); + + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + supplementArtifact: supplementArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err( + 'Unable to find class table link debug info file', + ), + ).called(1); + }); + }); + + group('when class table link info & debug info are present', () { + setUp(() { + when( + () => artifactManager.extractZip( + zipFile: releaseArtifactFile, + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async { + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; + File( + p.join(outDir.path, 'ios-arm64', 'App.framework', 'App'), + ).createSync(recursive: true); + }); + when( + () => artifactManager.extractZip( + zipFile: supplementArtifactFile, + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async { + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; + File( + p.join(outDir.path, 'App.ct.link'), + ).createSync(recursive: true); + File( + p.join(outDir.path, 'App.class_table.json'), + ).createSync(recursive: true); + }); + }); + + test('returns linked patch artifact in patch bundle', () async { + final patchBundle = await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + supplementArtifact: supplementArtifactFile, + ), + ); + + expect(patchBundle, hasLength(1)); + expect( + patchBundle[Arch.arm64], + isA().having( + (b) => b.path, + 'path', + endsWith(diffPath), + ), + ); + }); + }); }); }); - group('when does not use linker', () { + group('when generate patch diff base is not supported', () { setUp(() { when( - () => shorebirdEnv.flutterRevision, - ).thenReturn(preLinkerFlutterRevision); - when( - () => aotTools.isGeneratePatchDiffBaseSupported(), + aotTools.isGeneratePatchDiffBaseSupported, ).thenAnswer((_) async => false); - setUpProjectRootArtifacts(); }); - test('returns base patch artifact in patch bundle', () async { - final patchArtifacts = await runWithOverrides( + test('returns vmcode file as patch file', () async { + final patchBundle = await runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, releaseId: releaseId, @@ -974,136 +902,166 @@ void main() { ), ); - expect(patchArtifacts, hasLength(1)); - verifyNever( - () => aotTools.link( - base: any(named: 'base'), - patch: any(named: 'patch'), - analyzeSnapshot: any(named: 'analyzeSnapshot'), - genSnapshot: any(named: 'genSnapshot'), - kernel: any(named: 'kernel'), - outputPath: any(named: 'outputPath'), + expect(patchBundle, hasLength(1)); + expect( + patchBundle[Arch.arm64], + isA().having( + (b) => b.path, + 'path', + endsWith('out.vmcode'), ), ); }); }); }); - group('extractReleaseVersionFromArtifact', () { - test('throws UnimplementedError', () { + group('when does not use linker', () { + setUp(() { + when( + () => shorebirdEnv.flutterRevision, + ).thenReturn(preLinkerFlutterRevision); + when( + () => aotTools.isGeneratePatchDiffBaseSupported(), + ).thenAnswer((_) async => false); + + setUpProjectRootArtifacts(); + }); + + test('returns base patch artifact in patch bundle', () async { + final patchArtifacts = await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ); + + expect(patchArtifacts, hasLength(1)); + verifyNever( + () => aotTools.link( + base: any(named: 'base'), + patch: any(named: 'patch'), + analyzeSnapshot: any(named: 'analyzeSnapshot'), + genSnapshot: any(named: 'genSnapshot'), + kernel: any(named: 'kernel'), + outputPath: any(named: 'outputPath'), + ), + ); + }); + }); + }); + + group('extractReleaseVersionFromArtifact', () { + test('throws UnimplementedError', () { + expect( + () => patcher.extractReleaseVersionFromArtifact(File('')), + throwsUnimplementedError, + ); + }); + }); + + group('updatedCreatePatchMetadata', () { + const allowAssetDiffs = false; + const allowNativeDiffs = true; + const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; + const operatingSystem = 'Mac OS X'; + const operatingSystemVersion = '10.15.7'; + const xcodeVersion = '11'; + + setUp(() { + when(() => xcodeBuild.version()).thenAnswer((_) async => xcodeVersion); + }); + + group('when linker is not enabled', () { + test('returns correct metadata', () async { + const metadata = CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: true, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: true, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); + expect( - () => patcher.extractReleaseVersionFromArtifact(File('')), - throwsUnimplementedError, + runWithOverrides( + () => patcher.updatedCreatePatchMetadata(metadata), + ), + completion( + const CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: true, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: true, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + xcodeVersion: xcodeVersion, + ), + ), + ), ); }); }); - group('updatedCreatePatchMetadata', () { - const allowAssetDiffs = false; - const allowNativeDiffs = true; - const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; - const operatingSystem = 'Mac OS X'; - const operatingSystemVersion = '10.15.7'; - const xcodeVersion = '11'; + group('when linker is enabled', () { + const linkPercentage = 100.0; setUp(() { - when( - () => xcodeBuild.version(), - ).thenAnswer((_) async => xcodeVersion); + patcher.lastBuildLinkPercentage = linkPercentage; }); - group('when linker is not enabled', () { - test('returns correct metadata', () async { - const metadata = CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: true, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: true, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ); + test('returns correct metadata', () async { + const metadata = CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: false, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: false, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); - expect( - runWithOverrides( - () => patcher.updatedCreatePatchMetadata(metadata), - ), - completion( - const CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: true, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: true, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - xcodeVersion: xcodeVersion, - ), + expect( + runWithOverrides( + () => patcher.updatedCreatePatchMetadata(metadata), + ), + completion( + const CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: false, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: false, + linkPercentage: linkPercentage, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + xcodeVersion: xcodeVersion, ), ), - ); - }); - }); - - group('when linker is enabled', () { - const linkPercentage = 100.0; - - setUp(() { - patcher.lastBuildLinkPercentage = linkPercentage; - }); - - test('returns correct metadata', () async { - const metadata = CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: false, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: false, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ); - - expect( - runWithOverrides( - () => patcher.updatedCreatePatchMetadata(metadata), - ), - completion( - const CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: false, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: false, - linkPercentage: linkPercentage, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - xcodeVersion: xcodeVersion, - ), - ), - ), - ); - }); + ), + ); }); }); - }, - testOn: 'mac-os', - ); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart index a65a4767..ac9f04ec 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart @@ -44,364 +44,354 @@ import '../../matchers.dart'; import '../../mocks.dart'; void main() { - group( - IosPatcher, - () { - late AotTools aotTools; - late Apple apple; - late ArgParser argParser; - late ArgResults argResults; - late ArtifactBuilder artifactBuilder; - late ArtifactManager artifactManager; - late CodePushClientWrapper codePushClientWrapper; - late CodeSigner codeSigner; - late Doctor doctor; - late Directory flutterDirectory; - late Directory projectRoot; - late EngineConfig engineConfig; - late FlavorValidator flavorValidator; - late ShorebirdLogger logger; - late OperatingSystemInterface operatingSystemInterface; - late PatchDiffChecker patchDiffChecker; - late Progress progress; - late ShorebirdArtifacts shorebirdArtifacts; - late ShorebirdProcess shorebirdProcess; - late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutter; - late ShorebirdValidator shorebirdValidator; - late XcodeBuild xcodeBuild; - late IosPatcher patcher; + group(IosPatcher, () { + late AotTools aotTools; + late Apple apple; + late ArgParser argParser; + late ArgResults argResults; + late ArtifactBuilder artifactBuilder; + late ArtifactManager artifactManager; + late CodePushClientWrapper codePushClientWrapper; + late CodeSigner codeSigner; + late Doctor doctor; + late Directory flutterDirectory; + late Directory projectRoot; + late EngineConfig engineConfig; + late FlavorValidator flavorValidator; + late ShorebirdLogger logger; + late OperatingSystemInterface operatingSystemInterface; + late PatchDiffChecker patchDiffChecker; + late Progress progress; + late ShorebirdArtifacts shorebirdArtifacts; + late ShorebirdProcess shorebirdProcess; + late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutter shorebirdFlutter; + late ShorebirdValidator shorebirdValidator; + late XcodeBuild xcodeBuild; + late IosPatcher patcher; - R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - aotToolsRef.overrideWith(() => aotTools), - appleRef.overrideWith(() => apple), - artifactBuilderRef.overrideWith(() => artifactBuilder), - artifactManagerRef.overrideWith(() => artifactManager), - codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - codeSignerRef.overrideWith(() => codeSigner), - doctorRef.overrideWith(() => doctor), - engineConfigRef.overrideWith(() => engineConfig), - loggerRef.overrideWith(() => logger), - osInterfaceRef.overrideWith(() => operatingSystemInterface), - patchDiffCheckerRef.overrideWith(() => patchDiffChecker), - processRef.overrideWith(() => shorebirdProcess), - shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), - shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - xcodeBuildRef.overrideWith(() => xcodeBuild), - }, - ); - } + R runWithOverrides(R Function() body) { + return runScoped( + body, + values: { + aotToolsRef.overrideWith(() => aotTools), + appleRef.overrideWith(() => apple), + artifactBuilderRef.overrideWith(() => artifactBuilder), + artifactManagerRef.overrideWith(() => artifactManager), + codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + codeSignerRef.overrideWith(() => codeSigner), + doctorRef.overrideWith(() => doctor), + engineConfigRef.overrideWith(() => engineConfig), + loggerRef.overrideWith(() => logger), + osInterfaceRef.overrideWith(() => operatingSystemInterface), + patchDiffCheckerRef.overrideWith(() => patchDiffChecker), + processRef.overrideWith(() => shorebirdProcess), + shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), + xcodeBuildRef.overrideWith(() => xcodeBuild), + }, + ); + } - setUpAll(() { - registerFallbackValue(FakeArgResults()); - registerFallbackValue(Directory('')); - registerFallbackValue(File('')); - registerFallbackValue(const AppleArchiveDiffer()); - registerFallbackValue(ReleasePlatform.ios); - registerFallbackValue(ShorebirdArtifact.genSnapshotIos); - registerFallbackValue(Uri.parse('https://example.com')); + setUpAll(() { + registerFallbackValue(FakeArgResults()); + registerFallbackValue(Directory('')); + registerFallbackValue(File('')); + registerFallbackValue(const AppleArchiveDiffer()); + registerFallbackValue(ReleasePlatform.ios); + registerFallbackValue(ShorebirdArtifact.genSnapshotIos); + registerFallbackValue(Uri.parse('https://example.com')); + }); + + setUp(() { + aotTools = MockAotTools(); + apple = MockApple(); + argParser = MockArgParser(); + argResults = MockArgResults(); + artifactBuilder = MockArtifactBuilder(); + artifactManager = MockArtifactManager(); + codePushClientWrapper = MockCodePushClientWrapper(); + codeSigner = MockCodeSigner(); + doctor = MockDoctor(); + engineConfig = MockEngineConfig(); + flavorValidator = MockFlavorValidator(); + operatingSystemInterface = MockOperatingSystemInterface(); + patchDiffChecker = MockPatchDiffChecker(); + progress = MockProgress(); + projectRoot = Directory.systemTemp.createTempSync(); + logger = MockShorebirdLogger(); + shorebirdArtifacts = MockShorebirdArtifacts(); + shorebirdProcess = MockShorebirdProcess(); + shorebirdEnv = MockShorebirdEnv(); + shorebirdFlutter = MockShorebirdFlutter(); + shorebirdValidator = MockShorebirdValidator(); + xcodeBuild = MockXcodeBuild(); + + when(() => argParser.options).thenReturn({}); + + when(() => argResults.options).thenReturn([]); + when(() => argResults.rest).thenReturn([]); + when(() => argResults.wasParsed(any())).thenReturn(false); + + when(() => logger.progress(any())).thenReturn(progress); + + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); + when( + () => shorebirdEnv.buildDirectory, + ).thenReturn(Directory(p.join(projectRoot.path, 'build'))); + + when(aotTools.isLinkDebugInfoSupported).thenAnswer((_) async => false); + + patcher = IosPatcher( + argParser: argParser, + argResults: argResults, + flavor: null, + target: null, + ); + }); + + group('primaryReleaseArtifactArch', () { + test('is "xcarchive"', () { + expect(patcher.primaryReleaseArtifactArch, 'xcarchive'); + }); + }); + + group('supplementaryReleaseArtifactArch', () { + test('is "ios_supplement"', () { + expect(patcher.supplementaryReleaseArtifactArch, 'ios_supplement'); + }); + }); + + group('releaseType', () { + test('is ReleaseType.ios', () { + expect(patcher.releaseType, ReleaseType.ios); + }); + }); + + group('linkPercentage', () { + group('when linking has not occurred', () { + test('returns null', () { + expect(patcher.linkPercentage, isNull); + }); }); + group('when linking has occurred', () { + const linkPercentage = 42.1337; + + setUp(() { + patcher.lastBuildLinkPercentage = linkPercentage; + }); + + test('returns correct link percentage', () { + expect(patcher.linkPercentage, equals(linkPercentage)); + }); + }); + }); + + group('assertPreconditions', () { setUp(() { - aotTools = MockAotTools(); - apple = MockApple(); - argParser = MockArgParser(); - argResults = MockArgResults(); - artifactBuilder = MockArtifactBuilder(); - artifactManager = MockArtifactManager(); - codePushClientWrapper = MockCodePushClientWrapper(); - codeSigner = MockCodeSigner(); - doctor = MockDoctor(); - engineConfig = MockEngineConfig(); - flavorValidator = MockFlavorValidator(); - operatingSystemInterface = MockOperatingSystemInterface(); - patchDiffChecker = MockPatchDiffChecker(); - progress = MockProgress(); - projectRoot = Directory.systemTemp.createTempSync(); - logger = MockShorebirdLogger(); - shorebirdArtifacts = MockShorebirdArtifacts(); - shorebirdProcess = MockShorebirdProcess(); - shorebirdEnv = MockShorebirdEnv(); - shorebirdFlutter = MockShorebirdFlutter(); - shorebirdValidator = MockShorebirdValidator(); - xcodeBuild = MockXcodeBuild(); - - when(() => argParser.options).thenReturn({}); - - when(() => argResults.options).thenReturn([]); - when(() => argResults.rest).thenReturn([]); - when(() => argResults.wasParsed(any())).thenReturn(false); - - when(() => logger.progress(any())).thenReturn(progress); - - when( - () => shorebirdEnv.getShorebirdProjectRoot(), - ).thenReturn(projectRoot); - when(() => shorebirdEnv.buildDirectory).thenReturn( - Directory(p.join(projectRoot.path, 'build')), - ); - - when(aotTools.isLinkDebugInfoSupported).thenAnswer((_) async => false); - - patcher = IosPatcher( - argParser: argParser, - argResults: argResults, - flavor: null, - target: null, - ); + when(() => doctor.iosCommandValidators).thenReturn([flavorValidator]); }); - group('primaryReleaseArtifactArch', () { - test('is "xcarchive"', () { - expect(patcher.primaryReleaseArtifactArch, 'xcarchive'); - }); - }); - - group('supplementaryReleaseArtifactArch', () { - test('is "ios_supplement"', () { - expect(patcher.supplementaryReleaseArtifactArch, 'ios_supplement'); - }); - }); - - group('releaseType', () { - test('is ReleaseType.ios', () { - expect(patcher.releaseType, ReleaseType.ios); - }); - }); - - group('linkPercentage', () { - group('when linking has not occurred', () { - test('returns null', () { - expect(patcher.linkPercentage, isNull); - }); - }); - - group('when linking has occurred', () { - const linkPercentage = 42.1337; - - setUp(() { - patcher.lastBuildLinkPercentage = linkPercentage; - }); - - test('returns correct link percentage', () { - expect(patcher.linkPercentage, equals(linkPercentage)); - }); - }); - }); - - group('assertPreconditions', () { + group('when validation succeeds', () { setUp(() { when( - () => doctor.iosCommandValidators, - ).thenReturn([flavorValidator]); + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); }); - group('when validation succeeds', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenAnswer((_) async {}); - }); - - test('returns normally', () async { - await expectLater( - () => runWithOverrides(patcher.assertPreconditions), - returnsNormally, - ); - }); - }); - - group('when validation fails', () { - setUp(() { - final exception = ValidationFailedException(); - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any( - named: 'validators', - ), - ), - ).thenThrow(exception); - }); - - test('exits with code 70', () async { - final exception = ValidationFailedException(); - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenThrow(exception); - await expectLater( - () => runWithOverrides(patcher.assertPreconditions), - exitsWithCode(exception.exitCode), - ); - verify( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: true, - checkShorebirdInitialized: true, - validators: [flavorValidator], - supportedOperatingSystems: {Platform.macOS}, - ), - ).called(1); - }); + test('returns normally', () async { + await expectLater( + () => runWithOverrides(patcher.assertPreconditions), + returnsNormally, + ); }); }); - group('assertUnpatchableDiffs', () { - group('when no native changes are detected', () { - const noChangeDiffStatus = DiffStatus( - hasAssetChanges: false, - hasNativeChanges: false, - ); - - setUp(() { - when( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: any(named: 'localArchive'), - releaseArchive: any(named: 'releaseArchive'), - archiveDiffer: any(named: 'archiveDiffer'), - allowAssetChanges: any(named: 'allowAssetChanges'), - allowNativeChanges: any(named: 'allowNativeChanges'), - confirmNativeChanges: false, + group('when validation fails', () { + setUp(() { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', ), - ).thenAnswer((_) async => noChangeDiffStatus); - }); - - test('returns diff status from patchDiffChecker', () async { - final diffStatus = await runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: FakeReleaseArtifact(), - releaseArchive: File(''), - patchArchive: File(''), - ), - ); - expect(diffStatus, equals(noChangeDiffStatus)); - verifyNever( - () => logger.warn( - '''Your ios/Podfile.lock is different from the one used to build the release.''', - ), - ); - }); + validators: any(named: 'validators'), + ), + ).thenThrow(exception); }); - group('when native changes are detected', () { - const nativeChangeDiffStatus = DiffStatus( - hasAssetChanges: false, - hasNativeChanges: true, + test('exits with code 70', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenThrow(exception); + await expectLater( + () => runWithOverrides(patcher.assertPreconditions), + exitsWithCode(exception.exitCode), ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flavorValidator], + supportedOperatingSystems: {Platform.macOS}, + ), + ).called(1); + }); + }); + }); - late String podfileLockHash; + group('assertUnpatchableDiffs', () { + group('when no native changes are detected', () { + const noChangeDiffStatus = DiffStatus( + hasAssetChanges: false, + hasNativeChanges: false, + ); - setUp(() { - when( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: any(named: 'localArchive'), - releaseArchive: any(named: 'releaseArchive'), - archiveDiffer: any(named: 'archiveDiffer'), - allowAssetChanges: any(named: 'allowAssetChanges'), - allowNativeChanges: any(named: 'allowNativeChanges'), - confirmNativeChanges: false, - ), - ).thenAnswer((_) async => nativeChangeDiffStatus); + setUp(() { + when( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: any(named: 'localArchive'), + releaseArchive: any(named: 'releaseArchive'), + archiveDiffer: any(named: 'archiveDiffer'), + allowAssetChanges: any(named: 'allowAssetChanges'), + allowNativeChanges: any(named: 'allowNativeChanges'), + confirmNativeChanges: false, + ), + ).thenAnswer((_) async => noChangeDiffStatus); + }); - const podfileLockContents = 'lock file'; - podfileLockHash = - sha256.convert(utf8.encode(podfileLockContents)).toString(); - final podfileLockFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'Podfile.lock', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync(podfileLockContents); + test('returns diff status from patchDiffChecker', () async { + final diffStatus = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: FakeReleaseArtifact(), + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(diffStatus, equals(noChangeDiffStatus)); + verifyNever( + () => logger.warn( + '''Your ios/Podfile.lock is different from the one used to build the release.''', + ), + ); + }); + }); - when(() => shorebirdEnv.iosPodfileLockFile) - .thenReturn(podfileLockFile); + group('when native changes are detected', () { + const nativeChangeDiffStatus = DiffStatus( + hasAssetChanges: false, + hasNativeChanges: true, + ); + + late String podfileLockHash; + + setUp(() { + when( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: any(named: 'localArchive'), + releaseArchive: any(named: 'releaseArchive'), + archiveDiffer: any(named: 'archiveDiffer'), + allowAssetChanges: any(named: 'allowAssetChanges'), + allowNativeChanges: any(named: 'allowNativeChanges'), + confirmNativeChanges: false, + ), + ).thenAnswer((_) async => nativeChangeDiffStatus); + + const podfileLockContents = 'lock file'; + podfileLockHash = + sha256.convert(utf8.encode(podfileLockContents)).toString(); + final podfileLockFile = + File( + p.join( + Directory.systemTemp.createTempSync().path, + 'Podfile.lock', + ), + ) + ..createSync(recursive: true) + ..writeAsStringSync(podfileLockContents); + + when( + () => shorebirdEnv.iosPodfileLockFile, + ).thenReturn(podfileLockFile); + }); + + group('when release has podspec lock hash', () { + group('when release podspec lock hash matches patch', () { + late final releaseArtifact = ReleaseArtifact( + id: 0, + releaseId: 0, + arch: 'aarch64', + platform: ReleasePlatform.ios, + hash: '#', + size: 42, + url: 'https://example.com', + podfileLockHash: podfileLockHash, + canSideload: true, + ); + + test('does not warn of native changes', () async { + final diffStatus = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: releaseArtifact, + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(diffStatus, equals(nativeChangeDiffStatus)); + verifyNever( + () => logger.warn( + '''Your ios/Podfile.lock is different from the one used to build the release.''', + ), + ); + }); }); - group('when release has podspec lock hash', () { - group('when release podspec lock hash matches patch', () { - late final releaseArtifact = ReleaseArtifact( - id: 0, - releaseId: 0, - arch: 'aarch64', - platform: ReleasePlatform.ios, - hash: '#', - size: 42, - url: 'https://example.com', - podfileLockHash: podfileLockHash, - canSideload: true, - ); + group('when release podspec lock hash does not match patch', () { + const releaseArtifact = ReleaseArtifact( + id: 0, + releaseId: 0, + arch: 'aarch64', + platform: ReleasePlatform.ios, + hash: '#', + size: 42, + url: 'https://example.com', + podfileLockHash: 'podfile-lock-hash', + canSideload: true, + ); - test('does not warn of native changes', () async { - final diffStatus = await runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: releaseArtifact, - releaseArchive: File(''), - patchArchive: File(''), - ), - ); - expect(diffStatus, equals(nativeChangeDiffStatus)); - verifyNever( - () => logger.warn( - '''Your ios/Podfile.lock is different from the one used to build the release.''', - ), - ); + group('when native diffs are allowed', () { + setUp(() { + when(() => argResults['allow-native-diffs']).thenReturn(true); }); - }); - group('when release podspec lock hash does not match patch', () { - const releaseArtifact = ReleaseArtifact( - id: 0, - releaseId: 0, - arch: 'aarch64', - platform: ReleasePlatform.ios, - hash: '#', - size: 42, - url: 'https://example.com', - podfileLockHash: 'podfile-lock-hash', - canSideload: true, - ); - - group('when native diffs are allowed', () { - setUp(() { - when(() => argResults['allow-native-diffs']).thenReturn(true); - }); - - test( - 'logs warning, does not prompt for confirmation to proceed', - () async { + test( + 'logs warning, does not prompt for confirmation to proceed', + () async { final diffStatus = await runWithOverrides( () => patcher.assertUnpatchableDiffs( releaseArtifact: releaseArtifact, @@ -418,58 +408,60 @@ This may indicate that the patch contains native changes, which cannot be applie ), ).called(1); verifyNever(() => logger.confirm(any())); - }); - }); + }, + ); + }); - group('when native diffs are not allowed', () { - group('when in an environment that accepts user input', () { + group('when native diffs are not allowed', () { + group('when in an environment that accepts user input', () { + setUp(() { + when(() => shorebirdEnv.canAcceptUserInput).thenReturn(true); + }); + + group('when user opts to continue at prompt', () { setUp(() { - when(() => shorebirdEnv.canAcceptUserInput) - .thenReturn(true); + when(() => logger.confirm(any())).thenReturn(true); }); - group('when user opts to continue at prompt', () { - setUp(() { - when(() => logger.confirm(any())).thenReturn(true); - }); + test('returns diff status from patchDiffChecker', () async { + final diffStatus = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: releaseArtifact, + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(diffStatus, equals(nativeChangeDiffStatus)); + }); + }); - test('returns diff status from patchDiffChecker', () async { - final diffStatus = await runWithOverrides( + group('when user aborts at prompt', () { + setUp(() { + when(() => logger.confirm(any())).thenReturn(false); + }); + + test('throws UserCancelledException', () async { + await expectLater( + () => runWithOverrides( () => patcher.assertUnpatchableDiffs( releaseArtifact: releaseArtifact, releaseArchive: File(''), patchArchive: File(''), ), - ); - expect(diffStatus, equals(nativeChangeDiffStatus)); - }); - }); - - group('when user aborts at prompt', () { - setUp(() { - when(() => logger.confirm(any())).thenReturn(false); - }); - - test('throws UserCancelledException', () async { - await expectLater( - () => runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: releaseArtifact, - releaseArchive: File(''), - patchArchive: File(''), - ), - ), - throwsA(isA()), - ); - }); + ), + throwsA(isA()), + ); }); }); + }); - group('when in an environment that does not accept user input', - () { + group( + 'when in an environment that does not accept user input', + () { setUp(() { - when(() => shorebirdEnv.canAcceptUserInput) - .thenReturn(false); + when( + () => shorebirdEnv.canAcceptUserInput, + ).thenReturn(false); }); test('throws UnpatchableChangeException', () async { @@ -484,451 +476,156 @@ This may indicate that the patch contains native changes, which cannot be applie throwsA(isA()), ); }); - }); - }); + }, + ); }); }); - - group('when release does not have podspec lock hash', () {}); }); + + group('when release does not have podspec lock hash', () {}); + }); + }); + + group('buildPatchArtifact', () { + const flutterVersionAndRevision = '3.22.2 (83305b5088)'; + setUp(() { + when( + () => shorebirdFlutter.getVersionAndRevision(), + ).thenAnswer((_) async => flutterVersionAndRevision); + when( + () => shorebirdFlutter.getVersion(), + ).thenAnswer((_) async => Version(3, 22, 2)); }); - group('buildPatchArtifact', () { - const flutterVersionAndRevision = '3.22.2 (83305b5088)'; + group('when specified flutter version is less than minimum', () { setUp(() { when( - () => shorebirdFlutter.getVersionAndRevision(), - ).thenAnswer((_) async => flutterVersionAndRevision); + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); when( () => shorebirdFlutter.getVersion(), - ).thenAnswer((_) async => Version(3, 22, 2)); + ).thenAnswer((_) async => Version(3, 0, 0)); }); - group('when specified flutter version is less than minimum', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenAnswer((_) async {}); - when( - () => shorebirdFlutter.getVersion(), - ).thenAnswer((_) async => Version(3, 0, 0)); - }); + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - ''' + verify( + () => logger.err(''' iOS patches are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), - ).called(1); - }); - }); - - group('when build fails with ProcessException', () { - setUp(() { - when( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenThrow( - const ProcessException( - 'flutter', - ['build', 'ipa'], - 'Build failed', - ), - ); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify(() => progress.fail('Failed to build: Build failed')); - }); - }); - - group('when build fails with ArtifactBuildException', () { - setUp(() { - when( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenThrow( - ArtifactBuildException('Build failed'), - ); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify(() => progress.fail('Failed to build IPA')); - }); - }); - - group('when elf aot snapshot build fails', () { - setUp(() { - when( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => - IpaBuildResult(kernelFile: File('/path/to/app.dill')), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - ), - ).thenThrow(const FileSystemException('error')); - }); - - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify( - () => progress.fail("FileSystemException: error, path = ''"), - ); - }); - }); - - group('when build succeeds', () { - late File kernelFile; - setUp(() { - kernelFile = File( - p.join(Directory.systemTemp.createTempSync().path, 'app.dill'), - )..createSync(recursive: true); - when( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - base64PublicKey: any(named: 'base64PublicKey'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => IpaBuildResult(kernelFile: kernelFile), - ); - when(() => artifactManager.getXcarchiveDirectory()).thenReturn( - Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - ), - )..createSync(recursive: true), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), - ), - ).thenAnswer( - (invocation) async => - File(invocation.namedArguments[#outFilePath] as String) - ..createSync(recursive: true), - ); - }); - - group('when --split-debug-info is provided', () { - final tempDir = Directory.systemTemp.createTempSync(); - final splitDebugInfoPath = p.join(tempDir.path, 'symbols'); - final splitDebugInfoFile = File( - p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'), - ); - setUp(() { - when( - () => argResults.wasParsed( - CommonArguments.splitDebugInfoArg.name, - ), - ).thenReturn(true); - when( - () => argResults[CommonArguments.splitDebugInfoArg.name], - ).thenReturn(splitDebugInfoPath); - }); - - test('forwards --split-debug-info to builder', () async { - try { - await runWithOverrides(patcher.buildPatchArtifact); - } on Exception { - // ignore - } - verify( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: [ - '--dwarf-stack-traces', - '--resolve-dwarf-paths', - '--save-debugging-info=${splitDebugInfoFile.path}', - ], - ), - ).called(1); - }); - }); - - group('when releaseVersion is provided', () { - test('forwards --build-name and --build-number to builder', - () async { - await runWithOverrides( - () => patcher.buildPatchArtifact(releaseVersion: '1.2.3+4'), - ); - verify( - () => artifactBuilder.buildIpa( - flavor: any(named: 'flavor'), - codesign: any(named: 'codesign'), - target: any(named: 'target'), - args: any( - named: 'args', - that: containsAll( - ['--build-name=1.2.3', '--build-number=4'], - ), - ), - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); - }); - - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['ios', '--verbose']); - }); - - test('returns xcarchive zip', () async { - final artifact = await runWithOverrides( - patcher.buildPatchArtifact, - ); - expect(p.basename(artifact.path), endsWith('.zip')); - verify( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - args: ['--verbose'], - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); - }); - - group('when the key pair is provided', () { - setUp(() { - when( - () => codeSigner.base64PublicKey(any()), - ).thenReturn('public_key_encoded'); - }); - - test('calls the buildIpa passing the key', () async { - when( - () => argResults.wasParsed(CommonArguments.publicKeyArg.name), - ).thenReturn(true); - - final key = createTempFile('public.pem') - ..writeAsStringSync('public_key'); - - when( - () => argResults[CommonArguments.publicKeyArg.name], - ).thenReturn(key.path); - when( - () => argResults[CommonArguments.publicKeyArg.name], - ).thenReturn(key.path); - await runWithOverrides(patcher.buildPatchArtifact); - - verify( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - base64PublicKey: 'public_key_encoded', - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); - }); - - test('returns xcarchive zip', () async { - final artifact = await runWithOverrides(patcher.buildPatchArtifact); - expect(p.basename(artifact.path), endsWith('.zip')); - }); - - test('copies app.dill to build directory', () async { - final copiedKernelFile = File( - p.join( - projectRoot.path, - 'build', - 'app.dill', - ), - ); - expect(copiedKernelFile.existsSync(), isFalse); - await runWithOverrides(patcher.buildPatchArtifact); - expect(copiedKernelFile.existsSync(), isTrue); - }); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), + ).called(1); }); }); - group('createPatchArtifacts', () { - const postLinkerFlutterRevision = // cspell: disable-next-line - 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'; - const preLinkerFlutterRevision = - '83305b5088e6fe327fb3334a73ff190828d85713'; - const appId = 'appId'; - const arch = 'aarch64'; - const releaseId = 1; - const linkFileName = 'out.vmcode'; - const elfAotSnapshotFileName = 'out.aot'; - const releaseArtifact = ReleaseArtifact( - id: 0, - releaseId: releaseId, - arch: arch, - platform: ReleasePlatform.ios, - hash: '#', - size: 42, - url: 'https://example.com', - podfileLockHash: 'podfile-lock-hash', - canSideload: true, - ); - late File releaseArtifactFile; - late File supplementArtifactFile; - - void setUpProjectRootArtifacts() { - File( - p.join(projectRoot.path, 'build', elfAotSnapshotFileName), - ).createSync(recursive: true); - Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - ), - ).createSync(recursive: true); - File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - 'Products', - 'Applications', - 'Runner.app', - 'Frameworks', - 'App.framework', - 'App', - ), - ).createSync(recursive: true); - File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'shorebird', - 'App.ct.link', - ), - ).createSync(recursive: true); - File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'shorebird', - 'App.class_table.json', - ), - ).createSync(recursive: true); - File( - p.join(projectRoot.path, 'build', linkFileName), - ).createSync(recursive: true); - } - + group('when build fails with ProcessException', () { setUp(() { - releaseArtifactFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'release.xcarchive', + when( + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), ), - )..createSync(recursive: true); - supplementArtifactFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'ios_supplement.zip', - ), - )..createSync(recursive: true); + ).thenThrow( + const ProcessException('flutter', ['build', 'ipa'], 'Build failed'), + ); + }); + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail('Failed to build: Build failed')); + }); + }); + + group('when build fails with ArtifactBuildException', () { + setUp(() { when( - () => codePushClientWrapper.getReleaseArtifact( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), ), - ).thenAnswer((_) async => releaseArtifact); - when(() => artifactManager.downloadFile(any())).thenAnswer((_) async { - final tempDirectory = Directory.systemTemp.createTempSync(); - final file = File(p.join(tempDirectory.path, 'libapp.so')) - ..createSync(); - return file; - }); + ).thenThrow(ArtifactBuildException('Build failed')); + }); + + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail('Failed to build IPA')); + }); + }); + + group('when elf aot snapshot build fails', () { + setUp(() { when( - () => artifactManager.extractZip( - zipFile: any(named: 'zipFile'), - outputDirectory: any(named: 'outputDirectory'), + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), ), - ).thenAnswer((invocation) async { - final zipFile = invocation.namedArguments[#zipFile] as File; - final outDir = - invocation.namedArguments[#outputDirectory] as Directory; - File( - p.join(outDir.path, '${p.basename(zipFile.path)}.zip'), - ).createSync(); - }); + ).thenAnswer( + (_) async => IpaBuildResult(kernelFile: File('/path/to/app.dill')), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + ), + ).thenThrow(const FileSystemException('error')); + }); + + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail("FileSystemException: error, path = ''")); + }); + }); + + group('when build succeeds', () { + late File kernelFile; + setUp(() { + kernelFile = File( + p.join(Directory.systemTemp.createTempSync().path, 'app.dill'), + )..createSync(recursive: true); + when( + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + base64PublicKey: any(named: 'base64PublicKey'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer((_) async => IpaBuildResult(kernelFile: kernelFile)); when(() => artifactManager.getXcarchiveDirectory()).thenReturn( Directory( p.join( @@ -939,150 +636,556 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', 'Release', 'App.xcframework', ), + )..createSync(recursive: true), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), + ), + ).thenAnswer( + (invocation) async => + File(invocation.namedArguments[#outFilePath] as String) + ..createSync(recursive: true), + ); + }); + + group('when --split-debug-info is provided', () { + final tempDir = Directory.systemTemp.createTempSync(); + final splitDebugInfoPath = p.join(tempDir.path, 'symbols'); + final splitDebugInfoFile = File( + p.join(splitDebugInfoPath, 'app.ios-arm64.symbols'), + ); + setUp(() { + when( + () => + argResults.wasParsed(CommonArguments.splitDebugInfoArg.name), + ).thenReturn(true); + when( + () => argResults[CommonArguments.splitDebugInfoArg.name], + ).thenReturn(splitDebugInfoPath); + }); + + test('forwards --split-debug-info to builder', () async { + try { + await runWithOverrides(patcher.buildPatchArtifact); + } on Exception { + // ignore + } + verify( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: [ + '--dwarf-stack-traces', + '--resolve-dwarf-paths', + '--save-debugging-info=${splitDebugInfoFile.path}', + ], + ), + ).called(1); + }); + }); + + group('when releaseVersion is provided', () { + test('forwards --build-name and --build-number to builder', () async { + await runWithOverrides( + () => patcher.buildPatchArtifact(releaseVersion: '1.2.3+4'), + ); + verify( + () => artifactBuilder.buildIpa( + flavor: any(named: 'flavor'), + codesign: any(named: 'codesign'), + target: any(named: 'target'), + args: any( + named: 'args', + that: containsAll(['--build-name=1.2.3', '--build-number=4']), + ), + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); + }); + }); + + group('when platform was specified via arg results rest', () { + setUp(() { + when(() => argResults.rest).thenReturn(['ios', '--verbose']); + }); + + test('returns xcarchive zip', () async { + final artifact = await runWithOverrides(patcher.buildPatchArtifact); + expect(p.basename(artifact.path), endsWith('.zip')); + verify( + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + args: ['--verbose'], + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); + }); + }); + + group('when the key pair is provided', () { + setUp(() { + when( + () => codeSigner.base64PublicKey(any()), + ).thenReturn('public_key_encoded'); + }); + + test('calls the buildIpa passing the key', () async { + when( + () => argResults.wasParsed(CommonArguments.publicKeyArg.name), + ).thenReturn(true); + + final key = createTempFile('public.pem') + ..writeAsStringSync('public_key'); + + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(key.path); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(key.path); + await runWithOverrides(patcher.buildPatchArtifact); + + verify( + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + base64PublicKey: 'public_key_encoded', + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); + }); + }); + + test('returns xcarchive zip', () async { + final artifact = await runWithOverrides(patcher.buildPatchArtifact); + expect(p.basename(artifact.path), endsWith('.zip')); + }); + + test('copies app.dill to build directory', () async { + final copiedKernelFile = File( + p.join(projectRoot.path, 'build', 'app.dill'), + ); + expect(copiedKernelFile.existsSync(), isFalse); + await runWithOverrides(patcher.buildPatchArtifact); + expect(copiedKernelFile.existsSync(), isTrue); + }); + }); + }); + + group('createPatchArtifacts', () { + const postLinkerFlutterRevision = // cspell: disable-next-line + 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'; + const preLinkerFlutterRevision = + '83305b5088e6fe327fb3334a73ff190828d85713'; + const appId = 'appId'; + const arch = 'aarch64'; + const releaseId = 1; + const linkFileName = 'out.vmcode'; + const elfAotSnapshotFileName = 'out.aot'; + const releaseArtifact = ReleaseArtifact( + id: 0, + releaseId: releaseId, + arch: arch, + platform: ReleasePlatform.ios, + hash: '#', + size: 42, + url: 'https://example.com', + podfileLockHash: 'podfile-lock-hash', + canSideload: true, + ); + late File releaseArtifactFile; + late File supplementArtifactFile; + + void setUpProjectRootArtifacts() { + File( + p.join(projectRoot.path, 'build', elfAotSnapshotFileName), + ).createSync(recursive: true); + Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + ), + ).createSync(recursive: true); + File( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + 'Products', + 'Applications', + 'Runner.app', + 'Frameworks', + 'App.framework', + 'App', + ), + ).createSync(recursive: true); + File( + p.join(projectRoot.path, 'build', 'ios', 'shorebird', 'App.ct.link'), + ).createSync(recursive: true); + File( + p.join( + projectRoot.path, + 'build', + 'ios', + 'shorebird', + 'App.class_table.json', + ), + ).createSync(recursive: true); + File( + p.join(projectRoot.path, 'build', linkFileName), + ).createSync(recursive: true); + } + + setUp(() { + releaseArtifactFile = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'release.xcarchive', + ), + )..createSync(recursive: true); + supplementArtifactFile = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'ios_supplement.zip', + ), + )..createSync(recursive: true); + + when( + () => codePushClientWrapper.getReleaseArtifact( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + ), + ).thenAnswer((_) async => releaseArtifact); + when(() => artifactManager.downloadFile(any())).thenAnswer((_) async { + final tempDirectory = Directory.systemTemp.createTempSync(); + final file = File(p.join(tempDirectory.path, 'libapp.so')) + ..createSync(); + return file; + }); + when( + () => artifactManager.extractZip( + zipFile: any(named: 'zipFile'), + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async { + final zipFile = invocation.namedArguments[#zipFile] as File; + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; + File( + p.join(outDir.path, '${p.basename(zipFile.path)}.zip'), + ).createSync(); + }); + when(() => artifactManager.getXcarchiveDirectory()).thenReturn( + Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + ), + ), + ); + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn(projectRoot); + when(() => engineConfig.localEngine).thenReturn(null); + }); + + group('when patch .xcarchive does not exist', () { + setUp(() { + when(() => artifactManager.getXcarchiveDirectory()).thenReturn(null); + }); + + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + }); + }); + + group('when uses linker', () { + const linkPercentage = 50.0; + late File analyzeSnapshotFile; + late File genSnapshotFile; + + setUp(() { + final shorebirdRoot = Directory.systemTemp.createTempSync(); + flutterDirectory = Directory( + p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'), + ); + genSnapshotFile = File( + p.join( + flutterDirectory.path, + 'bin', + 'cache', + 'artifacts', + 'engine', + 'ios-release', + 'gen_snapshot_arm64', ), ); + analyzeSnapshotFile = File( + p.join( + flutterDirectory.path, + 'bin', + 'cache', + 'artifacts', + 'engine', + 'ios-release', + 'analyze_snapshot_arm64', + ), + )..createSync(recursive: true); + + when( + () => apple.runLinker( + kernelFile: any(named: 'kernelFile'), + aotOutputFile: any(named: 'aotOutputFile'), + releaseArtifact: any(named: 'releaseArtifact'), + splitDebugInfoArgs: any(named: 'splitDebugInfoArgs'), + vmCodeFile: any(named: 'vmCodeFile'), + ), + ).thenAnswer( + (_) async => ( + exitCode: ExitCode.success.code, + linkPercentage: linkPercentage, + ), + ); + when( + aotTools.isGeneratePatchDiffBaseSupported, + ).thenAnswer((_) async => false); when( () => artifactManager.getIosAppDirectory( xcarchiveDirectory: any(named: 'xcarchiveDirectory'), ), - ).thenReturn(projectRoot); - when(() => engineConfig.localEngine).thenReturn(null); + ).thenReturn(Directory(p.join(projectRoot.path, 'build', 'ios'))); + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn(Directory(p.join(projectRoot.path, 'build', 'ios'))); + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn( + Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + 'Products', + 'Applications', + 'Runner.app', + ), + ), + ); + when( + () => shorebirdEnv.flutterRevision, + ).thenReturn(postLinkerFlutterRevision); + when( + () => shorebirdArtifacts.getArtifactPath( + artifact: ShorebirdArtifact.analyzeSnapshotIos, + ), + ).thenReturn(analyzeSnapshotFile.path); + when( + () => shorebirdArtifacts.getArtifactPath( + artifact: ShorebirdArtifact.genSnapshotIos, + ), + ).thenReturn(genSnapshotFile.path); }); - group('when patch .xcarchive does not exist', () { + group('when linking fails', () { + group('when .app does not exist', () { + setUp(() { + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn(null); + }); + + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err( + any( + that: startsWith( + 'Unable to find release artifact .app directory', + ), + ), + ), + ).called(1); + }); + }); + }); + + group('when generate patch diff base is supported', () { setUp(() { when( - () => artifactManager.getXcarchiveDirectory(), - ).thenReturn(null); + () => aotTools.isGeneratePatchDiffBaseSupported(), + ).thenAnswer((_) async => true); + when( + () => aotTools.generatePatchDiffBase( + analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), + releaseSnapshot: any(named: 'releaseSnapshot'), + ), + ).thenAnswer((_) async => File('')); }); - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides( + group('when we fail to generate patch diff base', () { + setUp(() { + when( + () => aotTools.generatePatchDiffBase( + analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), + releaseSnapshot: any(named: 'releaseSnapshot'), + ), + ).thenThrow(Exception('oops')); + + setUpProjectRootArtifacts(); + }); + + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail('Exception: oops')).called(1); + }); + }); + + group('when linking and patch diff generation succeeds', () { + const diffPath = 'path/to/diff'; + + setUp(() { + when( + () => artifactManager.createDiff( + releaseArtifactPath: any(named: 'releaseArtifactPath'), + patchArtifactPath: any(named: 'patchArtifactPath'), + ), + ).thenAnswer((_) async => diffPath); + setUpProjectRootArtifacts(); + }); + + test('returns linked patch artifact in patch bundle', () async { + final patchBundle = await runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, releaseId: releaseId, releaseArtifact: releaseArtifactFile, ), - ), - exitsWithCode(ExitCode.software), - ); - }); - }); + ); - group('when uses linker', () { - const linkPercentage = 50.0; - late File analyzeSnapshotFile; - late File genSnapshotFile; - - setUp(() { - final shorebirdRoot = Directory.systemTemp.createTempSync(); - flutterDirectory = Directory( - p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'), - ); - genSnapshotFile = File( - p.join( - flutterDirectory.path, - 'bin', - 'cache', - 'artifacts', - 'engine', - 'ios-release', - 'gen_snapshot_arm64', - ), - ); - analyzeSnapshotFile = File( - p.join( - flutterDirectory.path, - 'bin', - 'cache', - 'artifacts', - 'engine', - 'ios-release', - 'analyze_snapshot_arm64', - ), - )..createSync(recursive: true); - - when( - () => apple.runLinker( - kernelFile: any(named: 'kernelFile'), - aotOutputFile: any(named: 'aotOutputFile'), - releaseArtifact: any(named: 'releaseArtifact'), - splitDebugInfoArgs: any(named: 'splitDebugInfoArgs'), - vmCodeFile: any(named: 'vmCodeFile'), - ), - ).thenAnswer( - (_) async => ( - exitCode: ExitCode.success.code, - linkPercentage: linkPercentage - ), - ); - when( - aotTools.isGeneratePatchDiffBaseSupported, - ).thenAnswer((_) async => false); - when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), - ), - ).thenReturn( - Directory(p.join(projectRoot.path, 'build', 'ios')), - ); - when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), - ), - ).thenReturn(Directory(p.join(projectRoot.path, 'build', 'ios'))); - when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), - ), - ).thenReturn( - Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - 'Products', - 'Applications', - 'Runner.app', + expect(patchBundle, hasLength(1)); + expect( + patchBundle[Arch.arm64], + isA().having( + (b) => b.path, + 'path', + endsWith(diffPath), ), - ), - ); - when( - () => shorebirdEnv.flutterRevision, - ).thenReturn(postLinkerFlutterRevision); - when( - () => shorebirdArtifacts.getArtifactPath( - artifact: ShorebirdArtifact.analyzeSnapshotIos, - ), - ).thenReturn(analyzeSnapshotFile.path); - when( - () => shorebirdArtifacts.getArtifactPath( - artifact: ShorebirdArtifact.genSnapshotIos, - ), - ).thenReturn(genSnapshotFile.path); - }); + ); + }); - group('when linking fails', () { - group('when .app does not exist', () { + group('when class table link info is not present', () { setUp(() { when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + () => artifactManager.extractZip( + zipFile: supplementArtifactFile, + outputDirectory: any(named: 'outputDirectory'), ), - ).thenReturn(null); + ).thenAnswer((invocation) async {}); }); - test('logs error and exits with code 70', () async { + test('exits with code 70', () async { await expectLater( () => runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, releaseId: releaseId, releaseArtifact: releaseArtifactFile, + supplementArtifact: supplementArtifactFile, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err('Unable to find class table link info file'), + ).called(1); + }); + }); + + group('when debug info is missing', () { + setUp(() { + when( + () => artifactManager.extractZip( + zipFile: supplementArtifactFile, + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer((invocation) async { + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; + File( + p.join(outDir.path, 'App.ct.link'), + ).createSync(recursive: true); + }); + }); + + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + supplementArtifact: supplementArtifactFile, ), ), exitsWithCode(ExitCode.software), @@ -1090,69 +1193,29 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', verify( () => logger.err( - any( - that: startsWith( - 'Unable to find release artifact .app directory', - ), - ), + 'Unable to find class table link debug info file', ), ).called(1); }); }); - }); - group('when generate patch diff base is supported', () { - setUp(() { - when( - () => aotTools.isGeneratePatchDiffBaseSupported(), - ).thenAnswer((_) async => true); - when( - () => aotTools.generatePatchDiffBase( - analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), - releaseSnapshot: any(named: 'releaseSnapshot'), - ), - ).thenAnswer((_) async => File('')); - }); - - group('when we fail to generate patch diff base', () { + group('when class table link info & debug info are present', () { setUp(() { when( - () => aotTools.generatePatchDiffBase( - analyzeSnapshotPath: any(named: 'analyzeSnapshotPath'), - releaseSnapshot: any(named: 'releaseSnapshot'), + () => artifactManager.extractZip( + zipFile: supplementArtifactFile, + outputDirectory: any(named: 'outputDirectory'), ), - ).thenThrow(Exception('oops')); - - setUpProjectRootArtifacts(); - }); - - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify(() => progress.fail('Exception: oops')).called(1); - }); - }); - - group('when linking and patch diff generation succeeds', () { - const diffPath = 'path/to/diff'; - - setUp(() { - when( - () => artifactManager.createDiff( - releaseArtifactPath: any(named: 'releaseArtifactPath'), - patchArtifactPath: any(named: 'patchArtifactPath'), - ), - ).thenAnswer((_) async => diffPath); - setUpProjectRootArtifacts(); + ).thenAnswer((invocation) async { + final outDir = + invocation.namedArguments[#outputDirectory] as Directory; + File( + p.join(outDir.path, 'App.ct.link'), + ).createSync(recursive: true); + File( + p.join(outDir.path, 'App.class_table.json'), + ).createSync(recursive: true); + }); }); test('returns linked patch artifact in patch bundle', () async { @@ -1161,6 +1224,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', appId: appId, releaseId: releaseId, releaseArtifact: releaseArtifactFile, + supplementArtifact: supplementArtifactFile, ), ); @@ -1174,143 +1238,35 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', ), ); }); + }); - group('when class table link info is not present', () { - setUp(() { - when( - () => artifactManager.extractZip( - zipFile: supplementArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async {}); - }); + group('when code signing the patch', () { + setUp(() { + final privateKey = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'test-private.pem', + ), + )..createSync(); - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - supplementArtifact: supplementArtifactFile, - ), - ), - exitsWithCode(ExitCode.software), - ); + when( + () => argResults[CommonArguments.privateKeyArg.name], + ).thenReturn(privateKey.path); - verify( - () => logger.err( - 'Unable to find class table link info file', - ), - ).called(1); + when( + () => codeSigner.sign( + message: any(named: 'message'), + privateKeyPemFile: any(named: 'privateKeyPemFile'), + ), + ).thenAnswer((invocation) { + final message = invocation.namedArguments[#message] as String; + return '$message-signature'; }); }); - group('when debug info is missing', () { - setUp(() { - when( - () => artifactManager.extractZip( - zipFile: supplementArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async { - final outDir = invocation.namedArguments[#outputDirectory] - as Directory; - File( - p.join(outDir.path, 'App.ct.link'), - ).createSync(recursive: true); - }); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - supplementArtifact: supplementArtifactFile, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - 'Unable to find class table link debug info file', - ), - ).called(1); - }); - }); - - group('when class table link info & debug info are present', () { - setUp(() { - when( - () => artifactManager.extractZip( - zipFile: supplementArtifactFile, - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer((invocation) async { - final outDir = invocation.namedArguments[#outputDirectory] - as Directory; - File( - p.join(outDir.path, 'App.ct.link'), - ).createSync(recursive: true); - File( - p.join(outDir.path, 'App.class_table.json'), - ).createSync(recursive: true); - }); - }); - - test('returns linked patch artifact in patch bundle', () async { - final patchBundle = await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - supplementArtifact: supplementArtifactFile, - ), - ); - - expect(patchBundle, hasLength(1)); - expect( - patchBundle[Arch.arm64], - isA().having( - (b) => b.path, - 'path', - endsWith(diffPath), - ), - ); - }); - }); - - group('when code signing the patch', () { - setUp(() { - final privateKey = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'test-private.pem', - ), - )..createSync(); - - when(() => argResults[CommonArguments.privateKeyArg.name]) - .thenReturn(privateKey.path); - - when( - () => codeSigner.sign( - message: any(named: 'message'), - privateKeyPemFile: any(named: 'privateKeyPemFile'), - ), - ).thenAnswer((invocation) { - final message = - invocation.namedArguments[#message] as String; - return '$message-signature'; - }); - }); - - test( - '''returns patch artifact bundles with proper hash signatures''', - () async { + test( + '''returns patch artifact bundles with proper hash signatures''', + () async { final result = await runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, @@ -1327,59 +1283,24 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', expect( result.values.first.hashSignature, - equals( - expectedSignature, - ), + equals(expectedSignature), ); - }); - }); - }); - }); - - group('when generate patch diff base is not supported', () { - setUp(() { - when( - aotTools.isGeneratePatchDiffBaseSupported, - ).thenAnswer((_) async => false); - setUpProjectRootArtifacts(); - }); - - test('returns vmcode file as patch file', () async { - final patchBundle = await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - ), - ); - - expect(patchBundle, hasLength(1)); - expect( - patchBundle[Arch.arm64], - isA().having( - (b) => b.path, - 'path', - endsWith('out.vmcode'), - ), + }, ); }); }); }); - group('when does not use linker', () { + group('when generate patch diff base is not supported', () { setUp(() { when( - () => shorebirdEnv.flutterRevision, - ).thenReturn(preLinkerFlutterRevision); - when( - () => aotTools.isGeneratePatchDiffBaseSupported(), + aotTools.isGeneratePatchDiffBaseSupported, ).thenAnswer((_) async => false); - setUpProjectRootArtifacts(); }); - test('returns base patch artifact in patch bundle', () async { - final patchArtifacts = await runWithOverrides( + test('returns vmcode file as patch file', () async { + final patchBundle = await runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, releaseId: releaseId, @@ -1387,85 +1308,89 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', ), ); - expect(patchArtifacts, hasLength(1)); - verifyNever( - () => aotTools.link( - base: any(named: 'base'), - patch: any(named: 'patch'), - analyzeSnapshot: any(named: 'analyzeSnapshot'), - genSnapshot: any(named: 'genSnapshot'), - kernel: any(named: 'kernel'), - outputPath: any(named: 'outputPath'), + expect(patchBundle, hasLength(1)); + expect( + patchBundle[Arch.arm64], + isA().having( + (b) => b.path, + 'path', + endsWith('out.vmcode'), ), ); }); }); }); - group('extractReleaseVersionFromArtifact', () { + group('when does not use linker', () { setUp(() { - when(() => artifactManager.getXcarchiveDirectory()).thenReturn( - Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - ), + when( + () => shorebirdEnv.flutterRevision, + ).thenReturn(preLinkerFlutterRevision); + when( + () => aotTools.isGeneratePatchDiffBaseSupported(), + ).thenAnswer((_) async => false); + + setUpProjectRootArtifacts(); + }); + + test('returns base patch artifact in patch bundle', () async { + final patchArtifacts = await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ); + + expect(patchArtifacts, hasLength(1)); + verifyNever( + () => aotTools.link( + base: any(named: 'base'), + patch: any(named: 'patch'), + analyzeSnapshot: any(named: 'analyzeSnapshot'), + genSnapshot: any(named: 'genSnapshot'), + kernel: any(named: 'kernel'), + outputPath: any(named: 'outputPath'), ), ); }); + }); + }); - group('when xcarchive directory does not exist', () { - setUp(() { - when( - () => artifactManager.getXcarchiveDirectory(), - ).thenReturn(null); - }); + group('extractReleaseVersionFromArtifact', () { + setUp(() { + when(() => artifactManager.getXcarchiveDirectory()).thenReturn( + Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + ), + ), + ); + }); - test('exit with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - exitsWithCode(ExitCode.software), - ); - }); + group('when xcarchive directory does not exist', () { + setUp(() { + when(() => artifactManager.getXcarchiveDirectory()).thenReturn(null); }); - group('when Info.plist does not exist', () { - setUp(() { - try { - File( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'App.xcframework', - 'Info.plist', - ), - ).deleteSync(recursive: true); - } on Exception { - // ignore - } - }); - - test('exit with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - exitsWithCode(ExitCode.software), - ); - }); + test('exit with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + exitsWithCode(ExitCode.software), + ); }); + }); - group('when empty Info.plist does exist', () { - setUp(() { + group('when Info.plist does not exist', () { + setUp(() { + try { File( p.join( projectRoot.path, @@ -1476,37 +1401,63 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', 'App.xcframework', 'Info.plist', ), + ).deleteSync(recursive: true); + } on Exception { + // ignore + } + }); + + test('exit with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + exitsWithCode(ExitCode.software), + ); + }); + }); + + group('when empty Info.plist does exist', () { + setUp(() { + File( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'App.xcframework', + 'Info.plist', + ), ) - ..createSync(recursive: true) - ..writeAsStringSync(''' + ..createSync(recursive: true) + ..writeAsStringSync(''' '''); - }); - - test('exits with code 70 and logs error', () async { - await expectLater( - runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - exitsWithCode(ExitCode.software), - ); - verify( - () => logger.err( - any( - that: startsWith('Failed to determine release version'), - ), - ), - ).called(1); - }); }); - group('when Info.plist does exist', () { - setUp(() { - File( + test('exits with code 70 and logs error', () async { + await expectLater( + runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + exitsWithCode(ExitCode.software), + ); + verify( + () => logger.err( + any(that: startsWith('Failed to determine release version')), + ), + ).called(1); + }); + }); + + group('when Info.plist does exist', () { + setUp(() { + File( p.join( projectRoot.path, 'build', @@ -1517,8 +1468,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', 'Info.plist', ), ) - ..createSync(recursive: true) - ..writeAsStringSync(''' + ..createSync(recursive: true) + ..writeAsStringSync(''' @@ -1547,125 +1498,121 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', '''); - }); + }); - test('returns correct version', () async { - await expectLater( - runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), + test('returns correct version', () async { + await expectLater( + runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + completion('1.2.3+1'), + ); + }); + }); + }); + + group('updatedCreatePatchMetadata', () { + const allowAssetDiffs = false; + const allowNativeDiffs = true; + const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; + const operatingSystem = 'Mac OS X'; + const operatingSystemVersion = '10.15.7'; + const xcodeVersion = '11'; + + setUp(() { + when(() => xcodeBuild.version()).thenAnswer((_) async => xcodeVersion); + }); + + group('when linker is not enabled', () { + test('returns correct metadata', () async { + const metadata = CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: false, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: true, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); + + expect( + runWithOverrides( + () => patcher.updatedCreatePatchMetadata(metadata), + ), + completion( + const CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: false, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: true, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + xcodeVersion: xcodeVersion, + ), ), - completion('1.2.3+1'), - ); - }); + ), + ); }); }); - group('updatedCreatePatchMetadata', () { - const allowAssetDiffs = false; - const allowNativeDiffs = true; - const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; - const operatingSystem = 'Mac OS X'; - const operatingSystemVersion = '10.15.7'; - const xcodeVersion = '11'; + group('when linker is enabled', () { + const linkPercentage = 100.0; setUp(() { - when( - () => xcodeBuild.version(), - ).thenAnswer((_) async => xcodeVersion); + patcher.lastBuildLinkPercentage = linkPercentage; }); - group('when linker is not enabled', () { - test('returns correct metadata', () async { - const metadata = CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: false, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: true, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ); + test('returns correct metadata', () async { + const metadata = CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: true, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: false, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); - expect( - runWithOverrides( - () => patcher.updatedCreatePatchMetadata(metadata), - ), - completion( - const CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: false, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: true, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - xcodeVersion: xcodeVersion, - ), + expect( + runWithOverrides( + () => patcher.updatedCreatePatchMetadata(metadata), + ), + completion( + const CreatePatchMetadata( + releasePlatform: ReleasePlatform.ios, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: true, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: false, + linkPercentage: linkPercentage, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + xcodeVersion: xcodeVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), ), ), - ); - }); - }); - - group('when linker is enabled', () { - const linkPercentage = 100.0; - - setUp(() { - patcher.lastBuildLinkPercentage = linkPercentage; - }); - - test('returns correct metadata', () async { - const metadata = CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: true, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: false, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ); - - expect( - runWithOverrides( - () => patcher.updatedCreatePatchMetadata(metadata), - ), - completion( - const CreatePatchMetadata( - releasePlatform: ReleasePlatform.ios, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: true, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: false, - linkPercentage: linkPercentage, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - xcodeVersion: xcodeVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ), - ), - ); - }); + ), + ); }); }); - }, - testOn: 'mac-os', - ); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart index 79927828..dfd8c2dd 100644 --- a/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart @@ -219,9 +219,7 @@ void main() { test('exits with software error code', () async { expect( - () => runWithOverrides( - () => patcher.buildPatchArtifact(), - ), + () => runWithOverrides(() => patcher.buildPatchArtifact()), throwsA( isA().having((e) => e.exitCode, 'exitCode', 70), ), @@ -251,9 +249,7 @@ void main() { test('returns a zipped exe file', () async { await expectLater( - runWithOverrides( - () => patcher.buildPatchArtifact(), - ), + runWithOverrides(() => patcher.buildPatchArtifact()), completion( isA().having((f) => f.path, 'path', endsWith('.zip')), ), @@ -290,13 +286,8 @@ void main() { ), )..createSync(recursive: true); - patchArtifact = File( - p.join( - releaseDirectory.path, - 'lib', - 'libapp.so', - ), - )..createSync(recursive: true); + patchArtifact = File(p.join(releaseDirectory.path, 'lib', 'libapp.so')) + ..createSync(recursive: true); when( () => artifactManager.linuxBundleDirectory, @@ -307,8 +298,9 @@ void main() { outputDirectory: any(named: 'outputDirectory'), ), ).thenAnswer((invocation) async { - (invocation.namedArguments[#outputDirectory] as Directory) - .createSync(recursive: true); + (invocation.namedArguments[#outputDirectory] as Directory).createSync( + recursive: true, + ); }); }); @@ -383,10 +375,12 @@ void main() { patchArtifactPath: any(named: 'patchArtifactPath'), ), ).thenAnswer((_) async => diffFile.path); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn('public-key.pem'); - when(() => argResults[CommonArguments.privateKeyArg.name]) - .thenReturn('private-key.pem'); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn('public-key.pem'); + when( + () => argResults[CommonArguments.privateKeyArg.name], + ).thenReturn('private-key.pem'); when( () => codeSigner.sign( message: any(named: 'message'), @@ -430,8 +424,9 @@ void main() { outputDirectory: any(named: 'outputDirectory'), ), ).thenAnswer((invocation) async { - (invocation.namedArguments[#outputDirectory] as Directory) - .createSync(recursive: true); + (invocation.namedArguments[#outputDirectory] as Directory).createSync( + recursive: true, + ); }); when( @@ -443,9 +438,7 @@ void main() { test('returns version from archived exe', () async { final version = await runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact( - File('bundle.zip'), - ), + () => patcher.extractReleaseVersionFromArtifact(File('bundle.zip')), ); expect(version, '1.2.3'); diff --git a/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart index 713a6c56..edb3b6f0 100644 --- a/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart @@ -43,363 +43,353 @@ import '../../matchers.dart'; import '../../mocks.dart'; void main() { - group( - MacosPatcher, - () { - late ArgParser argParser; - late ArgResults argResults; - late ArtifactBuilder artifactBuilder; - late ArtifactManager artifactManager; - late CodePushClientWrapper codePushClientWrapper; - late CodeSigner codeSigner; - late Ditto ditto; - late Doctor doctor; - late EngineConfig engineConfig; - late Directory projectRoot; - late Directory appDirectory; - late FlavorValidator flavorValidator; - late ShorebirdLogger logger; - late OperatingSystemInterface operatingSystemInterface; - late PatchDiffChecker patchDiffChecker; - late Progress progress; - late ShorebirdArtifacts shorebirdArtifacts; - late ShorebirdProcess shorebirdProcess; - late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutter; - late ShorebirdValidator shorebirdValidator; - late XcodeBuild xcodeBuild; - late MacosPatcher patcher; + group(MacosPatcher, () { + late ArgParser argParser; + late ArgResults argResults; + late ArtifactBuilder artifactBuilder; + late ArtifactManager artifactManager; + late CodePushClientWrapper codePushClientWrapper; + late CodeSigner codeSigner; + late Ditto ditto; + late Doctor doctor; + late EngineConfig engineConfig; + late Directory projectRoot; + late Directory appDirectory; + late FlavorValidator flavorValidator; + late ShorebirdLogger logger; + late OperatingSystemInterface operatingSystemInterface; + late PatchDiffChecker patchDiffChecker; + late Progress progress; + late ShorebirdArtifacts shorebirdArtifacts; + late ShorebirdProcess shorebirdProcess; + late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutter shorebirdFlutter; + late ShorebirdValidator shorebirdValidator; + late XcodeBuild xcodeBuild; + late MacosPatcher patcher; - R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - artifactBuilderRef.overrideWith(() => artifactBuilder), - artifactManagerRef.overrideWith(() => artifactManager), - codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - codeSignerRef.overrideWith(() => codeSigner), - dittoRef.overrideWith(() => ditto), - doctorRef.overrideWith(() => doctor), - engineConfigRef.overrideWith(() => engineConfig), - loggerRef.overrideWith(() => logger), - osInterfaceRef.overrideWith(() => operatingSystemInterface), - patchDiffCheckerRef.overrideWith(() => patchDiffChecker), - processRef.overrideWith(() => shorebirdProcess), - shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), - shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - xcodeBuildRef.overrideWith(() => xcodeBuild), - }, - ); - } + R runWithOverrides(R Function() body) { + return runScoped( + body, + values: { + artifactBuilderRef.overrideWith(() => artifactBuilder), + artifactManagerRef.overrideWith(() => artifactManager), + codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + codeSignerRef.overrideWith(() => codeSigner), + dittoRef.overrideWith(() => ditto), + doctorRef.overrideWith(() => doctor), + engineConfigRef.overrideWith(() => engineConfig), + loggerRef.overrideWith(() => logger), + osInterfaceRef.overrideWith(() => operatingSystemInterface), + patchDiffCheckerRef.overrideWith(() => patchDiffChecker), + processRef.overrideWith(() => shorebirdProcess), + shorebirdArtifactsRef.overrideWith(() => shorebirdArtifacts), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), + xcodeBuildRef.overrideWith(() => xcodeBuild), + }, + ); + } - setUpAll(() { - registerFallbackValue(const AppleArchiveDiffer()); - registerFallbackValue(Directory('')); - registerFallbackValue(File('')); - registerFallbackValue(ReleasePlatform.macos); - registerFallbackValue(ShorebirdArtifact.genSnapshotMacosArm64); - registerFallbackValue(Uri.parse('https://example.com')); + setUpAll(() { + registerFallbackValue(const AppleArchiveDiffer()); + registerFallbackValue(Directory('')); + registerFallbackValue(File('')); + registerFallbackValue(ReleasePlatform.macos); + registerFallbackValue(ShorebirdArtifact.genSnapshotMacosArm64); + registerFallbackValue(Uri.parse('https://example.com')); + }); + + setUp(() { + argParser = MockArgParser(); + argResults = MockArgResults(); + artifactBuilder = MockArtifactBuilder(); + artifactManager = MockArtifactManager(); + codePushClientWrapper = MockCodePushClientWrapper(); + ditto = MockDitto(); + codeSigner = MockCodeSigner(); + doctor = MockDoctor(); + engineConfig = MockEngineConfig(); + flavorValidator = MockFlavorValidator(); + operatingSystemInterface = MockOperatingSystemInterface(); + patchDiffChecker = MockPatchDiffChecker(); + progress = MockProgress(); + projectRoot = Directory.systemTemp.createTempSync(); + logger = MockShorebirdLogger(); + shorebirdArtifacts = MockShorebirdArtifacts(); + shorebirdProcess = MockShorebirdProcess(); + shorebirdEnv = MockShorebirdEnv(); + shorebirdFlutter = MockShorebirdFlutter(); + shorebirdValidator = MockShorebirdValidator(); + xcodeBuild = MockXcodeBuild(); + + when(() => argParser.options).thenReturn({}); + + when(() => argResults.options).thenReturn([]); + when(() => argResults.rest).thenReturn([]); + when(() => argResults.wasParsed(any())).thenReturn(false); + + when( + () => ditto.archive( + source: any(named: 'source'), + destination: any(named: 'destination'), + ), + ).thenAnswer((_) async {}); + when( + () => ditto.extract( + source: any(named: 'source'), + destination: any(named: 'destination'), + ), + ).thenAnswer((_) async {}); + + when(() => logger.progress(any())).thenReturn(progress); + + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); + when( + () => shorebirdEnv.buildDirectory, + ).thenReturn(Directory(p.join(projectRoot.path, 'build'))); + when( + () => shorebirdEnv.flutterRevision, + ).thenReturn('5c1dcc19ebcee3565c65262dd95970186e4d81cc'); + + appDirectory = Directory( + p.join( + projectRoot.path, + 'build', + 'macos', + 'Build', + 'Products', + 'Release', + 'my.app', + ), + )..createSync(recursive: true); + when( + () => artifactManager.getMacOSAppDirectory(), + ).thenReturn(appDirectory); + + patcher = MacosPatcher( + argParser: argParser, + argResults: argResults, + flavor: null, + target: null, + ); + }); + + group('primaryReleaseArtifactArch', () { + test('is "app"', () { + expect(patcher.primaryReleaseArtifactArch, 'app'); }); + }); + group('releaseType', () { + test('is ReleaseType.macos', () { + expect(patcher.releaseType, ReleaseType.macos); + }); + }); + + group('assertPreconditions', () { setUp(() { - argParser = MockArgParser(); - argResults = MockArgResults(); - artifactBuilder = MockArtifactBuilder(); - artifactManager = MockArtifactManager(); - codePushClientWrapper = MockCodePushClientWrapper(); - ditto = MockDitto(); - codeSigner = MockCodeSigner(); - doctor = MockDoctor(); - engineConfig = MockEngineConfig(); - flavorValidator = MockFlavorValidator(); - operatingSystemInterface = MockOperatingSystemInterface(); - patchDiffChecker = MockPatchDiffChecker(); - progress = MockProgress(); - projectRoot = Directory.systemTemp.createTempSync(); - logger = MockShorebirdLogger(); - shorebirdArtifacts = MockShorebirdArtifacts(); - shorebirdProcess = MockShorebirdProcess(); - shorebirdEnv = MockShorebirdEnv(); - shorebirdFlutter = MockShorebirdFlutter(); - shorebirdValidator = MockShorebirdValidator(); - xcodeBuild = MockXcodeBuild(); - - when(() => argParser.options).thenReturn({}); - - when(() => argResults.options).thenReturn([]); - when(() => argResults.rest).thenReturn([]); - when(() => argResults.wasParsed(any())).thenReturn(false); - - when( - () => ditto.archive( - source: any(named: 'source'), - destination: any(named: 'destination'), - ), - ).thenAnswer((_) async {}); - when( - () => ditto.extract( - source: any(named: 'source'), - destination: any(named: 'destination'), - ), - ).thenAnswer((_) async {}); - - when(() => logger.progress(any())).thenReturn(progress); - - when( - () => shorebirdEnv.getShorebirdProjectRoot(), - ).thenReturn(projectRoot); - when( - () => shorebirdEnv.buildDirectory, - ).thenReturn(Directory(p.join(projectRoot.path, 'build'))); - when( - () => shorebirdEnv.flutterRevision, - ).thenReturn('5c1dcc19ebcee3565c65262dd95970186e4d81cc'); - - appDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'macos', - 'Build', - 'Products', - 'Release', - 'my.app', - ), - )..createSync(recursive: true); - when( - () => artifactManager.getMacOSAppDirectory(), - ).thenReturn(appDirectory); - - patcher = MacosPatcher( - argParser: argParser, - argResults: argResults, - flavor: null, - target: null, - ); + when(() => doctor.macosCommandValidators).thenReturn([flavorValidator]); }); - group('primaryReleaseArtifactArch', () { - test('is "app"', () { - expect(patcher.primaryReleaseArtifactArch, 'app'); - }); - }); - - group('releaseType', () { - test('is ReleaseType.macos', () { - expect(patcher.releaseType, ReleaseType.macos); - }); - }); - - group('assertPreconditions', () { + group('when validation succeeds', () { setUp(() { when( - () => doctor.macosCommandValidators, - ).thenReturn([flavorValidator]); + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); }); - group('when validation succeeds', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenAnswer((_) async {}); - }); - - test('returns normally', () async { - await expectLater( - () => runWithOverrides(patcher.assertPreconditions), - returnsNormally, - ); - }); - }); - - group('when validation fails', () { - setUp(() { - final exception = ValidationFailedException(); - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any( - named: 'validators', - ), - ), - ).thenThrow(exception); - }); - - test('exits with code 70', () async { - final exception = ValidationFailedException(); - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenThrow(exception); - await expectLater( - () => runWithOverrides(patcher.assertPreconditions), - exitsWithCode(exception.exitCode), - ); - verify( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: true, - checkShorebirdInitialized: true, - validators: [flavorValidator], - supportedOperatingSystems: {Platform.macOS}, - ), - ).called(1); - }); + test('returns normally', () async { + await expectLater( + () => runWithOverrides(patcher.assertPreconditions), + returnsNormally, + ); }); }); - group('assertUnpatchableDiffs', () { - group('when no native changes are detected', () { - const noChangeDiffStatus = DiffStatus( - hasAssetChanges: false, - hasNativeChanges: false, - ); - - setUp(() { - when( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: any(named: 'localArchive'), - releaseArchive: any(named: 'releaseArchive'), - archiveDiffer: any(named: 'archiveDiffer'), - allowAssetChanges: any(named: 'allowAssetChanges'), - allowNativeChanges: any(named: 'allowNativeChanges'), - confirmNativeChanges: false, + group('when validation fails', () { + setUp(() { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', ), - ).thenAnswer((_) async => noChangeDiffStatus); - }); - - test('returns diff status from patchDiffChecker', () async { - final diffStatus = await runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: FakeReleaseArtifact(), - releaseArchive: File(''), - patchArchive: File(''), - ), - ); - expect(diffStatus, equals(noChangeDiffStatus)); - verifyNever( - () => logger.warn( - '''Your macos/Podfile.lock is different from the one used to build the release.''', - ), - ); - }); + validators: any(named: 'validators'), + ), + ).thenThrow(exception); }); - group('when native changes are detected', () { - const nativeChangeDiffStatus = DiffStatus( - hasAssetChanges: false, - hasNativeChanges: true, + test('exits with code 70', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenThrow(exception); + await expectLater( + () => runWithOverrides(patcher.assertPreconditions), + exitsWithCode(exception.exitCode), ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flavorValidator], + supportedOperatingSystems: {Platform.macOS}, + ), + ).called(1); + }); + }); + }); - late String podfileLockHash; + group('assertUnpatchableDiffs', () { + group('when no native changes are detected', () { + const noChangeDiffStatus = DiffStatus( + hasAssetChanges: false, + hasNativeChanges: false, + ); - setUp(() { - when( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: any(named: 'localArchive'), - releaseArchive: any(named: 'releaseArchive'), - archiveDiffer: any(named: 'archiveDiffer'), - allowAssetChanges: any(named: 'allowAssetChanges'), - allowNativeChanges: any(named: 'allowNativeChanges'), - confirmNativeChanges: false, - ), - ).thenAnswer((_) async => nativeChangeDiffStatus); + setUp(() { + when( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: any(named: 'localArchive'), + releaseArchive: any(named: 'releaseArchive'), + archiveDiffer: any(named: 'archiveDiffer'), + allowAssetChanges: any(named: 'allowAssetChanges'), + allowNativeChanges: any(named: 'allowNativeChanges'), + confirmNativeChanges: false, + ), + ).thenAnswer((_) async => noChangeDiffStatus); + }); - const podfileLockContents = 'lock file'; - podfileLockHash = - sha256.convert(utf8.encode(podfileLockContents)).toString(); - final podfileLockFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'Podfile.lock', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync(podfileLockContents); + test('returns diff status from patchDiffChecker', () async { + final diffStatus = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: FakeReleaseArtifact(), + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(diffStatus, equals(noChangeDiffStatus)); + verifyNever( + () => logger.warn( + '''Your macos/Podfile.lock is different from the one used to build the release.''', + ), + ); + }); + }); - when(() => shorebirdEnv.macosPodfileLockFile) - .thenReturn(podfileLockFile); + group('when native changes are detected', () { + const nativeChangeDiffStatus = DiffStatus( + hasAssetChanges: false, + hasNativeChanges: true, + ); + + late String podfileLockHash; + + setUp(() { + when( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: any(named: 'localArchive'), + releaseArchive: any(named: 'releaseArchive'), + archiveDiffer: any(named: 'archiveDiffer'), + allowAssetChanges: any(named: 'allowAssetChanges'), + allowNativeChanges: any(named: 'allowNativeChanges'), + confirmNativeChanges: false, + ), + ).thenAnswer((_) async => nativeChangeDiffStatus); + + const podfileLockContents = 'lock file'; + podfileLockHash = + sha256.convert(utf8.encode(podfileLockContents)).toString(); + final podfileLockFile = + File( + p.join( + Directory.systemTemp.createTempSync().path, + 'Podfile.lock', + ), + ) + ..createSync(recursive: true) + ..writeAsStringSync(podfileLockContents); + + when( + () => shorebirdEnv.macosPodfileLockFile, + ).thenReturn(podfileLockFile); + }); + + group('when release has podspec lock hash', () { + group('when release podspec lock hash matches patch', () { + late final releaseArtifact = ReleaseArtifact( + id: 0, + releaseId: 0, + arch: 'aarch64', + platform: ReleasePlatform.macos, + hash: '#', + size: 42, + url: 'https://example.com', + podfileLockHash: podfileLockHash, + canSideload: true, + ); + + test('does not warn of native changes', () async { + final diffStatus = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: releaseArtifact, + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(diffStatus, equals(nativeChangeDiffStatus)); + verifyNever( + () => logger.warn( + '''Your macos/Podfile.lock is different from the one used to build the release.''', + ), + ); + }); }); - group('when release has podspec lock hash', () { - group('when release podspec lock hash matches patch', () { - late final releaseArtifact = ReleaseArtifact( - id: 0, - releaseId: 0, - arch: 'aarch64', - platform: ReleasePlatform.macos, - hash: '#', - size: 42, - url: 'https://example.com', - podfileLockHash: podfileLockHash, - canSideload: true, - ); + group('when release podspec lock hash does not match patch', () { + const releaseArtifact = ReleaseArtifact( + id: 0, + releaseId: 0, + arch: 'aarch64', + platform: ReleasePlatform.macos, + hash: '#', + size: 42, + url: 'https://example.com', + podfileLockHash: 'podfile-lock-hash', + canSideload: true, + ); - test('does not warn of native changes', () async { - final diffStatus = await runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: releaseArtifact, - releaseArchive: File(''), - patchArchive: File(''), - ), - ); - expect(diffStatus, equals(nativeChangeDiffStatus)); - verifyNever( - () => logger.warn( - '''Your macos/Podfile.lock is different from the one used to build the release.''', - ), - ); + group('when native diffs are allowed', () { + setUp(() { + when(() => argResults['allow-native-diffs']).thenReturn(true); }); - }); - group('when release podspec lock hash does not match patch', () { - const releaseArtifact = ReleaseArtifact( - id: 0, - releaseId: 0, - arch: 'aarch64', - platform: ReleasePlatform.macos, - hash: '#', - size: 42, - url: 'https://example.com', - podfileLockHash: 'podfile-lock-hash', - canSideload: true, - ); - - group('when native diffs are allowed', () { - setUp(() { - when(() => argResults['allow-native-diffs']).thenReturn(true); - }); - - test( - 'logs warning, does not prompt for confirmation to proceed', - () async { + test( + 'logs warning, does not prompt for confirmation to proceed', + () async { final diffStatus = await runWithOverrides( () => patcher.assertUnpatchableDiffs( releaseArtifact: releaseArtifact, @@ -416,58 +406,60 @@ This may indicate that the patch contains native changes, which cannot be applie ), ).called(1); verifyNever(() => logger.confirm(any())); - }); - }); + }, + ); + }); - group('when native diffs are not allowed', () { - group('when in an environment that accepts user input', () { + group('when native diffs are not allowed', () { + group('when in an environment that accepts user input', () { + setUp(() { + when(() => shorebirdEnv.canAcceptUserInput).thenReturn(true); + }); + + group('when user opts to continue at prompt', () { setUp(() { - when(() => shorebirdEnv.canAcceptUserInput) - .thenReturn(true); + when(() => logger.confirm(any())).thenReturn(true); }); - group('when user opts to continue at prompt', () { - setUp(() { - when(() => logger.confirm(any())).thenReturn(true); - }); + test('returns diff status from patchDiffChecker', () async { + final diffStatus = await runWithOverrides( + () => patcher.assertUnpatchableDiffs( + releaseArtifact: releaseArtifact, + releaseArchive: File(''), + patchArchive: File(''), + ), + ); + expect(diffStatus, equals(nativeChangeDiffStatus)); + }); + }); - test('returns diff status from patchDiffChecker', () async { - final diffStatus = await runWithOverrides( + group('when user aborts at prompt', () { + setUp(() { + when(() => logger.confirm(any())).thenReturn(false); + }); + + test('throws UserCancelledException', () async { + await expectLater( + () => runWithOverrides( () => patcher.assertUnpatchableDiffs( releaseArtifact: releaseArtifact, releaseArchive: File(''), patchArchive: File(''), ), - ); - expect(diffStatus, equals(nativeChangeDiffStatus)); - }); - }); - - group('when user aborts at prompt', () { - setUp(() { - when(() => logger.confirm(any())).thenReturn(false); - }); - - test('throws UserCancelledException', () async { - await expectLater( - () => runWithOverrides( - () => patcher.assertUnpatchableDiffs( - releaseArtifact: releaseArtifact, - releaseArchive: File(''), - patchArchive: File(''), - ), - ), - throwsA(isA()), - ); - }); + ), + throwsA(isA()), + ); }); }); + }); - group('when in an environment that does not accept user input', - () { + group( + 'when in an environment that does not accept user input', + () { setUp(() { - when(() => shorebirdEnv.canAcceptUserInput) - .thenReturn(false); + when( + () => shorebirdEnv.canAcceptUserInput, + ).thenReturn(false); }); test('throws UnpatchableChangeException', () async { @@ -482,532 +474,538 @@ This may indicate that the patch contains native changes, which cannot be applie throwsA(isA()), ); }); - }); - }); + }, + ); }); }); + }); - group('when release does not have podspec lock hash', () {}); + group('when release does not have podspec lock hash', () {}); + }); + }); + + group('buildPatchArtifact', () { + const flutterVersionAndRevision = '3.27.0 (8495dee1fd)'; + + setUp(() { + when( + () => ditto.archive( + source: any(named: 'source'), + destination: any(named: 'destination'), + ), + ).thenAnswer((invocation) async { + File( + invocation.namedArguments[#destination] as String, + ).createSync(recursive: true); + }); + + when( + () => shorebirdFlutter.getVersionAndRevision(), + ).thenAnswer((_) async => flutterVersionAndRevision); + when( + () => shorebirdFlutter.getVersion(), + ).thenAnswer((_) async => Version(3, 27, 4)); + }); + + group('when specified flutter version is less than minimum', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + when( + () => shorebirdFlutter.getVersion(), + ).thenAnswer((_) async => Version(3, 0, 0)); + }); + + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err(''' +macOS patches are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion. +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), + ).called(1); }); }); - group('buildPatchArtifact', () { - const flutterVersionAndRevision = '3.27.0 (8495dee1fd)'; - + group('when build fails with ProcessException', () { setUp(() { when( - () => ditto.archive( - source: any(named: 'source'), - destination: any(named: 'destination'), + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenThrow( + const ProcessException('flutter', [ + 'build', + 'macos', + ], 'Build failed'), + ); + }); + + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail('Failed to build: Build failed')); + }); + }); + + group('when build fails with ArtifactBuildException', () { + setUp(() { + when( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenThrow(ArtifactBuildException('Build failed')); + }); + + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail('Failed to build macOS app')); + }); + }); + + group('when elf aot snapshot build fails', () { + setUp(() { + when( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer( + (_) async => + MacosBuildResult(kernelFile: File('/path/to/app.dill')), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + ), + ).thenThrow(const FileSystemException('error')); + }); + + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + + verify(() => progress.fail("FileSystemException: error, path = ''")); + }); + }); + + group('when build fails to produce arm64 aot snapshot', () { + setUp(() { + when( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer( + (_) async => + MacosBuildResult(kernelFile: File('/path/to/app.dill')), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), ), ).thenAnswer((invocation) async { - File( - invocation.namedArguments[#destination] as String, - ).createSync(recursive: true); + final file = File( + invocation.namedArguments[#outFilePath] as String, + ); + if (!file.path.contains('arm64')) { + file.createSync(recursive: true); + } + return file; }); + }); + + test('exits with code 70', () async { + await expectLater( + runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + }); + }); + + group('when build fails to produce x64 aot snapshot', () { + setUp(() { + when( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer( + (_) async => + MacosBuildResult(kernelFile: File('/path/to/app.dill')), + ); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), + ), + ).thenAnswer((invocation) async { + final file = File( + invocation.namedArguments[#outFilePath] as String, + ); + if (!file.path.contains('x64')) { + file.createSync(recursive: true); + } + return file; + }); + }); + + test('exits with code 70', () async { + await expectLater( + runWithOverrides(patcher.buildPatchArtifact), + exitsWithCode(ExitCode.software), + ); + }); + }); + + group('when --split-debug-info is specified', () { + late Directory splitDebugInfo; + + setUp(() { + final tempDir = Directory.systemTemp.createTempSync(); + splitDebugInfo = Directory(p.join(tempDir.path, 'debug')); + when( + () => argResults.wasParsed(CommonArguments.splitDebugInfoArg.name), + ).thenReturn(true); + when( + () => argResults[CommonArguments.splitDebugInfoArg.name], + ).thenReturn(splitDebugInfo.path); when( - () => shorebirdFlutter.getVersionAndRevision(), - ).thenAnswer((_) async => flutterVersionAndRevision); + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer( + (_) async => + MacosBuildResult(kernelFile: File('/path/to/app.dill')), + ); when( - () => shorebirdFlutter.getVersion(), - ).thenAnswer((_) async => Version(3, 27, 4)); + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), + ), + ).thenAnswer((invocation) async { + final file = File( + invocation.namedArguments[#outFilePath] as String, + ); + if (!file.path.contains('x64')) { + file.createSync(recursive: true); + } + return file; + }); }); - group( - 'when specified flutter version is less than minimum', - () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), + test('creates the directory', () async { + expect(splitDebugInfo.existsSync(), isFalse); + + try { + await runWithOverrides(patcher.buildPatchArtifact); + } on Exception catch (_) { + // swallow exception + } + + expect(splitDebugInfo.existsSync(), isTrue); + }); + }); + + group('when build succeeds', () { + late File kernelFile; + setUp(() { + kernelFile = File( + p.join(Directory.systemTemp.createTempSync().path, 'app.dill'), + )..createSync(recursive: true); + when( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + base64PublicKey: any(named: 'base64PublicKey'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer((_) async => MacosBuildResult(kernelFile: kernelFile)); + when( + () => artifactBuilder.buildElfAotSnapshot( + appDillPath: any(named: 'appDillPath'), + outFilePath: any(named: 'outFilePath'), + genSnapshotArtifact: any(named: 'genSnapshotArtifact'), + additionalArgs: any(named: 'additionalArgs'), + ), + ).thenAnswer( + (invocation) async => + File(invocation.namedArguments[#outFilePath] as String) + ..createSync(recursive: true), + ); + }); + + group('when flavor is provided', () { + const flavor = 'my-flavor'; + + setUp(() { + patcher = MacosPatcher( + argParser: argParser, + argResults: argResults, + flavor: flavor, + target: null, + ); + + when( + () => artifactManager.getMacOSAppDirectory(flavor: flavor), + ).thenReturn(appDirectory); + }); + + test('builds with flavor', () async { + await runWithOverrides(patcher.buildPatchArtifact); + verify( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: any(named: 'args'), + flavor: flavor, + target: any(named: 'target'), + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); + verify( + () => artifactManager.getMacOSAppDirectory(flavor: flavor), + ).called(1); + }); + }); + + group('when releaseVersion is provided', () { + test('forwards --build-name and --build-number to builder', () async { + await runWithOverrides( + () => patcher.buildPatchArtifact(releaseVersion: '1.2.3+4'), + ); + verify( + () => artifactBuilder.buildMacos( + flavor: any(named: 'flavor'), + codesign: any(named: 'codesign'), + target: any(named: 'target'), + args: any( + named: 'args', + that: containsAll(['--build-name=1.2.3', '--build-number=4']), ), - ).thenAnswer((_) async {}); - when( - () => shorebirdFlutter.getVersion(), - ).thenAnswer((_) async => Version(3, 0, 0)); - }); - - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - ''' -macOS patches are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), - ).called(1); - }); - }, - skip: - '''Skipping while we use the flutter revision when checking for patchability''', - ); - - group('when build fails with ProcessException', () { - setUp(() { - when( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), buildProgress: any(named: 'buildProgress'), ), - ).thenThrow( - const ProcessException( - 'flutter', - ['build', 'macos'], - 'Build failed', - ), - ); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify(() => progress.fail('Failed to build: Build failed')); + ).called(1); }); }); - group('when build fails with ArtifactBuildException', () { + group('when the key pair is provided', () { setUp(() { when( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenThrow( - ArtifactBuildException('Build failed'), - ); + () => codeSigner.base64PublicKey(any()), + ).thenReturn('public_key_encoded'); }); - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - - verify(() => progress.fail('Failed to build macOS app')); - }); - }); - - group('when elf aot snapshot build fails', () { - setUp(() { + test('calls the buildMacos passing the key', () async { when( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => - MacosBuildResult(kernelFile: File('/path/to/app.dill')), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - ), - ).thenThrow(const FileSystemException('error')); - }); + () => argResults.wasParsed(CommonArguments.publicKeyArg.name), + ).thenReturn(true); - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); + final key = createTempFile('public.pem') + ..writeAsStringSync('public_key'); + + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(key.path); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(key.path); + await runWithOverrides(patcher.buildPatchArtifact); verify( - () => progress.fail("FileSystemException: error, path = ''"), - ); - }); - }); - - group('when build fails to produce arm64 aot snapshot', () { - setUp(() { - when( () => artifactBuilder.buildMacos( codesign: any(named: 'codesign'), args: any(named: 'args'), flavor: any(named: 'flavor'), target: any(named: 'target'), + base64PublicKey: 'public_key_encoded', buildProgress: any(named: 'buildProgress'), ), - ).thenAnswer( - (_) async => MacosBuildResult( - kernelFile: File('/path/to/app.dill'), - ), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), - ), - ).thenAnswer((invocation) async { - final file = - File(invocation.namedArguments[#outFilePath] as String); - if (!file.path.contains('arm64')) { - file.createSync(recursive: true); - } - return file; - }); - }); - - test('exits with code 70', () async { - await expectLater( - runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); + ).called(1); }); }); - group('when build fails to produce x64 aot snapshot', () { + group('when platform was specified via arg results rest', () { setUp(() { - when( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => MacosBuildResult( - kernelFile: File('/path/to/app.dill'), - ), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), - ), - ).thenAnswer((invocation) async { - final file = - File(invocation.namedArguments[#outFilePath] as String); - if (!file.path.contains('x64')) { - file.createSync(recursive: true); - } - return file; - }); - }); - - test('exits with code 70', () async { - await expectLater( - runWithOverrides(patcher.buildPatchArtifact), - exitsWithCode(ExitCode.software), - ); - }); - }); - - group('when --split-debug-info is specified', () { - late Directory splitDebugInfo; - - setUp(() { - final tempDir = Directory.systemTemp.createTempSync(); - splitDebugInfo = Directory(p.join(tempDir.path, 'debug')); - when( - () => argResults.wasParsed( - CommonArguments.splitDebugInfoArg.name, - ), - ).thenReturn(true); - when( - () => argResults[CommonArguments.splitDebugInfoArg.name], - ).thenReturn(splitDebugInfo.path); - - when( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => MacosBuildResult( - kernelFile: File('/path/to/app.dill'), - ), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), - ), - ).thenAnswer((invocation) async { - final file = - File(invocation.namedArguments[#outFilePath] as String); - if (!file.path.contains('x64')) { - file.createSync(recursive: true); - } - return file; - }); - }); - - test('creates the directory', () async { - expect(splitDebugInfo.existsSync(), isFalse); - - try { - await runWithOverrides(patcher.buildPatchArtifact); - } on Exception catch (_) { - // swallow exception - } - - expect(splitDebugInfo.existsSync(), isTrue); - }); - }); - - group('when build succeeds', () { - late File kernelFile; - setUp(() { - kernelFile = File( - p.join(Directory.systemTemp.createTempSync().path, 'app.dill'), - )..createSync(recursive: true); - when( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - base64PublicKey: any(named: 'base64PublicKey'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => MacosBuildResult(kernelFile: kernelFile), - ); - when( - () => artifactBuilder.buildElfAotSnapshot( - appDillPath: any(named: 'appDillPath'), - outFilePath: any(named: 'outFilePath'), - genSnapshotArtifact: any(named: 'genSnapshotArtifact'), - additionalArgs: any(named: 'additionalArgs'), - ), - ).thenAnswer( - (invocation) async => - File(invocation.namedArguments[#outFilePath] as String) - ..createSync(recursive: true), - ); - }); - - group('when flavor is provided', () { - const flavor = 'my-flavor'; - - setUp(() { - patcher = MacosPatcher( - argParser: argParser, - argResults: argResults, - flavor: flavor, - target: null, - ); - - when( - () => artifactManager.getMacOSAppDirectory(flavor: flavor), - ).thenReturn(appDirectory); - }); - - test('builds with flavor', () async { - await runWithOverrides(patcher.buildPatchArtifact); - verify( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: flavor, - target: any(named: 'target'), - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - verify( - () => artifactManager.getMacOSAppDirectory(flavor: flavor), - ).called(1); - }); - }); - - group('when releaseVersion is provided', () { - test('forwards --build-name and --build-number to builder', - () async { - await runWithOverrides( - () => patcher.buildPatchArtifact(releaseVersion: '1.2.3+4'), - ); - verify( - () => artifactBuilder.buildMacos( - flavor: any(named: 'flavor'), - codesign: any(named: 'codesign'), - target: any(named: 'target'), - args: any( - named: 'args', - that: containsAll( - ['--build-name=1.2.3', '--build-number=4'], - ), - ), - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); - }); - - group('when the key pair is provided', () { - setUp(() { - when( - () => codeSigner.base64PublicKey(any()), - ).thenReturn('public_key_encoded'); - }); - - test('calls the buildMacos passing the key', () async { - when( - () => argResults.wasParsed(CommonArguments.publicKeyArg.name), - ).thenReturn(true); - - final key = createTempFile('public.pem') - ..writeAsStringSync('public_key'); - - when( - () => argResults[CommonArguments.publicKeyArg.name], - ).thenReturn(key.path); - when( - () => argResults[CommonArguments.publicKeyArg.name], - ).thenReturn(key.path); - await runWithOverrides(patcher.buildPatchArtifact); - - verify( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: any(named: 'args'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - base64PublicKey: 'public_key_encoded', - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); - }); - - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['macos', '--verbose']); - }); - - test('returns app zip', () async { - final artifact = await runWithOverrides( - patcher.buildPatchArtifact, - ); - expect(p.basename(artifact.path), endsWith('.zip')); - verify( - () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - args: ['--verbose'], - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); + when(() => argResults.rest).thenReturn(['macos', '--verbose']); }); test('returns app zip', () async { final artifact = await runWithOverrides(patcher.buildPatchArtifact); expect(p.basename(artifact.path), endsWith('.zip')); - }); - - test('copies app.dill to build directory', () async { - final copiedKernelFile = File( - p.join( - projectRoot.path, - 'build', - 'app.dill', + verify( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + args: ['--verbose'], + buildProgress: any(named: 'buildProgress'), ), - ); - expect(copiedKernelFile.existsSync(), isFalse); - await runWithOverrides(patcher.buildPatchArtifact); - expect(copiedKernelFile.existsSync(), isTrue); + ).called(1); }); }); + + test('returns app zip', () async { + final artifact = await runWithOverrides(patcher.buildPatchArtifact); + expect(p.basename(artifact.path), endsWith('.zip')); + }); + + test('copies app.dill to build directory', () async { + final copiedKernelFile = File( + p.join(projectRoot.path, 'build', 'app.dill'), + ); + expect(copiedKernelFile.existsSync(), isFalse); + await runWithOverrides(patcher.buildPatchArtifact); + expect(copiedKernelFile.existsSync(), isTrue); + }); }); + }); - group('createPatchArtifacts', () { - const appId = 'appId'; - const releaseId = 1; - const arm64ElfAotSnapshotFileName = 'out.arm64.aot'; - const x64ElfAotSnapshotFileName = 'out.x64.aot'; - late File releaseArtifactFile; + group('createPatchArtifacts', () { + const appId = 'appId'; + const releaseId = 1; + const arm64ElfAotSnapshotFileName = 'out.arm64.aot'; + const x64ElfAotSnapshotFileName = 'out.x64.aot'; + late File releaseArtifactFile; - setUp(() { - // This method assumes that the patch artifact has already been built. - File( - p.join(projectRoot.path, 'build', arm64ElfAotSnapshotFileName), - ).createSync(recursive: true); - File( - p.join(projectRoot.path, 'build', x64ElfAotSnapshotFileName), - ).createSync(recursive: true); + setUp(() { + // This method assumes that the patch artifact has already been built. + File( + p.join(projectRoot.path, 'build', arm64ElfAotSnapshotFileName), + ).createSync(recursive: true); + File( + p.join(projectRoot.path, 'build', x64ElfAotSnapshotFileName), + ).createSync(recursive: true); - releaseArtifactFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'release.app', - ), + releaseArtifactFile = File( + p.join(Directory.systemTemp.createTempSync().path, 'release.app'), + )..createSync(recursive: true); + + when( + () => ditto.extract( + source: any(named: 'source'), + destination: any(named: 'destination'), + ), + ).thenAnswer((invocation) async { + final releaseAppDirectory = Directory( + invocation.namedArguments[#destination] as String, )..createSync(recursive: true); - - when( - () => ditto.extract( - source: any(named: 'source'), - destination: any(named: 'destination'), + Directory( + p.join( + releaseAppDirectory.path, + 'Contents', + 'Frameworks', + 'App.framework', + 'App', ), - ).thenAnswer((invocation) async { - final releaseAppDirectory = Directory( - invocation.namedArguments[#destination] as String, - )..createSync(recursive: true); - Directory( - p.join( - releaseAppDirectory.path, - 'Contents', - 'Frameworks', - 'App.framework', - 'App', - ), - ).createSync(recursive: true); - }); - - when( - () => artifactManager.createDiff( - releaseArtifactPath: any(named: 'releaseArtifactPath'), - patchArtifactPath: any(named: 'patchArtifactPath'), - ), - ).thenAnswer((_) async { - final tempDir = Directory.systemTemp.createTempSync(); - final diffPath = p.join(tempDir.path, 'diff'); - File(diffPath) - ..createSync() - ..writeAsStringSync('test'); - return diffPath; - }); - - when(() => engineConfig.localEngine).thenReturn(null); + ).createSync(recursive: true); }); - test('returns artifact bundles for x86_64 and aarch64 archs', () async { + when( + () => artifactManager.createDiff( + releaseArtifactPath: any(named: 'releaseArtifactPath'), + patchArtifactPath: any(named: 'patchArtifactPath'), + ), + ).thenAnswer((_) async { + final tempDir = Directory.systemTemp.createTempSync(); + final diffPath = p.join(tempDir.path, 'diff'); + File(diffPath) + ..createSync() + ..writeAsStringSync('test'); + return diffPath; + }); + + when(() => engineConfig.localEngine).thenReturn(null); + }); + + test('returns artifact bundles for x86_64 and aarch64 archs', () async { + final artifacts = await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ); + + expect(artifacts, hasLength(2)); + expect(artifacts.keys, containsAll([Arch.x86_64, Arch.arm64])); + expect(artifacts[Arch.x86_64]!.hashSignature, isNull); + + verifyNever( + () => codeSigner.sign( + message: any(named: 'message'), + privateKeyPemFile: any(named: 'privateKeyPemFile'), + ), + ); + }); + + group('when generating a signed patch', () { + setUp(() { + when( + () => argResults[CommonArguments.privateKeyArg.name], + ).thenReturn(createTempFile('private.pem').path); + + when( + () => codeSigner.sign( + message: any(named: 'message'), + privateKeyPemFile: any(named: 'privateKeyPemFile'), + ), + ).thenReturn('my-signature'); + }); + + test('returns artifact bundles with non-null hash signature', () async { final artifacts = await runWithOverrides( () => patcher.createPatchArtifacts( appId: appId, @@ -1018,137 +1016,81 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', expect(artifacts, hasLength(2)); expect(artifacts.keys, containsAll([Arch.x86_64, Arch.arm64])); - expect(artifacts[Arch.x86_64]!.hashSignature, isNull); + expect(artifacts[Arch.x86_64]!.hashSignature, 'my-signature'); + }); + }); + }); - verifyNever( - () => codeSigner.sign( - message: any(named: 'message'), - privateKeyPemFile: any(named: 'privateKeyPemFile'), - ), - ); + group('extractReleaseVersionFromArtifact', () { + group('when app directory does not exist', () { + setUp(() { + when(() => artifactManager.getMacOSAppDirectory()).thenReturn(null); }); - group('when generating a signed patch', () { - setUp(() { - when( - () => argResults[CommonArguments.privateKeyArg.name], - ).thenReturn(createTempFile('private.pem').path); - - when( - () => codeSigner.sign( - message: any(named: 'message'), - privateKeyPemFile: any(named: 'privateKeyPemFile'), - ), - ).thenReturn('my-signature'); - }); - - test('returns artifact bundles with non-null hash signature', - () async { - final artifacts = await runWithOverrides( - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: releaseId, - releaseArtifact: releaseArtifactFile, - ), - ); - - expect(artifacts, hasLength(2)); - expect(artifacts.keys, containsAll([Arch.x86_64, Arch.arm64])); - expect(artifacts[Arch.x86_64]!.hashSignature, 'my-signature'); - }); + test('exit with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + exitsWithCode(ExitCode.software), + ); }); }); - group('extractReleaseVersionFromArtifact', () { - group('when app directory does not exist', () { - setUp(() { - when( - () => artifactManager.getMacOSAppDirectory(), - ).thenReturn(null); - }); - - test('exit with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - exitsWithCode(ExitCode.software), - ); - }); - }); - - group('when Info.plist does not exist', () { - setUp(() { - try { - File( - p.join( - appDirectory.path, - 'Contents', - 'Info.plist', - ), - ).deleteSync(recursive: true); - } on Exception { - // ignore - } - }); - - test('exit with code 70', () async { - await expectLater( - () => runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - exitsWithCode(ExitCode.software), - ); - }); - }); - - group('when empty Info.plist does exist', () { - setUp(() { + group('when Info.plist does not exist', () { + setUp(() { + try { File( - p.join( - appDirectory.path, - 'Contents', - 'Info.plist', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync(''' + p.join(appDirectory.path, 'Contents', 'Info.plist'), + ).deleteSync(recursive: true); + } on Exception { + // ignore + } + }); + + test('exit with code 70', () async { + await expectLater( + () => runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + exitsWithCode(ExitCode.software), + ); + }); + }); + + group('when empty Info.plist does exist', () { + setUp(() { + File(p.join(appDirectory.path, 'Contents', 'Info.plist')) + ..createSync(recursive: true) + ..writeAsStringSync(''' '''); - }); - - test('exits with code 70 and logs error', () async { - await expectLater( - runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - exitsWithCode(ExitCode.software), - ); - verify( - () => logger.err( - any( - that: startsWith('Failed to determine release version'), - ), - ), - ).called(1); - }); }); - group('when Info.plist does exist', () { - setUp(() { - File( - p.join( - appDirectory.path, - 'Contents', - 'Info.plist', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync(''' + test('exits with code 70 and logs error', () async { + await expectLater( + runWithOverrides( + () => patcher.extractReleaseVersionFromArtifact(File('')), + ), + exitsWithCode(ExitCode.software), + ); + verify( + () => logger.err( + any(that: startsWith('Failed to determine release version')), + ), + ).called(1); + }); + }); + + group('when Info.plist does exist', () { + setUp(() { + File(p.join(appDirectory.path, 'Contents', 'Info.plist')) + ..createSync(recursive: true) + ..writeAsStringSync(''' @@ -1177,74 +1119,68 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', '''); - }); - - test('returns correct version', () async { - await expectLater( - runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact(File('')), - ), - completion('1.2.3+1'), - ); - }); - }); - }); - - group('updatedCreatePatchMetadata', () { - const allowAssetDiffs = false; - const allowNativeDiffs = true; - const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; - const operatingSystem = 'Mac OS X'; - const operatingSystemVersion = '10.15.7'; - const xcodeVersion = '11'; - - setUp(() { - when( - () => xcodeBuild.version(), - ).thenAnswer((_) async => xcodeVersion); }); - test('returns correct metadata', () async { - const metadata = CreatePatchMetadata( - releasePlatform: ReleasePlatform.macos, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: true, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: false, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ); - - expect( + test('returns correct version', () async { + await expectLater( runWithOverrides( - () => patcher.updatedCreatePatchMetadata(metadata), - ), - completion( - const CreatePatchMetadata( - releasePlatform: ReleasePlatform.macos, - usedIgnoreAssetChangesFlag: allowAssetDiffs, - hasAssetChanges: true, - usedIgnoreNativeChangesFlag: allowNativeDiffs, - hasNativeChanges: false, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - xcodeVersion: xcodeVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - ), - ), + () => patcher.extractReleaseVersionFromArtifact(File('')), ), + completion('1.2.3+1'), ); }); }); - }, - testOn: 'mac-os', - ); + }); + + group('updatedCreatePatchMetadata', () { + const allowAssetDiffs = false; + const allowNativeDiffs = true; + const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; + const operatingSystem = 'Mac OS X'; + const operatingSystemVersion = '10.15.7'; + const xcodeVersion = '11'; + + setUp(() { + when(() => xcodeBuild.version()).thenAnswer((_) async => xcodeVersion); + }); + + test('returns correct metadata', () async { + const metadata = CreatePatchMetadata( + releasePlatform: ReleasePlatform.macos, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: true, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: false, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); + + expect( + runWithOverrides(() => patcher.updatedCreatePatchMetadata(metadata)), + completion( + const CreatePatchMetadata( + releasePlatform: ReleasePlatform.macos, + usedIgnoreAssetChangesFlag: allowAssetDiffs, + hasAssetChanges: true, + usedIgnoreNativeChangesFlag: allowNativeDiffs, + hasNativeChanges: false, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + xcodeVersion: xcodeVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ), + ), + ); + }); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart index 5a81eea2..1c9d54b6 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart @@ -318,9 +318,8 @@ void main() { () => shorebirdFlutter.getVersionAndRevision(), ).thenAnswer((_) async => flutterRevision); when( - () => shorebirdFlutter.installRevision( - revision: any(named: 'revision'), - ), + () => + shorebirdFlutter.installRevision(revision: any(named: 'revision')), ).thenAnswer((_) async => {}); when( @@ -344,18 +343,19 @@ void main() { }); test( - '''warns that staging flag will be deprecated and exits with usage code''', - () async { - await expectLater( - runWithOverrides(command.run), - completion(equals(ExitCode.usage.code)), - ); - verify( - () => logger.err( - '''The --staging flag is deprecated and will be removed in a future release. Use --track=staging instead.''', - ), - ).called(1); - }); + '''warns that staging flag will be deprecated and exits with usage code''', + () async { + await expectLater( + runWithOverrides(command.run), + completion(equals(ExitCode.usage.code)), + ); + verify( + () => logger.err( + '''The --staging flag is deprecated and will be removed in a future release. Use --track=staging instead.''', + ), + ).called(1); + }, + ); }); }); @@ -411,28 +411,27 @@ void main() { }); }); - group( - 'when given existing private and public key files', - () { - test('is valid', () async { - when( - () => argResults.wasParsed(CommonArguments.privateKeyArg.name), - ).thenReturn(true); - when( - () => argResults.wasParsed(CommonArguments.publicKeyArg.name), - ).thenReturn(true); - when(() => argResults[CommonArguments.privateKeyArg.name]) - .thenReturn(createTempFile('private.pem').path); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn(createTempFile('public.pem').path); + group('when given existing private and public key files', () { + test('is valid', () async { + when( + () => argResults.wasParsed(CommonArguments.privateKeyArg.name), + ).thenReturn(true); + when( + () => argResults.wasParsed(CommonArguments.publicKeyArg.name), + ).thenReturn(true); + when( + () => argResults[CommonArguments.privateKeyArg.name], + ).thenReturn(createTempFile('private.pem').path); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(createTempFile('public.pem').path); - await expectLater( - runWithOverrides(() => command.createPatch(patcher)), - completes, - ); - }); - }, - ); + await expectLater( + runWithOverrides(() => command.createPatch(patcher)), + completes, + ); + }); + }); group( 'when given an existing private key and nonexistent public key', @@ -461,30 +460,32 @@ void main() { }, ); - group('when given an existing public key and nonexistent private key', - () { - test('fails and logs the err', () async { - when( - () => argResults.wasParsed(CommonArguments.privateKeyArg.name), - ).thenReturn(false); - when( - () => argResults.wasParsed(CommonArguments.publicKeyArg.name), - ).thenReturn(true); - when( - () => argResults[CommonArguments.publicKeyArg.name], - ).thenReturn(createTempFile('public.pem').path); + group( + 'when given an existing public key and nonexistent private key', + () { + test('fails and logs the err', () async { + when( + () => argResults.wasParsed(CommonArguments.privateKeyArg.name), + ).thenReturn(false); + when( + () => argResults.wasParsed(CommonArguments.publicKeyArg.name), + ).thenReturn(true); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(createTempFile('public.pem').path); - await expectLater( - runWithOverrides(() => command.createPatch(patcher)), - exitsWithCode(ExitCode.usage), - ); - verify( - () => logger.err( - 'Both public and private keys must be provided.', - ), - ).called(1); - }); - }); + await expectLater( + runWithOverrides(() => command.createPatch(patcher)), + exitsWithCode(ExitCode.usage), + ); + verify( + () => logger.err( + 'Both public and private keys must be provided.', + ), + ).called(1); + }); + }, + ); group('when a supplemental release artifact exists', () { setUp(() { @@ -544,34 +545,16 @@ void main() { group('getPatcher', () { test('maps the correct platform to the patcher', () async { - expect( - command.getPatcher(ReleaseType.aar), - isA(), - ); - expect( - command.getPatcher(ReleaseType.android), - isA(), - ); - expect( - command.getPatcher(ReleaseType.ios), - isA(), - ); + expect(command.getPatcher(ReleaseType.aar), isA()); + expect(command.getPatcher(ReleaseType.android), isA()); + expect(command.getPatcher(ReleaseType.ios), isA()); expect( command.getPatcher(ReleaseType.iosFramework), isA(), ); - expect( - command.getPatcher(ReleaseType.linux), - isA(), - ); - expect( - command.getPatcher(ReleaseType.macos), - isA(), - ); - expect( - command.getPatcher(ReleaseType.windows), - isA(), - ); + expect(command.getPatcher(ReleaseType.linux), isA()); + expect(command.getPatcher(ReleaseType.macos), isA()); + expect(command.getPatcher(ReleaseType.windows), isA()); }); }); @@ -602,9 +585,7 @@ void main() { completes, ); verify( - () => logger.info( - any(that: contains(expectedSummary.join('\n'))), - ), + () => logger.info(any(that: contains(expectedSummary.join('\n')))), ).called(1); }); }); @@ -639,9 +620,7 @@ void main() { completes, ); verify( - () => logger.info( - any(that: contains(expectedSummary.join('\n'))), - ), + () => logger.info(any(that: contains(expectedSummary.join('\n')))), ).called(1); }); }); @@ -672,9 +651,7 @@ void main() { completes, ); verify( - () => logger.info( - any(that: contains(expectedSummary.join('\n'))), - ), + () => logger.info(any(that: contains(expectedSummary.join('\n')))), ).called(1); }); }); @@ -712,9 +689,7 @@ void main() { completes, ); verify( - () => logger.info( - any(that: contains(expectedSummary.join('\n'))), - ), + () => logger.info(any(that: contains(expectedSummary.join('\n')))), ).called(1); }); @@ -817,35 +792,35 @@ void main() { () => cache.updateAll(), () => codePushClientWrapper.getApp(appId: appId), () => codePushClientWrapper.getRelease( - appId: appId, - releaseVersion: releaseVersion, - ), + appId: appId, + releaseVersion: releaseVersion, + ), () => codePushClientWrapper.getReleaseArtifact( - appId: appId, - releaseId: release.id, - arch: patcher.primaryReleaseArtifactArch, - platform: releasePlatform, - ), + appId: appId, + releaseId: release.id, + arch: patcher.primaryReleaseArtifactArch, + platform: releasePlatform, + ), () => patcher.buildPatchArtifact(releaseVersion: releaseVersion), () => patcher.assertUnpatchableDiffs( - releaseArtifact: any(named: 'releaseArtifact'), - releaseArchive: any(named: 'releaseArchive'), - patchArchive: any(named: 'patchArchive'), - ), + releaseArtifact: any(named: 'releaseArtifact'), + releaseArchive: any(named: 'releaseArchive'), + patchArchive: any(named: 'patchArchive'), + ), () => patcher.createPatchArtifacts( - appId: appId, - releaseId: release.id, - releaseArtifact: any(named: 'releaseArtifact'), - ), + appId: appId, + releaseId: release.id, + releaseArtifact: any(named: 'releaseArtifact'), + ), () => logger.confirm('Would you like to continue?'), () => patcher.updatedCreatePatchMetadata(any()), () => patcher.uploadPatchArtifacts( - appId: appId, - releaseId: release.id, - metadata: patchMetadata.toJson(), - artifacts: any(named: 'artifacts'), - track: DeploymentTrack.stable, - ), + appId: appId, + releaseId: release.id, + metadata: patchMetadata.toJson(), + artifacts: any(named: 'artifacts'), + track: DeploymentTrack.stable, + ), ]); }); }); @@ -908,9 +883,7 @@ void main() { flutterRevision: flutterRevision, flutterVersion: flutterVersion, displayName: releaseVersion, - platformStatuses: { - releasePlatform: ReleaseStatus.active, - }, + platformStatuses: {releasePlatform: ReleaseStatus.active}, createdAt: DateTime(2024), updatedAt: DateTime(2024), ), @@ -921,9 +894,7 @@ void main() { flutterRevision: flutterRevision, flutterVersion: flutterVersion, displayName: '99.99.99+99', - platformStatuses: { - releasePlatform: ReleaseStatus.active, - }, + platformStatuses: {releasePlatform: ReleaseStatus.active}, createdAt: DateTime(2023), updatedAt: DateTime(2023), ), @@ -948,58 +919,58 @@ void main() { when(() => argResults.wasParsed('release-version')).thenReturn(false); }); - test('executes commands in order, prompts to determine release version', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.success.code)); + test( + 'executes commands in order, prompts to determine release version', + () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.success.code)); - final verificationResult = verifyInOrder([ - () => patcher.assertPreconditions(), - () => patcher.assertArgsAreValid(), - () => shorebirdValidator.validateFlavors(flavorArg: null), - () => cache.updateAll(), - () => codePushClientWrapper.getApp(appId: appId), - () => codePushClientWrapper.getReleases(appId: appId), - () => logger.chooseOne( - 'Which release would you like to patch?', - choices: any(named: 'choices'), - display: captureAny(named: 'display'), - ), - () => codePushClientWrapper.getReleaseArtifact( - appId: appId, - releaseId: release.id, - arch: patcher.primaryReleaseArtifactArch, - platform: releasePlatform, - ), - () => patcher.assertUnpatchableDiffs( - releaseArtifact: any(named: 'releaseArtifact'), - releaseArchive: any(named: 'releaseArchive'), - patchArchive: any(named: 'patchArchive'), - ), - () => patcher.createPatchArtifacts( - appId: appId, - releaseId: release.id, - releaseArtifact: any(named: 'releaseArtifact'), - ), - () => logger.confirm('Would you like to continue?'), - () => patcher.uploadPatchArtifacts( - appId: appId, - releaseId: release.id, - metadata: any(named: 'metadata'), - artifacts: any(named: 'artifacts'), - track: DeploymentTrack.stable, - ), - ]); + final verificationResult = verifyInOrder([ + () => patcher.assertPreconditions(), + () => patcher.assertArgsAreValid(), + () => shorebirdValidator.validateFlavors(flavorArg: null), + () => cache.updateAll(), + () => codePushClientWrapper.getApp(appId: appId), + () => codePushClientWrapper.getReleases(appId: appId), + () => logger.chooseOne( + 'Which release would you like to patch?', + choices: any(named: 'choices'), + display: captureAny(named: 'display'), + ), + () => codePushClientWrapper.getReleaseArtifact( + appId: appId, + releaseId: release.id, + arch: patcher.primaryReleaseArtifactArch, + platform: releasePlatform, + ), + () => patcher.assertUnpatchableDiffs( + releaseArtifact: any(named: 'releaseArtifact'), + releaseArchive: any(named: 'releaseArchive'), + patchArchive: any(named: 'patchArchive'), + ), + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: release.id, + releaseArtifact: any(named: 'releaseArtifact'), + ), + () => logger.confirm('Would you like to continue?'), + () => patcher.uploadPatchArtifacts( + appId: appId, + releaseId: release.id, + metadata: any(named: 'metadata'), + artifacts: any(named: 'artifacts'), + track: DeploymentTrack.stable, + ), + ]); - // Verify that the logger.chooseOne display function is correct - final displayFunctionCapture = verificationResult.captured.flattened - .whereType() - .first; - expect( - displayFunctionCapture(release), - equals(release.version), - ); - }); + // Verify that the logger.chooseOne display function is correct + final displayFunctionCapture = + verificationResult.captured.flattened + .whereType() + .first; + expect(displayFunctionCapture(release), equals(release.version)); + }, + ); group('when prompting for releases, but there is none', () { setUp(() { @@ -1035,9 +1006,7 @@ void main() { flutterRevision: flutterRevision, flutterVersion: flutterVersion, displayName: releaseVersion, - platformStatuses: { - releasePlatform: ReleaseStatus.active, - }, + platformStatuses: {releasePlatform: ReleaseStatus.active}, createdAt: DateTime(2023), updatedAt: DateTime(2023), ), @@ -1059,17 +1028,18 @@ void main() { ); }); - test( - 'only lists and uses releases ' + test('only lists and uses releases ' 'for the specified platform', () async { await expectLater(runWithOverrides(command.run), completes); - final captured = verify( - () => logger.chooseOne( - any(), - choices: captureAny(named: 'choices'), - display: any(named: 'display'), - ), - ).captured.single as List; + final captured = + verify( + () => logger.chooseOne( + any(), + choices: captureAny(named: 'choices'), + display: any(named: 'display'), + ), + ).captured.single + as List; expect(captured.length, equals(1)); expect(captured.first.version, equals(releaseVersion)); @@ -1109,39 +1079,44 @@ void main() { ); }); - test('builds app twice if release flutter version is not default', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.success.code)); + test( + 'builds app twice if release flutter version is not default', + () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.success.code)); - verifyInOrder([ - () => logger.info( - '''Tip: make your patches build faster by specifying --release-version''', - ), - () => patcher.buildPatchArtifact(), - () => patcher.extractReleaseVersionFromArtifact(any()), - () => shorebirdFlutter.installRevision( - revision: releaseFlutterRevision, - ), - () => shorebirdEnv.copyWith( - flutterRevisionOverride: releaseFlutterRevision, - ), - () => patcher.buildPatchArtifact(releaseVersion: releaseVersion), - ]); - }); + verifyInOrder([ + () => logger.info( + '''Tip: make your patches build faster by specifying --release-version''', + ), + () => patcher.buildPatchArtifact(), + () => patcher.extractReleaseVersionFromArtifact(any()), + () => shorebirdFlutter.installRevision( + revision: releaseFlutterRevision, + ), + () => shorebirdEnv.copyWith( + flutterRevisionOverride: releaseFlutterRevision, + ), + () => + patcher.buildPatchArtifact(releaseVersion: releaseVersion), + ]); + }, + ); - test('updates cache with both default and release Flutter revisions', - () async { - await runWithOverrides(command.run); + test( + 'updates cache with both default and release Flutter revisions', + () async { + await runWithOverrides(command.run); - verifyInOrder([ - cache.updateAll, - () => shorebirdEnv.copyWith( - flutterRevisionOverride: releaseFlutterRevision, - ), - cache.updateAll, - ]); - }); + verifyInOrder([ + cache.updateAll, + () => shorebirdEnv.copyWith( + flutterRevisionOverride: releaseFlutterRevision, + ), + cache.updateAll, + ]); + }, + ); }); }); }); @@ -1237,11 +1212,9 @@ void main() { ); verify( - () => logger.err( - ''' + () => logger.err(''' Release ${release.version} is in an incomplete state. It's possible that the original release was terminated or failed to complete. -Please re-run the release command for this version or create a new release.''', - ), +Please re-run the release command for this version or create a new release.'''), ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/commands/patch/patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/patcher_test.dart index 84cdffc5..51f41f58 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patcher_test.dart @@ -184,8 +184,7 @@ void main() { }); group('uploadPatchArtifacts', () { - test( - 'calls codePushClientWrapper.publishPatch ' + test('calls codePushClientWrapper.publishPatch ' 'with correct args', () async { final args = MockArgResults(); final patcher = _TestPatcher( diff --git a/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart index 294d97f8..39f0a4ec 100644 --- a/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart @@ -156,9 +156,7 @@ void main() { setUp(() { when( () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), checkShorebirdInitialized: any( named: 'checkShorebirdInitialized', ), @@ -183,15 +181,11 @@ void main() { final exception = ValidationFailedException(); when( () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), checkShorebirdInitialized: any( named: 'checkShorebirdInitialized', ), - validators: any( - named: 'validators', - ), + validators: any(named: 'validators'), ), ).thenThrow(exception); }); @@ -200,9 +194,7 @@ void main() { final exception = ValidationFailedException(); when( () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), checkShorebirdInitialized: any( named: 'checkShorebirdInitialized', ), @@ -301,9 +293,7 @@ void main() { test('exits with software error code', () async { expect( - () => runWithOverrides( - () => patcher.buildPatchArtifact(), - ), + () => runWithOverrides(() => patcher.buildPatchArtifact()), throwsA( isA().having((e) => e.exitCode, 'exitCode', 70), ), @@ -330,9 +320,7 @@ void main() { test('returns a zipped exe file', () async { await expectLater( - runWithOverrides( - () => patcher.buildPatchArtifact(), - ), + runWithOverrides(() => patcher.buildPatchArtifact()), completion( isA().having((f) => f.path, 'path', endsWith('.zip')), ), @@ -369,13 +357,8 @@ void main() { ), )..createSync(recursive: true); - patchArtifact = File( - p.join( - releaseDirectory.path, - 'data', - 'app.so', - ), - )..createSync(recursive: true); + patchArtifact = File(p.join(releaseDirectory.path, 'data', 'app.so')) + ..createSync(recursive: true); when( () => artifactManager.getWindowsReleaseDirectory(), @@ -386,8 +369,9 @@ void main() { outputDirectory: any(named: 'outputDirectory'), ), ).thenAnswer((invocation) async { - (invocation.namedArguments[#outputDirectory] as Directory) - .createSync(recursive: true); + (invocation.namedArguments[#outputDirectory] as Directory).createSync( + recursive: true, + ); }); }); @@ -462,10 +446,12 @@ void main() { patchArtifactPath: any(named: 'patchArtifactPath'), ), ).thenAnswer((_) async => diffFile.path); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn('public-key.pem'); - when(() => argResults[CommonArguments.privateKeyArg.name]) - .thenReturn('private-key.pem'); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn('public-key.pem'); + when( + () => argResults[CommonArguments.privateKeyArg.name], + ).thenReturn('private-key.pem'); when( () => codeSigner.sign( message: any(named: 'message'), @@ -511,8 +497,9 @@ void main() { ).thenAnswer((invocation) async { final outPath = (invocation.namedArguments[#outputDirectory] as Directory).path; - File(p.join(outPath, 'hello_windows.exe')) - .createSync(recursive: true); + File( + p.join(outPath, 'hello_windows.exe'), + ).createSync(recursive: true); }); when( () => powershell.getExeVersionString(any()), @@ -521,9 +508,7 @@ void main() { test('returns version from archived exe', () async { final version = await runWithOverrides( - () => patcher.extractReleaseVersionFromArtifact( - File(''), - ), + () => patcher.extractReleaseVersionFromArtifact(File('')), ); expect(version, '1.2.3'); diff --git a/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart b/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart index d42659d3..375c4e8c 100644 --- a/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart @@ -147,12 +147,14 @@ void main() { ).thenAnswer((_) async => [prodPatch]); }); - test('tells user patch is already in prod, exits with usage code', - () async { - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.usage.code)); - verify(() => logger.err('Patch 1 is already live')).called(1); - }); + test( + 'tells user patch is already in prod, exits with usage code', + () async { + final result = await runWithOverrides(command.run); + expect(result, equals(ExitCode.usage.code)); + verify(() => logger.err('Patch 1 is already live')).called(1); + }, + ); }); group('when app has no stable channel', () { @@ -182,8 +184,9 @@ void main() { channel: stableChannel, ), ); - verify(() => logger.success('Patch 1 is now live for release 1.0.0!')) - .called(1); + verify( + () => logger.success('Patch 1 is now live for release 1.0.0!'), + ).called(1); }); }); }); diff --git a/packages/shorebird_cli/test/src/commands/preview_command_test.dart b/packages/shorebird_cli/test/src/commands/preview_command_test.dart index 69fd4ff5..3c000fb0 100644 --- a/packages/shorebird_cli/test/src/commands/preview_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/preview_command_test.dart @@ -92,28 +92,29 @@ void main() { ); } - File setupIOSShorebirdYaml() => File( - p.join( - iosRunnerPath(), - 'Frameworks', - 'App.framework', - 'flutter_assets', - 'shorebird.yaml', - ), - ) + File setupIOSShorebirdYaml() => + File( + p.join( + iosRunnerPath(), + 'Frameworks', + 'App.framework', + 'flutter_assets', + 'shorebird.yaml', + ), + ) ..createSync(recursive: true) ..writeAsStringSync('app_id: $appId', flush: true); Future setupAndroidShorebirdYaml(Invocation invocation) async { File( - p.join( - (invocation.namedArguments[#outputDirectory] as Directory).path, - 'base', - 'assets', - 'flutter_assets', - 'shorebird.yaml', - ), - ) + p.join( + (invocation.namedArguments[#outputDirectory] as Directory).path, + 'base', + 'assets', + 'flutter_assets', + 'shorebird.yaml', + ), + ) ..createSync(recursive: true) ..writeAsStringSync('app_id: $appId', flush: true); } @@ -122,19 +123,20 @@ void main() { final tempDir = Directory.systemTemp.createTempSync(); final aabDirectory = Directory(p.join(tempDir.path, 'app-release')) ..createSync(recursive: true); - final yamlContents = [ - 'app_id: $appId\n', - if (channel != null) 'channel: $channel\n', - ].join(); + final yamlContents = + [ + 'app_id: $appId\n', + if (channel != null) 'channel: $channel\n', + ].join(); File( - p.join( - aabDirectory.path, - 'base', - 'assets', - 'flutter_assets', - 'shorebird.yaml', - ), - ) + p.join( + aabDirectory.path, + 'base', + 'assets', + 'flutter_assets', + 'shorebird.yaml', + ), + ) ..createSync(recursive: true) ..writeAsStringSync(yamlContents); @@ -310,11 +312,7 @@ void main() { () => runWithOverrides(command.run), throwsA(exception), ); - verify( - () => codePushClientWrapper.getReleases( - appId: appId, - ), - ).called(1); + verify(() => codePushClientWrapper.getReleases(appId: appId)).called(1); }); }); @@ -367,9 +365,9 @@ void main() { when(() => platform.isMacOS).thenReturn(false); when(() => platform.isWindows).thenReturn(true); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.ios: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.ios: ReleaseStatus.active}); }); test('prints error message and exits with code 70', () async { @@ -485,206 +483,208 @@ void main() { // This should probably be outside of the android group, but because // we want to verify that android-specific behavior is used, here it is. - group('when release has a mix of supported and unsupported platforms', - () { - setUp(() { - when(() => platform.isLinux).thenReturn(false); - when(() => platform.isMacOS).thenReturn(false); - when(() => platform.isWindows).thenReturn(true); - - when( - () => artifactManager.extractZip( - zipFile: any(named: 'zipFile'), - outputDirectory: any(named: 'outputDirectory'), - ), - ).thenAnswer(setupAndroidShorebirdYaml); - - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.ios: ReleaseStatus.active, - ReleasePlatform.android: ReleaseStatus.active, - }); - }); - - test('outputs apks path', () async { - await runWithOverrides(command.run); - final apksLink = link(uri: Uri.parse(apksPath())); - verify( - () => progress.complete('Built apks: ${cyan.wrap(apksLink)}'), - ).called(1); - }); - - test('does not prompt for platform, uses android', () async { - await runWithOverrides(command.run); - - verify(() => bundletool.installApks(apks: apksPath())).called(1); - verifyNever( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - display: any(named: 'display'), - ), - ); - }); - - group('when valid keystore is specified', () { - const keystore = 'keystore.jks'; - const keystorePassword = 'pass:keystorePassword'; - const keyPassword = 'pass:keyPassword'; - const keyAlias = 'keyAlias'; - + group( + 'when release has a mix of supported and unsupported platforms', + () { setUp(() { - when(() => argResults['ks']).thenReturn(keystore); - when(() => argResults['ks-pass']).thenReturn(keystorePassword); - when(() => argResults['ks-key-pass']).thenReturn(keyPassword); - when(() => argResults['ks-key-alias']).thenReturn(keyAlias); + when(() => platform.isLinux).thenReturn(false); + when(() => platform.isMacOS).thenReturn(false); + when(() => platform.isWindows).thenReturn(true); + + when( + () => artifactManager.extractZip( + zipFile: any(named: 'zipFile'), + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer(setupAndroidShorebirdYaml); + + when(() => release.platformStatuses).thenReturn({ + ReleasePlatform.ios: ReleaseStatus.active, + ReleasePlatform.android: ReleaseStatus.active, + }); }); - test('builds apks with keystore', () async { + test('outputs apks path', () async { + await runWithOverrides(command.run); + final apksLink = link(uri: Uri.parse(apksPath())); + verify( + () => progress.complete('Built apks: ${cyan.wrap(apksLink)}'), + ).called(1); + }); + + test('does not prompt for platform, uses android', () async { await runWithOverrides(command.run); - verify( - () => bundletool.buildApks( - bundle: aabPath(), - output: apksPath(), - keystore: keystore, - keystorePassword: keystorePassword, - keyPassword: keyPassword, - keyAlias: keyAlias, - ), - ).called(1); - }); - }); - - group('when keystorePassword is missing', () { - const keystore = 'keystore.jks'; - - setUp(() { - when(() => argResults['ks']).thenReturn(keystore); - }); - - test('exits with usage error', () async { - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.usage.code)); - - verify( - () => logger.err('You must provide a keystore password.'), - ).called(1); - + verify(() => bundletool.installApks(apks: apksPath())).called(1); verifyNever( - () => bundletool.buildApks( - bundle: aabPath(), - output: apksPath(), - keystore: any(named: 'keystore'), - keystorePassword: any(named: 'keystorePassword'), - keyPassword: any(named: 'keyPassword'), - keyAlias: any(named: 'keyAlias'), + () => logger.chooseOne( + any(), + choices: any(named: 'choices'), + display: any(named: 'display'), ), ); }); - }); - group('when keyAlias is missing', () { - const keystore = 'keystore.jks'; - const keystorePassword = 'keystorePassword'; + group('when valid keystore is specified', () { + const keystore = 'keystore.jks'; + const keystorePassword = 'pass:keystorePassword'; + const keyPassword = 'pass:keyPassword'; + const keyAlias = 'keyAlias'; - setUp(() { - when(() => argResults['ks']).thenReturn(keystore); - when(() => argResults['ks-pass']).thenReturn(keystorePassword); + setUp(() { + when(() => argResults['ks']).thenReturn(keystore); + when(() => argResults['ks-pass']).thenReturn(keystorePassword); + when(() => argResults['ks-key-pass']).thenReturn(keyPassword); + when(() => argResults['ks-key-alias']).thenReturn(keyAlias); + }); + + test('builds apks with keystore', () async { + await runWithOverrides(command.run); + + verify( + () => bundletool.buildApks( + bundle: aabPath(), + output: apksPath(), + keystore: keystore, + keystorePassword: keystorePassword, + keyPassword: keyPassword, + keyAlias: keyAlias, + ), + ).called(1); + }); }); - test('exits with usage error', () async { - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.usage.code)); + group('when keystorePassword is missing', () { + const keystore = 'keystore.jks'; - verify( - () => logger.err('You must provide a key alias.'), - ).called(1); + setUp(() { + when(() => argResults['ks']).thenReturn(keystore); + }); - verifyNever( - () => bundletool.buildApks( - bundle: aabPath(), - output: apksPath(), - keystore: any(named: 'keystore'), - keystorePassword: any(named: 'keystorePassword'), - keyPassword: any(named: 'keyPassword'), - keyAlias: any(named: 'keyAlias'), - ), - ); - }); - }); + test('exits with usage error', () async { + final result = await runWithOverrides(command.run); + expect(result, equals(ExitCode.usage.code)); - group('when keystorePassword is invalid', () { - const keystore = 'keystore.jks'; - const keystorePassword = 'keystorePassword'; - const keyPassword = 'pass:keyPassword'; - const keyAlias = 'keyAlias'; + verify( + () => logger.err('You must provide a keystore password.'), + ).called(1); - setUp(() { - when(() => argResults['ks']).thenReturn(keystore); - when(() => argResults['ks-pass']).thenReturn(keystorePassword); - when(() => argResults['ks-key-pass']).thenReturn(keyPassword); - when(() => argResults['ks-key-alias']).thenReturn(keyAlias); + verifyNever( + () => bundletool.buildApks( + bundle: aabPath(), + output: apksPath(), + keystore: any(named: 'keystore'), + keystorePassword: any(named: 'keystorePassword'), + keyPassword: any(named: 'keyPassword'), + keyAlias: any(named: 'keyAlias'), + ), + ); + }); }); - test('exits with usage error', () async { - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.usage.code)); + group('when keyAlias is missing', () { + const keystore = 'keystore.jks'; + const keystorePassword = 'keystorePassword'; - verify( - () => logger.err( - 'Keystore password must start with "pass:" or "file:".', - ), - ).called(1); + setUp(() { + when(() => argResults['ks']).thenReturn(keystore); + when(() => argResults['ks-pass']).thenReturn(keystorePassword); + }); - verifyNever( - () => bundletool.buildApks( - bundle: aabPath(), - output: apksPath(), - keystore: any(named: 'keystore'), - keystorePassword: any(named: 'keystorePassword'), - keyPassword: any(named: 'keyPassword'), - keyAlias: any(named: 'keyAlias'), - ), - ); - }); - }); + test('exits with usage error', () async { + final result = await runWithOverrides(command.run); + expect(result, equals(ExitCode.usage.code)); - group('when keyPassword is invalid', () { - const keystore = 'keystore.jks'; - const keystorePassword = 'file:keystorePasswordFile'; - const keyPassword = 'keyPassword'; - const keyAlias = 'keyAlias'; + verify( + () => logger.err('You must provide a key alias.'), + ).called(1); - setUp(() { - when(() => argResults['ks']).thenReturn(keystore); - when(() => argResults['ks-pass']).thenReturn(keystorePassword); - when(() => argResults['ks-key-pass']).thenReturn(keyPassword); - when(() => argResults['ks-key-alias']).thenReturn(keyAlias); + verifyNever( + () => bundletool.buildApks( + bundle: aabPath(), + output: apksPath(), + keystore: any(named: 'keystore'), + keystorePassword: any(named: 'keystorePassword'), + keyPassword: any(named: 'keyPassword'), + keyAlias: any(named: 'keyAlias'), + ), + ); + }); }); - test('exits with usage error', () async { - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.usage.code)); + group('when keystorePassword is invalid', () { + const keystore = 'keystore.jks'; + const keystorePassword = 'keystorePassword'; + const keyPassword = 'pass:keyPassword'; + const keyAlias = 'keyAlias'; - verify( - () => logger.err( - 'Key password must start with "pass:" or "file:".', - ), - ).called(1); + setUp(() { + when(() => argResults['ks']).thenReturn(keystore); + when(() => argResults['ks-pass']).thenReturn(keystorePassword); + when(() => argResults['ks-key-pass']).thenReturn(keyPassword); + when(() => argResults['ks-key-alias']).thenReturn(keyAlias); + }); - verifyNever( - () => bundletool.buildApks( - bundle: aabPath(), - output: apksPath(), - keystore: any(named: 'keystore'), - keystorePassword: any(named: 'keystorePassword'), - keyPassword: any(named: 'keyPassword'), - keyAlias: any(named: 'keyAlias'), - ), - ); + test('exits with usage error', () async { + final result = await runWithOverrides(command.run); + expect(result, equals(ExitCode.usage.code)); + + verify( + () => logger.err( + 'Keystore password must start with "pass:" or "file:".', + ), + ).called(1); + + verifyNever( + () => bundletool.buildApks( + bundle: aabPath(), + output: apksPath(), + keystore: any(named: 'keystore'), + keystorePassword: any(named: 'keystorePassword'), + keyPassword: any(named: 'keyPassword'), + keyAlias: any(named: 'keyAlias'), + ), + ); + }); }); - }); - }); + + group('when keyPassword is invalid', () { + const keystore = 'keystore.jks'; + const keystorePassword = 'file:keystorePasswordFile'; + const keyPassword = 'keyPassword'; + const keyAlias = 'keyAlias'; + + setUp(() { + when(() => argResults['ks']).thenReturn(keystore); + when(() => argResults['ks-pass']).thenReturn(keystorePassword); + when(() => argResults['ks-key-pass']).thenReturn(keyPassword); + when(() => argResults['ks-key-alias']).thenReturn(keyAlias); + }); + + test('exits with usage error', () async { + final result = await runWithOverrides(command.run); + expect(result, equals(ExitCode.usage.code)); + + verify( + () => logger.err( + 'Key password must start with "pass:" or "file:".', + ), + ).called(1); + + verifyNever( + () => bundletool.buildApks( + bundle: aabPath(), + output: apksPath(), + keystore: any(named: 'keystore'), + keystorePassword: any(named: 'keystorePassword'), + keyPassword: any(named: 'keyPassword'), + keyAlias: any(named: 'keyAlias'), + ), + ); + }); + }); + }, + ); group('setChannelOnAab', () { late File aabFile; @@ -704,8 +704,9 @@ void main() { ), ); - final updatedShorebirdYamlFile = - await shorebirdYamlFileFromAab(aabFile); + final updatedShorebirdYamlFile = await shorebirdYamlFileFromAab( + aabFile, + ); expect( updatedShorebirdYamlFile.readAsStringSync(), 'app_id: $appId\n', @@ -717,14 +718,13 @@ void main() { test('sets shorebird.yaml channel to target channel', () async { aabFile = await createAabFile(channel: null); await runWithOverrides( - () => command.setChannelOnAab( - aabFile: aabFile, - channel: 'live', - ), + () => + command.setChannelOnAab(aabFile: aabFile, channel: 'live'), ); - final updatedShorebirdYamlFile = - await shorebirdYamlFileFromAab(aabFile); + final updatedShorebirdYamlFile = await shorebirdYamlFileFromAab( + aabFile, + ); expect(updatedShorebirdYamlFile.readAsStringSync(), ''' app_id: $appId channel: live @@ -744,8 +744,9 @@ channel: live ), ); - final updatedShorebirdYamlFile = - await shorebirdYamlFileFromAab(aabFile); + final updatedShorebirdYamlFile = await shorebirdYamlFileFromAab( + aabFile, + ); expect(updatedShorebirdYamlFile.readAsStringSync(), ''' app_id: $appId channel: ${track.channel} @@ -765,8 +766,9 @@ channel: ${track.channel} ), ); - final updatedShorebirdYamlFile = - await shorebirdYamlFileFromAab(aabFile); + final updatedShorebirdYamlFile = await shorebirdYamlFileFromAab( + aabFile, + ); expect(updatedShorebirdYamlFile.readAsStringSync(), ''' app_id: $appId channel: ${track.channel} @@ -825,8 +827,9 @@ channel: ${track.channel} late Progress mockDownloadingProgress; setUp(() { mockDownloadingProgress = MockProgress(); - when(() => logger.progress('Downloading release')) - .thenReturn(mockDownloadingProgress); + when( + () => logger.progress('Downloading release'), + ).thenReturn(mockDownloadingProgress); }); test('downloading release progress completes', () async { @@ -946,8 +949,9 @@ channel: ${track.channel} ).thenAnswer(setupAndroidShorebirdYaml); final exception = Exception('oops'); - when(() => adb.startApp(package: any(named: 'package'))) - .thenThrow(exception); + when( + () => adb.startApp(package: any(named: 'package')), + ).thenThrow(exception); }); test('exits with code 70', () async { @@ -1018,8 +1022,9 @@ channel: ${track.channel} group('when in a shorebird project without flavors', () { setUp(() { - when(() => shorebirdEnv.getShorebirdYaml()) - .thenReturn(const ShorebirdYaml(appId: 'test-app-id')); + when( + () => shorebirdEnv.getShorebirdYaml(), + ).thenReturn(const ShorebirdYaml(appId: 'test-app-id')); when(() => argResults.wasParsed('app-id')).thenReturn(false); when(() => argResults['app-id']).thenReturn(null); }); @@ -1043,19 +1048,14 @@ channel: ${track.channel} when(() => shorebirdEnv.getShorebirdYaml()).thenReturn( const ShorebirdYaml( appId: 'test-app-id', - flavors: { - 'dev': 'dev-app-id', - 'prod': 'prod-app-id', - }, + flavors: {'dev': 'dev-app-id', 'prod': 'prod-app-id'}, ), ); when(() => argResults.wasParsed('app-id')).thenReturn(false); when(() => argResults['app-id']).thenReturn(null); when( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - ), + () => + logger.chooseOne(any(), choices: any(named: 'choices')), ).thenReturn('dev'); }); @@ -1063,10 +1063,7 @@ channel: ${track.channel} await runWithOverrides(command.run); verify( - () => logger.chooseOne( - any(), - choices: ['dev', 'prod'], - ), + () => logger.chooseOne(any(), choices: ['dev', 'prod']), ).called(1); }); }); @@ -1094,13 +1091,15 @@ channel: ${track.channel} test('queries for apps', () async { final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.success.code)); - final captured = verify( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - display: captureAny(named: 'display'), - ), - ).captured.single as String Function(AppMetadata); + final captured = + verify( + () => logger.chooseOne( + any(), + choices: any(named: 'choices'), + display: captureAny(named: 'display'), + ), + ).captured.single + as String Function(AppMetadata); expect(captured(app), equals(app.displayName)); verify(() => codePushClientWrapper.getApps()).called(1); }); @@ -1135,17 +1134,18 @@ channel: ${track.channel} ).thenAnswer((_) async => [release, otherRelease]); }); - test( - 'only prompts for release versions that ' + test('only prompts for release versions that ' 'support the current platform', () async { await runWithOverrides(command.run); - final releases = verify( - () => logger.chooseOne( - any(), - choices: captureAny(named: 'choices'), - display: any(named: 'display'), - ), - ).captured.single as List; + final releases = + verify( + () => logger.chooseOne( + any(), + choices: captureAny(named: 'choices'), + display: any(named: 'display'), + ), + ).captured.single + as List; expect(releases.length, equals(1)); expect(releases.first.version, equals(releaseVersion)); }); @@ -1175,13 +1175,15 @@ channel: ${track.channel} test('prompts for platforms', () async { final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.success.code)); - final platforms = verify( - () => logger.chooseOne( - any(), - choices: captureAny(named: 'choices'), - display: any(named: 'display'), - ), - ).captured.single as List; + final platforms = + verify( + () => logger.chooseOne( + any(), + choices: captureAny(named: 'choices'), + display: any(named: 'display'), + ), + ).captured.single + as List; expect( platforms, equals([ @@ -1196,8 +1198,9 @@ channel: ${track.channel} setUp(() { when(() => argResults.wasParsed('app-id')).thenReturn(false); when(() => argResults['app-id']).thenReturn(null); - when(() => codePushClientWrapper.getApps()) - .thenAnswer((_) async => []); + when( + () => codePushClientWrapper.getApps(), + ).thenAnswer((_) async => []); }); test('exits early with success code', () async { @@ -1270,13 +1273,15 @@ channel: ${track.channel} test('queries for releases', () async { final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.success.code)); - final captured = verify( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - display: captureAny(named: 'display'), - ), - ).captured.single as String Function(Release); + final captured = + verify( + () => logger.chooseOne( + any(), + choices: any(named: 'choices'), + display: captureAny(named: 'display'), + ), + ).captured.single + as String Function(Release); expect(captured(release), equals(releaseVersion)); verify( () => codePushClientWrapper.getReleases( @@ -1306,10 +1311,8 @@ channel: ${track.channel} ), ).called(1); verify( - () => adb.startApp( - package: any(named: 'package'), - deviceId: deviceId, - ), + () => + adb.startApp(package: any(named: 'package'), deviceId: deviceId), ).called(1); verify( () => adb.logcat(filter: any(named: 'filter'), deviceId: deviceId), @@ -1332,9 +1335,7 @@ channel: ${track.channel} final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); verify( - () => logger.err( - 'Error getting release artifact: Exception: oops', - ), + () => logger.err('Error getting release artifact: Exception: oops'), ).called(1); }); }); @@ -1356,8 +1357,9 @@ channel: ${track.channel} authRef.overrideWith(() => auth), bundletoolRef.overrideWith(() => bundletool), cacheRef.overrideWith(() => cache), - codePushClientWrapperRef - .overrideWith(() => codePushClientWrapper), + codePushClientWrapperRef.overrideWith( + () => codePushClientWrapper, + ), devicectlRef.overrideWith(() => devicectl), httpClientRef.overrideWith(() => httpClient), iosDeployRef.overrideWith(() => iosDeploy), @@ -1388,8 +1390,9 @@ channel: ${track.channel} ), ).thenAnswer((_) async {}); - when(() => devicectl.deviceForLaunch(deviceId: any(named: 'deviceId'))) - .thenAnswer((_) async => null); + when( + () => devicectl.deviceForLaunch(deviceId: any(named: 'deviceId')), + ).thenAnswer((_) async => null); when( () => devicectl.installAndLaunchApp( runnerAppDirectory: any(named: 'runnerAppDirectory'), @@ -1446,9 +1449,7 @@ channel: ${track.channel} group('when downloading release artifact fails', () { final exception = Exception('oops'); setUp(() { - when( - () => artifactManager.downloadFile(any()), - ).thenThrow(exception); + when(() => artifactManager.downloadFile(any())).thenThrow(exception); }); test('exits with code 70', () async { @@ -1493,13 +1494,15 @@ channel: ${track.channel} const devicectlDeviceId = '12345'; setUp(() { when(() => appleDevice.udid).thenReturn(devicectlDeviceId); - when(() => devicectl.listAvailableIosDevices()) - .thenAnswer((_) async => [appleDevice]); + when( + () => devicectl.listAvailableIosDevices(), + ).thenAnswer((_) async => [appleDevice]); }); test('uses matching devicectl device if found', () async { - when(() => argResults['device-id']) - .thenAnswer((_) => devicectlDeviceId); + when( + () => argResults['device-id'], + ).thenAnswer((_) => devicectlDeviceId); setupIOSShorebirdYaml(); await runWithOverrides(command.run); @@ -1511,8 +1514,9 @@ channel: ${track.channel} group('when no devicectl devices have matching id', () { setUp(() { - when(() => argResults['device-id']) - .thenAnswer((_) => 'not-a-device-id'); + when( + () => argResults['device-id'], + ).thenAnswer((_) => 'not-a-device-id'); setupIOSShorebirdYaml(); }); @@ -1537,9 +1541,7 @@ channel: ${track.channel} group('when devicectl returns a usable device', () { setUp(() { when( - () => devicectl.deviceForLaunch( - deviceId: any(named: 'deviceId'), - ), + () => devicectl.deviceForLaunch(deviceId: any(named: 'deviceId')), ).thenAnswer((_) async => appleDevice); setupIOSShorebirdYaml(); }); @@ -1600,10 +1602,7 @@ channel: ${track.channel} verify( () => iosDeploy.installAndLaunchApp(bundlePath: iosRunnerPath()), ).called(1); - expect( - shorebirdYaml.readAsStringSync(), - equals('app_id: $appId'), - ); + expect(shorebirdYaml.readAsStringSync(), equals('app_id: $appId')); }); }); @@ -1611,17 +1610,18 @@ channel: ${track.channel} late File shorebirdYaml; setUp(() { when(() => argResults['staging']).thenReturn(true); - shorebirdYaml = File( - p.join( - iosRunnerPath(), - 'Frameworks', - 'App.framework', - 'flutter_assets', - 'shorebird.yaml', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync('app_id: $appId', flush: true); + shorebirdYaml = + File( + p.join( + iosRunnerPath(), + 'Frameworks', + 'App.framework', + 'flutter_assets', + 'shorebird.yaml', + ), + ) + ..createSync(recursive: true) + ..writeAsStringSync('app_id: $appId', flush: true); when( () => iosDeploy.installAndLaunchApp( bundlePath: any(named: 'bundlePath'), @@ -1668,9 +1668,7 @@ channel: ${DeploymentTrack.staging.channel} final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); verify( - () => logger.err( - 'Error getting release artifact: Exception: oops', - ), + () => logger.err('Error getting release artifact: Exception: oops'), ).called(1); }); }); @@ -1679,15 +1677,16 @@ channel: ${DeploymentTrack.staging.channel} setUp(() { final releaseWithAllPlatforms = MockRelease(); when(() => releaseWithAllPlatforms.id).thenReturn(releaseId); - when(() => releaseWithAllPlatforms.version) - .thenReturn(releaseVersion); + when( + () => releaseWithAllPlatforms.version, + ).thenReturn(releaseVersion); when(() => releaseWithAllPlatforms.platformStatuses).thenReturn({ ReleasePlatform.ios: ReleaseStatus.active, ReleasePlatform.android: ReleaseStatus.active, }); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.ios: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.ios: ReleaseStatus.active}); when( () => codePushClientWrapper.getReleases( appId: any(named: 'appId'), @@ -1695,40 +1694,36 @@ channel: ${DeploymentTrack.staging.channel} ), ).thenAnswer((_) async => [release]); when( - () => codePushClientWrapper.getReleases( - appId: any(named: 'appId'), - ), + () => codePushClientWrapper.getReleases(appId: any(named: 'appId')), ).thenAnswer((_) async => [releaseWithAllPlatforms]); }); - test( - 'does not warn since the user explicitly asked for iOS', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.software.code)); + test('does not warn since the user explicitly asked for iOS', () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.software.code)); - verifyNever( - () => logger.warn( - '''The ${ReleasePlatform.android.displayName} artifact for this release is not previewable.''', - ), - ); - }, - ); + verifyNever( + () => logger.warn( + '''The ${ReleasePlatform.android.displayName} artifact for this release is not previewable.''', + ), + ); + }); }); group('when the ios release is not sideloadable', () { setUp(() { final releaseWithAllPlatforms = MockRelease(); when(() => releaseWithAllPlatforms.id).thenReturn(releaseId); - when(() => releaseWithAllPlatforms.version) - .thenReturn(releaseVersion); + when( + () => releaseWithAllPlatforms.version, + ).thenReturn(releaseVersion); when(() => releaseWithAllPlatforms.platformStatuses).thenReturn({ ReleasePlatform.ios: ReleaseStatus.active, ReleasePlatform.android: ReleaseStatus.active, }); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.android: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.android: ReleaseStatus.active}); when( () => codePushClientWrapper.getReleases( appId: any(named: 'appId'), @@ -1736,23 +1731,23 @@ channel: ${DeploymentTrack.staging.channel} ), ).thenAnswer((_) async => [release]); when( - () => codePushClientWrapper.getReleases( - appId: any(named: 'appId'), - ), + () => codePushClientWrapper.getReleases(appId: any(named: 'appId')), ).thenAnswer((_) async => [releaseWithAllPlatforms]); }); - test('prints error that platform is not previewable and exits', - () async { - await expectLater( - runWithOverrides(command.run), - completion(equals(ExitCode.usage.code)), - ); + test( + 'prints error that platform is not previewable and exits', + () async { + await expectLater( + runWithOverrides(command.run), + completion(equals(ExitCode.usage.code)), + ); - verify( - () => logger.err('No previewable iOS releases found'), - ).called(1); - }); + verify( + () => logger.err('No previewable iOS releases found'), + ).called(1); + }, + ); }); }); @@ -1768,20 +1763,17 @@ channel: ${DeploymentTrack.staging.channel} void setupPreviewArtifact({required Directory baseDirectory}) { File( - p.join( - baseDirectory.path, - 'data', - 'flutter_assets', - 'shorebird.yaml', - ), - ) + p.join( + baseDirectory.path, + 'data', + 'flutter_assets', + 'shorebird.yaml', + ), + ) ..createSync(recursive: true) ..writeAsStringSync('app_id: $appId', flush: true); File( - p.join( - baseDirectory.path, - executableName, - ), + p.join(baseDirectory.path, executableName), ).createSync(recursive: true); } @@ -1793,8 +1785,9 @@ channel: ${DeploymentTrack.staging.channel} artifactManagerRef.overrideWith(() => artifactManager), authRef.overrideWith(() => auth), cacheRef.overrideWith(() => cache), - codePushClientWrapperRef - .overrideWith(() => codePushClientWrapper), + codePushClientWrapperRef.overrideWith( + () => codePushClientWrapper, + ), httpClientRef.overrideWith(() => httpClient), loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), @@ -1814,12 +1807,15 @@ channel: ${DeploymentTrack.staging.channel} final tempDir = Directory.systemTemp.createTempSync(); releaseArtifactFile = File(p.join(tempDir.path, 'Release.zip')); - when(() => process.stdout) - .thenAnswer((_) => Stream.value(utf8.encode('hello world'))); - when(() => process.stderr) - .thenAnswer((_) => Stream.value(utf8.encode('hello error'))); - when(() => process.exitCode) - .thenAnswer((_) async => ExitCode.success.code); + when( + () => process.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('hello world'))); + when( + () => process.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode('hello error'))); + when( + () => process.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); when( () => shorebirdProcess.start(any(), any()), @@ -1835,9 +1831,9 @@ channel: ${DeploymentTrack.staging.channel} when(() => linuxReleaseArtifact.id).thenReturn(releaseArtifactId); when(() => linuxReleaseArtifact.url).thenReturn(releaseArtifactUrl); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.linux: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.linux: ReleaseStatus.active}); when( () => codePushClientWrapper.getReleaseArtifact( @@ -1886,9 +1882,7 @@ channel: ${DeploymentTrack.staging.channel} final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); verify( - () => artifactManager.downloadFile( - Uri.parse(releaseArtifactUrl), - ), + () => artifactManager.downloadFile(Uri.parse(releaseArtifactUrl)), ).called(1); }); }); @@ -1910,9 +1904,7 @@ channel: ${DeploymentTrack.staging.channel} final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); verify( - () => progress.fail( - 'Exception: Unable to find shorebird.yaml', - ), + () => progress.fail('Exception: Unable to find shorebird.yaml'), ).called(1); }); }); @@ -1942,10 +1934,8 @@ channel: ${DeploymentTrack.staging.channel} () => artifactManager.downloadFile(Uri.parse(releaseArtifactUrl)), ).called(1); verify( - () => shorebirdProcess.start( - any(that: endsWith(executableName)), - [], - ), + () => + shorebirdProcess.start(any(that: endsWith(executableName)), []), ).called(1); verify(() => logger.info('hello world')).called(1); verify(() => logger.err('hello error')).called(1); @@ -1971,10 +1961,8 @@ channel: ${DeploymentTrack.staging.channel} verifyNever(() => artifactManager.downloadFile(any())); verify(() => shorebirdProcess.run('chmod', any())).called(1); verify( - () => shorebirdProcess.start( - any(that: endsWith(executableName)), - [], - ), + () => + shorebirdProcess.start(any(that: endsWith(executableName)), []), ).called(1); verify(() => logger.info('hello world')).called(1); verify(() => logger.err('hello error')).called(1); @@ -1999,8 +1987,9 @@ channel: ${DeploymentTrack.staging.channel} authRef.overrideWith(() => auth), bundletoolRef.overrideWith(() => bundletool), cacheRef.overrideWith(() => cache), - codePushClientWrapperRef - .overrideWith(() => codePushClientWrapper), + codePushClientWrapperRef.overrideWith( + () => codePushClientWrapper, + ), dittoRef.overrideWith(() => ditto), httpClientRef.overrideWith(() => httpClient), loggerRef.overrideWith(() => logger), @@ -2013,17 +2002,18 @@ channel: ${DeploymentTrack.staging.channel} ); } - File setupMacosShorebirdYaml() => File( - p.join( - macosAppPath(), - 'Contents', - 'Frameworks', - 'App.framework', - 'Resources', - 'flutter_assets', - 'shorebird.yaml', - ), - ) + File setupMacosShorebirdYaml() => + File( + p.join( + macosAppPath(), + 'Contents', + 'Frameworks', + 'App.framework', + 'Resources', + 'flutter_assets', + 'shorebird.yaml', + ), + ) ..createSync(recursive: true) ..writeAsStringSync('app_id: $appId', flush: true); @@ -2048,9 +2038,9 @@ channel: ${DeploymentTrack.staging.channel} destination: any(named: 'destination'), ), ).thenAnswer((_) async {}); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.macos: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.macos: ReleaseStatus.active}); when(() => releaseArtifact.url).thenReturn(releaseArtifactUrl); when(() => platform.isMacOS).thenReturn(true); when( @@ -2088,9 +2078,7 @@ channel: ${DeploymentTrack.staging.channel} group('when downloading release artifact fails', () { final exception = Exception('oops'); setUp(() { - when( - () => artifactManager.downloadFile(any()), - ).thenThrow(exception); + when(() => artifactManager.downloadFile(any())).thenThrow(exception); }); test('exits with code 70', () async { @@ -2181,8 +2169,7 @@ channel: ${DeploymentTrack.staging.channel} libinfo check path: satisfied (Path is satisfied), interface: en0, ipv4, dns 2025-01-31 10:15:29.808 Df macos_sandbox[33557:2cc12] [dev.shorebird:updater::updater] [shorebird] Reporting successful launch. -''' - .split('\n'); +'''.split('\n'); setUp(() { setupMacosShorebirdYaml(); @@ -2206,9 +2193,8 @@ channel: ${DeploymentTrack.staging.channel} ), ); verifyNever( - () => logger.info( - any(that: contains('nw_path_libinfo_path_check')), - ), + () => + logger.info(any(that: contains('nw_path_libinfo_path_check'))), ); }); }); @@ -2232,8 +2218,9 @@ channel: ${DeploymentTrack.staging.channel} artifactManagerRef.overrideWith(() => artifactManager), authRef.overrideWith(() => auth), cacheRef.overrideWith(() => cache), - codePushClientWrapperRef - .overrideWith(() => codePushClientWrapper), + codePushClientWrapperRef.overrideWith( + () => codePushClientWrapper, + ), httpClientRef.overrideWith(() => httpClient), loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), @@ -2246,16 +2233,17 @@ channel: ${DeploymentTrack.staging.channel} } void createShorebirdYaml({String? channel}) { - final shorebirdYaml = File( - p.join( - windowsReleaseDirectory.path, - 'data', - 'flutter_assets', - 'shorebird.yaml', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync('app_id: $appId', flush: true); + final shorebirdYaml = + File( + p.join( + windowsReleaseDirectory.path, + 'data', + 'flutter_assets', + 'shorebird.yaml', + ), + ) + ..createSync(recursive: true) + ..writeAsStringSync('app_id: $appId', flush: true); if (channel != null) { shorebirdYaml.writeAsStringSync( 'channel: $channel', @@ -2280,12 +2268,15 @@ channel: ${DeploymentTrack.staging.channel} ), ); - when(() => process.stdout) - .thenAnswer((_) => Stream.value(utf8.encode('hello world'))); - when(() => process.stderr) - .thenAnswer((_) => Stream.value(utf8.encode('hello error'))); - when(() => process.exitCode) - .thenAnswer((_) async => ExitCode.success.code); + when( + () => process.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('hello world'))); + when( + () => process.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode('hello error'))); + when( + () => process.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); when( () => shorebirdProcess.start(any(), any()), @@ -2294,9 +2285,9 @@ channel: ${DeploymentTrack.staging.channel} when(() => windowsReleaseArtifact.id).thenReturn(releaseArtifactId); when(() => windowsReleaseArtifact.url).thenReturn(releaseArtifactUrl); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.windows: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.windows: ReleaseStatus.active}); when( () => codePushClientWrapper.getReleaseArtifact( @@ -2345,17 +2336,16 @@ channel: ${DeploymentTrack.staging.channel} final result = await runWithOverrides(command.run); expect(result, equals(ExitCode.software.code)); verify( - () => artifactManager.downloadFile( - Uri.parse(releaseArtifactUrl), - ), + () => artifactManager.downloadFile(Uri.parse(releaseArtifactUrl)), ).called(1); }); }); group('when preview artifact is not cached', () { setUp(() { - when(() => artifactManager.downloadFile(any())) - .thenAnswer((_) async => releaseArtifactFile); + when( + () => artifactManager.downloadFile(any()), + ).thenAnswer((_) async => releaseArtifactFile); when( () => artifactManager.extractZip( zipFile: any(named: 'zipFile'), @@ -2364,8 +2354,9 @@ channel: ${DeploymentTrack.staging.channel} ).thenAnswer((invocation) async { final outDirectory = invocation.namedArguments[#outputDirectory] as Directory; - File(p.join(outDirectory.path, 'runner.exe')) - .createSync(recursive: true); + File( + p.join(outDirectory.path, 'runner.exe'), + ).createSync(recursive: true); createShorebirdYaml(); }); }); @@ -2434,8 +2425,9 @@ channel: ${DeploymentTrack.staging.channel} authRef.overrideWith(() => auth), bundletoolRef.overrideWith(() => bundletool), cacheRef.overrideWith(() => cache), - codePushClientWrapperRef - .overrideWith(() => codePushClientWrapper), + codePushClientWrapperRef.overrideWith( + () => codePushClientWrapper, + ), devicectlRef.overrideWith(() => devicectl), httpClientRef.overrideWith(() => httpClient), iosDeployRef.overrideWith(() => iosDeploy), @@ -2470,8 +2462,9 @@ channel: ${DeploymentTrack.staging.channel} ), ).thenAnswer((_) async {}); - when(() => devicectl.deviceForLaunch(deviceId: any(named: 'deviceId'))) - .thenAnswer((_) async => null); + when( + () => devicectl.deviceForLaunch(deviceId: any(named: 'deviceId')), + ).thenAnswer((_) async => null); when( () => devicectl.installAndLaunchApp( runnerAppDirectory: any(named: 'runnerAppDirectory'), @@ -2570,15 +2563,16 @@ channel: ${DeploymentTrack.staging.channel} setUp(() { final releaseWithAllPlatforms = MockRelease(); when(() => releaseWithAllPlatforms.id).thenReturn(releaseId); - when(() => releaseWithAllPlatforms.version) - .thenReturn(releaseVersion); + when( + () => releaseWithAllPlatforms.version, + ).thenReturn(releaseVersion); when(() => releaseWithAllPlatforms.platformStatuses).thenReturn({ ReleasePlatform.ios: ReleaseStatus.active, ReleasePlatform.android: ReleaseStatus.active, }); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.ios: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.ios: ReleaseStatus.active}); when( () => codePushClientWrapper.getReleases( appId: any(named: 'appId'), @@ -2586,9 +2580,8 @@ channel: ${DeploymentTrack.staging.channel} ), ).thenAnswer((_) async => [release]); when( - () => codePushClientWrapper.getReleases( - appId: any(named: 'appId'), - ), + () => + codePushClientWrapper.getReleases(appId: any(named: 'appId')), ).thenAnswer((_) async => [releaseWithAllPlatforms]); }); @@ -2684,15 +2677,16 @@ channel: ${DeploymentTrack.staging.channel} setUp(() { final releaseWithAllPlatforms = MockRelease(); when(() => releaseWithAllPlatforms.id).thenReturn(releaseId); - when(() => releaseWithAllPlatforms.version) - .thenReturn(releaseVersion); + when( + () => releaseWithAllPlatforms.version, + ).thenReturn(releaseVersion); when(() => releaseWithAllPlatforms.platformStatuses).thenReturn({ ReleasePlatform.ios: ReleaseStatus.active, ReleasePlatform.android: ReleaseStatus.active, }); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.android: ReleaseStatus.active, - }); + when( + () => release.platformStatuses, + ).thenReturn({ReleasePlatform.android: ReleaseStatus.active}); when( () => codePushClientWrapper.getReleases( appId: any(named: 'appId'), @@ -2700,9 +2694,8 @@ channel: ${DeploymentTrack.staging.channel} ), ).thenAnswer((_) async => [release]); when( - () => codePushClientWrapper.getReleases( - appId: any(named: 'appId'), - ), + () => + codePushClientWrapper.getReleases(appId: any(named: 'appId')), ).thenAnswer((_) async => [releaseWithAllPlatforms]); }); @@ -2726,12 +2719,7 @@ channel: ${DeploymentTrack.staging.channel} R runWithOverrides(R Function() body) { return HttpOverrides.runZoned( - () => runScoped( - body, - values: { - loggerRef.overrideWith(() => logger), - }, - ), + () => runScoped(body, values: {loggerRef.overrideWith(() => logger)}), ); } @@ -2793,63 +2781,67 @@ channel: ${DeploymentTrack.staging.channel} }); test( - 'exits early when user explicitly specifies a non-previewable platform', - () async { - final releaseWithAllPlatforms = MockRelease(); - final release = MockRelease(); + 'exits early when user explicitly specifies a non-previewable platform', + () async { + final releaseWithAllPlatforms = MockRelease(); + final release = MockRelease(); - when(() => releaseWithAllPlatforms.platformStatuses).thenReturn({ - ReleasePlatform.android: ReleaseStatus.active, - ReleasePlatform.ios: ReleaseStatus.active, - ReleasePlatform.linux: ReleaseStatus.active, - ReleasePlatform.macos: ReleaseStatus.active, - ReleasePlatform.windows: ReleaseStatus.active, - }); + when(() => releaseWithAllPlatforms.platformStatuses).thenReturn({ + ReleasePlatform.android: ReleaseStatus.active, + ReleasePlatform.ios: ReleaseStatus.active, + ReleasePlatform.linux: ReleaseStatus.active, + ReleasePlatform.macos: ReleaseStatus.active, + ReleasePlatform.windows: ReleaseStatus.active, + }); - when(() => release.platformStatuses).thenReturn({ - ReleasePlatform.android: ReleaseStatus.active, - ReleasePlatform.linux: ReleaseStatus.active, - ReleasePlatform.windows: ReleaseStatus.active, - }); - expect( - () => runWithOverrides( - () => assertPreviewableReleases( - releaseWithAllPlatforms: releaseWithAllPlatforms, - release: release, - targetPlatform: ReleasePlatform.ios, + when(() => release.platformStatuses).thenReturn({ + ReleasePlatform.android: ReleaseStatus.active, + ReleasePlatform.linux: ReleaseStatus.active, + ReleasePlatform.windows: ReleaseStatus.active, + }); + expect( + () => runWithOverrides( + () => assertPreviewableReleases( + releaseWithAllPlatforms: releaseWithAllPlatforms, + release: release, + targetPlatform: ReleasePlatform.ios, + ), ), - ), - throwsA( - isA() - .having((e) => e.exitCode, 'exitCode', ExitCode.software.code), - ), - ); - verify( - () => logger.err( - '''The ${ReleasePlatform.ios.displayName} artifact for this release is not previewable.''', - ), - ).called(1); + throwsA( + isA().having( + (e) => e.exitCode, + 'exitCode', + ExitCode.software.code, + ), + ), + ); + verify( + () => logger.err( + '''The ${ReleasePlatform.ios.displayName} artifact for this release is not previewable.''', + ), + ).called(1); - verifyNever( - () => logger.warn( - '''The ${ReleasePlatform.macos.displayName} artifact for this release is not previewable.''', - ), - ); - verifyNever( - () => logger.warn( - '''The ${ReleasePlatform.android.displayName} artifact for this release is not previewable.''', - ), - ); - verifyNever( - () => logger.warn( - '''The ${ReleasePlatform.linux.displayName} artifact for this release is not previewable.''', - ), - ); - verifyNever( - () => logger.warn( - '''The ${ReleasePlatform.windows.displayName} artifact for this release is not previewable.''', - ), - ); - }); + verifyNever( + () => logger.warn( + '''The ${ReleasePlatform.macos.displayName} artifact for this release is not previewable.''', + ), + ); + verifyNever( + () => logger.warn( + '''The ${ReleasePlatform.android.displayName} artifact for this release is not previewable.''', + ), + ); + verifyNever( + () => logger.warn( + '''The ${ReleasePlatform.linux.displayName} artifact for this release is not previewable.''', + ), + ); + verifyNever( + () => logger.warn( + '''The ${ReleasePlatform.windows.displayName} artifact for this release is not previewable.''', + ), + ); + }, + ); }); } diff --git a/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart index fb2188dd..8bc424e0 100644 --- a/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart @@ -54,8 +54,9 @@ void main() { processRef.overrideWith(() => shorebirdProcess), shorebirdEnvRef.overrideWith(() => shorebirdEnv), shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - shorebirdAndroidArtifactsRef - .overrideWith(() => shorebirdAndroidArtifacts), + shorebirdAndroidArtifactsRef.overrideWith( + () => shorebirdAndroidArtifacts, + ), }, ); } @@ -79,8 +80,9 @@ void main() { shorebirdAndroidArtifacts = MockShorebirdAndroidArtifacts(); when(() => argResults['build-number']).thenReturn(buildNumber); - when(() => argResults['target-platform']) - .thenReturn(Arch.values.map((a) => a.targetPlatformCliArg).toList()); + when( + () => argResults['target-platform'], + ).thenReturn(Arch.values.map((a) => a.targetPlatformCliArg).toList()); when(() => argResults.rest).thenReturn([]); when(() => argResults.wasParsed(any())).thenReturn(false); @@ -122,11 +124,13 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); }); @@ -162,8 +166,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -174,8 +179,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -257,9 +263,7 @@ void main() { targetPlatforms: any(named: 'targetPlatforms'), args: any(named: 'args'), ), - ).thenAnswer( - (_) async => File(''), - ); + ).thenAnswer((_) async => File('')); setUpProjectRootArtifacts(); }); @@ -311,9 +315,8 @@ void main() { test('returns value from argResults', () async { final result = await runWithOverrides( - () => aarReleaser.getReleaseVersion( - releaseArtifactRoot: Directory(''), - ), + () => + aarReleaser.getReleaseVersion(releaseArtifactRoot: Directory('')), ); expect(result, releaseVersion); }); @@ -408,7 +411,8 @@ void main() { test('returns expected instructions', () { expect( runWithOverrides(() => aarReleaser.postReleaseInstructions), - equals(''' + equals( + ''' Your next steps: @@ -440,7 +444,8 @@ dependencies { releaseImplementation '${shorebirdEnv.androidPackageName}:flutter_release:$buildNumber' // ... }''')} -'''), +''', + ), ); }); }); diff --git a/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart index 9a542188..5071750c 100644 --- a/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart @@ -62,8 +62,9 @@ void main() { processRef.overrideWith(() => shorebirdProcess), shorebirdEnvRef.overrideWith(() => shorebirdEnv), shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - shorebirdAndroidArtifactsRef - .overrideWith(() => shorebirdAndroidArtifacts), + shorebirdAndroidArtifactsRef.overrideWith( + () => shorebirdAndroidArtifacts, + ), }, ); } @@ -90,8 +91,9 @@ void main() { shorebirdValidator = MockShorebirdValidator(); shorebirdAndroidArtifacts = MockShorebirdAndroidArtifacts(); - when(() => argResults['target-platform']) - .thenReturn(Arch.values.map((a) => a.targetPlatformCliArg).toList()); + when( + () => argResults['target-platform'], + ).thenReturn(Arch.values.map((a) => a.targetPlatformCliArg).toList()); when(() => argResults.rest).thenReturn([]); when(() => argResults.wasParsed(any())).thenReturn(false); @@ -122,8 +124,9 @@ void main() { group('assertPreconditions', () { setUp(() { - when(() => doctor.androidCommandValidators) - .thenReturn([flavorValidator]); + when( + () => doctor.androidCommandValidators, + ).thenReturn([flavorValidator]); when(flavorValidator.validate).thenAnswer((_) async => []); }); @@ -132,11 +135,13 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); }); @@ -155,8 +160,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -167,8 +173,9 @@ void main() { when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), ), ).thenThrow(exception); @@ -264,9 +271,7 @@ To change the version of this release, change your app's version in your pubspec targetPlatforms: any(named: 'targetPlatforms'), args: any(named: 'args'), ), - ).thenAnswer( - (_) async => File(''), - ); + ).thenAnswer((_) async => File('')); when( () => shorebirdAndroidArtifacts.findAab( project: any(named: 'project'), @@ -310,9 +315,7 @@ To change the version of this release, change your app's version in your pubspec }); test('does not built apk by default', () async { - await runWithOverrides( - () => androidReleaser.buildReleaseArtifacts(), - ); + await runWithOverrides(() => androidReleaser.buildReleaseArtifacts()); verifyNever( () => artifactBuilder.buildApk( flavor: any(named: 'flavor'), @@ -387,8 +390,9 @@ To change the version of this release, change your app's version in your pubspec 'patch-signing-public-key.pem', ), )..createSync(recursive: true); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn(patchSigningPublicKeyFile.path); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(patchSigningPublicKeyFile.path); when( () => artifactBuilder.buildAppBundle( @@ -410,8 +414,9 @@ To change the version of this release, change your app's version in your pubspec ), ).thenAnswer((_) async => File('')); - when(() => codeSigner.base64PublicKey(any())) - .thenReturn(base64PublicKey); + when( + () => codeSigner.base64PublicKey(any()), + ).thenReturn(base64PublicKey); }); test( @@ -560,26 +565,28 @@ To change the version of this release, change your app's version in your pubspec ).thenReturn(aabFile); }); - test('calls codePushClientWrapper.createAndroidReleaseArtifacts', - () async { - await runWithOverrides( - () => androidReleaser.uploadReleaseArtifacts( - appId: appId, - release: release, - ), - ); - verify( - () => codePushClientWrapper.createAndroidReleaseArtifacts( - appId: appId, - releaseId: release.id, - projectRoot: projectRoot.path, - aabPath: aabFile.path, - platform: ReleasePlatform.android, - architectures: Arch.values, - flavor: androidReleaser.flavor, - ), - ).called(1); - }); + test( + 'calls codePushClientWrapper.createAndroidReleaseArtifacts', + () async { + await runWithOverrides( + () => androidReleaser.uploadReleaseArtifacts( + appId: appId, + release: release, + ), + ); + verify( + () => codePushClientWrapper.createAndroidReleaseArtifacts( + appId: appId, + releaseId: release.id, + projectRoot: projectRoot.path, + aabPath: aabFile.path, + platform: ReleasePlatform.android, + architectures: Arch.values, + flavor: androidReleaser.flavor, + ), + ).called(1); + }, + ); }); group('updatedReleaseMetadata', () { diff --git a/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart index e31deca8..069d1d39 100644 --- a/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart @@ -33,316 +33,285 @@ import '../../matchers.dart'; import '../../mocks.dart'; void main() { - group( - IosFrameworkReleaser, - () { - late ArgResults argResults; - late ArtifactBuilder artifactBuilder; - late ArtifactManager artifactManager; - late CodePushClientWrapper codePushClientWrapper; - late Doctor doctor; - late Directory projectRoot; - late FlavorValidator flavorValidator; - late ShorebirdLogger logger; - late OperatingSystemInterface operatingSystemInterface; - late Progress progress; - late ShorebirdProcess shorebirdProcess; - late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutter; - late ShorebirdValidator shorebirdValidator; - late XcodeBuild xcodeBuild; - late IosFrameworkReleaser iosFrameworkReleaser; + group(IosFrameworkReleaser, () { + late ArgResults argResults; + late ArtifactBuilder artifactBuilder; + late ArtifactManager artifactManager; + late CodePushClientWrapper codePushClientWrapper; + late Doctor doctor; + late Directory projectRoot; + late FlavorValidator flavorValidator; + late ShorebirdLogger logger; + late OperatingSystemInterface operatingSystemInterface; + late Progress progress; + late ShorebirdProcess shorebirdProcess; + late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutter shorebirdFlutter; + late ShorebirdValidator shorebirdValidator; + late XcodeBuild xcodeBuild; + late IosFrameworkReleaser iosFrameworkReleaser; - R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - artifactBuilderRef.overrideWith(() => artifactBuilder), - artifactManagerRef.overrideWith(() => artifactManager), - codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - doctorRef.overrideWith(() => doctor), - loggerRef.overrideWith(() => logger), - osInterfaceRef.overrideWith(() => operatingSystemInterface), - processRef.overrideWith(() => shorebirdProcess), - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), - shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - xcodeBuildRef.overrideWith(() => xcodeBuild), - }, - ); - } + R runWithOverrides(R Function() body) { + return runScoped( + body, + values: { + artifactBuilderRef.overrideWith(() => artifactBuilder), + artifactManagerRef.overrideWith(() => artifactManager), + codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + doctorRef.overrideWith(() => doctor), + loggerRef.overrideWith(() => logger), + osInterfaceRef.overrideWith(() => operatingSystemInterface), + processRef.overrideWith(() => shorebirdProcess), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), + xcodeBuildRef.overrideWith(() => xcodeBuild), + }, + ); + } - setUpAll(() { - registerFallbackValue(Directory('')); - registerFallbackValue(ReleasePlatform.ios); + setUpAll(() { + registerFallbackValue(Directory('')); + registerFallbackValue(ReleasePlatform.ios); + }); + + setUp(() { + argResults = MockArgResults(); + artifactBuilder = MockArtifactBuilder(); + artifactManager = MockArtifactManager(); + codePushClientWrapper = MockCodePushClientWrapper(); + doctor = MockDoctor(); + flavorValidator = MockFlavorValidator(); + operatingSystemInterface = MockOperatingSystemInterface(); + progress = MockProgress(); + projectRoot = Directory.systemTemp.createTempSync(); + logger = MockShorebirdLogger(); + shorebirdProcess = MockShorebirdProcess(); + shorebirdEnv = MockShorebirdEnv(); + shorebirdFlutter = MockShorebirdFlutter(); + shorebirdValidator = MockShorebirdValidator(); + xcodeBuild = MockXcodeBuild(); + + when(() => argResults.rest).thenReturn([]); + when(() => argResults.wasParsed(any())).thenReturn(false); + + when(() => logger.progress(any())).thenReturn(progress); + + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); + + iosFrameworkReleaser = IosFrameworkReleaser( + argResults: argResults, + flavor: null, + target: null, + ); + }); + + group('requiresReleaseVersionArg', () { + test('is true', () { + expect(iosFrameworkReleaser.requiresReleaseVersionArg, isTrue); }); + }); + + group('releaseType', () { + test('is xcframework', () { + expect(iosFrameworkReleaser.releaseType, ReleaseType.iosFramework); + }); + }); + + group('artifactDisplayName', () { + test('has expected value', () { + expect(iosFrameworkReleaser.artifactDisplayName, 'iOS framework'); + }); + }); + + group('assertArgsAreValid', () { + group('when split-per-abi is true', () { + setUp(() { + when(() => argResults.wasParsed('release-version')).thenReturn(false); + }); + + test('exits with code 64', () async { + await expectLater( + () => runWithOverrides(iosFrameworkReleaser.assertArgsAreValid), + exitsWithCode(ExitCode.usage), + ); + }); + }); + + group('when arguments are valid', () { + setUp(() { + when(() => argResults.wasParsed('release-version')).thenReturn(true); + }); + + test('returns normally', () { + expect( + () => runWithOverrides(iosFrameworkReleaser.assertArgsAreValid), + returnsNormally, + ); + }); + }); + }); + + group('assertPreconditions', () { + final flutterVersion = Version(3, 0, 0); setUp(() { - argResults = MockArgResults(); - artifactBuilder = MockArtifactBuilder(); - artifactManager = MockArtifactManager(); - codePushClientWrapper = MockCodePushClientWrapper(); - doctor = MockDoctor(); - flavorValidator = MockFlavorValidator(); - operatingSystemInterface = MockOperatingSystemInterface(); - progress = MockProgress(); - projectRoot = Directory.systemTemp.createTempSync(); - logger = MockShorebirdLogger(); - shorebirdProcess = MockShorebirdProcess(); - shorebirdEnv = MockShorebirdEnv(); - shorebirdFlutter = MockShorebirdFlutter(); - shorebirdValidator = MockShorebirdValidator(); - xcodeBuild = MockXcodeBuild(); - - when(() => argResults.rest).thenReturn([]); - when(() => argResults.wasParsed(any())).thenReturn(false); - - when(() => logger.progress(any())).thenReturn(progress); - + when(() => doctor.iosCommandValidators).thenReturn([flavorValidator]); when( - () => shorebirdEnv.getShorebirdProjectRoot(), - ).thenReturn(projectRoot); - - iosFrameworkReleaser = IosFrameworkReleaser( - argResults: argResults, - flavor: null, - target: null, - ); + () => shorebirdFlutter.resolveFlutterVersion(any()), + ).thenAnswer((_) async => flutterVersion); + when(flavorValidator.validate).thenAnswer((_) async => []); }); - group('requiresReleaseVersionArg', () { - test('is true', () { - expect(iosFrameworkReleaser.requiresReleaseVersionArg, isTrue); + group('when validation succeeds', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + }); + + test('returns normally', () async { + await expectLater( + () => runWithOverrides(iosFrameworkReleaser.assertPreconditions), + returnsNormally, + ); }); }); - group('releaseType', () { - test('is xcframework', () { - expect(iosFrameworkReleaser.releaseType, ReleaseType.iosFramework); - }); - }); - - group('artifactDisplayName', () { - test('has expected value', () { - expect(iosFrameworkReleaser.artifactDisplayName, 'iOS framework'); - }); - }); - - group('assertArgsAreValid', () { - group('when split-per-abi is true', () { - setUp(() { - when(() => argResults.wasParsed('release-version')) - .thenReturn(false); - }); - - test('exits with code 64', () async { - await expectLater( - () => runWithOverrides(iosFrameworkReleaser.assertArgsAreValid), - exitsWithCode(ExitCode.usage), - ); - }); - }); - - group('when arguments are valid', () { - setUp(() { - when( - () => argResults.wasParsed('release-version'), - ).thenReturn(true); - }); - - test('returns normally', () { - expect( - () => runWithOverrides(iosFrameworkReleaser.assertArgsAreValid), - returnsNormally, - ); - }); - }); - }); - - group('assertPreconditions', () { - final flutterVersion = Version(3, 0, 0); + group('when validation fails', () { + final exception = ValidationFailedException(); setUp(() { when( - () => doctor.iosCommandValidators, - ).thenReturn([flavorValidator]); - when(() => shorebirdFlutter.resolveFlutterVersion(any())) - .thenAnswer((_) async => flutterVersion); - when(flavorValidator.validate).thenAnswer((_) async => []); + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenThrow(exception); }); - group('when validation succeeds', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenAnswer((_) async {}); - }); + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(iosFrameworkReleaser.assertPreconditions), + exitsWithCode(exception.exitCode), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flavorValidator], + supportedOperatingSystems: {Platform.macOS}, + ), + ).called(1); + }); + }); - test('returns normally', () async { - await expectLater( - () => runWithOverrides(iosFrameworkReleaser.assertPreconditions), - returnsNormally, - ); - }); + group('when specified flutter version is less than minimum', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + when(() => argResults['flutter-version']).thenReturn('3.0.0'); }); - group('when validation fails', () { - final exception = ValidationFailedException(); + test('logs error and exits with code 64', () async { + await expectLater( + () => runWithOverrides(iosFrameworkReleaser.assertPreconditions), + exitsWithCode(ExitCode.usage), + ); - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: any( - named: 'checkUserIsAuthenticated', - ), - checkShorebirdInitialized: any( - named: 'checkShorebirdInitialized', - ), - validators: any(named: 'validators'), - supportedOperatingSystems: any( - named: 'supportedOperatingSystems', - ), - ), - ).thenThrow(exception); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(iosFrameworkReleaser.assertPreconditions), - exitsWithCode(exception.exitCode), - ); - verify( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: true, - checkShorebirdInitialized: true, - validators: [flavorValidator], - supportedOperatingSystems: {Platform.macOS}, - ), - ).called(1); - }); - }); - - group('when specified flutter version is less than minimum', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenAnswer((_) async {}); - when(() => argResults['flutter-version']).thenReturn('3.0.0'); - }); - - test('logs error and exits with code 64', () async { - await expectLater( - () => runWithOverrides(iosFrameworkReleaser.assertPreconditions), - exitsWithCode(ExitCode.usage), - ); - - verify( - () => logger.err( - ''' + verify( + () => logger.err(''' iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), - ).called(1); - }); +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), + ).called(1); + }); + }); + }); + + group('buildReleaseArtifacts', () { + void setUpProjectRootArtifacts() { + // Create an xcframework in the release directory to simulate running + // this command a subsequent time. + Directory( + p.join(projectRoot.path, 'release', 'Flutter.xcframework'), + ).createSync(recursive: true); + Directory( + p.join( + projectRoot.path, + 'build', + 'ios', + 'framework', + 'Release', + 'Flutter.xcframework', + ), + ).createSync(recursive: true); + } + + setUp(() { + when( + () => artifactBuilder.buildIosFramework(args: any(named: 'args')), + ).thenAnswer( + (_) async => + IosFrameworkBuildResult(kernelFile: File('/path/to/app.dill')), + ); + when(() => artifactManager.getAppXcframeworkDirectory()).thenReturn( + Directory( + p.join(projectRoot.path, 'build', 'ios', 'framework', 'Release'), + ), + ); + + setUpProjectRootArtifacts(); + }); + + group('when stale build/ios/shorebird directory exists', () { + late Directory shorebirdSupplementDir; + + setUp(() { + shorebirdSupplementDir = Directory( + p.join(projectRoot.path, 'build', 'ios', 'shorebird'), + )..createSync(recursive: true); + when( + () => artifactManager.getIosReleaseSupplementDirectory(), + ).thenReturn(shorebirdSupplementDir); + }); + + test('deletes the directory', () async { + expect(shorebirdSupplementDir.existsSync(), isTrue); + await runWithOverrides(iosFrameworkReleaser.buildReleaseArtifacts); + expect(shorebirdSupplementDir.existsSync(), isFalse); }); }); - group('buildReleaseArtifacts', () { - void setUpProjectRootArtifacts() { - // Create an xcframework in the release directory to simulate running - // this command a subsequent time. - Directory(p.join(projectRoot.path, 'release', 'Flutter.xcframework')) - .createSync(recursive: true); - Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - 'Flutter.xcframework', - ), - ).createSync(recursive: true); - } - + group('when platform was specified via arg results rest', () { setUp(() { - when( - () => artifactBuilder.buildIosFramework(args: any(named: 'args')), - ).thenAnswer( - (_) async => IosFrameworkBuildResult( - kernelFile: File('/path/to/app.dill'), - ), - ); - when(() => artifactManager.getAppXcframeworkDirectory()).thenReturn( - Directory( - p.join( - projectRoot.path, - 'build', - 'ios', - 'framework', - 'Release', - ), - ), - ); - - setUpProjectRootArtifacts(); - }); - - group('when stale build/ios/shorebird directory exists', () { - late Directory shorebirdSupplementDir; - - setUp(() { - shorebirdSupplementDir = Directory( - p.join(projectRoot.path, 'build', 'ios', 'shorebird'), - )..createSync(recursive: true); - when( - () => artifactManager.getIosReleaseSupplementDirectory(), - ).thenReturn(shorebirdSupplementDir); - }); - - test('deletes the directory', () async { - expect(shorebirdSupplementDir.existsSync(), isTrue); - await runWithOverrides( - iosFrameworkReleaser.buildReleaseArtifacts, - ); - expect(shorebirdSupplementDir.existsSync(), isFalse); - }); - }); - - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['ios', '--verbose']); - }); - - test('produces xcframework in release directory', () async { - final xcframework = await runWithOverrides( - iosFrameworkReleaser.buildReleaseArtifacts, - ); - - expect(xcframework.path, p.join(projectRoot.path, 'release')); - verify( - () => artifactBuilder.buildIosFramework(args: ['--verbose']), - ).called(1); - }); + when(() => argResults.rest).thenReturn(['ios', '--verbose']); }); test('produces xcframework in release directory', () async { @@ -352,135 +321,141 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', expect(xcframework.path, p.join(projectRoot.path, 'release')); verify( - () => artifactBuilder.buildIosFramework(args: []), + () => artifactBuilder.buildIosFramework(args: ['--verbose']), ).called(1); }); }); - group('getReleaseVersion', () { - const releaseVersion = '1.0.0'; - setUp(() { - when(() => argResults['release-version']).thenReturn(releaseVersion); - }); - - test('returns value from argResults', () async { - final result = await runWithOverrides( - () => iosFrameworkReleaser.getReleaseVersion( - releaseArtifactRoot: Directory(''), - ), - ); - expect(result, releaseVersion); - }); - }); - - group('uploadReleaseArtifacts', () { - const releaseVersion = '1.0.0'; - const appId = 'appId'; - const flutterRevision = 'deadbeef'; - const flutterVersion = '3.22.1'; - - final release = Release( - id: 42, - appId: appId, - version: releaseVersion, - flutterRevision: flutterRevision, - flutterVersion: flutterVersion, - displayName: '1.2.3+1', - platformStatuses: {}, - createdAt: DateTime(2023), - updatedAt: DateTime(2023), + test('produces xcframework in release directory', () async { + final xcframework = await runWithOverrides( + iosFrameworkReleaser.buildReleaseArtifacts, ); - setUp(() { - when( - () => codePushClientWrapper.createIosFrameworkReleaseArtifacts( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - appFrameworkPath: any(named: 'appFrameworkPath'), - supplementPath: any(named: 'supplementPath'), - ), - ).thenAnswer((_) async {}); - }); + expect(xcframework.path, p.join(projectRoot.path, 'release')); + verify(() => artifactBuilder.buildIosFramework(args: [])).called(1); + }); + }); - test('uploads artifacts', () async { - await runWithOverrides( - () => iosFrameworkReleaser.uploadReleaseArtifacts( - release: release, - appId: appId, - ), - ); - - verify( - () => codePushClientWrapper.createIosFrameworkReleaseArtifacts( - appId: appId, - releaseId: release.id, - appFrameworkPath: p.join( - projectRoot.path, - 'release', - ArtifactManager.appXcframeworkName, - ), - supplementPath: null, - ), - ).called(1); - }); + group('getReleaseVersion', () { + const releaseVersion = '1.0.0'; + setUp(() { + when(() => argResults['release-version']).thenReturn(releaseVersion); }); - group('updatedReleaseMetadata', () { - const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; - const operatingSystem = 'macos'; - const operatingSystemVersion = '11.0.0'; - const xcodeVersion = '123'; - const metadata = UpdateReleaseMetadata( - releasePlatform: ReleasePlatform.ios, - flutterVersionOverride: null, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + test('returns value from argResults', () async { + final result = await runWithOverrides( + () => iosFrameworkReleaser.getReleaseVersion( + releaseArtifactRoot: Directory(''), + ), + ); + expect(result, releaseVersion); + }); + }); + + group('uploadReleaseArtifacts', () { + const releaseVersion = '1.0.0'; + const appId = 'appId'; + const flutterRevision = 'deadbeef'; + const flutterVersion = '3.22.1'; + + final release = Release( + id: 42, + appId: appId, + version: releaseVersion, + flutterRevision: flutterRevision, + flutterVersion: flutterVersion, + displayName: '1.2.3+1', + platformStatuses: {}, + createdAt: DateTime(2023), + updatedAt: DateTime(2023), + ); + + setUp(() { + when( + () => codePushClientWrapper.createIosFrameworkReleaseArtifacts( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + appFrameworkPath: any(named: 'appFrameworkPath'), + supplementPath: any(named: 'supplementPath'), + ), + ).thenAnswer((_) async {}); + }); + + test('uploads artifacts', () async { + await runWithOverrides( + () => iosFrameworkReleaser.uploadReleaseArtifacts( + release: release, + appId: appId, ), ); - setUp(() { - when( - () => xcodeBuild.version(), - ).thenAnswer((_) async => xcodeVersion); - }); + verify( + () => codePushClientWrapper.createIosFrameworkReleaseArtifacts( + appId: appId, + releaseId: release.id, + appFrameworkPath: p.join( + projectRoot.path, + 'release', + ArtifactManager.appXcframeworkName, + ), + supplementPath: null, + ), + ).called(1); + }); + }); - test('returns expected metadata', () async { - expect( - runWithOverrides( - () => iosFrameworkReleaser.updatedReleaseMetadata(metadata), - ), - completion( - const UpdateReleaseMetadata( - releasePlatform: ReleasePlatform.ios, - flutterVersionOverride: null, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - xcodeVersion: xcodeVersion, - ), - ), - ), - ); - }); + group('updatedReleaseMetadata', () { + const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; + const operatingSystem = 'macos'; + const operatingSystemVersion = '11.0.0'; + const xcodeVersion = '123'; + const metadata = UpdateReleaseMetadata( + releasePlatform: ReleasePlatform.ios, + flutterVersionOverride: null, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); + + setUp(() { + when(() => xcodeBuild.version()).thenAnswer((_) async => xcodeVersion); }); - group('postReleaseInstructions', () { - test('returns expected instructions', () { - final relativeFrameworkDirectoryPath = p.relative( - p.join(projectRoot.path, 'release'), - ); - expect( - runWithOverrides( - () => iosFrameworkReleaser.postReleaseInstructions, + test('returns expected metadata', () async { + expect( + runWithOverrides( + () => iosFrameworkReleaser.updatedReleaseMetadata(metadata), + ), + completion( + const UpdateReleaseMetadata( + releasePlatform: ReleasePlatform.ios, + flutterVersionOverride: null, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + xcodeVersion: xcodeVersion, + ), ), - equals(''' + ), + ); + }); + }); + + group('postReleaseInstructions', () { + test('returns expected instructions', () { + final relativeFrameworkDirectoryPath = p.relative( + p.join(projectRoot.path, 'release'), + ); + expect( + runWithOverrides(() => iosFrameworkReleaser.postReleaseInstructions), + equals(''' Your next step is to add the .xcframework files found in the ${lightCyan.wrap(relativeFrameworkDirectoryPath)} directory to your iOS app. @@ -490,10 +465,8 @@ To do this: Instructions for these steps can be found at https://docs.flutter.dev/add-to-app/ios/project-setup#option-b---embed-frameworks-in-xcode. '''), - ); - }); + ); }); - }, - testOn: 'mac-os', - ); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart index 986fdb10..ae7b7831 100644 --- a/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart @@ -37,414 +37,386 @@ import '../../matchers.dart'; import '../../mocks.dart'; void main() { - group( - IosReleaser, - () { - late Apple apple; - late ArgResults argResults; - late ArtifactBuilder artifactBuilder; - late ArtifactManager artifactManager; - late CodePushClientWrapper codePushClientWrapper; - late CodeSigner codeSigner; - late Directory projectRoot; - late Doctor doctor; - late FlavorValidator flavorValidator; - late Progress progress; - late ShorebirdLogger logger; - late OperatingSystemInterface operatingSystemInterface; - late ShorebirdProcess shorebirdProcess; - late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutter; - late ShorebirdValidator shorebirdValidator; - late XcodeBuild xcodeBuild; - late IosReleaser iosReleaser; + group(IosReleaser, () { + late Apple apple; + late ArgResults argResults; + late ArtifactBuilder artifactBuilder; + late ArtifactManager artifactManager; + late CodePushClientWrapper codePushClientWrapper; + late CodeSigner codeSigner; + late Directory projectRoot; + late Doctor doctor; + late FlavorValidator flavorValidator; + late Progress progress; + late ShorebirdLogger logger; + late OperatingSystemInterface operatingSystemInterface; + late ShorebirdProcess shorebirdProcess; + late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutter shorebirdFlutter; + late ShorebirdValidator shorebirdValidator; + late XcodeBuild xcodeBuild; + late IosReleaser iosReleaser; - R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - appleRef.overrideWith(() => apple), - artifactBuilderRef.overrideWith(() => artifactBuilder), - artifactManagerRef.overrideWith(() => artifactManager), - codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - codeSignerRef.overrideWith(() => codeSigner), - doctorRef.overrideWith(() => doctor), - loggerRef.overrideWith(() => logger), - osInterfaceRef.overrideWith(() => operatingSystemInterface), - processRef.overrideWith(() => shorebirdProcess), - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), - shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - xcodeBuildRef.overrideWith(() => xcodeBuild), - }, - ); - } + R runWithOverrides(R Function() body) { + return runScoped( + body, + values: { + appleRef.overrideWith(() => apple), + artifactBuilderRef.overrideWith(() => artifactBuilder), + artifactManagerRef.overrideWith(() => artifactManager), + codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + codeSignerRef.overrideWith(() => codeSigner), + doctorRef.overrideWith(() => doctor), + loggerRef.overrideWith(() => logger), + osInterfaceRef.overrideWith(() => operatingSystemInterface), + processRef.overrideWith(() => shorebirdProcess), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), + xcodeBuildRef.overrideWith(() => xcodeBuild), + }, + ); + } - setUpAll(() { - registerFallbackValue(Directory('')); - registerFallbackValue(File('')); - registerFallbackValue(ReleasePlatform.android); + setUpAll(() { + registerFallbackValue(Directory('')); + registerFallbackValue(File('')); + registerFallbackValue(ReleasePlatform.android); + }); + + setUp(() { + apple = MockApple(); + argResults = MockArgResults(); + artifactBuilder = MockArtifactBuilder(); + artifactManager = MockArtifactManager(); + codePushClientWrapper = MockCodePushClientWrapper(); + codeSigner = MockCodeSigner(); + doctor = MockDoctor(); + flavorValidator = MockFlavorValidator(); + projectRoot = Directory.systemTemp.createTempSync(); + operatingSystemInterface = MockOperatingSystemInterface(); + progress = MockProgress(); + logger = MockShorebirdLogger(); + shorebirdProcess = MockShorebirdProcess(); + shorebirdEnv = MockShorebirdEnv(); + shorebirdFlutter = MockShorebirdFlutter(); + shorebirdValidator = MockShorebirdValidator(); + xcodeBuild = MockXcodeBuild(); + + when(() => argResults.rest).thenReturn([]); + when(() => argResults.wasParsed(any())).thenReturn(false); + + when(() => logger.progress(any())).thenReturn(progress); + + iosReleaser = IosReleaser( + argResults: argResults, + flavor: null, + target: null, + ); + }); + + group('releaseType', () { + test('is ios', () { + expect(iosReleaser.releaseType, ReleaseType.ios); }); + }); + + group('artifactDisplayName', () { + test('has expected value', () { + expect(iosReleaser.artifactDisplayName, 'iOS app'); + }); + }); + + group('assertPreconditions', () { + final flutterVersion = Version(3, 0, 0); setUp(() { - apple = MockApple(); - argResults = MockArgResults(); - artifactBuilder = MockArtifactBuilder(); - artifactManager = MockArtifactManager(); - codePushClientWrapper = MockCodePushClientWrapper(); - codeSigner = MockCodeSigner(); - doctor = MockDoctor(); - flavorValidator = MockFlavorValidator(); - projectRoot = Directory.systemTemp.createTempSync(); - operatingSystemInterface = MockOperatingSystemInterface(); - progress = MockProgress(); - logger = MockShorebirdLogger(); - shorebirdProcess = MockShorebirdProcess(); - shorebirdEnv = MockShorebirdEnv(); - shorebirdFlutter = MockShorebirdFlutter(); - shorebirdValidator = MockShorebirdValidator(); - xcodeBuild = MockXcodeBuild(); - - when(() => argResults.rest).thenReturn([]); - when(() => argResults.wasParsed(any())).thenReturn(false); - - when(() => logger.progress(any())).thenReturn(progress); - - iosReleaser = IosReleaser( - argResults: argResults, - flavor: null, - target: null, - ); + when(() => doctor.iosCommandValidators).thenReturn([flavorValidator]); + when( + () => shorebirdFlutter.resolveFlutterVersion(any()), + ).thenAnswer((_) async => flutterVersion); + when(flavorValidator.validate).thenAnswer((_) async => []); }); - group('releaseType', () { - test('is ios', () { - expect(iosReleaser.releaseType, ReleaseType.ios); + group('when validation succeeds', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + }); + + test('returns normally', () async { + await expectLater( + () => runWithOverrides(iosReleaser.assertPreconditions), + returnsNormally, + ); }); }); - group('artifactDisplayName', () { - test('has expected value', () { - expect(iosReleaser.artifactDisplayName, 'iOS app'); - }); - }); - - group('assertPreconditions', () { - final flutterVersion = Version(3, 0, 0); + group('when validation fails', () { + final exception = ValidationFailedException(); setUp(() { - when(() => doctor.iosCommandValidators).thenReturn([flavorValidator]); - when(() => shorebirdFlutter.resolveFlutterVersion(any())) - .thenAnswer((_) async => flutterVersion); - when(flavorValidator.validate).thenAnswer((_) async => []); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenThrow(exception); }); - group('when validation succeeds', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenAnswer((_) async {}); - }); - - test('returns normally', () async { - await expectLater( - () => runWithOverrides(iosReleaser.assertPreconditions), - returnsNormally, - ); - }); - }); - - group('when validation fails', () { - final exception = ValidationFailedException(); - - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenThrow(exception); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(iosReleaser.assertPreconditions), - exitsWithCode(exception.exitCode), - ); - verify( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: true, - checkShorebirdInitialized: true, - validators: [flavorValidator], - supportedOperatingSystems: {Platform.macOS}, - ), - ).called(1); - }); - }); - - group('when specified flutter version is less than minimum', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenAnswer((_) async {}); - when(() => argResults['flutter-version']).thenReturn('3.0.0'); - }); - - test('logs error and exits with code 64', () async { - await expectLater( - () => runWithOverrides(iosReleaser.assertPreconditions), - exitsWithCode(ExitCode.usage), - ); - - verify( - () => logger.err( - ''' -iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), - ).called(1); - }); + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(iosReleaser.assertPreconditions), + exitsWithCode(exception.exitCode), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flavorValidator], + supportedOperatingSystems: {Platform.macOS}, + ), + ).called(1); }); }); - group('assertArgsAreValid', () { - group('when release-version is passed', () { - setUp(() { - when(() => argResults.wasParsed('release-version')) - .thenReturn(true); - }); + group('when specified flutter version is less than minimum', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + when(() => argResults['flutter-version']).thenReturn('3.0.0'); + }); - test('logs error and exits with usage err', () async { - await expectLater( - () => runWithOverrides(iosReleaser.assertArgsAreValid), - exitsWithCode(ExitCode.usage), - ); + test('logs error and exits with code 64', () async { + await expectLater( + () => runWithOverrides(iosReleaser.assertPreconditions), + exitsWithCode(ExitCode.usage), + ); - verify( - () => logger.err( - ''' + verify( + () => logger.err(''' +iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion. +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), + ).called(1); + }); + }); + }); + + group('assertArgsAreValid', () { + group('when release-version is passed', () { + setUp(() { + when(() => argResults.wasParsed('release-version')).thenReturn(true); + }); + + test('logs error and exits with usage err', () async { + await expectLater( + () => runWithOverrides(iosReleaser.assertArgsAreValid), + exitsWithCode(ExitCode.usage), + ); + + verify( + () => logger.err( + ''' The "--release-version" flag is only supported for aar and ios-framework releases. To change the version of this release, change your app's version in your pubspec.yaml.''', - ), - ).called(1); - }); - }); - - group('when --obfuscate is passed', () { - setUp(() { - when(() => argResults.rest).thenReturn(['--obfuscate']); - }); - - test('logs error and exits', () async { - await expectLater( - runWithOverrides(iosReleaser.assertArgsAreValid), - exitsWithCode(ExitCode.unavailable), - ); - - verify( - () => logger.err( - 'Shorebird does not currently support obfuscation on iOS.', - ), - ).called(1); - verify( - () => logger.info( - '''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''', - ), - ).called(1); - }); - }); - - group('when --obfuscate is not passed', () { - test('returns normally', () async { - await expectLater( - runWithOverrides(iosReleaser.assertArgsAreValid), - completes, - ); - }); + ), + ).called(1); }); }); - group('buildReleaseArtifacts', () { - const flutterVersionAndRevision = '3.10.6 (83305b5088)'; - const base64PublicKey = 'base64PublicKey'; - - late Directory xcarchiveDirectory; - late Directory iosAppDirectory; - + group('when --obfuscate is passed', () { setUp(() { - xcarchiveDirectory = Directory.systemTemp.createTempSync(); - iosAppDirectory = Directory.systemTemp.createTempSync(); - when(() => argResults['codesign']).thenReturn(true); + when(() => argResults.rest).thenReturn(['--obfuscate']); + }); + + test('logs error and exits', () async { + await expectLater( + runWithOverrides(iosReleaser.assertArgsAreValid), + exitsWithCode(ExitCode.unavailable), + ); + + verify( + () => logger.err( + 'Shorebird does not currently support obfuscation on iOS.', + ), + ).called(1); + verify( + () => logger.info( + '''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''', + ), + ).called(1); + }); + }); + + group('when --obfuscate is not passed', () { + test('returns normally', () async { + await expectLater( + runWithOverrides(iosReleaser.assertArgsAreValid), + completes, + ); + }); + }); + }); + + group('buildReleaseArtifacts', () { + const flutterVersionAndRevision = '3.10.6 (83305b5088)'; + const base64PublicKey = 'base64PublicKey'; + + late Directory xcarchiveDirectory; + late Directory iosAppDirectory; + + setUp(() { + xcarchiveDirectory = Directory.systemTemp.createTempSync(); + iosAppDirectory = Directory.systemTemp.createTempSync(); + when(() => argResults['codesign']).thenReturn(true); + when( + () => artifactBuilder.buildIpa( + codesign: any(named: 'codesign'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + args: any(named: 'args'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer( + (_) async => IpaBuildResult(kernelFile: File('/path/to/app.dill')), + ); + + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn(iosAppDirectory); + when( + () => artifactManager.getXcarchiveDirectory(), + ).thenReturn(xcarchiveDirectory); + + when( + () => codeSigner.base64PublicKey(any()), + ).thenReturn(base64PublicKey); + + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); + when( + () => shorebirdFlutter.getVersionAndRevision(), + ).thenAnswer((_) async => flutterVersionAndRevision); + }); + + group('when a patch signing key path is provided', () { + setUp(() { + final patchSigningPublicKeyFile = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'patch-signing-public-key.pem', + ), + )..createSync(recursive: true); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(patchSigningPublicKeyFile.path); + when( () => artifactBuilder.buildIpa( codesign: any(named: 'codesign'), flavor: any(named: 'flavor'), target: any(named: 'target'), args: any(named: 'args'), + base64PublicKey: any(named: 'base64PublicKey'), buildProgress: any(named: 'buildProgress'), ), ).thenAnswer( - (_) async => IpaBuildResult( - kernelFile: File('/path/to/app.dill'), - ), + (_) async => IpaBuildResult(kernelFile: File('/path/to/app.dill')), ); - - when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), - ), - ).thenReturn(iosAppDirectory); - when( - () => artifactManager.getXcarchiveDirectory(), - ).thenReturn(xcarchiveDirectory); - - when( - () => codeSigner.base64PublicKey(any()), - ).thenReturn(base64PublicKey); - - when( - () => shorebirdEnv.getShorebirdProjectRoot(), - ).thenReturn(projectRoot); - when( - () => shorebirdFlutter.getVersionAndRevision(), - ).thenAnswer((_) async => flutterVersionAndRevision); }); - group('when a patch signing key path is provided', () { - setUp(() { - final patchSigningPublicKeyFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'patch-signing-public-key.pem', - ), - )..createSync(recursive: true); - when( - () => argResults[CommonArguments.publicKeyArg.name], - ).thenReturn(patchSigningPublicKeyFile.path); + test( + 'encodes the patch signing public key and forward it to buildIpa', + () async { + await runWithOverrides(() => iosReleaser.buildReleaseArtifacts()); - when( + verify( () => artifactBuilder.buildIpa( codesign: any(named: 'codesign'), flavor: any(named: 'flavor'), target: any(named: 'target'), args: any(named: 'args'), - base64PublicKey: any(named: 'base64PublicKey'), - buildProgress: any(named: 'buildProgress'), - ), - ).thenAnswer( - (_) async => IpaBuildResult( - kernelFile: File('/path/to/app.dill'), - ), - ); - }); - - test( - 'encodes the patch signing public key and forward it to buildIpa', - () async { - await runWithOverrides( - () => iosReleaser.buildReleaseArtifacts(), - ); - - verify( - () => artifactBuilder.buildIpa( - codesign: any(named: 'codesign'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), - args: any(named: 'args'), - base64PublicKey: base64PublicKey, - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }, - ); - }); - - group('when not codesigning', () { - setUp(() { - when(() => argResults['codesign']).thenReturn(false); - }); - - test('logs warning about patching', () async { - await runWithOverrides(iosReleaser.buildReleaseArtifacts); - - verify( - () => logger.info( - '''Building for device with codesigning disabled. You will have to manually codesign before deploying to device.''', - ), - ).called(1); - verify( - () => logger.warn( - '''shorebird preview will not work for releases created with "--no-codesign". However, you can still preview your app by signing the generated .xcarchive in Xcode.''', - ), - ).called(1); - }); - }); - - group('when stale build/ios/shorebird directory exists', () { - late Directory shorebirdSupplementDir; - - setUp(() { - shorebirdSupplementDir = Directory( - p.join(projectRoot.path, 'build', 'ios', 'shorebird'), - )..createSync(recursive: true); - when( - () => artifactManager.getIosReleaseSupplementDirectory(), - ).thenReturn(shorebirdSupplementDir); - }); - - test('deletes the directory', () async { - expect(shorebirdSupplementDir.existsSync(), isTrue); - await runWithOverrides(iosReleaser.buildReleaseArtifacts); - expect(shorebirdSupplementDir.existsSync(), isFalse); - }); - }); - - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['ios', '--verbose']); - }); - - test('verifies artifacts exist and returns xcarchive path', () async { - expect( - await runWithOverrides(iosReleaser.buildReleaseArtifacts), - equals(xcarchiveDirectory), - ); - - verify(() => artifactManager.getXcarchiveDirectory()).called(1); - verify( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: xcarchiveDirectory, - ), - ).called(1); - verify( - () => artifactBuilder.buildIpa( - args: ['--verbose'], + base64PublicKey: base64PublicKey, buildProgress: any(named: 'buildProgress'), ), ).called(1); - }); + }, + ); + }); + + group('when not codesigning', () { + setUp(() { + when(() => argResults['codesign']).thenReturn(false); + }); + + test('logs warning about patching', () async { + await runWithOverrides(iosReleaser.buildReleaseArtifacts); + + verify( + () => logger.info( + '''Building for device with codesigning disabled. You will have to manually codesign before deploying to device.''', + ), + ).called(1); + verify( + () => logger.warn( + '''shorebird preview will not work for releases created with "--no-codesign". However, you can still preview your app by signing the generated .xcarchive in Xcode.''', + ), + ).called(1); + }); + }); + + group('when stale build/ios/shorebird directory exists', () { + late Directory shorebirdSupplementDir; + + setUp(() { + shorebirdSupplementDir = Directory( + p.join(projectRoot.path, 'build', 'ios', 'shorebird'), + )..createSync(recursive: true); + when( + () => artifactManager.getIosReleaseSupplementDirectory(), + ).thenReturn(shorebirdSupplementDir); + }); + + test('deletes the directory', () async { + expect(shorebirdSupplementDir.existsSync(), isTrue); + await runWithOverrides(iosReleaser.buildReleaseArtifacts); + expect(shorebirdSupplementDir.existsSync(), isFalse); + }); + }); + + group('when platform was specified via arg results rest', () { + setUp(() { + when(() => argResults.rest).thenReturn(['ios', '--verbose']); }); test('verifies artifacts exist and returns xcarchive path', () async { @@ -459,81 +431,99 @@ To change the version of this release, change your app's version in your pubspec xcarchiveDirectory: xcarchiveDirectory, ), ).called(1); - }); - - group('when xcarchive not found after build', () { - setUp(() { - when(() => artifactManager.getXcarchiveDirectory()) - .thenReturn(null); - }); - - test('logs message and exits with code 70', () async { - await expectLater( - () => runWithOverrides(iosReleaser.buildReleaseArtifacts), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err('Unable to find .xcarchive directory'), - ).called(1); - }); - }); - - group('when app not found after build', () { - setUp(() { - when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), - ), - ).thenReturn(null); - }); - - test('logs message and exits with code 70', () async { - await expectLater( - () => runWithOverrides(iosReleaser.buildReleaseArtifacts), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err('Unable to find .app directory'), - ).called(1); - }); + verify( + () => artifactBuilder.buildIpa( + args: ['--verbose'], + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); }); }); - group('getReleaseVersion', () { - late Directory xcarchiveDirectory; + test('verifies artifacts exist and returns xcarchive path', () async { + expect( + await runWithOverrides(iosReleaser.buildReleaseArtifacts), + equals(xcarchiveDirectory), + ); + verify(() => artifactManager.getXcarchiveDirectory()).called(1); + verify( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: xcarchiveDirectory, + ), + ).called(1); + }); + + group('when xcarchive not found after build', () { setUp(() { - xcarchiveDirectory = Directory.systemTemp.createTempSync(); + when(() => artifactManager.getXcarchiveDirectory()).thenReturn(null); }); - group('when plist does not exist', () { - test('logs error and exits', () async { - await expectLater( - () => runWithOverrides( - () => iosReleaser.getReleaseVersion( - releaseArtifactRoot: xcarchiveDirectory, - ), - ), - exitsWithCode(ExitCode.software), - ); + test('logs message and exits with code 70', () async { + await expectLater( + () => runWithOverrides(iosReleaser.buildReleaseArtifacts), + exitsWithCode(ExitCode.software), + ); - verify( - () => logger.err( - '''No Info.plist file found at ${p.join(xcarchiveDirectory.path, 'Info.plist')}''', - ), - ).called(1); - }); + verify( + () => logger.err('Unable to find .xcarchive directory'), + ).called(1); + }); + }); + + group('when app not found after build', () { + setUp(() { + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn(null); }); - group('when plist does not contain version number', () { - late File plist; - setUp(() { - plist = File(p.join(xcarchiveDirectory.path, 'Info.plist')) - ..createSync() - ..writeAsStringSync( - ''' + test('logs message and exits with code 70', () async { + await expectLater( + () => runWithOverrides(iosReleaser.buildReleaseArtifacts), + exitsWithCode(ExitCode.software), + ); + + verify(() => logger.err('Unable to find .app directory')).called(1); + }); + }); + }); + + group('getReleaseVersion', () { + late Directory xcarchiveDirectory; + + setUp(() { + xcarchiveDirectory = Directory.systemTemp.createTempSync(); + }); + + group('when plist does not exist', () { + test('logs error and exits', () async { + await expectLater( + () => runWithOverrides( + () => iosReleaser.getReleaseVersion( + releaseArtifactRoot: xcarchiveDirectory, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err( + '''No Info.plist file found at ${p.join(xcarchiveDirectory.path, 'Info.plist')}''', + ), + ).called(1); + }); + }); + + group('when plist does not contain version number', () { + late File plist; + setUp(() { + plist = + File(p.join(xcarchiveDirectory.path, 'Info.plist')) + ..createSync() + ..writeAsStringSync(''' @@ -543,38 +533,36 @@ To change the version of this release, change your app's version in your pubspec ' -''', - ); - }); - - test('logs error and exits', () async { - await expectLater( - () => runWithOverrides( - () => iosReleaser.getReleaseVersion( - releaseArtifactRoot: xcarchiveDirectory, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - any( - that: startsWith( - 'Failed to determine release version from ${plist.path}', - ), - ), - ), - ).called(1); - }); +'''); }); - group('when plist contains version number', () { - setUp(() { - File(p.join(xcarchiveDirectory.path, 'Info.plist')) - ..createSync() - ..writeAsStringSync( - ''' + test('logs error and exits', () async { + await expectLater( + () => runWithOverrides( + () => iosReleaser.getReleaseVersion( + releaseArtifactRoot: xcarchiveDirectory, + ), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err( + any( + that: startsWith( + 'Failed to determine release version from ${plist.path}', + ), + ), + ), + ).called(1); + }); + }); + + group('when plist contains version number', () { + setUp(() { + File(p.join(xcarchiveDirectory.path, 'Info.plist')) + ..createSync() + ..writeAsStringSync(''' @@ -601,175 +589,172 @@ To change the version of this release, change your app's version in your pubspec SchemeName Runner -''', - ); - }); +'''); + }); - test('returns version number from plist', () async { - expect( - await runWithOverrides( - () => iosReleaser.getReleaseVersion( - releaseArtifactRoot: xcarchiveDirectory, - ), + test('returns version number from plist', () async { + expect( + await runWithOverrides( + () => iosReleaser.getReleaseVersion( + releaseArtifactRoot: xcarchiveDirectory, ), - equals('1.2.3+1'), - ); - }); - }); - }); - - group('uploadReleaseArtifacts', () { - const appId = 'appId'; - const releaseVersion = '1.0.0'; - const flutterRevision = 'deadbeef'; - const flutterVersion = '3.22.0'; - const codesign = true; - const podfileLockContent = 'podfile-lock'; - - final release = Release( - id: 42, - appId: appId, - version: releaseVersion, - flutterRevision: flutterRevision, - flutterVersion: flutterVersion, - displayName: '1.2.3+1', - platformStatuses: {}, - createdAt: DateTime(2023), - updatedAt: DateTime(2023), - ); - - late Directory xcarchiveDirectory; - late Directory iosAppDirectory; - late Directory supplementDirectory; - late File podfileLockFile; - - setUp(() { - when(() => argResults['codesign']).thenReturn(codesign); - - xcarchiveDirectory = Directory.systemTemp.createTempSync(); - iosAppDirectory = Directory.systemTemp.createTempSync(); - supplementDirectory = Directory.systemTemp.createTempSync(); - podfileLockFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'Podfile.lock', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync(podfileLockContent); - when(artifactManager.getXcarchiveDirectory) - .thenReturn(xcarchiveDirectory); - when( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: any(named: 'xcarchiveDirectory'), - ), - ).thenReturn(iosAppDirectory); - when( - () => artifactManager.getIosReleaseSupplementDirectory(), - ).thenReturn(supplementDirectory); - when( - () => codePushClientWrapper.createIosReleaseArtifacts( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - xcarchivePath: any(named: 'xcarchivePath'), - runnerPath: any(named: 'runnerPath'), - isCodesigned: any(named: 'isCodesigned'), - podfileLockHash: any(named: 'podfileLockHash'), - supplementPath: any(named: 'supplementPath'), - ), - ).thenAnswer((_) async => {}); - when(() => shorebirdEnv.iosPodfileLockFile) - .thenReturn(podfileLockFile); - }); - - test('forwards call to codePushClientWrapper', () async { - await runWithOverrides( - () => iosReleaser.uploadReleaseArtifacts( - release: release, - appId: appId, ), + equals('1.2.3+1'), ); - - verify( - () => codePushClientWrapper.createIosReleaseArtifacts( - appId: appId, - releaseId: release.id, - xcarchivePath: xcarchiveDirectory.path, - runnerPath: iosAppDirectory.path, - isCodesigned: codesign, - podfileLockHash: - '${sha256.convert(utf8.encode(podfileLockContent))}', - supplementPath: supplementDirectory.path, - ), - ).called(1); }); }); + }); - group('updatedReleaseMetadata', () { - const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; - const operatingSystem = 'macOS'; - const operatingSystemVersion = '11.0.0'; - const xcodeVersion = '123'; - const flutterVersionOverride = '1.2.3'; - const metadata = UpdateReleaseMetadata( - releasePlatform: ReleasePlatform.ios, - flutterVersionOverride: flutterVersionOverride, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + group('uploadReleaseArtifacts', () { + const appId = 'appId'; + const releaseVersion = '1.0.0'; + const flutterRevision = 'deadbeef'; + const flutterVersion = '3.22.0'; + const codesign = true; + const podfileLockContent = 'podfile-lock'; + + final release = Release( + id: 42, + appId: appId, + version: releaseVersion, + flutterRevision: flutterRevision, + flutterVersion: flutterVersion, + displayName: '1.2.3+1', + platformStatuses: {}, + createdAt: DateTime(2023), + updatedAt: DateTime(2023), + ); + + late Directory xcarchiveDirectory; + late Directory iosAppDirectory; + late Directory supplementDirectory; + late File podfileLockFile; + + setUp(() { + when(() => argResults['codesign']).thenReturn(codesign); + + xcarchiveDirectory = Directory.systemTemp.createTempSync(); + iosAppDirectory = Directory.systemTemp.createTempSync(); + supplementDirectory = Directory.systemTemp.createTempSync(); + podfileLockFile = + File( + p.join( + Directory.systemTemp.createTempSync().path, + 'Podfile.lock', + ), + ) + ..createSync(recursive: true) + ..writeAsStringSync(podfileLockContent); + when( + artifactManager.getXcarchiveDirectory, + ).thenReturn(xcarchiveDirectory); + when( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: any(named: 'xcarchiveDirectory'), + ), + ).thenReturn(iosAppDirectory); + when( + () => artifactManager.getIosReleaseSupplementDirectory(), + ).thenReturn(supplementDirectory); + when( + () => codePushClientWrapper.createIosReleaseArtifacts( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + xcarchivePath: any(named: 'xcarchivePath'), + runnerPath: any(named: 'runnerPath'), + isCodesigned: any(named: 'isCodesigned'), + podfileLockHash: any(named: 'podfileLockHash'), + supplementPath: any(named: 'supplementPath'), + ), + ).thenAnswer((_) async => {}); + when(() => shorebirdEnv.iosPodfileLockFile).thenReturn(podfileLockFile); + }); + + test('forwards call to codePushClientWrapper', () async { + await runWithOverrides( + () => iosReleaser.uploadReleaseArtifacts( + release: release, + appId: appId, ), ); - setUp(() { - when( - () => xcodeBuild.version(), - ).thenAnswer((_) async => xcodeVersion); - }); + verify( + () => codePushClientWrapper.createIosReleaseArtifacts( + appId: appId, + releaseId: release.id, + xcarchivePath: xcarchiveDirectory.path, + runnerPath: iosAppDirectory.path, + isCodesigned: codesign, + podfileLockHash: + '${sha256.convert(utf8.encode(podfileLockContent))}', + supplementPath: supplementDirectory.path, + ), + ).called(1); + }); + }); - test('returns expected metadata', () async { - expect( - runWithOverrides( - () => iosReleaser.updatedReleaseMetadata(metadata), - ), - completion( - const UpdateReleaseMetadata( - releasePlatform: ReleasePlatform.ios, - flutterVersionOverride: flutterVersionOverride, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - xcodeVersion: xcodeVersion, - ), - ), - ), - ); - }); + group('updatedReleaseMetadata', () { + const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; + const operatingSystem = 'macOS'; + const operatingSystemVersion = '11.0.0'; + const xcodeVersion = '123'; + const flutterVersionOverride = '1.2.3'; + const metadata = UpdateReleaseMetadata( + releasePlatform: ReleasePlatform.ios, + flutterVersionOverride: flutterVersionOverride, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); + + setUp(() { + when(() => xcodeBuild.version()).thenAnswer((_) async => xcodeVersion); }); - group('postReleaseInstructions', () { - late Directory xcarchiveDirectory; + test('returns expected metadata', () async { + expect( + runWithOverrides(() => iosReleaser.updatedReleaseMetadata(metadata)), + completion( + const UpdateReleaseMetadata( + releasePlatform: ReleasePlatform.ios, + flutterVersionOverride: flutterVersionOverride, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + xcodeVersion: xcodeVersion, + ), + ), + ), + ); + }); + }); + group('postReleaseInstructions', () { + late Directory xcarchiveDirectory; + + setUp(() { + xcarchiveDirectory = Directory.systemTemp.createTempSync(); + when( + () => artifactManager.getXcarchiveDirectory(), + ).thenReturn(xcarchiveDirectory); + }); + + group('when codesigning', () { setUp(() { - xcarchiveDirectory = Directory.systemTemp.createTempSync(); - when(() => artifactManager.getXcarchiveDirectory()) - .thenReturn(xcarchiveDirectory); + when(() => argResults['codesign']).thenReturn(true); }); - group('when codesigning', () { - setUp(() { - when(() => argResults['codesign']).thenReturn(true); - }); - - test('prints ipa upload steps', () { - expect( - runWithOverrides(() => iosReleaser.postReleaseInstructions), - equals(''' + test('prints ipa upload steps', () { + expect( + runWithOverrides(() => iosReleaser.postReleaseInstructions), + equals(''' Your next step is to upload your app to App Store Connect. @@ -779,20 +764,19 @@ To upload to the App Store, do one of the following: 3. Run ${lightCyan.wrap('xcrun altool --upload-app --type ios -f ${p.relative('build/ios/ipa/*.ipa')} --apiKey your_api_key --apiIssuer your_issuer_id')}. See "man altool" for details about how to authenticate with the App Store Connect API key. '''), - ); - }); + ); + }); + }); + + group('when not codesigning', () { + setUp(() { + when(() => argResults['codesign']).thenReturn(false); }); - group('when not codesigning', () { - setUp(() { - when(() => argResults['codesign']).thenReturn(false); - }); - - test('prints xcarchive upload steps', () { - expect( - runWithOverrides(() => iosReleaser.postReleaseInstructions), - equals( - ''' + test('prints xcarchive upload steps', () { + expect( + runWithOverrides(() => iosReleaser.postReleaseInstructions), + equals(''' Your next step is to submit the archive at ${lightCyan.wrap(p.relative(xcarchiveDirectory.path))} to the App Store using Xcode. @@ -800,13 +784,10 @@ You can open the archive in Xcode by running: ${lightCyan.wrap('open ${p.relative(xcarchiveDirectory.path)}')} ${styleBold.wrap('Make sure to uncheck "Manage Version and Build Number", or else shorebird will not work.')} -''', - ), - ); - }); +'''), + ); }); }); - }, - testOn: 'mac-os', - ); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart index 0865866e..d694efc0 100644 --- a/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart @@ -95,14 +95,7 @@ void main() { when(() => argResults.wasParsed(any())).thenReturn(false); releaseDirectory = Directory( - p.join( - projectRoot.path, - 'build', - 'linux', - 'x64', - 'release', - 'bundle', - ), + p.join(projectRoot.path, 'build', 'linux', 'x64', 'release', 'bundle'), )..createSync(recursive: true); when( @@ -169,11 +162,13 @@ To change the version of this release, change your app's version in your pubspec when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); }); @@ -193,11 +188,13 @@ To change the version of this release, change your app's version in your pubspec when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenThrow(exception); }); @@ -223,17 +220,21 @@ To change the version of this release, change your app's version in your pubspec when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); - when(() => argResults['flutter-version'] as String?) - .thenReturn('3.27.1'); - when(() => shorebirdFlutter.resolveFlutterVersion('3.27.1')) - .thenAnswer((_) async => Version(3, 27, 1)); + when( + () => argResults['flutter-version'] as String?, + ).thenReturn('3.27.1'); + when( + () => shorebirdFlutter.resolveFlutterVersion('3.27.1'), + ).thenAnswer((_) async => Version(3, 27, 1)); }); test('logs error and exits with usage err', () async { @@ -243,11 +244,9 @@ To change the version of this release, change your app's version in your pubspec ); verify( - () => logger.err( - ''' + () => logger.err(''' Linux releases are not supported with Flutter versions older than $minimumSupportedLinuxFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), ).called(1); }); }); @@ -265,8 +264,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', }); test('returns path to release directory', () async { - final releaseDir = - await runWithOverrides(releaser.buildReleaseArtifacts); + final releaseDir = await runWithOverrides( + releaser.buildReleaseArtifacts, + ); expect(releaseDir, releaseDirectory); }); }); @@ -316,9 +316,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', test('returns version from linux bundle', () async { final version = await runWithOverrides( - () => releaser.getReleaseVersion( - releaseArtifactRoot: releaseDirectory, - ), + () => + releaser.getReleaseVersion(releaseArtifactRoot: releaseDirectory), ); expect(version, '3.27.3'); }); @@ -343,10 +342,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', test('zips and uploads release directory', () async { await runWithOverrides( - () => releaser.uploadReleaseArtifacts( - release: release, - appId: appId, - ), + () => releaser.uploadReleaseArtifacts(release: release, appId: appId), ); verify( () => codePushClientWrapper.createLinuxReleaseArtifacts( diff --git a/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart index c17e91d5..23efbac7 100644 --- a/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart @@ -33,430 +33,420 @@ import '../../matchers.dart'; import '../../mocks.dart'; void main() { - group( - MacosReleaser, - () { - late ArgResults argResults; - late ArtifactBuilder artifactBuilder; - late ArtifactManager artifactManager; - late CodePushClientWrapper codePushClientWrapper; - late Directory projectRoot; - late Doctor doctor; - late FlavorValidator flavorValidator; - late Progress progress; - late ShorebirdLogger logger; - late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutter; - late ShorebirdValidator shorebirdValidator; - late XcodeBuild xcodeBuild; + group(MacosReleaser, () { + late ArgResults argResults; + late ArtifactBuilder artifactBuilder; + late ArtifactManager artifactManager; + late CodePushClientWrapper codePushClientWrapper; + late Directory projectRoot; + late Doctor doctor; + late FlavorValidator flavorValidator; + late Progress progress; + late ShorebirdLogger logger; + late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutter shorebirdFlutter; + late ShorebirdValidator shorebirdValidator; + late XcodeBuild xcodeBuild; - late MacosReleaser releaser; + late MacosReleaser releaser; - R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - artifactBuilderRef.overrideWith(() => artifactBuilder), - artifactManagerRef.overrideWith(() => artifactManager), - codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - doctorRef.overrideWith(() => doctor), - loggerRef.overrideWith(() => logger), - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), - shorebirdValidatorRef.overrideWith(() => shorebirdValidator), - xcodeBuildRef.overrideWith(() => xcodeBuild), - }, - ); - } + R runWithOverrides(R Function() body) { + return runScoped( + body, + values: { + artifactBuilderRef.overrideWith(() => artifactBuilder), + artifactManagerRef.overrideWith(() => artifactManager), + codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + doctorRef.overrideWith(() => doctor), + loggerRef.overrideWith(() => logger), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), + xcodeBuildRef.overrideWith(() => xcodeBuild), + }, + ); + } + + setUp(() { + argResults = MockArgResults(); + artifactBuilder = MockArtifactBuilder(); + artifactManager = MockArtifactManager(); + codePushClientWrapper = MockCodePushClientWrapper(); + doctor = MockDoctor(); + flavorValidator = MockFlavorValidator(); + projectRoot = Directory.systemTemp.createTempSync(); + progress = MockProgress(); + logger = MockShorebirdLogger(); + shorebirdEnv = MockShorebirdEnv(); + shorebirdFlutter = MockShorebirdFlutter(); + shorebirdValidator = MockShorebirdValidator(); + xcodeBuild = MockXcodeBuild(); + + when(() => argResults.rest).thenReturn([]); + when(() => argResults.wasParsed(any())).thenReturn(false); + + when(() => logger.progress(any())).thenReturn(progress); + + releaser = MacosReleaser( + argResults: argResults, + flavor: null, + target: null, + ); + }); + + group('releaseType', () { + test('is macos', () { + expect(releaser.releaseType, ReleaseType.macos); + }); + }); + + group('artifactDisplayName', () { + test('has expected value', () { + expect(releaser.artifactDisplayName, 'macOS app'); + }); + }); + + group('assertPreconditions', () { + final flutterVersion = Version(3, 0, 0); setUp(() { - argResults = MockArgResults(); - artifactBuilder = MockArtifactBuilder(); - artifactManager = MockArtifactManager(); - codePushClientWrapper = MockCodePushClientWrapper(); - doctor = MockDoctor(); - flavorValidator = MockFlavorValidator(); - projectRoot = Directory.systemTemp.createTempSync(); - progress = MockProgress(); - logger = MockShorebirdLogger(); - shorebirdEnv = MockShorebirdEnv(); - shorebirdFlutter = MockShorebirdFlutter(); - shorebirdValidator = MockShorebirdValidator(); - xcodeBuild = MockXcodeBuild(); - - when(() => argResults.rest).thenReturn([]); - when(() => argResults.wasParsed(any())).thenReturn(false); - - when(() => logger.progress(any())).thenReturn(progress); - - releaser = MacosReleaser( - argResults: argResults, - flavor: null, - target: null, - ); + when(() => doctor.macosCommandValidators).thenReturn([flavorValidator]); + when( + () => shorebirdFlutter.resolveFlutterVersion(any()), + ).thenAnswer((_) async => flutterVersion); + when(flavorValidator.validate).thenAnswer((_) async => []); }); - group('releaseType', () { - test('is macos', () { - expect(releaser.releaseType, ReleaseType.macos); + group('when validation succeeds', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + }); + + test('returns normally', () async { + await expectLater( + () => runWithOverrides(releaser.assertPreconditions), + returnsNormally, + ); }); }); - group('artifactDisplayName', () { - test('has expected value', () { - expect(releaser.artifactDisplayName, 'macOS app'); - }); - }); - - group('assertPreconditions', () { - final flutterVersion = Version(3, 0, 0); + group('when validation fails', () { + final exception = ValidationFailedException(); setUp(() { - when(() => doctor.macosCommandValidators) - .thenReturn([flavorValidator]); - when(() => shorebirdFlutter.resolveFlutterVersion(any())) - .thenAnswer((_) async => flutterVersion); - when(flavorValidator.validate).thenAnswer((_) async => []); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenThrow(exception); }); - group('when validation succeeds', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenAnswer((_) async {}); - }); - - test('returns normally', () async { - await expectLater( - () => runWithOverrides(releaser.assertPreconditions), - returnsNormally, - ); - }); - }); - - group('when validation fails', () { - final exception = ValidationFailedException(); - - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenThrow(exception); - }); - - test('exits with code 70', () async { - await expectLater( - () => runWithOverrides(releaser.assertPreconditions), - exitsWithCode(exception.exitCode), - ); - verify( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: true, - checkShorebirdInitialized: true, - validators: [flavorValidator], - supportedOperatingSystems: {Platform.macOS}, - ), - ).called(1); - }); - }); - - group('when specified flutter version is less than minimum', () { - setUp(() { - when( - () => shorebirdValidator.validatePreconditions( - checkUserIsAuthenticated: - any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), - validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), - ), - ).thenAnswer((_) async {}); - when(() => argResults['flutter-version']).thenReturn('3.0.0'); - }); - - test('logs error and exits with code 64', () async { - await expectLater( - () => runWithOverrides(releaser.assertPreconditions), - exitsWithCode(ExitCode.usage), - ); - - verify( - () => logger.err( - ''' -macOS releases are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), - ).called(1); - }); + test('exits with code 70', () async { + await expectLater( + () => runWithOverrides(releaser.assertPreconditions), + exitsWithCode(exception.exitCode), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flavorValidator], + supportedOperatingSystems: {Platform.macOS}, + ), + ).called(1); }); }); - group('assertArgsAreValid', () { - group('when release-version is passed', () { - setUp(() { - when(() => argResults.wasParsed('release-version')) - .thenReturn(true); - }); + group('when specified flutter version is less than minimum', () { + setUp(() { + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), + validators: any(named: 'validators'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), + ), + ).thenAnswer((_) async {}); + when(() => argResults['flutter-version']).thenReturn('3.0.0'); + }); - test('logs error and exits with usage err', () async { - await expectLater( - () => runWithOverrides(releaser.assertArgsAreValid), - exitsWithCode(ExitCode.usage), - ); + test('logs error and exits with code 64', () async { + await expectLater( + () => runWithOverrides(releaser.assertPreconditions), + exitsWithCode(ExitCode.usage), + ); - verify( - () => logger.err( - ''' + verify( + () => logger.err(''' +macOS releases are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion. +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), + ).called(1); + }); + }); + }); + + group('assertArgsAreValid', () { + group('when release-version is passed', () { + setUp(() { + when(() => argResults.wasParsed('release-version')).thenReturn(true); + }); + + test('logs error and exits with usage err', () async { + await expectLater( + () => runWithOverrides(releaser.assertArgsAreValid), + exitsWithCode(ExitCode.usage), + ); + + verify( + () => logger.err( + ''' The "--release-version" flag is only supported for aar and ios-framework releases. To change the version of this release, change your app's version in your pubspec.yaml.''', - ), - ).called(1); - }); - }); - - group('when --obfuscate is passed', () { - setUp(() { - when(() => argResults.rest).thenReturn(['--obfuscate']); - }); - - test('logs error and exits', () async { - await expectLater( - runWithOverrides(releaser.assertArgsAreValid), - exitsWithCode(ExitCode.unavailable), - ); - - verify( - () => logger.err( - 'Shorebird does not currently support obfuscation on macOS.', - ), - ).called(1); - verify( - () => logger.info( - '''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''', - ), - ).called(1); - }); - }); - - group('when --obfuscate is not passed', () { - test('returns normally', () async { - await expectLater( - runWithOverrides(releaser.assertArgsAreValid), - completes, - ); - }); + ), + ).called(1); }); }); - group('buildReleaseArtifacts', () { - const flutterVersionAndRevision = '3.10.6 (83305b5088)'; + group('when --obfuscate is passed', () { + setUp(() { + when(() => argResults.rest).thenReturn(['--obfuscate']); + }); - late Directory appDirectory; + test('logs error and exits', () async { + await expectLater( + runWithOverrides(releaser.assertArgsAreValid), + exitsWithCode(ExitCode.unavailable), + ); + + verify( + () => logger.err( + 'Shorebird does not currently support obfuscation on macOS.', + ), + ).called(1); + verify( + () => logger.info( + '''We hope to support obfuscation in the future. We are tracking this work at ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/1619'))}.''', + ), + ).called(1); + }); + }); + + group('when --obfuscate is not passed', () { + test('returns normally', () async { + await expectLater( + runWithOverrides(releaser.assertArgsAreValid), + completes, + ); + }); + }); + }); + + group('buildReleaseArtifacts', () { + const flutterVersionAndRevision = '3.10.6 (83305b5088)'; + + late Directory appDirectory; + + setUp(() { + when(() => argResults['codesign']).thenReturn(true); + + when( + () => artifactBuilder.buildMacos( + codesign: any(named: 'codesign'), + flavor: any(named: 'flavor'), + target: any(named: 'target'), + args: any(named: 'args'), + buildProgress: any(named: 'buildProgress'), + ), + ).thenAnswer( + (_) async => MacosBuildResult(kernelFile: File('/path/to/app.dill')), + ); + + appDirectory = Directory.systemTemp.createTempSync(); + when( + () => artifactManager.getMacOSAppDirectory(), + ).thenReturn(appDirectory); + + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); + when( + () => shorebirdFlutter.getVersionAndRevision(), + ).thenAnswer((_) async => flutterVersionAndRevision); + }); + + group('when flavor is provided', () { + const flavor = 'myFlavor'; setUp(() { - when(() => argResults['codesign']).thenReturn(true); + releaser = MacosReleaser( + argResults: argResults, + flavor: flavor, + target: null, + ); when( + () => artifactManager.getMacOSAppDirectory(flavor: flavor), + ).thenReturn(appDirectory); + }); + + test('forwards flavor to artifact builder', () async { + await runWithOverrides(releaser.buildReleaseArtifacts); + + verify( () => artifactBuilder.buildMacos( - codesign: any(named: 'codesign'), - flavor: any(named: 'flavor'), - target: any(named: 'target'), + flavor: flavor, args: any(named: 'args'), buildProgress: any(named: 'buildProgress'), ), - ).thenAnswer( - (_) async => MacosBuildResult( - kernelFile: File('/path/to/app.dill'), + ).called(1); + verify( + () => artifactManager.getMacOSAppDirectory(flavor: flavor), + ).called(1); + }); + }); + + group('when not codesigning', () { + setUp(() { + when(() => argResults['codesign']).thenReturn(false); + }); + + test('logs warning about patching', () async { + await runWithOverrides(releaser.buildReleaseArtifacts); + + verify( + () => logger.info( + '''Building for device with codesigning disabled. You will have to manually codesign before deploying to device.''', ), - ); + ).called(1); + verify( + () => logger.warn( + '''shorebird preview will not work for releases created with "--no-codesign". However, you can still preview your app by signing the generated .xcarchive in Xcode.''', + ), + ).called(1); + }); + }); - appDirectory = Directory.systemTemp.createTempSync(); - when( - () => artifactManager.getMacOSAppDirectory(), - ).thenReturn(appDirectory); - - when( - () => shorebirdEnv.getShorebirdProjectRoot(), - ).thenReturn(projectRoot); - when( - () => shorebirdFlutter.getVersionAndRevision(), - ).thenAnswer((_) async => flutterVersionAndRevision); + group('when platform was specified via arg results rest', () { + setUp(() { + when(() => argResults.rest).thenReturn(['macos', '--verbose']); }); - group('when flavor is provided', () { - const flavor = 'myFlavor'; - - setUp(() { - releaser = MacosReleaser( - argResults: argResults, - flavor: flavor, - target: null, - ); - - when( - () => artifactManager.getMacOSAppDirectory(flavor: flavor), - ).thenReturn(appDirectory); - }); - - test('forwards flavor to artifact builder', () async { - await runWithOverrides(releaser.buildReleaseArtifacts); - - verify( - () => artifactBuilder.buildMacos( - flavor: flavor, - args: any(named: 'args'), - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - verify( - () => artifactManager.getMacOSAppDirectory(flavor: flavor), - ).called(1); - }); - }); - - group('when not codesigning', () { - setUp(() { - when(() => argResults['codesign']).thenReturn(false); - }); - - test('logs warning about patching', () async { - await runWithOverrides(releaser.buildReleaseArtifacts); - - verify( - () => logger.info( - '''Building for device with codesigning disabled. You will have to manually codesign before deploying to device.''', - ), - ).called(1); - verify( - () => logger.warn( - '''shorebird preview will not work for releases created with "--no-codesign". However, you can still preview your app by signing the generated .xcarchive in Xcode.''', - ), - ).called(1); - }); - }); - - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['macos', '--verbose']); - }); - - test('verifies artifacts exist and returns xcarchive path', () async { - expect( - await runWithOverrides(releaser.buildReleaseArtifacts), - equals(appDirectory), - ); - - verify(() => artifactManager.getMacOSAppDirectory()).called(1); - verify( - () => artifactBuilder.buildMacos( - args: ['--verbose'], - buildProgress: any(named: 'buildProgress'), - ), - ).called(1); - }); - }); - - test('verifies artifacts exist and returns app path', () async { + test('verifies artifacts exist and returns xcarchive path', () async { expect( await runWithOverrides(releaser.buildReleaseArtifacts), equals(appDirectory), ); verify(() => artifactManager.getMacOSAppDirectory()).called(1); - }); - - group('when app not found after build', () { - setUp(() { - when(() => artifactManager.getMacOSAppDirectory()).thenReturn(null); - }); - - test('logs message and exits with code 70', () async { - await expectLater( - () => runWithOverrides(releaser.buildReleaseArtifacts), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err('Unable to find .app directory'), - ).called(1); - }); - }); - - group('when app not found after build', () { - setUp(() { - when( - () => artifactManager.getMacOSAppDirectory(), - ).thenReturn(null); - }); - - test('logs message and exits with code 70', () async { - await expectLater( - () => runWithOverrides(releaser.buildReleaseArtifacts), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err('Unable to find .app directory'), - ).called(1); - }); + verify( + () => artifactBuilder.buildMacos( + args: ['--verbose'], + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); }); }); - group('getReleaseVersion', () { - late Directory appDirectory; + test('verifies artifacts exist and returns app path', () async { + expect( + await runWithOverrides(releaser.buildReleaseArtifacts), + equals(appDirectory), + ); + verify(() => artifactManager.getMacOSAppDirectory()).called(1); + }); + + group('when app not found after build', () { setUp(() { - appDirectory = Directory.systemTemp.createTempSync(); - // The Info.plist file is expected to be in the app directory at - // Contents/Info.plist - Directory(p.join(appDirectory.path, 'Contents')) - .createSync(recursive: true); + when(() => artifactManager.getMacOSAppDirectory()).thenReturn(null); }); - group('when plist does not exist', () { - test('logs error and exits', () async { - await expectLater( - () => runWithOverrides( - () => releaser.getReleaseVersion( - releaseArtifactRoot: appDirectory, - ), - ), - exitsWithCode(ExitCode.software), - ); + test('logs message and exits with code 70', () async { + await expectLater( + () => runWithOverrides(releaser.buildReleaseArtifacts), + exitsWithCode(ExitCode.software), + ); - verify( - () => logger.err( - '''No Info.plist file found at ${p.join(appDirectory.path, 'Contents', 'Info.plist')}''', - ), - ).called(1); - }); + verify(() => logger.err('Unable to find .app directory')).called(1); + }); + }); + + group('when app not found after build', () { + setUp(() { + when(() => artifactManager.getMacOSAppDirectory()).thenReturn(null); }); - group('when plist does not contain version number', () { - late File plist; - setUp(() { - plist = File(p.join(appDirectory.path, 'Contents', 'Info.plist')) - ..createSync() - ..writeAsStringSync( - ''' + test('logs message and exits with code 70', () async { + await expectLater( + () => runWithOverrides(releaser.buildReleaseArtifacts), + exitsWithCode(ExitCode.software), + ); + + verify(() => logger.err('Unable to find .app directory')).called(1); + }); + }); + }); + + group('getReleaseVersion', () { + late Directory appDirectory; + + setUp(() { + appDirectory = Directory.systemTemp.createTempSync(); + // The Info.plist file is expected to be in the app directory at + // Contents/Info.plist + Directory( + p.join(appDirectory.path, 'Contents'), + ).createSync(recursive: true); + }); + + group('when plist does not exist', () { + test('logs error and exits', () async { + await expectLater( + () => runWithOverrides( + () => + releaser.getReleaseVersion(releaseArtifactRoot: appDirectory), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err( + '''No Info.plist file found at ${p.join(appDirectory.path, 'Contents', 'Info.plist')}''', + ), + ).called(1); + }); + }); + + group('when plist does not contain version number', () { + late File plist; + setUp(() { + plist = + File(p.join(appDirectory.path, 'Contents', 'Info.plist')) + ..createSync() + ..writeAsStringSync(''' @@ -466,38 +456,35 @@ To change the version of this release, change your app's version in your pubspec ' -''', - ); - }); - - test('logs error and exits', () async { - await expectLater( - () => runWithOverrides( - () => releaser.getReleaseVersion( - releaseArtifactRoot: appDirectory, - ), - ), - exitsWithCode(ExitCode.software), - ); - - verify( - () => logger.err( - any( - that: startsWith( - 'Failed to determine release version from ${plist.path}', - ), - ), - ), - ).called(1); - }); +'''); }); - group('when plist contains version number', () { - setUp(() { - File(p.join(appDirectory.path, 'Contents', 'Info.plist')) - ..createSync() - ..writeAsStringSync( - ''' + test('logs error and exits', () async { + await expectLater( + () => runWithOverrides( + () => + releaser.getReleaseVersion(releaseArtifactRoot: appDirectory), + ), + exitsWithCode(ExitCode.software), + ); + + verify( + () => logger.err( + any( + that: startsWith( + 'Failed to determine release version from ${plist.path}', + ), + ), + ), + ).called(1); + }); + }); + + group('when plist contains version number', () { + setUp(() { + File(p.join(appDirectory.path, 'Contents', 'Info.plist')) + ..createSync() + ..writeAsStringSync(''' @@ -524,188 +511,177 @@ To change the version of this release, change your app's version in your pubspec SchemeName Runner -''', - ); - }); +'''); + }); - test('returns version number from plist', () async { - expect( - await runWithOverrides( - () => releaser.getReleaseVersion( - releaseArtifactRoot: appDirectory, - ), - ), - equals('1.2.3+1'), - ); - }); + test('returns version number from plist', () async { + expect( + await runWithOverrides( + () => + releaser.getReleaseVersion(releaseArtifactRoot: appDirectory), + ), + equals('1.2.3+1'), + ); }); }); + }); - group('uploadReleaseArtifacts', () { - const appId = 'appId'; - const releaseVersion = '1.0.0'; - const flutterRevision = 'deadbeef'; - const flutterVersion = '3.22.0'; - const codesign = true; - const podfileLockContent = 'podfile-lock'; + group('uploadReleaseArtifacts', () { + const appId = 'appId'; + const releaseVersion = '1.0.0'; + const flutterRevision = 'deadbeef'; + const flutterVersion = '3.22.0'; + const codesign = true; + const podfileLockContent = 'podfile-lock'; - final release = Release( - id: 42, - appId: appId, - version: releaseVersion, - flutterRevision: flutterRevision, - flutterVersion: flutterVersion, - displayName: '1.2.3+1', - platformStatuses: {}, - createdAt: DateTime(2023), - updatedAt: DateTime(2023), - ); + final release = Release( + id: 42, + appId: appId, + version: releaseVersion, + flutterRevision: flutterRevision, + flutterVersion: flutterVersion, + displayName: '1.2.3+1', + platformStatuses: {}, + createdAt: DateTime(2023), + updatedAt: DateTime(2023), + ); - late Directory appDirectory; - late File podfileLockFile; + late Directory appDirectory; + late File podfileLockFile; - setUp(() { - when(() => argResults['codesign']).thenReturn(codesign); + setUp(() { + when(() => argResults['codesign']).thenReturn(codesign); - appDirectory = Directory.systemTemp.createTempSync(); + appDirectory = Directory.systemTemp.createTempSync(); - podfileLockFile = File( - p.join( - Directory.systemTemp.createTempSync().path, - 'Podfile.lock', - ), - ) - ..createSync(recursive: true) - ..writeAsStringSync(podfileLockContent); - - when( - () => artifactManager.getMacOSAppDirectory(), - ).thenReturn(appDirectory); - when( - () => codePushClientWrapper.createMacosReleaseArtifacts( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - appPath: any(named: 'appPath'), - isCodesigned: any(named: 'isCodesigned'), - podfileLockHash: any(named: 'podfileLockHash'), - ), - ).thenAnswer((_) async => {}); - - when( - () => shorebirdEnv.macosPodfileLockFile, - ).thenReturn(podfileLockFile); - }); - - group('when app directory does not exist', () { - setUp(() { - when(() => artifactManager.getMacOSAppDirectory()).thenReturn(null); - }); - - test('logs error and exits with code 70', () async { - await expectLater( - () => runWithOverrides( - () => releaser.uploadReleaseArtifacts( - release: release, - appId: appId, + podfileLockFile = + File( + p.join( + Directory.systemTemp.createTempSync().path, + 'Podfile.lock', ), - ), - exitsWithCode(ExitCode.software), - ); + ) + ..createSync(recursive: true) + ..writeAsStringSync(podfileLockContent); - verify( - () => logger.err('Unable to find .app directory'), - ).called(1); - }); + when( + () => artifactManager.getMacOSAppDirectory(), + ).thenReturn(appDirectory); + when( + () => codePushClientWrapper.createMacosReleaseArtifacts( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), + appPath: any(named: 'appPath'), + isCodesigned: any(named: 'isCodesigned'), + podfileLockHash: any(named: 'podfileLockHash'), + ), + ).thenAnswer((_) async => {}); + + when( + () => shorebirdEnv.macosPodfileLockFile, + ).thenReturn(podfileLockFile); + }); + + group('when app directory does not exist', () { + setUp(() { + when(() => artifactManager.getMacOSAppDirectory()).thenReturn(null); }); - test('forwards call to codePushClientWrapper', () async { - await runWithOverrides( - () => releaser.uploadReleaseArtifacts( - release: release, - appId: appId, + test('logs error and exits with code 70', () async { + await expectLater( + () => runWithOverrides( + () => releaser.uploadReleaseArtifacts( + release: release, + appId: appId, + ), ), + exitsWithCode(ExitCode.software), ); - verify( - () => codePushClientWrapper.createMacosReleaseArtifacts( - appId: appId, - releaseId: release.id, - appPath: appDirectory.path, - isCodesigned: codesign, - podfileLockHash: - '${sha256.convert(utf8.encode(podfileLockContent))}', - ), - ).called(1); + verify(() => logger.err('Unable to find .app directory')).called(1); }); }); - group('updatedReleaseMetadata', () { - const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; - const operatingSystem = 'macOS'; - const operatingSystemVersion = '11.0.0'; - const xcodeVersion = '123'; - const flutterVersionOverride = '1.2.3'; - const metadata = UpdateReleaseMetadata( - releasePlatform: ReleasePlatform.macos, - flutterVersionOverride: flutterVersionOverride, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + test('forwards call to codePushClientWrapper', () async { + await runWithOverrides( + () => releaser.uploadReleaseArtifacts(release: release, appId: appId), + ); + + verify( + () => codePushClientWrapper.createMacosReleaseArtifacts( + appId: appId, + releaseId: release.id, + appPath: appDirectory.path, + isCodesigned: codesign, + podfileLockHash: + '${sha256.convert(utf8.encode(podfileLockContent))}', + ), + ).called(1); + }); + }); + + group('updatedReleaseMetadata', () { + const flutterRevision = '853d13d954df3b6e9c2f07b72062f33c52a9a64b'; + const operatingSystem = 'macOS'; + const operatingSystemVersion = '11.0.0'; + const xcodeVersion = '123'; + const flutterVersionOverride = '1.2.3'; + const metadata = UpdateReleaseMetadata( + releasePlatform: ReleasePlatform.macos, + flutterVersionOverride: flutterVersionOverride, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + ), + ); + + setUp(() { + when(() => xcodeBuild.version()).thenAnswer((_) async => xcodeVersion); + }); + + test('returns expected metadata', () async { + expect( + runWithOverrides(() => releaser.updatedReleaseMetadata(metadata)), + completion( + const UpdateReleaseMetadata( + releasePlatform: ReleasePlatform.macos, + flutterVersionOverride: flutterVersionOverride, + environment: BuildEnvironmentMetadata( + flutterRevision: flutterRevision, + operatingSystem: operatingSystem, + operatingSystemVersion: operatingSystemVersion, + shorebirdVersion: packageVersion, + shorebirdYaml: ShorebirdYaml(appId: 'app-id'), + xcodeVersion: xcodeVersion, + ), + ), ), ); + }); + }); - setUp(() { - when( - () => xcodeBuild.version(), - ).thenAnswer((_) async => xcodeVersion); - }); + group('postReleaseInstructions', () { + late Directory appDirectory; - test('returns expected metadata', () async { - expect( - runWithOverrides( - () => releaser.updatedReleaseMetadata(metadata), - ), - completion( - const UpdateReleaseMetadata( - releasePlatform: ReleasePlatform.macos, - flutterVersionOverride: flutterVersionOverride, - environment: BuildEnvironmentMetadata( - flutterRevision: flutterRevision, - operatingSystem: operatingSystem, - operatingSystemVersion: operatingSystemVersion, - shorebirdVersion: packageVersion, - shorebirdYaml: ShorebirdYaml(appId: 'app-id'), - xcodeVersion: xcodeVersion, - ), - ), - ), - ); - }); + setUp(() { + appDirectory = Directory.systemTemp.createTempSync(); + when( + () => artifactManager.getMacOSAppDirectory(), + ).thenReturn(appDirectory); }); - group('postReleaseInstructions', () { - late Directory appDirectory; - - setUp(() { - appDirectory = Directory.systemTemp.createTempSync(); - when(() => artifactManager.getMacOSAppDirectory()) - .thenReturn(appDirectory); - }); - - test('prints xcarchive upload steps', () { - expect( - runWithOverrides(() => releaser.postReleaseInstructions), - equals(''' + test('prints xcarchive upload steps', () { + expect( + runWithOverrides(() => releaser.postReleaseInstructions), + equals(''' macOS app created at ${appDirectory.path}. '''), - ); - }); + ); }); - }, - testOn: 'mac-os', - ); + }); + }, testOn: 'mac-os'); } diff --git a/packages/shorebird_cli/test/src/commands/release/release_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_command_test.dart index dc3603e4..70bac764 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_command_test.dart @@ -184,9 +184,8 @@ void main() { () => shorebirdFlutter.getVersionAndRevision(), ).thenAnswer((_) async => flutterRevision); when( - () => shorebirdFlutter.installRevision( - revision: any(named: 'revision'), - ), + () => + shorebirdFlutter.installRevision(revision: any(named: 'revision')), ).thenAnswer((_) async => {}); when( @@ -205,30 +204,18 @@ void main() { group('getReleaser', () { test('maps the correct platform to the releaser', () async { - expect( - command.getReleaser(ReleaseType.aar), - isA(), - ); + expect(command.getReleaser(ReleaseType.aar), isA()); expect( command.getReleaser(ReleaseType.android), isA(), ); - expect( - command.getReleaser(ReleaseType.ios), - isA(), - ); + expect(command.getReleaser(ReleaseType.ios), isA()); expect( command.getReleaser(ReleaseType.iosFramework), isA(), ); - expect( - command.getReleaser(ReleaseType.linux), - isA(), - ); - expect( - command.getReleaser(ReleaseType.macos), - isA(), - ); + expect(command.getReleaser(ReleaseType.linux), isA()); + expect(command.getReleaser(ReleaseType.macos), isA()); expect( command.getReleaser(ReleaseType.windows), isA(), @@ -238,8 +225,9 @@ void main() { group('when public key path is provided but no key exists', () { setUp(() { - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn('/path/to/nonexistent/file'); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn('/path/to/nonexistent/file'); }); test('exits with usage code', () async { @@ -265,41 +253,35 @@ void main() { cache.updateAll, () => codePushClientWrapper.getApp(appId: appId), () => logger.progress( - 'Building $artifactDisplayName with Flutter $flutterRevision', - ), + 'Building $artifactDisplayName with Flutter $flutterRevision', + ), () => releaser.buildReleaseArtifacts(progress: any(named: 'progress')), () => progress.complete( - 'Building $artifactDisplayName with Flutter $flutterRevision', - ), + 'Building $artifactDisplayName with Flutter $flutterRevision', + ), () => releaser.getReleaseVersion( - releaseArtifactRoot: any(named: 'releaseArtifactRoot'), - ), - () => releaser.uploadReleaseArtifacts( - release: release, - appId: appId, - ), + releaseArtifactRoot: any(named: 'releaseArtifactRoot'), + ), + () => releaser.uploadReleaseArtifacts(release: release, appId: appId), () => logger.success(''' ✅ Published Release ${release.version}!'''), () => logger.info(postReleaseInstructions), () => logger.info( - '''To create a patch for this release, run ${lightCyan.wrap('shorebird patch --platforms=android --release-version=${release.version}')}''', - ), - () => logger.info( - ''' + '''To create a patch for this release, run ${lightCyan.wrap('shorebird patch --platforms=android --release-version=${release.version}')}''', + ), + () => logger.info(''' Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --release-version option will patch the current version of the app. -''', - ), +'''), ]); }); group('when build fails', () { setUp(() { when( - () => releaser.buildReleaseArtifacts( - progress: any(named: 'progress'), - ), + () => + releaser.buildReleaseArtifacts(progress: any(named: 'progress')), ).thenThrow(Exception('oops')); }); @@ -416,20 +398,20 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel when(() => releaser.requiresReleaseVersionArg).thenReturn(true); }); - test('does not print patch instructions for no release version', - () async { - final exitCode = await runWithOverrides(command.run); - expect(exitCode, equals(ExitCode.success.code)); + test( + 'does not print patch instructions for no release version', + () async { + final exitCode = await runWithOverrides(command.run); + expect(exitCode, equals(ExitCode.success.code)); - verifyNever( - () => logger.info( - ''' + verifyNever( + () => logger.info(''' Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --release-version option will patch the current version of the app. -''', - ), - ); - }); +'''), + ); + }, + ); }); group('when flavor and target are provided', () { @@ -452,36 +434,28 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel cache.updateAll, () => codePushClientWrapper.getApp(appId: appId), () => logger.progress( - 'Building $artifactDisplayName with Flutter $flutterRevision', - ), - () => releaser.buildReleaseArtifacts( - progress: any(named: 'progress'), - ), + 'Building $artifactDisplayName with Flutter $flutterRevision', + ), + () => + releaser.buildReleaseArtifacts(progress: any(named: 'progress')), () => progress.complete( - 'Building $artifactDisplayName with Flutter $flutterRevision', - ), + 'Building $artifactDisplayName with Flutter $flutterRevision', + ), () => releaser.getReleaseVersion( - releaseArtifactRoot: any(named: 'releaseArtifactRoot'), - ), - () => releaser.uploadReleaseArtifacts( - release: release, - appId: appId, - ), - () => logger.success( - ''' + releaseArtifactRoot: any(named: 'releaseArtifactRoot'), + ), + () => releaser.uploadReleaseArtifacts(release: release, appId: appId), + () => logger.success(''' -✅ Published Release ${release.version}!''', - ), +✅ Published Release ${release.version}!'''), () => logger.info(postReleaseInstructions), () => logger.info( - '''To create a patch for this release, run ${lightCyan.wrap('shorebird patch --platforms=android --flavor=$flavor --target=$target --release-version=${release.version}')}''', - ), - () => logger.info( - ''' + '''To create a patch for this release, run ${lightCyan.wrap('shorebird patch --platforms=android --flavor=$flavor --target=$target --release-version=${release.version}')}''', + ), + () => logger.info(''' Note: ${lightCyan.wrap('shorebird patch --platforms=android --flavor=$flavor --target=$target')} without the --release-version option will patch the current version of the app. -''', - ), +'''), ]); }); }); @@ -590,11 +564,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android --flavor=$flavor --t exitsWithCode(ExitCode.software), ); verify( - () => logger.err( - ''' + () => logger.err(''' Unable to determine revision for Flutter version: $flutterVersion. -$exception''', - ), +$exception'''), ).called(1); }); }); @@ -627,8 +599,7 @@ $exception''', ).thenAnswer((_) async => revision); }); - test( - 'uses specified flutter version to build ' + test('uses specified flutter version to build ' 'and reverts to original flutter version', () async { when( () => releaser.buildReleaseArtifacts( @@ -642,8 +613,9 @@ $exception''', await runWithOverrides(command.run); - verify(() => shorebirdFlutter.installRevision(revision: revision)) - .called(1); + verify( + () => shorebirdFlutter.installRevision(revision: revision), + ).called(1); }); group('when flutter version install fails', () { @@ -673,13 +645,11 @@ $exception''', group('when the key exists', () { setUp(() { final file = File( - p.join( - Directory.systemTemp.createTempSync().path, - keyName, - ), + p.join(Directory.systemTemp.createTempSync().path, keyName), )..writeAsStringSync('KEY'); - when(() => argResults[CommonArguments.publicKeyArg.name]) - .thenReturn(file.path); + when( + () => argResults[CommonArguments.publicKeyArg.name], + ).thenReturn(file.path); }); test('completes successfully', () async { diff --git a/packages/shorebird_cli/test/src/commands/release/releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/releaser_test.dart index d4f24b9e..9b7e7c4f 100644 --- a/packages/shorebird_cli/test/src/commands/release/releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/releaser_test.dart @@ -35,9 +35,7 @@ class FakeReleaser extends Releaser { String get artifactDisplayName => 'Fake artifact'; @override - Future buildReleaseArtifacts({ - DetailProgress? progress, - }) { + Future buildReleaseArtifacts({DetailProgress? progress}) { throw UnimplementedError(); } @@ -54,8 +52,7 @@ class FakeReleaser extends Releaser { @override Future updatedReleaseMetadata( UpdateReleaseMetadata metadata, - ) => - throw UnimplementedError(); + ) => throw UnimplementedError(); @override ReleaseType get releaseType => throw UnimplementedError(); diff --git a/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart index 35a060da..dbb5f66c 100644 --- a/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart @@ -161,8 +161,9 @@ To change the version of this release, change your app's version in your pubspec group('assertPreconditions', () { setUp(() { - when(() => doctor.windowsCommandValidators) - .thenReturn([flavorValidator]); + when( + () => doctor.windowsCommandValidators, + ).thenReturn([flavorValidator]); when(flavorValidator.validate).thenAnswer((_) async => []); }); @@ -171,11 +172,13 @@ To change the version of this release, change your app's version in your pubspec when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); }); @@ -195,11 +198,13 @@ To change the version of this release, change your app's version in your pubspec when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenThrow(exception); }); @@ -225,17 +230,21 @@ To change the version of this release, change your app's version in your pubspec when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), - checkShorebirdInitialized: - any(named: 'checkShorebirdInitialized'), + checkShorebirdInitialized: any( + named: 'checkShorebirdInitialized', + ), validators: any(named: 'validators'), - supportedOperatingSystems: - any(named: 'supportedOperatingSystems'), + supportedOperatingSystems: any( + named: 'supportedOperatingSystems', + ), ), ).thenAnswer((_) async {}); - when(() => argResults['flutter-version'] as String?) - .thenReturn('3.27.1'); - when(() => shorebirdFlutter.resolveFlutterVersion('3.27.1')) - .thenAnswer((_) async => Version(3, 27, 1)); + when( + () => argResults['flutter-version'] as String?, + ).thenReturn('3.27.1'); + when( + () => shorebirdFlutter.resolveFlutterVersion('3.27.1'), + ).thenAnswer((_) async => Version(3, 27, 1)); when( () => shorebirdFlutter.getRevisionForVersion(any()), ).thenAnswer((_) async => 'deadbeef'); @@ -248,11 +257,9 @@ To change the version of this release, change your app's version in your pubspec ); verify( - () => logger.err( - ''' + () => logger.err(''' Windows releases are not supported with Flutter versions older than $minimumSupportedWindowsFlutterVersion. -For more information see: ${supportedFlutterVersionsUrl.toLink()}''', - ), +For more information see: ${supportedFlutterVersionsUrl.toLink()}'''), ).called(1); }); @@ -288,8 +295,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', }); test('returns path to release directory', () async { - final releaseDir = - await runWithOverrides(releaser.buildReleaseArtifacts); + final releaseDir = await runWithOverrides( + releaser.buildReleaseArtifacts, + ); expect(releaseDir, projectRoot); }); @@ -335,9 +343,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', test('throws exception', () { expect( () => runWithOverrides( - () => releaser.getReleaseVersion( - releaseArtifactRoot: projectRoot, - ), + () => + releaser.getReleaseVersion(releaseArtifactRoot: projectRoot), ), throwsA(isA()), ); @@ -347,17 +354,16 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', group('when exe exists', () { setUp(() { File(p.join(projectRoot.path, 'app.exe')).createSync(); - when(() => powershell.getExeVersionString(any())).thenAnswer( - (_) async => '1.2.3', - ); + when( + () => powershell.getExeVersionString(any()), + ).thenAnswer((_) async => '1.2.3'); }); test('returns result of getExeVersionString', () async { await expectLater( runWithOverrides( - () => releaser.getReleaseVersion( - releaseArtifactRoot: projectRoot, - ), + () => + releaser.getReleaseVersion(releaseArtifactRoot: projectRoot), ), completion(equals('1.2.3')), ); @@ -412,10 +418,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', test('zips and uploads release directory', () async { await runWithOverrides( - () => releaser.uploadReleaseArtifacts( - release: release, - appId: appId, - ), + () => + releaser.uploadReleaseArtifacts(release: release, appId: appId), ); verify( () => codePushClientWrapper.createWindowsReleaseArtifacts( diff --git a/packages/shorebird_cli/test/src/commands/releases/get_apks_command_test.dart b/packages/shorebird_cli/test/src/commands/releases/get_apks_command_test.dart index d714b32f..791e52ae 100644 --- a/packages/shorebird_cli/test/src/commands/releases/get_apks_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/releases/get_apks_command_test.dart @@ -151,9 +151,7 @@ void main() { when( () => shorebirdEnv.getShorebirdProjectRoot(), ).thenReturn(projectRoot); - when( - () => shorebirdEnv.getShorebirdYaml(), - ).thenReturn(shorebirdYaml); + when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml); when( () => shorebirdValidator.validatePreconditions( @@ -291,13 +289,15 @@ void main() { test('prompts for release', () async { await runWithOverrides(command.run); - final capturedDisplay = verify( - () => logger.chooseOne( - any(), - choices: any(named: 'choices'), - display: captureAny(named: 'display'), - ), - ).captured.single as String Function(Release); + final capturedDisplay = + verify( + () => logger.chooseOne( + any(), + choices: any(named: 'choices'), + display: captureAny(named: 'display'), + ), + ).captured.single + as String Function(Release); expect(capturedDisplay(release), equals(releaseVersion)); }); diff --git a/packages/shorebird_cli/test/src/commands/run_command_test.dart b/packages/shorebird_cli/test/src/commands/run_command_test.dart index 20bc8af4..452d2fc0 100644 --- a/packages/shorebird_cli/test/src/commands/run_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/run_command_test.dart @@ -119,13 +119,11 @@ Please use "shorebird preview" instead.'''), const error = 'oops something went wrong'; const expectedExitCode = 1; - when( - () => process.stdout, - ).thenAnswer((_) => const Stream.empty()); + when(() => process.stdout).thenAnswer((_) => const Stream.empty()); when(() => process.stdin).thenAnswer((_) => ioSink); - when(() => process.stderr).thenAnswer( - (_) => Stream.value(utf8.encode(error)), - ); + when( + () => process.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode(error))); when(() => process.exitCode).thenAnswer((_) async => expectedExitCode); final exitCode = await runWithOverrides(command.run); @@ -177,13 +175,15 @@ Please use "shorebird preview" instead.'''), final exitCode = await runWithOverrides(command.run); - final args = verify( - () => shorebirdProcess.start( - any(), - captureAny(), - runInShell: any(named: 'runInShell'), - ), - ).captured.first as List; + final args = + verify( + () => shorebirdProcess.start( + any(), + captureAny(), + runInShell: any(named: 'runInShell'), + ), + ).captured.first + as List; expect( args, equals([ diff --git a/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart b/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart index 440680de..98830db8 100644 --- a/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart @@ -59,47 +59,33 @@ void main() { expect(command, isNotNull); }); - test( - 'handles errors when determining the current version', - () async { - const errorMessage = 'oops'; - when(shorebirdVersion.fetchCurrentGitHash).thenThrow( - const ProcessException( - 'git', - ['rev-parse'], - errorMessage, - ), - ); + test('handles errors when determining the current version', () async { + const errorMessage = 'oops'; + when( + shorebirdVersion.fetchCurrentGitHash, + ).thenThrow(const ProcessException('git', ['rev-parse'], errorMessage)); - final result = await runWithOverrides(command.run); + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.software.code)); - verify(() => logger.progress('Checking for updates')).called(1); - verify( - () => logger.err('Fetching current version failed: $errorMessage'), - ).called(1); - }, - ); + expect(result, equals(ExitCode.software.code)); + verify(() => logger.progress('Checking for updates')).called(1); + verify( + () => logger.err('Fetching current version failed: $errorMessage'), + ).called(1); + }); - test( - 'handles errors when determining the latest version', - () async { - const errorMessage = 'oops'; - when(shorebirdVersion.fetchLatestGitHash).thenThrow( - const ProcessException( - 'git', - ['rev-parse'], - errorMessage, - ), - ); + test('handles errors when determining the latest version', () async { + const errorMessage = 'oops'; + when( + shorebirdVersion.fetchLatestGitHash, + ).thenThrow(const ProcessException('git', ['rev-parse'], errorMessage)); - final result = await runWithOverrides(command.run); + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.software.code)); - verify(() => logger.progress('Checking for updates')).called(1); - verify(() => logger.err('Checking for updates failed: oops')).called(1); - }, - ); + expect(result, equals(ExitCode.software.code)); + verify(() => logger.progress('Checking for updates')).called(1); + verify(() => logger.err('Checking for updates failed: oops')).called(1); + }); test('handles errors when updating', () async { const errorMessage = 'oops'; @@ -114,33 +100,28 @@ void main() { verify(() => logger.err('Updating failed: oops')).called(1); }); - test( - 'updates when newer version exists', - () async { - when(() => logger.progress(any())).thenReturn(MockProgress()); + test('updates when newer version exists', () async { + when(() => logger.progress(any())).thenReturn(MockProgress()); - final result = await runWithOverrides(command.run); + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.success.code)); - verify(() => logger.progress('Checking for updates')).called(1); - verify(() => logger.progress('Updating')).called(1); - }, - ); + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Checking for updates')).called(1); + verify(() => logger.progress('Updating')).called(1); + }); - test( - 'does not update when already on latest version', - () async { - when(shorebirdVersion.fetchLatestGitHash) - .thenAnswer((_) async => currentShorebirdRevision); - when(() => logger.progress(any())).thenReturn(MockProgress()); + test('does not update when already on latest version', () async { + when( + shorebirdVersion.fetchLatestGitHash, + ).thenAnswer((_) async => currentShorebirdRevision); + when(() => logger.progress(any())).thenReturn(MockProgress()); - final result = await runWithOverrides(command.run); + final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.success.code)); - verify( - () => logger.info('Shorebird is already at the latest version.'), - ).called(1); - }, - ); + expect(result, equals(ExitCode.success.code)); + verify( + () => logger.info('Shorebird is already at the latest version.'), + ).called(1); + }); }); } diff --git a/packages/shorebird_cli/test/src/config/shorebird_yaml_test.dart b/packages/shorebird_cli/test/src/config/shorebird_yaml_test.dart index 0d56e354..569b073b 100644 --- a/packages/shorebird_cli/test/src/config/shorebird_yaml_test.dart +++ b/packages/shorebird_cli/test/src/config/shorebird_yaml_test.dart @@ -69,9 +69,7 @@ auto_update: true group('AppIdExtension', () { test('getAppId returns base app id when no flavor is provided', () { - const shorebirdYaml = ShorebirdYaml( - appId: 'test_app_id', - ); + const shorebirdYaml = ShorebirdYaml(appId: 'test_app_id'); expect(shorebirdYaml.getAppId(), 'test_app_id'); }); diff --git a/packages/shorebird_cli/test/src/doctor_test.dart b/packages/shorebird_cli/test/src/doctor_test.dart index 41eb6039..5128ccc7 100644 --- a/packages/shorebird_cli/test/src/doctor_test.dart +++ b/packages/shorebird_cli/test/src/doctor_test.dart @@ -29,12 +29,7 @@ with multiple lines''', late Doctor doctor; R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - loggerRef.overrideWith(() => logger), - }, - ); + return runScoped(body, values: {loggerRef.overrideWith(() => logger)}); } setUp(() { @@ -51,12 +46,13 @@ with multiple lines''', when(noIssuesValidator.validate).thenAnswer((_) async => []); when(noIssuesValidator.canRunInCurrentContext).thenReturn(true); - when(() => noIssuesValidator.description) - .thenReturn('no issues validator'); + when( + () => noIssuesValidator.description, + ).thenReturn('no issues validator'); - when(warningValidator.validate).thenAnswer( - (_) async => [validationWarning], - ); + when( + warningValidator.validate, + ).thenAnswer((_) async => [validationWarning]); when(warningValidator.canRunInCurrentContext).thenReturn(true); when(() => warningValidator.description).thenReturn('warning validator'); @@ -67,10 +63,7 @@ with multiple lines''', group('runValidators', () { test('prints messages when warnings and errors found', () async { - final validators = [ - warningValidator, - errorValidator, - ]; + final validators = [warningValidator, errorValidator]; await runWithOverrides(() => doctor.runValidators(validators)); for (final validator in validators) { @@ -96,35 +89,35 @@ with multiple lines''', group('when validators yield errors', () { test('completes validator progress as failure', () async { - await runWithOverrides( - () => doctor.runValidators([errorValidator]), - ); + await runWithOverrides(() => doctor.runValidators([errorValidator])); verify(() => progress.fail(any())).called(1); verifyNever(() => progress.complete(any())); }); }); - test('only runs validators that can run in the current context', - () async { - final validators = [ - noIssuesValidator, - warningValidator, - errorValidator, - ]; - when(() => warningValidator.canRunInCurrentContext()).thenReturn(false); + test( + 'only runs validators that can run in the current context', + () async { + final validators = [ + noIssuesValidator, + warningValidator, + errorValidator, + ]; + when( + () => warningValidator.canRunInCurrentContext(), + ).thenReturn(false); - await runWithOverrides(() async => doctor.runValidators(validators)); + await runWithOverrides(() async => doctor.runValidators(validators)); - verify(noIssuesValidator.validate).called(1); - verifyNever(warningValidator.validate); - verify(errorValidator.validate).called(1); - }); + verify(noIssuesValidator.validate).called(1); + verifyNever(warningValidator.validate); + verify(errorValidator.validate).called(1); + }, + ); test('tells the user when no issues are found', () async { - final validators = [ - noIssuesValidator, - ]; + final validators = [noIssuesValidator]; await runWithOverrides(() async => doctor.runValidators(validators)); @@ -152,32 +145,34 @@ with multiple lines''', wasFixCalled = false; fixableWarningValidator = MockValidator(); - when(fixableWarningValidator.validate).thenAnswer( - (_) async => [fixableValidationWarning], - ); - when(() => fixableWarningValidator.description) - .thenReturn('fixable warning validator'); + when( + fixableWarningValidator.validate, + ).thenAnswer((_) async => [fixableValidationWarning]); + when( + () => fixableWarningValidator.description, + ).thenReturn('fixable warning validator'); when(fixableWarningValidator.canRunInCurrentContext).thenReturn(true); }); test( - '''does not tell the user we can fix issues if no fixable issues are found''', - () async { - await runWithOverrides( - () => doctor.runValidators([warningValidator, errorValidator]), - ); + '''does not tell the user we can fix issues if no fixable issues are found''', + () async { + await runWithOverrides( + () => doctor.runValidators([warningValidator, errorValidator]), + ); - verifyNever( - () => logger.info( - any( - that: stringContainsInOrder([ - 'can be fixed automatically with', - 'shorebird doctor --fix', - ]), + verifyNever( + () => logger.info( + any( + that: stringContainsInOrder([ + 'can be fixed automatically with', + 'shorebird doctor --fix', + ]), + ), ), - ), - ); - }); + ); + }, + ); test('does not perform fixes if applyFixes is false', () async { await runWithOverrides( @@ -202,10 +197,9 @@ with multiple lines''', (_) async => wasFixCalled ? [] : [fixableValidationWarning], ); await runWithOverrides( - () async => doctor.runValidators( - [fixableWarningValidator], - applyFixes: true, - ), + () async => doctor.runValidators([ + fixableWarningValidator, + ], applyFixes: true), ); verify(() => progress.update('Fixing')); @@ -218,14 +212,13 @@ with multiple lines''', }); test('prints error if fixes fail to apply', () async { - when(fixableWarningValidator.validate).thenAnswer( - (_) async => [erroringValidationWarning], - ); + when( + fixableWarningValidator.validate, + ).thenAnswer((_) async => [erroringValidationWarning]); await runWithOverrides( - () async => doctor.runValidators( - [fixableWarningValidator], - applyFixes: true, - ), + () async => doctor.runValidators([ + fixableWarningValidator, + ], applyFixes: true), ); verify( diff --git a/packages/shorebird_cli/test/src/executables/adb_test.dart b/packages/shorebird_cli/test/src/executables/adb_test.dart index 84e798fd..5e3cf828 100644 --- a/packages/shorebird_cli/test/src/executables/adb_test.dart +++ b/packages/shorebird_cli/test/src/executables/adb_test.dart @@ -37,14 +37,9 @@ void main() { adb = Adb(); when(() => androidSdk.adbPath).thenReturn(adbPath); - when( - () => process.run(any(), any()), - ).thenAnswer( - (_) async => const ShorebirdProcessResult( - exitCode: 0, - stdout: '', - stderr: '', - ), + when(() => process.run(any(), any())).thenAnswer( + (_) async => + const ShorebirdProcessResult(exitCode: 0, stdout: '', stderr: ''), ); }); @@ -65,9 +60,7 @@ void main() { }); test('throws process exits with non-zero exit code', () async { - when( - () => process.run(any(), any()), - ).thenAnswer( + when(() => process.run(any(), any())).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, stdout: '', @@ -130,9 +123,7 @@ void main() { }); test('throws process exits with non-zero exit code', () async { - when( - () => process.run(any(), any()), - ).thenAnswer( + when(() => process.run(any(), any())).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, stdout: '', @@ -175,8 +166,9 @@ void main() { verify( () => process.run( adbPath, - '-s $deviceId shell monkey --pct-syskeys 0 -p $package 1' - .split(' '), + '-s $deviceId shell monkey --pct-syskeys 0 -p $package 1'.split( + ' ', + ), ), ).called(1); }); @@ -214,9 +206,7 @@ void main() { final logcatProcess = MockProcess(); const logcatStdout = Stream>.empty(); - when( - () => logcatProcess.stdout, - ).thenAnswer((_) => logcatStdout); + when(() => logcatProcess.stdout).thenAnswer((_) => logcatStdout); when(() => process.start(any(), any())).thenAnswer((invocation) async { final executable = invocation.positionalArguments[0] as String; if (executable == adbPath) return logcatProcess; diff --git a/packages/shorebird_cli/test/src/executables/aot_tools_test.dart b/packages/shorebird_cli/test/src/executables/aot_tools_test.dart index 3fe4f158..16ac42c3 100644 --- a/packages/shorebird_cli/test/src/executables/aot_tools_test.dart +++ b/packages/shorebird_cli/test/src/executables/aot_tools_test.dart @@ -91,20 +91,18 @@ void main() { any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 1); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => Stream.value(utf8.encode('info'))); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => Stream.value(utf8.encode('error'))); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 1); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('info'))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode('error'))); - return mockProcess; - }, - ); + return mockProcess; + }); await expectLater( () => runWithOverrides( () => aotTools.link( @@ -120,11 +118,9 @@ void main() { isA().having( (e) => '$e', 'toString', - contains( - ''' + contains(''' stdout: info -stderr: error''', - ), +stderr: error'''), ), ), ); @@ -161,20 +157,18 @@ stderr: error''', any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + return mockProcess; + }); await expectLater( runWithOverrides( () => aotTools.link( @@ -190,18 +184,14 @@ stderr: error''', completes, ); verify( - () => process.start( - aotToolsPath, - [ - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - ], - workingDirectory: any(named: 'workingDirectory'), - ), + () => process.start(aotToolsPath, [ + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + ], workingDirectory: any(named: 'workingDirectory')), ).called(1); }); @@ -225,20 +215,18 @@ stderr: error''', any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + return mockProcess; + }); await expectLater( runWithOverrides( () => aotTools.link( @@ -255,21 +243,17 @@ stderr: error''', completes, ); verify( - () => process.start( - aotToolsPath, - [ - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - '--', - '--foo', - 'bar', - ], - workingDirectory: any(named: 'workingDirectory'), - ), + () => process.start(aotToolsPath, [ + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + '--', + '--foo', + 'bar', + ], workingDirectory: any(named: 'workingDirectory')), ).called(1); }); @@ -280,20 +264,18 @@ stderr: error''', any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => Stream.value(utf8.encode('stdout'))); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('stdout'))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + return mockProcess; + }); await runWithOverrides( () => aotTools.link( @@ -312,87 +294,78 @@ stderr: error''', }); }); - group( - 'when --dump-debug-info is provided', - () { - const aotToolsPath = 'aot_tools'; + group('when --dump-debug-info is provided', () { + const aotToolsPath = 'aot_tools'; - setUp(() { + setUp(() { + when( + () => shorebirdArtifacts.getArtifactPath( + artifact: ShorebirdArtifact.aotTools, + ), + ).thenReturn(aotToolsPath); + }); + + test('forwards the option to aot_tools', () async { + const debugPath = 'my_debug_path'; + when( + () => process.run( + aotToolsPath, + any(), + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer( + (_) async => const ShorebirdProcessResult( + exitCode: 0, + stdout: '', + stderr: '', + ), + ); + when( + () => process.start( + aotToolsPath, + any(), + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); when( - () => shorebirdArtifacts.getArtifactPath( - artifact: ShorebirdArtifact.aotTools, - ), - ).thenReturn(aotToolsPath); + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); + + return mockProcess; }); - - test('forwards the option to aot_tools', () async { - const debugPath = 'my_debug_path'; - when( - () => process.run( - aotToolsPath, - any(), - workingDirectory: any(named: 'workingDirectory'), + await expectLater( + runWithOverrides( + () => aotTools.link( + base: base, + patch: patch, + analyzeSnapshot: analyzeSnapshot, + genSnapshot: genSnapshot, + kernel: kernel, + workingDirectory: workingDirectory.path, + outputPath: outputPath, + dumpDebugInfoPath: debugPath, ), - ).thenAnswer( - (_) async => const ShorebirdProcessResult( - exitCode: 0, - stdout: '', - stderr: '', - ), - ); - when( - () => process.start( - aotToolsPath, - any(), - workingDirectory: any(named: 'workingDirectory'), - ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); - - return mockProcess; - }, - ); - await expectLater( - runWithOverrides( - () => aotTools.link( - base: base, - patch: patch, - analyzeSnapshot: analyzeSnapshot, - genSnapshot: genSnapshot, - kernel: kernel, - workingDirectory: workingDirectory.path, - outputPath: outputPath, - dumpDebugInfoPath: debugPath, - ), - ), - completes, - ); - verify( - () => process.start( - aotToolsPath, - [ - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - '--dump-debug-info=$debugPath', - ], - workingDirectory: any(named: 'workingDirectory'), - ), - ).called(1); - }); - }, - ); + ), + completes, + ); + verify( + () => process.start(aotToolsPath, [ + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + '--dump-debug-info=$debugPath', + ], workingDirectory: any(named: 'workingDirectory')), + ).called(1); + }); + }); group('when aot-tools is a kernel file', () { const aotToolsPath = 'aot_tools.dill'; @@ -425,19 +398,17 @@ stderr: error''', any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); + return mockProcess; + }); await expectLater( runWithOverrides( () => aotTools.link( @@ -453,20 +424,16 @@ stderr: error''', completes, ); verify( - () => process.start( - dartBinaryFile.path, - [ - 'run', - aotToolsPath, - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - ], - workingDirectory: any(named: 'workingDirectory'), - ), + () => process.start(dartBinaryFile.path, [ + 'run', + aotToolsPath, + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + ], workingDirectory: any(named: 'workingDirectory')), ).called(1); }); }); @@ -502,20 +469,18 @@ stderr: error''', any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + return mockProcess; + }); await expectLater( runWithOverrides( () => aotTools.link( @@ -531,20 +496,16 @@ stderr: error''', completes, ); verify( - () => process.start( - dartBinaryFile.path, - [ - 'run', - aotToolsPath, - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - ], - workingDirectory: any(named: 'workingDirectory'), - ), + () => process.start(dartBinaryFile.path, [ + 'run', + aotToolsPath, + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + ], workingDirectory: any(named: 'workingDirectory')), ).called(1); }); }); @@ -562,44 +523,38 @@ stderr: error''', test('passes gen_snapshot to aot_tools', () async { when( - () => process.start( - aotToolsPath, - ['--version'], - workingDirectory: any(named: 'workingDirectory'), - ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => Stream.value(utf8.encode('0.0.1'))); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + () => process.start(aotToolsPath, [ + '--version', + ], workingDirectory: any(named: 'workingDirectory')), + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('0.0.1'))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); + return mockProcess; + }); when( () => process.start( aotToolsPath, any(that: contains('--gen-snapshot=$genSnapshot')), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + return mockProcess; + }); await expectLater( runWithOverrides( () => aotTools.link( @@ -616,73 +571,62 @@ stderr: error''', ); verify( - () => process.start( - aotToolsPath, - [ - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - '--gen-snapshot=$genSnapshot', - '--kernel=$kernel', - '--reporter=json', - '--redirect-to=$linkJsonPath', - ], - workingDirectory: any(named: 'workingDirectory'), - ), + () => process.start(aotToolsPath, [ + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + '--gen-snapshot=$genSnapshot', + '--kernel=$kernel', + '--reporter=json', + '--redirect-to=$linkJsonPath', + ], workingDirectory: any(named: 'workingDirectory')), ).called(1); }); test('returns link percentage', () async { workingDirectory = Directory.systemTemp.createTempSync(); when( - () => process.start( - aotToolsPath, - ['--version'], - workingDirectory: any(named: 'workingDirectory'), - ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => Stream.value(utf8.encode('0.0.1'))); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + () => process.start(aotToolsPath, [ + '--version', + ], workingDirectory: any(named: 'workingDirectory')), + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('0.0.1'))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); + return mockProcess; + }); when( () => process.start( aotToolsPath, any(that: contains('--gen-snapshot=$genSnapshot')), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - File(p.join(workingDirectory.path, 'link.jsonl')) - .writeAsStringSync( - ''' + ).thenAnswer((_) async { + File(p.join(workingDirectory.path, 'link.jsonl')).writeAsStringSync( + ''' {"type":"link_success","base_codes_length":3036,"patch_codes_length":3036,"base_code_size":861816,"patch_code_size":861816,"linked_code_size":860460,"link_percentage":99.8426578295135} {"type":"link_debug","message":"wrote vmcode file to out.vmcode"}''', - ); + ); - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => const Stream.empty()); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => const Stream.empty()); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + return mockProcess; + }); await expectLater( runWithOverrides( () => aotTools.link( @@ -699,22 +643,18 @@ stderr: error''', ); verify( - () => process.start( - aotToolsPath, - [ - 'link', - '--base=$base', - '--patch=$patch', - '--analyze-snapshot=$analyzeSnapshot', - '--output=$outputPath', - '--verbose', - '--gen-snapshot=$genSnapshot', - '--kernel=$kernel', - '--reporter=json', - '--redirect-to=$linkJsonPath', - ], - workingDirectory: any(named: 'workingDirectory'), - ), + () => process.start(aotToolsPath, [ + 'link', + '--base=$base', + '--patch=$patch', + '--analyze-snapshot=$analyzeSnapshot', + '--output=$outputPath', + '--verbose', + '--gen-snapshot=$genSnapshot', + '--kernel=$kernel', + '--reporter=json', + '--redirect-to=$linkJsonPath', + ], workingDirectory: any(named: 'workingDirectory')), ).called(1); }); }); @@ -831,19 +771,17 @@ Run "aot_tools help" to see global options. any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => 0); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => Stream.value(utf8.encode(stdout))); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => const Stream.empty()); - return mockProcess; - }, - ); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => 0); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode(stdout))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => const Stream.empty()); + return mockProcess; + }); }); group('when dump_blobs flag is not recognized', () { @@ -916,19 +854,17 @@ Run "aot_tools help " for more information about a command. any(), workingDirectory: any(named: 'workingDirectory'), ), - ).thenAnswer( - (_) async { - final mockProcess = MockProcess(); - when(() => mockProcess.exitCode).thenAnswer((_) async => exitCode); - when( - () => mockProcess.stdout, - ).thenAnswer((_) => Stream.value(utf8.encode(stdout))); - when( - () => mockProcess.stderr, - ).thenAnswer((_) => Stream.value(utf8.encode(stderr))); - return mockProcess; - }, - ); + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => exitCode); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode(stdout))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode(stderr))); + return mockProcess; + }); }); group('when command returns non-zero exit code', () { diff --git a/packages/shorebird_cli/test/src/executables/bundletool_test.dart b/packages/shorebird_cli/test/src/executables/bundletool_test.dart index 26a82562..c3bb3626 100644 --- a/packages/shorebird_cli/test/src/executables/bundletool_test.dart +++ b/packages/shorebird_cli/test/src/executables/bundletool_test.dart @@ -63,11 +63,8 @@ void main() { test('throws exception if process returns non-zero exit code', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, @@ -91,17 +88,11 @@ void main() { test('completes when process succeeds', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( - (_) async => const ShorebirdProcessResult( - exitCode: 0, - stdout: '', - stderr: '', - ), + (_) async => + const ShorebirdProcessResult(exitCode: 0, stdout: '', stderr: ''), ); await expectLater( runWithOverrides( @@ -245,11 +236,8 @@ void main() { test('throws exception if process returns non-zero exit code', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, @@ -272,17 +260,11 @@ void main() { test('completes when process succeeds', () async { const deviceId = '1234'; when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( - (_) async => const ShorebirdProcessResult( - exitCode: 0, - stdout: '', - stderr: '', - ), + (_) async => + const ShorebirdProcessResult(exitCode: 0, stdout: '', stderr: ''), ); await expectLater( runWithOverrides( @@ -313,11 +295,8 @@ void main() { group('getPackageName', () { test('throws exception if process returns non-zero exit code', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, @@ -326,9 +305,8 @@ void main() { ), ); await expectLater( - () => runWithOverrides( - () => bundletool.getPackageName(appBundlePath), - ), + () => + runWithOverrides(() => bundletool.getPackageName(appBundlePath)), throwsA( isA().having( (e) => e.toString(), @@ -341,11 +319,8 @@ void main() { test('returns the correct package name', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 0, @@ -382,11 +357,8 @@ void main() { group('getVersionName', () { test('throws exception if process returns non-zero exit code', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, @@ -395,9 +367,8 @@ void main() { ), ); await expectLater( - () => runWithOverrides( - () => bundletool.getVersionName(appBundlePath), - ), + () => + runWithOverrides(() => bundletool.getVersionName(appBundlePath)), throwsA( isA().having( (e) => e.toString(), @@ -410,11 +381,8 @@ void main() { test('returns the correct version name', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 0, @@ -451,11 +419,8 @@ void main() { group('getVersionCode', () { test('throws exception if process returns non-zero exit code', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, @@ -464,9 +429,8 @@ void main() { ), ); await expectLater( - () => runWithOverrides( - () => bundletool.getVersionCode(appBundlePath), - ), + () => + runWithOverrides(() => bundletool.getVersionCode(appBundlePath)), throwsA( isA().having( (e) => e.toString(), @@ -479,11 +443,8 @@ void main() { test('returns the correct version code', () async { when( - () => process.run( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.run(any(), any(), environment: any(named: 'environment')), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 0, diff --git a/packages/shorebird_cli/test/src/executables/devicectl/apple_device_test.dart b/packages/shorebird_cli/test/src/executables/devicectl/apple_device_test.dart index 503e442d..03dffae5 100644 --- a/packages/shorebird_cli/test/src/executables/devicectl/apple_device_test.dart +++ b/packages/shorebird_cli/test/src/executables/devicectl/apple_device_test.dart @@ -14,10 +14,7 @@ void main() { name: deviceName, osVersionNumber: '17.1', ); - const hardwareProperties = HardwareProperties( - platform: 'iOS', - udid: udid, - ); + const hardwareProperties = HardwareProperties(platform: 'iOS', udid: udid); late AppleDevice device; diff --git a/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart b/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart index 8dfd7a58..4fcd3983 100644 --- a/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart +++ b/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart @@ -92,39 +92,45 @@ void main() { ); }); - test('returns null if no CoreDevice with the given deviceID can be found', - () async { - exitCode = ExitCode.success; - jsonOutput = - File('$fixturesPath/device_list_success.json').readAsStringSync(); - expect( - await runWithOverrides( - () => devicectl.deviceForLaunch(deviceId: 'fake device id'), - ), - isNull, - ); - }); + test( + 'returns null if no CoreDevice with the given deviceID can be found', + () async { + exitCode = ExitCode.success; + jsonOutput = + File('$fixturesPath/device_list_success.json').readAsStringSync(); + expect( + await runWithOverrides( + () => devicectl.deviceForLaunch(deviceId: 'fake device id'), + ), + isNull, + ); + }, + ); test('returns null if no CoreDevice can be found', () async { exitCode = ExitCode.success; - jsonOutput = File('$fixturesPath/device_list_success_empty.json') - .readAsStringSync(); + jsonOutput = + File( + '$fixturesPath/device_list_success_empty.json', + ).readAsStringSync(); expect( await runWithOverrides(() => devicectl.deviceForLaunch()), isNull, ); }); - test("returns a device if device's OS version is 17 or greater", - () async { - exitCode = ExitCode.success; - jsonOutput = - File('$fixturesPath/device_list_success.json').readAsStringSync(); - expect( - await runWithOverrides(() => devicectl.deviceForLaunch()), - isNotNull, - ); - }); + test( + "returns a device if device's OS version is 17 or greater", + () async { + exitCode = ExitCode.success; + jsonOutput = + File('$fixturesPath/device_list_success.json').readAsStringSync(); + expect( + await runWithOverrides(() => devicectl.deviceForLaunch()), + isNotNull, + ); + }, + ); }); group('installApp', () { @@ -137,8 +143,9 @@ void main() { group('when no json output file is found', () { setUp(() { - when(() => process.run(any(), any())) - .thenAnswer((invocation) async => processResult); + when( + () => process.run(any(), any()), + ).thenAnswer((invocation) async => processResult); }); group('when the command returns a non-zero exit code', () { @@ -146,24 +153,26 @@ void main() { exitCode = ExitCode.cantCreate; }); - test('throws a DevicectlException with underlying ProcessException', - () { - expect( - runWithOverrides( - () => devicectl.installApp( - runnerApp: runnerApp, - deviceId: deviceId, + test( + 'throws a DevicectlException with underlying ProcessException', + () { + expect( + runWithOverrides( + () => devicectl.installApp( + runnerApp: runnerApp, + deviceId: deviceId, + ), ), - ), - throwsA( - isA().having( - (e) => e.underlyingException, - 'underlyingException', - isA(), + throwsA( + isA().having( + (e) => e.underlyingException, + 'underlyingException', + isA(), + ), ), - ), - ); - }); + ); + }, + ); }); group('when the command returns a zero exit code', () { @@ -227,8 +236,10 @@ void main() { group('when output json does not contain app bundleId', () { setUp(() { - jsonOutput = File('$fixturesPath/install_success_no_bundle_id.json') - .readAsStringSync(); + jsonOutput = + File( + '$fixturesPath/install_success_no_bundle_id.json', + ).readAsStringSync(); }); test('throws DevicectlException', () { @@ -275,24 +286,23 @@ void main() { }); test( - '''throws a DevicectlException with the underlying NSError message''', - () async { - expect( - runWithOverrides( - () => devicectl.launchApp( - deviceId: deviceId, - bundleId: bundleId, + '''throws a DevicectlException with the underlying NSError message''', + () async { + expect( + runWithOverrides( + () => + devicectl.launchApp(deviceId: deviceId, bundleId: bundleId), ), - ), - throwsA( - isA().having( - (e) => '${e.underlyingException}', - 'underlyingException', - '''Exception: Unable to launch dev.shorebird.ios-test because the device was not, or could not be, unlocked.''', + throwsA( + isA().having( + (e) => '${e.underlyingException}', + 'underlyingException', + '''Exception: Unable to launch dev.shorebird.ios-test because the device was not, or could not be, unlocked.''', + ), ), - ), - ); - }); + ); + }, + ); }); group('when launch succeeds', () { @@ -305,10 +315,7 @@ void main() { test('completes successfully', () async { expect( runWithOverrides( - () => devicectl.launchApp( - deviceId: deviceId, - bundleId: bundleId, - ), + () => devicectl.launchApp(deviceId: deviceId, bundleId: bundleId), ), completes, ); @@ -341,11 +348,13 @@ void main() { logger = MockShorebirdLogger(); progress = MockProgress(); - when(() => idevicesyslog.startLogger(device: any(named: 'device'))) - .thenAnswer((_) async => ExitCode.success.code); + when( + () => idevicesyslog.startLogger(device: any(named: 'device')), + ).thenAnswer((_) async => ExitCode.success.code); when(() => logger.progress(any())).thenReturn(progress); - when(() => process.run(any(), any(that: contains('list')))) - .thenAnswer((invocation) async { + when(() => process.run(any(), any(that: contains('list')))).thenAnswer(( + invocation, + ) async { final processRunArgs = invocation.positionalArguments.last as List; final jsonFilePath = processRunArgs.last; @@ -356,8 +365,9 @@ void main() { } return processResult; }); - when(() => process.run(any(), any(that: contains('install')))) - .thenAnswer((invocation) async { + when( + () => process.run(any(), any(that: contains('install'))), + ).thenAnswer((invocation) async { final processRunArgs = invocation.positionalArguments.last as List; final jsonFilePath = processRunArgs.last; @@ -368,8 +378,9 @@ void main() { } return processResult; }); - when(() => process.run(any(), any(that: contains('launch')))) - .thenAnswer((invocation) async { + when( + () => process.run(any(), any(that: contains('launch'))), + ).thenAnswer((invocation) async { final processRunArgs = invocation.positionalArguments.last as List; final jsonFilePath = processRunArgs.last; @@ -384,9 +395,10 @@ void main() { group('when no device is found', () { setUp(() { - deviceListJsonOutput = File( - '$fixturesPath/device_list_success_empty.json', - ).readAsStringSync(); + deviceListJsonOutput = + File( + '$fixturesPath/device_list_success_empty.json', + ).readAsStringSync(); }); test('returns exit code 70', () async { @@ -404,12 +416,10 @@ void main() { group('when install fails', () { setUp(() { - deviceListJsonOutput = File( - '$fixturesPath/device_list_success.json', - ).readAsStringSync(); - installJsonOutput = File( - '$fixturesPath/install_failure.json', - ).readAsStringSync(); + deviceListJsonOutput = + File('$fixturesPath/device_list_success.json').readAsStringSync(); + installJsonOutput = + File('$fixturesPath/install_failure.json').readAsStringSync(); }); test('returns exit code 70 ', () async { @@ -431,9 +441,8 @@ void main() { group('when launch fails', () { setUp(() { - deviceListJsonOutput = File( - '$fixturesPath/device_list_success.json', - ).readAsStringSync(); + deviceListJsonOutput = + File('$fixturesPath/device_list_success.json').readAsStringSync(); installJsonOutput = File('$fixturesPath/install_success.json').readAsStringSync(); launchJsonOutput = @@ -465,9 +474,8 @@ void main() { group('when install and launch succeed', () { setUp(() { - deviceListJsonOutput = File( - '$fixturesPath/device_list_success.json', - ).readAsStringSync(); + deviceListJsonOutput = + File('$fixturesPath/device_list_success.json').readAsStringSync(); installJsonOutput = File('$fixturesPath/install_success.json').readAsStringSync(); launchJsonOutput = @@ -519,8 +527,10 @@ void main() { setUp(() { // This fixture is synthetic, as I was not able to get this command // to fail. - jsonOutput = File('$fixturesPath/device_list_success_no_devices.json') - .readAsStringSync(); + jsonOutput = + File( + '$fixturesPath/device_list_success_no_devices.json', + ).readAsStringSync(); }); test('throws a DevicectlException', () { @@ -544,8 +554,9 @@ void main() { }); test('returns a list of iOS devices', () async { - final devices = - await runWithOverrides(devicectl.listAvailableIosDevices); + final devices = await runWithOverrides( + devicectl.listAvailableIosDevices, + ); expect(devices, hasLength(1)); final outputDevice = devices.first; expect(outputDevice.name, equals('Bryan Oltman’s iPhone')); diff --git a/packages/shorebird_cli/test/src/executables/devicectl/nserror_test.dart b/packages/shorebird_cli/test/src/executables/devicectl/nserror_test.dart index 46b94577..1bb383e9 100644 --- a/packages/shorebird_cli/test/src/executables/devicectl/nserror_test.dart +++ b/packages/shorebird_cli/test/src/executables/devicectl/nserror_test.dart @@ -32,9 +32,7 @@ void main() { ), ); - expect( - error.toString(), - ''' + expect(error.toString(), ''' NSError( code: 1, domain: com.example, @@ -44,8 +42,7 @@ NSError( localizedFailureReason: localizedFailureReason, underlyingError: null ) -)''', - ); +)'''); }); }); }); @@ -72,16 +69,13 @@ NSError( localizedFailureReason: StringContainer('localizedFailureReason'), ); - expect( - userInfo.toString(), - ''' + expect(userInfo.toString(), ''' UserInfo( description: description, localizedDescription: localizedDescription, localizedFailureReason: localizedFailureReason, underlyingError: null -)''', - ); +)'''); }); }); }); @@ -139,9 +133,7 @@ UserInfo( ), ); - expect( - underlyingError.toString(), - ''' + expect(underlyingError.toString(), ''' NSUnderlyingError( NSError( code: 1, @@ -153,8 +145,7 @@ NSUnderlyingError( underlyingError: null ) ) -)''', - ); +)'''); }); }); }); diff --git a/packages/shorebird_cli/test/src/executables/ditto_test.dart b/packages/shorebird_cli/test/src/executables/ditto_test.dart index cfb59b39..a86268af 100644 --- a/packages/shorebird_cli/test/src/executables/ditto_test.dart +++ b/packages/shorebird_cli/test/src/executables/ditto_test.dart @@ -12,12 +12,7 @@ void main() { late Ditto ditto; R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - processRef.overrideWith(() => process), - }, - ); + return runScoped(body, values: {processRef.overrideWith(() => process)}); } setUp(() { @@ -31,9 +26,7 @@ void main() { group('when process exits with code 0', () { setUp(() { - when( - () => process.run('ditto', any()), - ).thenAnswer( + when(() => process.run('ditto', any())).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 0, stdout: '', @@ -50,10 +43,7 @@ void main() { completes, ); verify( - () => process.run( - 'ditto', - ['-x', '-k', source, destination], - ), + () => process.run('ditto', ['-x', '-k', source, destination]), ).called(1); }); }); @@ -61,9 +51,7 @@ void main() { group('when process exits with non-zero exit code', () { const error = 'oops something went wrong'; setUp(() { - when( - () => process.run('ditto', any()), - ).thenAnswer( + when(() => process.run('ditto', any())).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, stdout: '', @@ -86,10 +74,7 @@ void main() { ), ); verify( - () => process.run( - 'ditto', - ['-x', '-k', source, destination], - ), + () => process.run('ditto', ['-x', '-k', source, destination]), ).called(1); }); }); @@ -102,10 +87,7 @@ void main() { group('when process exits with code 0', () { setUp(() { when( - () => process.run( - 'ditto', - ['-c', '-k', source, destination], - ), + () => process.run('ditto', ['-c', '-k', source, destination]), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 0, @@ -118,16 +100,13 @@ void main() { group('when keepParent is true', () { setUp(() { when( - () => process.run( - 'ditto', - [ - '-c', - '-k', - '--keepParent', - source, - destination, - ], - ), + () => process.run('ditto', [ + '-c', + '-k', + '--keepParent', + source, + destination, + ]), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 0, @@ -149,16 +128,13 @@ void main() { completes, ); verify( - () => process.run( - 'ditto', - [ - '-c', - '-k', - '--keepParent', - source, - destination, - ], - ), + () => process.run('ditto', [ + '-c', + '-k', + '--keepParent', + source, + destination, + ]), ).called(1); }); }); @@ -171,10 +147,7 @@ void main() { completes, ); verify( - () => process.run( - 'ditto', - ['-c', '-k', source, destination], - ), + () => process.run('ditto', ['-c', '-k', source, destination]), ).called(1); }); }); @@ -183,10 +156,7 @@ void main() { const error = 'oops something went wrong'; setUp(() { when( - () => process.run( - 'ditto', - ['-c', '-k', source, destination], - ), + () => process.run('ditto', ['-c', '-k', source, destination]), ).thenAnswer( (_) async => const ShorebirdProcessResult( exitCode: 1, @@ -210,10 +180,7 @@ void main() { ), ); verify( - () => process.run( - 'ditto', - ['-c', '-k', source, destination], - ), + () => process.run('ditto', ['-c', '-k', source, destination]), ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/executables/git_test.dart b/packages/shorebird_cli/test/src/executables/git_test.dart index f9ddc198..81d386d3 100644 --- a/packages/shorebird_cli/test/src/executables/git_test.dart +++ b/packages/shorebird_cli/test/src/executables/git_test.dart @@ -18,9 +18,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( () => body(), - values: { - processRef.overrideWith(() => process), - }, + values: {processRef.overrideWith(() => process)}, ); } @@ -46,17 +44,14 @@ void main() { test('executes correct command (no args)', () async { await runWithOverrides( - () => git.clone( - url: url, - outputDirectory: outputDirectory, - ), + () => git.clone(url: url, outputDirectory: outputDirectory), ); verify( - () => process.run( - 'git', - ['clone', url, outputDirectory], - runInShell: true, - ), + () => process.run('git', [ + 'clone', + url, + outputDirectory, + ], runInShell: true), ).called(1); }); @@ -70,11 +65,12 @@ void main() { ), ); verify( - () => process.run( - 'git', - ['clone', url, ...args, outputDirectory], - runInShell: true, - ), + () => process.run('git', [ + 'clone', + url, + ...args, + outputDirectory, + ], runInShell: true), ).called(1); }); @@ -84,10 +80,7 @@ void main() { when(() => processResult.stderr).thenReturn(error); expect( () => runWithOverrides( - () => git.clone( - url: url, - outputDirectory: outputDirectory, - ), + () => git.clone(url: url, outputDirectory: outputDirectory), ), throwsA( isA().having((e) => e.message, 'message', error), @@ -105,18 +98,14 @@ void main() { () => git.checkout(directory: directory, revision: revision), ); verify( - () => process.run( - 'git', - [ - '-C', - directory, - '-c', - 'advice.detachedHead=false', - 'checkout', - revision, - ], - runInShell: true, - ), + () => process.run('git', [ + '-C', + directory, + '-c', + 'advice.detachedHead=false', + 'checkout', + revision, + ], runInShell: true), ).called(1); }); @@ -126,10 +115,7 @@ void main() { when(() => processResult.stderr).thenReturn(error); expect( () => runWithOverrides( - () => git.checkout( - directory: directory, - revision: revision, - ), + () => git.checkout(directory: directory, revision: revision), ), throwsA( isA().having((e) => e.message, 'message', error), @@ -146,11 +132,7 @@ void main() { completes, ); verify( - () => process.run( - 'git', - ['fetch'], - workingDirectory: directory, - ), + () => process.run('git', ['fetch'], workingDirectory: directory), ).called(1); }); @@ -161,11 +143,10 @@ void main() { completes, ); verify( - () => process.run( - 'git', - ['fetch', ...args], - workingDirectory: directory, - ), + () => process.run('git', [ + 'fetch', + ...args, + ], workingDirectory: directory), ).called(1); }); @@ -208,11 +189,12 @@ origin/flutter_release/3.10.6'''; completion(equals(output.trim())), ); verify( - () => process.run( - 'git', - ['for-each-ref', '--format', format, pattern], - workingDirectory: directory, - ), + () => process.run('git', [ + 'for-each-ref', + '--format', + format, + pattern, + ], workingDirectory: directory), ).called(1); }); @@ -231,18 +213,14 @@ origin/flutter_release/3.10.6'''; completion(equals(output.trim())), ); verify( - () => process.run( - 'git', - [ - 'for-each-ref', - '--contains', - contains, - '--format', - format, - pattern, - ], - workingDirectory: directory, - ), + () => process.run('git', [ + 'for-each-ref', + '--contains', + contains, + '--format', + format, + pattern, + ], workingDirectory: directory), ).called(1); }); @@ -277,11 +255,10 @@ origin/flutter_release/3.10.6'''; completes, ); verify( - () => process.run( - 'git', - ['reset', revision], - workingDirectory: directory, - ), + () => process.run('git', [ + 'reset', + revision, + ], workingDirectory: directory), ).called(1); }); @@ -289,20 +266,17 @@ origin/flutter_release/3.10.6'''; const args = ['--hard']; await expectLater( runWithOverrides( - () => git.reset( - directory: directory, - revision: revision, - args: args, - ), + () => + git.reset(directory: directory, revision: revision, args: args), ), completes, ); verify( - () => process.run( - 'git', - ['reset', ...args, revision], - workingDirectory: directory, - ), + () => process.run('git', [ + 'reset', + ...args, + revision, + ], workingDirectory: directory), ).called(1); }); @@ -312,10 +286,7 @@ origin/flutter_release/3.10.6'''; when(() => processResult.stderr).thenReturn(error); expect( () => runWithOverrides( - () => git.reset( - directory: directory, - revision: revision, - ), + () => git.reset(directory: directory, revision: revision), ), throwsA( isA().having((e) => e.message, 'message', error), @@ -329,37 +300,25 @@ origin/flutter_release/3.10.6'''; test('executes correct command', () async { await expectLater( - runWithOverrides( - () => git.remote(directory: directory), - ), + runWithOverrides(() => git.remote(directory: directory)), completes, ); verify( - () => process.run( - 'git', - ['remote'], - workingDirectory: directory, - ), + () => process.run('git', ['remote'], workingDirectory: directory), ).called(1); }); test('executes correct command w/args', () async { const args = ['prune', 'origin']; await expectLater( - runWithOverrides( - () => git.remote( - directory: directory, - args: args, - ), - ), + runWithOverrides(() => git.remote(directory: directory, args: args)), completes, ); verify( - () => process.run( - 'git', - ['remote', ...args], - workingDirectory: directory, - ), + () => process.run('git', [ + 'remote', + ...args, + ], workingDirectory: directory), ).called(1); }); @@ -390,11 +349,11 @@ origin/flutter_release/3.10.6'''; completion(equals(output)), ); verify( - () => process.run( - 'git', - ['rev-parse', '--verify', revision], - workingDirectory: directory, - ), + () => process.run('git', [ + 'rev-parse', + '--verify', + revision, + ], workingDirectory: directory), ).called(1); }); @@ -404,10 +363,7 @@ origin/flutter_release/3.10.6'''; when(() => processResult.stderr).thenReturn(error); expect( () => runWithOverrides( - () => git.revParse( - directory: directory, - revision: revision, - ), + () => git.revParse(directory: directory, revision: revision), ), throwsA( isA().having((e) => e.message, 'message', error), @@ -440,11 +396,10 @@ origin/flutter_release/3.10.6'''; completion(equals(output)), ); verify( - () => process.run( - 'git', - ['status', ...args], - workingDirectory: directory, - ), + () => process.run('git', [ + 'status', + ...args, + ], workingDirectory: directory), ).called(1); }); @@ -476,11 +431,10 @@ origin/flutter_release/3.10.6'''; completion(equals('refs/heads/main')), ); verify( - () => process.run( - 'git', - ['symbolic-ref', '1234'], - workingDirectory: directory.path, - ), + () => process.run('git', [ + 'symbolic-ref', + '1234', + ], workingDirectory: directory.path), ).called(1); }); @@ -491,11 +445,10 @@ origin/flutter_release/3.10.6'''; completion(equals('refs/heads/main')), ); verify( - () => process.run( - 'git', - ['symbolic-ref', 'HEAD'], - workingDirectory: directory.path, - ), + () => process.run('git', [ + 'symbolic-ref', + 'HEAD', + ], workingDirectory: directory.path), ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/executables/gradlew_test.dart b/packages/shorebird_cli/test/src/executables/gradlew_test.dart index aa27db5f..8307bb7d 100644 --- a/packages/shorebird_cli/test/src/executables/gradlew_test.dart +++ b/packages/shorebird_cli/test/src/executables/gradlew_test.dart @@ -52,23 +52,17 @@ void main() { group(MissingAndroidProjectException, () { test('toString is correct', () { - expect( - const MissingAndroidProjectException('test').toString(), - ''' + expect(const MissingAndroidProjectException('test').toString(), ''' Could not find an android project in test. -To add android, run "flutter create . --platforms android"''', - ); +To add android, run "flutter create . --platforms android"'''); }); }); group(MissingGradleWrapperException, () { test('toString is correct', () { - expect( - const MissingGradleWrapperException('test').toString(), - ''' + expect(const MissingGradleWrapperException('test').toString(), ''' Could not find test. -Make sure you have run "flutter build apk" at least once.''', - ); +Make sure you have run "flutter build apk" at least once.'''); }); }); @@ -79,106 +73,90 @@ Make sure you have run "flutter build apk" at least once.''', } group('productFlavors', () { - test( - 'throws MissingAndroidProjectException ' - 'when android root does not exist', - () async { - final tempDir = Directory.systemTemp.createTempSync(); - await expectLater( - runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - throwsA(isA()), - ); - verifyNever( - () => process.run( - p.join(tempDir.path, 'android', 'gradlew'), - ['app:tasks', '--all', '--console=auto'], - runInShell: true, - workingDirectory: p.join(tempDir.path, 'android'), - environment: {'JAVA_HOME': javaHome}, - ), - ); - }, - testOn: 'linux || mac-os', - ); - - test( - 'throws MissingGradleWrapperException ' - 'when gradlew does not exist', - () async { - final tempDir = setUpAppTempDir(); - await expectLater( - runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - throwsA(isA()), - ); - verifyNever( - () => process.run( - p.join(tempDir.path, 'android', 'gradlew'), - ['app:tasks', '--all', '--console=auto'], - runInShell: true, - workingDirectory: p.join(tempDir.path, 'android'), - environment: {'JAVA_HOME': javaHome}, - ), - ); - }, - testOn: 'linux || mac-os', - ); - - test( - 'uses existing JAVA_HOME when set', - () async { - final tempDir = setUpAppTempDir(); - File( + test('throws MissingAndroidProjectException ' + 'when android root does not exist', () async { + final tempDir = Directory.systemTemp.createTempSync(); + await expectLater( + runWithOverrides(() => gradlew.productFlavors(tempDir.path)), + throwsA(isA()), + ); + verifyNever( + () => process.run( p.join(tempDir.path, 'android', 'gradlew'), - ).createSync(recursive: true); - await expectLater( - runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - completes, - ); - verify( - () => process.run( - p.join(tempDir.path, 'android', 'gradlew'), - ['app:tasks', '--all', '--console=auto'], - runInShell: true, - workingDirectory: p.join(tempDir.path, 'android'), - environment: {'JAVA_HOME': javaHome}, - ), - ).called(1); - }, - testOn: 'linux || mac-os', - ); + ['app:tasks', '--all', '--console=auto'], + runInShell: true, + workingDirectory: p.join(tempDir.path, 'android'), + environment: {'JAVA_HOME': javaHome}, + ), + ); + }, testOn: 'linux || mac-os'); - test( - 'throws Exception ' - 'when process exits with non-zero code', - () async { - final tempDir = setUpAppTempDir(); - File( + test('throws MissingGradleWrapperException ' + 'when gradlew does not exist', () async { + final tempDir = setUpAppTempDir(); + await expectLater( + runWithOverrides(() => gradlew.productFlavors(tempDir.path)), + throwsA(isA()), + ); + verifyNever( + () => process.run( p.join(tempDir.path, 'android', 'gradlew'), - ).createSync(recursive: true); - when(() => result.exitCode).thenReturn(1); - when(() => result.stderr).thenReturn('test error'); - await expectLater( - runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - throwsA( - isA().having( - (e) => '$e', - 'message', - contains('test error'), - ), + ['app:tasks', '--all', '--console=auto'], + runInShell: true, + workingDirectory: p.join(tempDir.path, 'android'), + environment: {'JAVA_HOME': javaHome}, + ), + ); + }, testOn: 'linux || mac-os'); + + test('uses existing JAVA_HOME when set', () async { + final tempDir = setUpAppTempDir(); + File( + p.join(tempDir.path, 'android', 'gradlew'), + ).createSync(recursive: true); + await expectLater( + runWithOverrides(() => gradlew.productFlavors(tempDir.path)), + completes, + ); + verify( + () => process.run( + p.join(tempDir.path, 'android', 'gradlew'), + ['app:tasks', '--all', '--console=auto'], + runInShell: true, + workingDirectory: p.join(tempDir.path, 'android'), + environment: {'JAVA_HOME': javaHome}, + ), + ).called(1); + }, testOn: 'linux || mac-os'); + + test('throws Exception ' + 'when process exits with non-zero code', () async { + final tempDir = setUpAppTempDir(); + File( + p.join(tempDir.path, 'android', 'gradlew'), + ).createSync(recursive: true); + when(() => result.exitCode).thenReturn(1); + when(() => result.stderr).thenReturn('test error'); + await expectLater( + runWithOverrides(() => gradlew.productFlavors(tempDir.path)), + throwsA( + isA().having( + (e) => '$e', + 'message', + contains('test error'), ), - ); - verify( - () => process.run( - p.join(tempDir.path, 'android', 'gradlew'), - ['app:tasks', '--all', '--console=auto'], - runInShell: true, - workingDirectory: p.join(tempDir.path, 'android'), - environment: {'JAVA_HOME': javaHome}, - ), - ).called(1); - }, - testOn: 'linux || mac-os', - ); + ), + ); + verify( + () => process.run( + p.join(tempDir.path, 'android', 'gradlew'), + ['app:tasks', '--all', '--console=auto'], + runInShell: true, + workingDirectory: p.join(tempDir.path, 'android'), + environment: {'JAVA_HOME': javaHome}, + ), + ).called(1); + }, testOn: 'linux || mac-os'); group( '''when the process fails with the Unsupported class file major version error XX''', @@ -233,49 +211,68 @@ BUILD FAILED in 3s }, ); - test( - 'extracts flavors', - () async { + test('extracts flavors', () async { + final tempDir = setUpAppTempDir(); + File( + p.join(tempDir.path, 'android', 'gradlew'), + ).createSync(recursive: true); + const javaHome = 'test_java_home'; + when(() => result.stdout).thenReturn( + File( + p.join('test', 'fixtures', 'gradle', 'gradle_app_tasks.txt'), + ).readAsStringSync(), + ); + await expectLater( + runWithOverrides(() => gradlew.productFlavors(tempDir.path)), + completion( + equals({ + 'development', + 'developmentInternal', + 'staging', + 'stagingInternal', + 'production', + 'productionInternal', + }), + ), + ); + verify( + () => process.run( + p.join(tempDir.path, 'android', 'gradlew'), + ['app:tasks', '--all', '--console=auto'], + runInShell: true, + workingDirectory: p.join(tempDir.path, 'android'), + environment: {'JAVA_HOME': javaHome}, + ), + ).called(1); + }, testOn: 'linux || mac-os'); + + group('when flavors are all upper case', () { + test('extracts flavors', () async { final tempDir = setUpAppTempDir(); File( p.join(tempDir.path, 'android', 'gradlew'), ).createSync(recursive: true); - const javaHome = 'test_java_home'; when(() => result.stdout).thenReturn( File( - p.join('test', 'fixtures', 'gradle', 'gradle_app_tasks.txt'), + p.join( + 'test', + 'fixtures', + 'gradle', + 'gradle_app_tasks_upper_case_flavors.txt', + ), ).readAsStringSync(), ); await expectLater( runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - completion( - equals({ - 'development', - 'developmentInternal', - 'staging', - 'stagingInternal', - 'production', - 'productionInternal', - }), - ), + completion(equals({'SP', 'RJ'})), ); - verify( - () => process.run( - p.join(tempDir.path, 'android', 'gradlew'), - ['app:tasks', '--all', '--console=auto'], - runInShell: true, - workingDirectory: p.join(tempDir.path, 'android'), - environment: {'JAVA_HOME': javaHome}, - ), - ).called(1); - }, - testOn: 'linux || mac-os', - ); + }, testOn: 'linux || mac-os'); + }); - group('when flavors are all upper case', () { - test( - 'extracts flavors', - () async { + group( + '''when flavors are mixed, starting with upper case, finishing with camel''', + () { + test('extracts flavors', () async { final tempDir = setUpAppTempDir(); File( p.join(tempDir.path, 'android', 'gradlew'), @@ -286,93 +283,40 @@ BUILD FAILED in 3s 'test', 'fixtures', 'gradle', - 'gradle_app_tasks_upper_case_flavors.txt', + 'gradle_app_tasks_mixed_case_flavors.txt', ), ).readAsStringSync(), ); await expectLater( runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - completion( - equals({ - 'SP', - 'RJ', - }), - ), + completion(equals({'SPaulo', 'RJaneiro'})), ); - }, - testOn: 'linux || mac-os', - ); + }, testOn: 'linux || mac-os'); + }, + ); + + group('when flavors starts with upper case, finishing with numbers', () { + test('extracts flavors', () async { + final tempDir = setUpAppTempDir(); + File( + p.join(tempDir.path, 'android', 'gradlew'), + ).createSync(recursive: true); + when(() => result.stdout).thenReturn( + File( + p.join( + 'test', + 'fixtures', + 'gradle', + 'gradle_app_tasks_numbers_upper_case_flavors.txt', + ), + ).readAsStringSync(), + ); + await expectLater( + runWithOverrides(() => gradlew.productFlavors(tempDir.path)), + completion(equals({'CB500', 'NX700'})), + ); + }, testOn: 'linux || mac-os'); }); - - group( - '''when flavors are mixed, starting with upper case, finishing with camel''', - () { - test( - 'extracts flavors', - () async { - final tempDir = setUpAppTempDir(); - File( - p.join(tempDir.path, 'android', 'gradlew'), - ).createSync(recursive: true); - when(() => result.stdout).thenReturn( - File( - p.join( - 'test', - 'fixtures', - 'gradle', - 'gradle_app_tasks_mixed_case_flavors.txt', - ), - ).readAsStringSync(), - ); - await expectLater( - runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - completion( - equals({ - 'SPaulo', - 'RJaneiro', - }), - ), - ); - }, - testOn: 'linux || mac-os', - ); - }, - ); - - group( - 'when flavors starts with upper case, finishing with numbers', - () { - test( - 'extracts flavors', - () async { - final tempDir = setUpAppTempDir(); - File( - p.join(tempDir.path, 'android', 'gradlew'), - ).createSync(recursive: true); - when(() => result.stdout).thenReturn( - File( - p.join( - 'test', - 'fixtures', - 'gradle', - 'gradle_app_tasks_numbers_upper_case_flavors.txt', - ), - ).readAsStringSync(), - ); - await expectLater( - runWithOverrides(() => gradlew.productFlavors(tempDir.path)), - completion( - equals({ - 'CB500', - 'NX700', - }), - ), - ); - }, - testOn: 'linux || mac-os', - ); - }, - ); }); group('exists', () { @@ -388,43 +332,29 @@ BUILD FAILED in 3s }); group('when gradlew exists', () { - group( - 'when on unix based OSs', - () { - setUp(() { - File( - p.join(tempDir.path, 'android', 'gradlew'), - ).createSync(recursive: true); - }); + group('when on unix based OSs', () { + setUp(() { + File( + p.join(tempDir.path, 'android', 'gradlew'), + ).createSync(recursive: true); + }); - test( - 'returns true', - () { - expect(gradlew.exists(tempDir.path), isTrue); - }, - testOn: 'linux || mac-os', - ); - }, - ); + test('returns true', () { + expect(gradlew.exists(tempDir.path), isTrue); + }, testOn: 'linux || mac-os'); + }); - group( - 'when on windows', - () { - setUp(() { - File( - p.join(tempDir.path, 'android', 'gradlew.bat'), - ).createSync(recursive: true); - }); + group('when on windows', () { + setUp(() { + File( + p.join(tempDir.path, 'android', 'gradlew.bat'), + ).createSync(recursive: true); + }); - test( - 'returns true', - () { - expect(gradlew.exists(tempDir.path), isTrue); - }, - testOn: 'windows', - ); - }, - ); + test('returns true', () { + expect(gradlew.exists(tempDir.path), isTrue); + }, testOn: 'windows'); + }); }); }); @@ -457,16 +387,12 @@ OS: Mac OS X 14.4.1 aarch64 '''); }); - test( - 'returns the correct version', - () async { - final version = await runWithOverrides( - () => gradlew.version(tempDir.path), - ); - expect(version, equals('12.34.56')); - }, - testOn: 'linux || mac-os', - ); + test('returns the correct version', () async { + final version = await runWithOverrides( + () => gradlew.version(tempDir.path), + ); + expect(version, equals('12.34.56')); + }, testOn: 'linux || mac-os'); }); group('when version string has only major and minor versions', () { @@ -488,16 +414,12 @@ OS: Mac OS X 14.4.1 aarch64 '''); }); - test( - 'returns the correct version', - () async { - final version = await runWithOverrides( - () => gradlew.version(tempDir.path), - ); - expect(version, equals('12.34')); - }, - testOn: 'linux || mac-os', - ); + test('returns the correct version', () async { + final version = await runWithOverrides( + () => gradlew.version(tempDir.path), + ); + expect(version, equals('12.34')); + }, testOn: 'linux || mac-os'); }); group('when the output cannot be parsed', () { @@ -505,16 +427,12 @@ OS: Mac OS X 14.4.1 aarch64 when(() => result.stdout).thenReturn('not a real version'); }); - test( - 'returns unknown', - () async { - final version = await runWithOverrides( - () => gradlew.version(tempDir.path), - ); - expect(version, 'unknown'); - }, - testOn: 'linux || mac-os', - ); + test('returns unknown', () async { + final version = await runWithOverrides( + () => gradlew.version(tempDir.path), + ); + expect(version, 'unknown'); + }, testOn: 'linux || mac-os'); }); }); }); diff --git a/packages/shorebird_cli/test/src/executables/idevicesyslog_test.dart b/packages/shorebird_cli/test/src/executables/idevicesyslog_test.dart index b1588cc6..36555df8 100644 --- a/packages/shorebird_cli/test/src/executables/idevicesyslog_test.dart +++ b/packages/shorebird_cli/test/src/executables/idevicesyslog_test.dart @@ -51,26 +51,24 @@ void main() { when(() => device.udid).thenReturn(deviceUdid); when(() => device.isWired).thenReturn(true); when( - () => process.start( - any(), - any(), - environment: any(named: 'environment'), - ), + () => + process.start(any(), any(), environment: any(named: 'environment')), ).thenAnswer((_) async { return loggerProcess; }); - when(() => loggerProcess.stdout).thenAnswer( - (_) => Stream.value(utf8.encode(stdoutOutput)), - ); - when(() => loggerProcess.stderr).thenAnswer( - (_) => Stream.value(utf8.encode(stderrOutput)), - ); + when( + () => loggerProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode(stdoutOutput))); + when( + () => loggerProcess.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode(stderrOutput))); when(() => loggerProcess.exitCode).thenAnswer((_) async => 0); when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory); - idevicesyslogPath = - runWithOverrides(() => IDeviceSysLog.idevicesyslogExecutable.path); + idevicesyslogPath = runWithOverrides( + () => IDeviceSysLog.idevicesyslogExecutable.path, + ); }); group('startLogger', () { @@ -130,25 +128,27 @@ void main() { }); }); - test('logs stdout lines matching appLogLineRegex at info level', - () async { - stdoutOutput = ''' + test( + 'logs stdout lines matching appLogLineRegex at info level', + () async { + stdoutOutput = ''' Nov 9 17:58:47 backboardd(QuartzCore)[51460] : IQCollectable client message err=0x10000004 : (ipc/send) timed out Nov 10 14:46:57 Runner(Flutter)[1044] : flutter: hello from stdout Nov 10 17:58:47 kernel(Sandbox)[0] : Sandbox: Runner(52662) deny(1) iokit-get-properties iokit-class:AGXAcceleratorG14P property:CFBundleIdentifier '''; - stderrOutput = ''' + stderrOutput = ''' Nov 9 17:58:47 backboardd(QuartzCore)[51460] : IQCollectable client message err=0x10000004 : (ipc/send) timed out Nov 10 14:46:57 Runner(Flutter)[1044] : flutter: hello from stderr Nov 10 17:58:47 kernel(Sandbox)[0] : Sandbox: Runner(52662) deny(1) iokit-get-properties iokit-class:AGXAcceleratorG14P property:CFBundleIdentifier '''; - await runWithOverrides( - () => idevicesyslog.startLogger(device: device), - ); + await runWithOverrides( + () => idevicesyslog.startLogger(device: device), + ); - verify(() => logger.info('flutter: hello from stdout')).called(1); - verify(() => logger.info('flutter: hello from stderr')).called(1); - }); + verify(() => logger.info('flutter: hello from stdout')).called(1); + verify(() => logger.info('flutter: hello from stderr')).called(1); + }, + ); }); }); } diff --git a/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart b/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart index 4cb68ec2..86569de1 100644 --- a/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart +++ b/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart @@ -107,9 +107,7 @@ void main() { ).thenAnswer((_) async => process); const bundlePath = 'test-bundle-path'; await runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ @@ -126,9 +124,7 @@ void main() { when(() => shorebirdProcess.start(any(), any())).thenThrow(exception); const bundlePath = 'test-bundle-path'; final exitCode = await runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); expect(exitCode, equals(ExitCode.software.code)); verify( @@ -144,15 +140,13 @@ void main() { test('dumps backtrace on process stopped', () async { final completer = Completer(); - when(() => process.stdout).thenAnswer( - (_) => Stream.value(utf8.encode('PROCESS_STOPPED')), - ); + when( + () => process.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode('PROCESS_STOPPED'))); when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => ioSink.writeln('thread backtrace all')); completer.complete(ExitCode.software.code); @@ -176,9 +170,7 @@ void main() { when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => ioSink.writeln('thread backtrace all')); await untilCalled(() => ioSink.writeln('process detach')); @@ -202,9 +194,7 @@ void main() { when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => process.kill()); completer.complete(ExitCode.software.code); @@ -228,9 +218,7 @@ void main() { when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => process.kill()); completer.complete(ExitCode.software.code); @@ -254,9 +242,7 @@ void main() { when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => progress.complete('Started app')); completer.complete(ExitCode.success.code); @@ -297,9 +283,7 @@ void main() { when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => progress.complete('Started app')); controller.add(ProcessSignal.sigint); @@ -319,15 +303,13 @@ void main() { test('handles process stderr', () async { const message = 'test-stderr'; final completer = Completer(); - when(() => process.stderr).thenAnswer( - (_) => Stream.value(utf8.encode(message)), - ); + when( + () => process.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode(message))); when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => logger.detail(message)); completer.complete(ExitCode.software.code); @@ -350,9 +332,7 @@ void main() { when(() => process.exitCode).thenAnswer((_) => completer.future); const bundlePath = 'test-bundle-path'; final exitCode = runWithOverrides( - () => iosDeploy.installAndLaunchApp( - bundlePath: bundlePath, - ), + () => iosDeploy.installAndLaunchApp(bundlePath: bundlePath), ); await untilCalled(() => progress.complete('Started app')); completer.complete(ExitCode.success.code); @@ -437,9 +417,7 @@ void main() { await expectLater( runWithOverrides(iosDeploy.installIfNeeded), - throwsA( - isA(), - ), + throwsA(isA()), ); }); @@ -455,18 +433,15 @@ void main() { ); await expectLater( runWithOverrides(iosDeploy.installIfNeeded), - throwsA( - isA(), - ), + throwsA(isA()), ); }, ); test( - '''completes successfully if ios-deploy is installed after running flutter precache''', - () async { - when(() => shorebirdProcess.run(any(), any())).thenAnswer( - (_) async { + '''completes successfully if ios-deploy is installed after running flutter precache''', + () async { + when(() => shorebirdProcess.run(any(), any())).thenAnswer((_) async { runWithOverrides( () => IOSDeploy.iosDeployExecutable.createSync(recursive: true), ); @@ -475,18 +450,18 @@ void main() { stdout: null, stderr: null, ); - }, - ); - await expectLater( - runWithOverrides(() async => iosDeploy.installIfNeeded()), - completes, - ); + }); + await expectLater( + runWithOverrides(() async => iosDeploy.installIfNeeded()), + completes, + ); - verify( - () => shorebirdProcess.run('flutter', ['precache', '--ios']), - ).called(1); - verify(progress.complete).called(1); - }); + verify( + () => shorebirdProcess.run('flutter', ['precache', '--ios']), + ).called(1); + verify(progress.complete).called(1); + }, + ); }); }); } diff --git a/packages/shorebird_cli/test/src/executables/java_test.dart b/packages/shorebird_cli/test/src/executables/java_test.dart index 31113e5f..7c21d21f 100644 --- a/packages/shorebird_cli/test/src/executables/java_test.dart +++ b/packages/shorebird_cli/test/src/executables/java_test.dart @@ -61,9 +61,9 @@ void main() { when(() => platform.environment).thenReturn({'JAVA_HOME': javaHome}); final processResult = MockShorebirdProcessResult(); - when(() => shorebirdProcess.runSync(any(), any())).thenReturn( - processResult, - ); + when( + () => shorebirdProcess.runSync(any(), any()), + ).thenReturn(processResult); when(() => processResult.exitCode).thenReturn(0); when(() => processResult.stderr).thenReturn('java version "11.0.1"'); }); @@ -78,9 +78,9 @@ void main() { group('when the command fails', () { setUp(() { final processResult = MockShorebirdProcessResult(); - when(() => shorebirdProcess.runSync(any(), any())).thenReturn( - processResult, - ); + when( + () => shorebirdProcess.runSync(any(), any()), + ).thenReturn(processResult); when(() => processResult.exitCode).thenReturn(1); }); @@ -101,47 +101,35 @@ void main() { }); group('executable', () { - group( - 'when on Windows', - () { - const javaHome = r'C:\Program Files\Java\jdk-11.0.1'; - setUp(() { - when(() => platform.isWindows).thenReturn(true); - when(() => platform.environment) - .thenReturn({'JAVA_HOME': javaHome}); - }); + group('when on Windows', () { + const javaHome = r'C:\Program Files\Java\jdk-11.0.1'; + setUp(() { + when(() => platform.isWindows).thenReturn(true); + when(() => platform.environment).thenReturn({'JAVA_HOME': javaHome}); + }); - test('returns correct executable on windows', () async { - expect( - runWithOverrides(() => java.executable), - equals(p.join(javaHome, 'bin', 'java.exe')), - ); - }); - }, - testOn: 'windows', - ); + test('returns correct executable on windows', () async { + expect( + runWithOverrides(() => java.executable), + equals(p.join(javaHome, 'bin', 'java.exe')), + ); + }); + }, testOn: 'windows'); - group( - 'when on a non-Windows OS', - () { - setUp(() { - const javaHome = '/path/to/jdk'; - when(() => platform.isWindows).thenReturn(false); - when(() => platform.environment) - .thenReturn({'JAVA_HOME': javaHome}); - }); + group('when on a non-Windows OS', () { + setUp(() { + const javaHome = '/path/to/jdk'; + when(() => platform.isWindows).thenReturn(false); + when(() => platform.environment).thenReturn({'JAVA_HOME': javaHome}); + }); - test('returns correct executable on non-windows', () async { - expect( - runWithOverrides(() => java.executable), - equals('/path/to/jdk/bin/java'), - ); - }); - }, - onPlatform: { - 'windows': const Skip(), - }, - ); + test('returns correct executable on non-windows', () async { + expect( + runWithOverrides(() => java.executable), + equals('/path/to/jdk/bin/java'), + ); + }); + }, onPlatform: {'windows': const Skip()}); group('when no jdk is found', () { setUp(() { @@ -149,10 +137,7 @@ void main() { }); test('returns java found on path', () async { - expect( - runWithOverrides(() => java.executable), - equals('/bin/java'), - ); + expect(runWithOverrides(() => java.executable), equals('/bin/java')); }); }); }); @@ -267,15 +252,13 @@ void main() { group('when JAVA_HOME is set', () { const javaHome = r'C:\Program Files\Java\jdk-11.0.1'; setUp(() { - when(() => platform.environment) - .thenReturn({'JAVA_HOME': javaHome}); + when( + () => platform.environment, + ).thenReturn({'JAVA_HOME': javaHome}); }); test('returns value of JAVA_HOME', () { - expect( - runWithOverrides(() => java.home), - equals(javaHome), - ); + expect(runWithOverrides(() => java.home), equals(javaHome)); }); test('does not check PATH', () { diff --git a/packages/shorebird_cli/test/src/executables/open_test.dart b/packages/shorebird_cli/test/src/executables/open_test.dart index c4e2ee2e..2b97fd8d 100644 --- a/packages/shorebird_cli/test/src/executables/open_test.dart +++ b/packages/shorebird_cli/test/src/executables/open_test.dart @@ -16,12 +16,7 @@ void main() { late ShorebirdProcess process; R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - processRef.overrideWith(() => process), - }, - ); + return runScoped(body, values: {processRef.overrideWith(() => process)}); } setUp(() { @@ -34,8 +29,9 @@ void main() { setUp(() { workingDirectory = Directory.systemTemp.createTempSync(); - File(p.join(workingDirectory.path, 'Contents', 'MacOS', 'test')) - .createSync(recursive: true); + File( + p.join(workingDirectory.path, 'Contents', 'MacOS', 'test'), + ).createSync(recursive: true); }); test('executes correct command and streams logs', () async { @@ -60,10 +56,12 @@ void main() { expect(stream, emits(utf8.encode('hello world'))); verify(() => process.start('open', ['-n', workingDirectory.path])); verify( - () => process.start( - 'log', - ['stream', '--style=compact', '--process', 'test'], - ), + () => process.start('log', [ + 'stream', + '--style=compact', + '--process', + 'test', + ]), ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/executables/patch_executable_test.dart b/packages/shorebird_cli/test/src/executables/patch_executable_test.dart index 7f453fb7..a2fab29b 100644 --- a/packages/shorebird_cli/test/src/executables/patch_executable_test.dart +++ b/packages/shorebird_cli/test/src/executables/patch_executable_test.dart @@ -45,16 +45,14 @@ void main() { any(), runInShell: any(named: 'runInShell'), ), - ).thenAnswer( - (invocation) async { - final args = invocation.positionalArguments[1] as List; - final diffPath = args[2]; - File(diffPath) - ..createSync(recursive: true) - ..writeAsStringSync('diff'); - return patchProcessResult; - }, - ); + ).thenAnswer((invocation) async { + final args = invocation.positionalArguments[1] as List; + final diffPath = args[2]; + File(diffPath) + ..createSync(recursive: true) + ..writeAsStringSync('diff'); + return patchProcessResult; + }); patchProcessResult = MockShorebirdProcessResult(); when(() => patchProcessResult.exitCode).thenReturn(ExitCode.success.code); @@ -68,12 +66,10 @@ void main() { test('runs the correct program', () async { final tmpDir = Directory.systemTemp.createTempSync(); - final releaseArtifactFile = File( - p.join(tmpDir.path, 'release_artifact'), - )..createSync(recursive: true); - final patchArtifactFile = File( - p.join(tmpDir.path, 'patch_artifact'), - )..createSync(recursive: true); + final releaseArtifactFile = File(p.join(tmpDir.path, 'release_artifact')) + ..createSync(recursive: true); + final patchArtifactFile = File(p.join(tmpDir.path, 'patch_artifact')) + ..createSync(recursive: true); await runWithOverrides( () => patchExecutable.run( diff --git a/packages/shorebird_cli/test/src/executables/powershell_test.dart b/packages/shorebird_cli/test/src/executables/powershell_test.dart index bb3bad2d..daab1acd 100644 --- a/packages/shorebird_cli/test/src/executables/powershell_test.dart +++ b/packages/shorebird_cli/test/src/executables/powershell_test.dart @@ -17,9 +17,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( () => body(), - values: { - processRef.overrideWith(() => process), - }, + values: {processRef.overrideWith(() => process)}, ); } @@ -47,9 +45,7 @@ void main() { test('throws an exception', () async { await expectLater( - runWithOverrides( - () => powershell.getExeVersionString(File('')), - ), + runWithOverrides(() => powershell.getExeVersionString(File(''))), throwsA(isA()), ); }); @@ -76,13 +72,15 @@ void main() { when(() => processResult.stdout).thenReturn('1.0.0'); }); - test('returns the version string with build number 0 added', - () async { - final version = await runWithOverrides( - () => powershell.getExeVersionString(File('')), - ); - expect(version, '1.0.0+0'); - }); + test( + 'returns the version string with build number 0 added', + () async { + final version = await runWithOverrides( + () => powershell.getExeVersionString(File('')), + ); + expect(version, '1.0.0+0'); + }, + ); }); }); }); diff --git a/packages/shorebird_cli/test/src/executables/shorebird_tools_test.dart b/packages/shorebird_cli/test/src/executables/shorebird_tools_test.dart index a0d69f60..6383c6e8 100644 --- a/packages/shorebird_cli/test/src/executables/shorebird_tools_test.dart +++ b/packages/shorebird_cli/test/src/executables/shorebird_tools_test.dart @@ -78,17 +78,15 @@ void main() { () => process.run( dartBinaryFile.path, any( - that: containsAllInOrder( - [ - 'run', - 'shorebird_tools', - 'package', - '-p', - 'patchPath', - '-o', - 'outputPath', - ], - ), + that: containsAllInOrder([ + 'run', + 'shorebird_tools', + 'package', + '-p', + 'patchPath', + '-o', + 'outputPath', + ]), ), workingDirectory: p.join( flutterDirectory.path, @@ -130,8 +128,9 @@ Failed to create package (exit code ${processResult.exitCode}). group('when the shorebird tools directory exists', () { test('isSupported returns true', () { - Directory(p.join(flutterDirectory.path, 'packages', 'shorebird_tools')) - .createSync(recursive: true); + Directory( + p.join(flutterDirectory.path, 'packages', 'shorebird_tools'), + ).createSync(recursive: true); final isSupported = runWithOverrides( () => shorebirdTools.isSupported(), ); diff --git a/packages/shorebird_cli/test/src/executables/xcodebuild_test.dart b/packages/shorebird_cli/test/src/executables/xcodebuild_test.dart index f1213df1..3200a88f 100644 --- a/packages/shorebird_cli/test/src/executables/xcodebuild_test.dart +++ b/packages/shorebird_cli/test/src/executables/xcodebuild_test.dart @@ -15,12 +15,7 @@ void main() { late XcodeBuild xcodeBuild; R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - processRef.overrideWith(() => process), - }, - ); + return runScoped(body, values: {processRef.overrideWith(() => process)}); } setUp(() { diff --git a/packages/shorebird_cli/test/src/extensions/arg_results_test.dart b/packages/shorebird_cli/test/src/extensions/arg_results_test.dart index 8fd44447..6c6b9e75 100644 --- a/packages/shorebird_cli/test/src/extensions/arg_results_test.dart +++ b/packages/shorebird_cli/test/src/extensions/arg_results_test.dart @@ -10,9 +10,10 @@ void main() { late ArgParser argParser; setUp(() { - argParser = ArgParser() - ..addOption('foo', abbr: 'f') - ..addOption('bar'); + argParser = + ArgParser() + ..addOption('foo', abbr: 'f') + ..addOption('bar'); }); group('findOption', () { @@ -31,10 +32,7 @@ void main() { test('returns null', () { final args = ['--bar=value']; final argResults = argParser.parse(args); - expect( - argResults.findOption('foo', argParser: argParser), - isNull, - ); + expect(argResults.findOption('foo', argParser: argParser), isNull); }); }); @@ -90,40 +88,41 @@ void main() { late ArgParser parser; setUp(() { - parser = ArgParser() - ..addMultiOption( - CommonArguments.dartDefineArg.name, - help: CommonArguments.dartDefineArg.description, - ) - ..addMultiOption( - CommonArguments.dartDefineFromFileArg.name, - help: CommonArguments.dartDefineFromFileArg.description, - ) - ..addOption( - CommonArguments.buildNameArg.name, - help: CommonArguments.buildNameArg.description, - ) - ..addOption( - CommonArguments.buildNumberArg.name, - help: CommonArguments.buildNumberArg.description, - ) - ..addOption( - CommonArguments.splitDebugInfoArg.name, - help: CommonArguments.splitDebugInfoArg.description, - ) - ..addOption( - CommonArguments.exportMethodArg.name, - help: CommonArguments.exportMethodArg.description, - ) - ..addOption( - CommonArguments.exportOptionsPlistArg.name, - help: CommonArguments.exportOptionsPlistArg.description, - ) - ..addMultiOption( - 'platforms', - allowed: ReleaseType.values.map((e) => e.cliName), - ) - ..addFlag('verbose', abbr: 'v'); + parser = + ArgParser() + ..addMultiOption( + CommonArguments.dartDefineArg.name, + help: CommonArguments.dartDefineArg.description, + ) + ..addMultiOption( + CommonArguments.dartDefineFromFileArg.name, + help: CommonArguments.dartDefineFromFileArg.description, + ) + ..addOption( + CommonArguments.buildNameArg.name, + help: CommonArguments.buildNameArg.description, + ) + ..addOption( + CommonArguments.buildNumberArg.name, + help: CommonArguments.buildNumberArg.description, + ) + ..addOption( + CommonArguments.splitDebugInfoArg.name, + help: CommonArguments.splitDebugInfoArg.description, + ) + ..addOption( + CommonArguments.exportMethodArg.name, + help: CommonArguments.exportMethodArg.description, + ) + ..addOption( + CommonArguments.exportOptionsPlistArg.name, + help: CommonArguments.exportOptionsPlistArg.description, + ) + ..addMultiOption( + 'platforms', + allowed: ReleaseType.values.map((e) => e.cliName), + ) + ..addFlag('verbose', abbr: 'v'); }); test('returns an empty list when rest is empty', () { @@ -169,9 +168,12 @@ void main() { expect(result.forwardedArgs, hasLength(4)); expect( result.forwardedArgs, - containsAll( - ['asdf', 'qwer', '--dart-define=foo=bar', '--dart-define=bar=baz'], - ), + containsAll([ + 'asdf', + 'qwer', + '--dart-define=foo=bar', + '--dart-define=bar=baz', + ]), ); }); }); @@ -189,13 +191,11 @@ void main() { expect(result.forwardedArgs, hasLength(3)); expect( result.forwardedArgs, - containsAll( - [ - '--dart-define=foo=bar', - '--dart-define-from-file=bar.json', - '--test', - ], - ), + containsAll([ + '--dart-define=foo=bar', + '--dart-define-from-file=bar.json', + '--test', + ]), ); }); }); @@ -212,43 +212,26 @@ void main() { expect(result.forwardedArgs, hasLength(2)); expect( result.forwardedArgs, - containsAll( - [ - '--build-name=1.2.3', - '--build-number=4', - ], - ), + containsAll(['--build-name=1.2.3', '--build-number=4']), ); }); }); group('when build-name and build-number are before the --', () { test('forwards build-name and build-number', () { - final args = [ - '--verbose', - '--build-name=1.2.3', - '--build-number=4', - ]; + final args = ['--verbose', '--build-name=1.2.3', '--build-number=4']; final result = parser.parse(args); expect(result.forwardedArgs, hasLength(2)); expect( result.forwardedArgs, - containsAll( - [ - '--build-name=1.2.3', - '--build-number=4', - ], - ), + containsAll(['--build-name=1.2.3', '--build-number=4']), ); }); }); group('when split-debug-info is provided before the --', () { test('forwards it', () { - final args = [ - '--verbose', - '--split-debug-info=build/symbols', - ]; + final args = ['--verbose', '--split-debug-info=build/symbols']; final result = parser.parse(args); expect(result.forwardedArgs, hasLength(1)); expect( @@ -260,11 +243,7 @@ void main() { group('when split-debug-info is provided after the --', () { test('forwards it', () { - final args = [ - '--verbose', - '--', - '--split-debug-info=build/symbols', - ]; + final args = ['--verbose', '--', '--split-debug-info=build/symbols']; final result = parser.parse(args); expect(result.forwardedArgs, hasLength(1)); expect( @@ -276,32 +255,19 @@ void main() { group('when export method is provided before the --', () { test('forwards it', () { - final args = [ - '--verbose', - '--export-method=development', - ]; + final args = ['--verbose', '--export-method=development']; final result = parser.parse(args); expect(result.forwardedArgs, hasLength(1)); - expect( - result.forwardedArgs, - contains('--export-method=development'), - ); + expect(result.forwardedArgs, contains('--export-method=development')); }); }); group('when export method is provided after the --', () { test('forwards it', () { - final args = [ - '--verbose', - '--', - '--export-method=development', - ]; + final args = ['--verbose', '--', '--export-method=development']; final result = parser.parse(args); expect(result.forwardedArgs, hasLength(1)); - expect( - result.forwardedArgs, - contains('--export-method=development'), - ); + expect(result.forwardedArgs, contains('--export-method=development')); }); }); diff --git a/packages/shorebird_cli/test/src/helpers.dart b/packages/shorebird_cli/test/src/helpers.dart index e313320c..bfbb73fb 100644 --- a/packages/shorebird_cli/test/src/helpers.dart +++ b/packages/shorebird_cli/test/src/helpers.dart @@ -2,10 +2,6 @@ import 'dart:io'; import 'package:path/path.dart' as p; File createTempFile(String name) { - return File( - p.join( - Directory.systemTemp.createTempSync().path, - name, - ), - )..createSync(); + return File(p.join(Directory.systemTemp.createTempSync().path, name)) + ..createSync(); } diff --git a/packages/shorebird_cli/test/src/http_client/http_client_test.dart b/packages/shorebird_cli/test/src/http_client/http_client_test.dart index 13a98470..07314dc9 100644 --- a/packages/shorebird_cli/test/src/http_client/http_client_test.dart +++ b/packages/shorebird_cli/test/src/http_client/http_client_test.dart @@ -6,10 +6,7 @@ import 'package:test/test.dart'; void main() { group('scoped', () { test('creates instance with default constructor', () { - final instance = runScoped( - () => httpClient, - values: {httpClientRef}, - ); + final instance = runScoped(() => httpClient, values: {httpClientRef}); expect(instance, isA()); }); }); diff --git a/packages/shorebird_cli/test/src/http_client/logging_client_test.dart b/packages/shorebird_cli/test/src/http_client/logging_client_test.dart index 39eda0a4..4b116045 100644 --- a/packages/shorebird_cli/test/src/http_client/logging_client_test.dart +++ b/packages/shorebird_cli/test/src/http_client/logging_client_test.dart @@ -17,12 +17,7 @@ void main() { late LoggingClient loggingClient; R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - loggerRef.overrideWith(() => logger), - }, - ); + return runScoped(body, values: {loggerRef.overrideWith(() => logger)}); } setUpAll(() { @@ -35,10 +30,7 @@ void main() { loggingClient = LoggingClient(httpClient: httpClient); when(() => httpClient.send(any())).thenAnswer( - (_) async => http.StreamedResponse( - const Stream.empty(), - HttpStatus.ok, - ), + (_) async => http.StreamedResponse(const Stream.empty(), HttpStatus.ok), ); }); diff --git a/packages/shorebird_cli/test/src/logging/detail_progress_test.dart b/packages/shorebird_cli/test/src/logging/detail_progress_test.dart index 123b92fd..8d27a81a 100644 --- a/packages/shorebird_cli/test/src/logging/detail_progress_test.dart +++ b/packages/shorebird_cli/test/src/logging/detail_progress_test.dart @@ -13,12 +13,7 @@ void main() { late DetailProgress detailProgress; R runWithOverrides(R Function() body) { - return runScoped( - body, - values: { - loggerRef.overrideWith(() => logger), - }, - ); + return runScoped(body, values: {loggerRef.overrideWith(() => logger)}); } setUp(() { diff --git a/packages/shorebird_cli/test/src/logging/shorebird_logger_test.dart b/packages/shorebird_cli/test/src/logging/shorebird_logger_test.dart index f8c90d43..bbc82dfa 100644 --- a/packages/shorebird_cli/test/src/logging/shorebird_logger_test.dart +++ b/packages/shorebird_cli/test/src/logging/shorebird_logger_test.dart @@ -17,9 +17,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( body, - values: { - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - }, + values: {shorebirdEnvRef.overrideWith(() => shorebirdEnv)}, ); } @@ -43,17 +41,15 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( body, - values: { - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - }, + values: {shorebirdEnvRef.overrideWith(() => shorebirdEnv)}, ); } setUp(() { shorebirdEnv = MockShorebirdEnv(); - when(() => shorebirdEnv.logsDirectory).thenReturn( - Directory.systemTemp.createTempSync(), - ); + when( + () => shorebirdEnv.logsDirectory, + ).thenReturn(Directory.systemTemp.createTempSync()); logger = ShorebirdLogger(); }); diff --git a/packages/shorebird_cli/test/src/matchers.dart b/packages/shorebird_cli/test/src/matchers.dart index ac40554d..3d10ec2c 100644 --- a/packages/shorebird_cli/test/src/matchers.dart +++ b/packages/shorebird_cli/test/src/matchers.dart @@ -3,9 +3,5 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da import 'package:test/test.dart'; Matcher exitsWithCode(ExitCode exitcode) => throwsA( - isA().having( - (e) => e.exitCode, - 'exitCode', - exitcode.code, - ), - ); + isA().having((e) => e.exitCode, 'exitCode', exitcode.code), +); diff --git a/packages/shorebird_cli/test/src/network_checker_test.dart b/packages/shorebird_cli/test/src/network_checker_test.dart index 21a9aae4..3ed5f01e 100644 --- a/packages/shorebird_cli/test/src/network_checker_test.dart +++ b/packages/shorebird_cli/test/src/network_checker_test.dart @@ -57,9 +57,9 @@ void main() { group('checkReachability', () { group('when endpoints are reachable', () { setUp(() { - when(() => httpClient.get(any())).thenAnswer( - (_) async => http.Response('', HttpStatus.ok), - ); + when( + () => httpClient.get(any()), + ).thenAnswer((_) async => http.Response('', HttpStatus.ok)); }); test('logs reachability for each checked url', () async { @@ -130,12 +130,10 @@ void main() { any(), outputPath: any(named: 'outputPath'), ), - ).thenAnswer( - (_) async { - await Future.delayed(responseTime); - return File(''); - }, - ); + ).thenAnswer((_) async { + await Future.delayed(responseTime); + return File(''); + }); }); test('throws a NetworkCheckerException', () async { @@ -227,9 +225,9 @@ void main() { final gcpUri = Uri.parse('http://localhost'); setUp(() { - when(() => codePushClientWrapper.getGCPUploadSpeedTestUrl()).thenAnswer( - (_) async => gcpUri, - ); + when( + () => codePushClientWrapper.getGCPUploadSpeedTestUrl(), + ).thenAnswer((_) async => gcpUri); }); group('when upload fails', () { @@ -262,15 +260,13 @@ void main() { // on slow (read: Windows) CI machines. final responseTime = uploadTimeout * 5; setUp(() { - when(() => httpClient.send(any())).thenAnswer( - (_) async { - await Future.delayed(responseTime); - return http.StreamedResponse( - const Stream.empty(), - HttpStatus.noContent, - ); - }, - ); + when(() => httpClient.send(any())).thenAnswer((_) async { + await Future.delayed(responseTime); + return http.StreamedResponse( + const Stream.empty(), + HttpStatus.noContent, + ); + }); }); test('throws a NetworkCheckerException', () async { @@ -324,9 +320,9 @@ void main() { // Our 5MB file took 1 second to upload, so our speed is 5 MB/s. expect(speed, equals(5.0)); - final capturedRequest = verify(() => httpClient.send(captureAny())) - .captured - .last as http.MultipartRequest; + final capturedRequest = + verify(() => httpClient.send(captureAny())).captured.last + as http.MultipartRequest; expect(capturedRequest.method, equals('POST')); expect(capturedRequest.url, equals(gcpUri)); }); diff --git a/packages/shorebird_cli/test/src/os/operating_system_interface_test.dart b/packages/shorebird_cli/test/src/os/operating_system_interface_test.dart index 81400b54..fd2daade 100644 --- a/packages/shorebird_cli/test/src/os/operating_system_interface_test.dart +++ b/packages/shorebird_cli/test/src/os/operating_system_interface_test.dart @@ -40,13 +40,15 @@ void main() { }); group('init', () { - test('throws UnsupportedError when operating system is not supported', - () { - expect( - () => runWithOverrides(OperatingSystemInterface.new), - throwsUnsupportedError, - ); - }); + test( + 'throws UnsupportedError when operating system is not supported', + () { + expect( + () => runWithOverrides(OperatingSystemInterface.new), + throwsUnsupportedError, + ); + }, + ); }); group('on macOS/Linux', () { diff --git a/packages/shorebird_cli/test/src/patch_diff_checker_test.dart b/packages/shorebird_cli/test/src/patch_diff_checker_test.dart index b0d7d06e..89b9e3af 100644 --- a/packages/shorebird_cli/test/src/patch_diff_checker_test.dart +++ b/packages/shorebird_cli/test/src/patch_diff_checker_test.dart @@ -57,16 +57,21 @@ void main() { shorebirdEnv = MockShorebirdEnv(); patchDiffChecker = PatchDiffChecker(); - when(() => archiveDiffer.changedFiles(any(), any())) - .thenAnswer((_) async => FileSetDiff.empty()); - when(() => archiveDiffer.assetsFileSetDiff(any())) - .thenReturn(assetsFileSetDiff); - when(() => archiveDiffer.nativeFileSetDiff(any())) - .thenReturn(nativeFileSetDiff); - when(() => archiveDiffer.containsPotentiallyBreakingAssetDiffs(any())) - .thenReturn(false); - when(() => archiveDiffer.containsPotentiallyBreakingNativeDiffs(any())) - .thenReturn(false); + when( + () => archiveDiffer.changedFiles(any(), any()), + ).thenAnswer((_) async => FileSetDiff.empty()); + when( + () => archiveDiffer.assetsFileSetDiff(any()), + ).thenReturn(assetsFileSetDiff); + when( + () => archiveDiffer.nativeFileSetDiff(any()), + ).thenReturn(nativeFileSetDiff); + when( + () => archiveDiffer.containsPotentiallyBreakingAssetDiffs(any()), + ).thenReturn(false); + when( + () => archiveDiffer.containsPotentiallyBreakingNativeDiffs(any()), + ).thenReturn(false); when(() => httpClient.send(any())).thenAnswer( (_) async => http.StreamedResponse(const Stream.empty(), HttpStatus.ok), @@ -77,10 +82,12 @@ void main() { when(() => shorebirdEnv.canAcceptUserInput).thenReturn(true); - when(() => assetsFileSetDiff.prettyString) - .thenReturn(assetsDiffPrettyString); - when(() => nativeFileSetDiff.prettyString) - .thenReturn(nativeDiffPrettyString); + when( + () => assetsFileSetDiff.prettyString, + ).thenReturn(assetsDiffPrettyString); + when( + () => nativeFileSetDiff.prettyString, + ).thenReturn(nativeDiffPrettyString); }); group('confirmUnpatchableDiffsIfNecessary', () { @@ -113,8 +120,9 @@ void main() { verify( () => logger.info( any( - that: - contains("If you don't know why you're seeing this error"), + that: contains( + "If you don't know why you're seeing this error", + ), ), ), ).called(1); @@ -148,27 +156,27 @@ void main() { verifyNever(() => logger.confirm('Continue anyways?')); }); - test('throws UserCancelledException if user declines to continue', - () async { - when(() => logger.confirm(any())).thenReturn(false); + test( + 'throws UserCancelledException if user declines to continue', + () async { + when(() => logger.confirm(any())).thenReturn(false); - await expectLater( - runWithOverrides( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: localArtifact, - releaseArchive: releaseArtifact, - archiveDiffer: archiveDiffer, - allowAssetChanges: false, - allowNativeChanges: false, + await expectLater( + runWithOverrides( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: localArtifact, + releaseArchive: releaseArtifact, + archiveDiffer: archiveDiffer, + allowAssetChanges: false, + allowNativeChanges: false, + ), ), - ), - throwsA( - isA(), - ), - ); + throwsA(isA()), + ); - verify(() => logger.confirm('Continue anyways?')).called(1); - }); + verify(() => logger.confirm('Continue anyways?')).called(1); + }, + ); test('does not prompt when unable to accept user input', () async { when(() => shorebirdEnv.canAcceptUserInput).thenReturn(false); @@ -246,25 +254,27 @@ void main() { verifyNever(() => logger.confirm('Continue anyways?')); }); - test('throws UserCancelledException if user declines to continue', - () async { - when(() => logger.confirm(any())).thenReturn(false); + test( + 'throws UserCancelledException if user declines to continue', + () async { + when(() => logger.confirm(any())).thenReturn(false); - await expectLater( - runWithOverrides( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: localArtifact, - releaseArchive: releaseArtifact, - archiveDiffer: archiveDiffer, - allowAssetChanges: false, - allowNativeChanges: false, + await expectLater( + runWithOverrides( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: localArtifact, + releaseArchive: releaseArtifact, + archiveDiffer: archiveDiffer, + allowAssetChanges: false, + allowNativeChanges: false, + ), ), - ), - throwsA(isA()), - ); + throwsA(isA()), + ); - verify(() => logger.confirm('Continue anyways?')).called(1); - }); + verify(() => logger.confirm('Continue anyways?')).called(1); + }, + ); test('does not prompt when unable to accept user input', () async { when(() => shorebirdEnv.canAcceptUserInput).thenReturn(false); @@ -286,21 +296,23 @@ void main() { }); }); - test('returns true if no potentially breaking diffs are detected', - () async { - await expectLater( - runWithOverrides( - () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( - localArchive: localArtifact, - releaseArchive: releaseArtifact, - archiveDiffer: archiveDiffer, - allowAssetChanges: false, - allowNativeChanges: false, + test( + 'returns true if no potentially breaking diffs are detected', + () async { + await expectLater( + runWithOverrides( + () => patchDiffChecker.confirmUnpatchableDiffsIfNecessary( + localArchive: localArtifact, + releaseArchive: releaseArtifact, + archiveDiffer: archiveDiffer, + allowAssetChanges: false, + allowNativeChanges: false, + ), ), - ), - completes, - ); - }); + completes, + ); + }, + ); }); }); } diff --git a/packages/shorebird_cli/test/src/platform/android_test.dart b/packages/shorebird_cli/test/src/platform/android_test.dart index 7aa6bc38..cc227898 100644 --- a/packages/shorebird_cli/test/src/platform/android_test.dart +++ b/packages/shorebird_cli/test/src/platform/android_test.dart @@ -19,9 +19,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( body, - values: { - engineConfigRef.overrideWith(() => engineConfig), - }, + values: {engineConfigRef.overrideWith(() => engineConfig)}, ); } @@ -53,21 +51,23 @@ void main() { ); }); - test('throws exception when unknown engine architecture is used', - () async { - when(() => engineConfig.localEngine).thenReturn('unknown_arch'); + test( + 'throws exception when unknown engine architecture is used', + () async { + when(() => engineConfig.localEngine).thenReturn('unknown_arch'); - expect( - () => runWithOverrides(() => AndroidArch.availableAndroidArchs), - throwsA( - isA().having( - (e) => e.toString(), - 'toString()', - contains('Unknown local engine architecture for'), + expect( + () => runWithOverrides(() => AndroidArch.availableAndroidArchs), + throwsA( + isA().having( + (e) => e.toString(), + 'toString()', + contains('Unknown local engine architecture for'), + ), ), - ), - ); - }); + ); + }, + ); }); }); diff --git a/packages/shorebird_cli/test/src/platform/apple_test.dart b/packages/shorebird_cli/test/src/platform/apple_test.dart index 76cfefa2..f9481f35 100644 --- a/packages/shorebird_cli/test/src/platform/apple_test.dart +++ b/packages/shorebird_cli/test/src/platform/apple_test.dart @@ -61,13 +61,10 @@ void main() { group(MissingXcodeProjectException, () { test('toString', () { const exception = MissingXcodeProjectException('test_project_path'); - expect( - exception.toString(), - ''' + expect(exception.toString(), ''' Could not find an Xcode project in test_project_path. To add iOS, run "flutter create . --platforms ios" -To add macOS, run "flutter create . --platforms macos"''', - ); +To add macOS, run "flutter create . --platforms macos"'''); }); }); @@ -79,20 +76,10 @@ To add macOS, run "flutter create . --platforms macos"''', return; } - final fixturesDir = Directory( - p.join( - 'test', - 'fixtures', - 'xcschemes', - ), - ); + final fixturesDir = Directory(p.join('test', 'fixtures', 'xcschemes')); for (final file in fixturesDir.listSync().whereType()) { final destination = File( - p.join( - projectRoot.path, - schemesPath, - p.basename(file.path), - ), + p.join(projectRoot.path, schemesPath, p.basename(file.path)), )..createSync(recursive: true); file.copySync(destination.path); } @@ -100,8 +87,9 @@ To add macOS, run "flutter create . --platforms macos"''', setUp(() { projectRoot = Directory.systemTemp.createTempSync(); - when(() => shorebirdEnv.getFlutterProjectRoot()) - .thenReturn(projectRoot); + when( + () => shorebirdEnv.getFlutterProjectRoot(), + ).thenReturn(projectRoot); }); group('ios', () { @@ -319,9 +307,8 @@ To add macOS, run "flutter create . --platforms macos"''', analyzeSnapshotFile = File( p.join(buildDirectory.path, 'analyze_snapshot'), )..createSync(); - genSnapshotFile = File( - p.join(buildDirectory.path, 'gen_snapshot'), - )..createSync(); + genSnapshotFile = File(p.join(buildDirectory.path, 'gen_snapshot')) + ..createSync(); when( () => shorebirdArtifacts.getArtifactPath( @@ -448,9 +435,7 @@ To add macOS, run "flutter create . --platforms macos"''', group('when isLinkDebugInfoSupported is true', () { setUp(() { - when( - aotTools.isLinkDebugInfoSupported, - ).thenAnswer((_) async => true); + when(aotTools.isLinkDebugInfoSupported).thenAnswer((_) async => true); }); test('dumps debug info', () async { @@ -479,9 +464,8 @@ To add macOS, run "flutter create . --platforms macos"''', ), ).called(1); verify( - () => logger.detail( - any(that: contains('Link debug info saved to')), - ), + () => + logger.detail(any(that: contains('Link debug info saved to'))), ).called(1); }); @@ -490,9 +474,9 @@ To add macOS, run "flutter create . --platforms macos"''', setUp(() { codemagicExportDir = Directory.systemTemp.createTempSync(); - when(() => platform.environment).thenReturn( - {'CM_EXPORT_DIR': codemagicExportDir.path}, - ); + when( + () => platform.environment, + ).thenReturn({'CM_EXPORT_DIR': codemagicExportDir.path}); }); test('copies debug info to codemagic exports', () async { @@ -518,9 +502,9 @@ To add macOS, run "flutter create . --platforms macos"''', }); test('gracefully handles errors', () async { - when(() => platform.environment).thenReturn( - {'CM_EXPORT_DIR': 'invalid path'}, - ); + when( + () => platform.environment, + ).thenReturn({'CM_EXPORT_DIR': 'invalid path'}); await runWithOverrides( () => apple.runLinker( aotOutputFile: aotOutputFile, @@ -569,9 +553,7 @@ To add macOS, run "flutter create . --platforms macos"''', ); verify( - () => progress.fail( - 'Failed to link AOT files: Exception: oops', - ), + () => progress.fail('Failed to link AOT files: Exception: oops'), ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/platform/linux_test.dart b/packages/shorebird_cli/test/src/platform/linux_test.dart index 8e0cdb67..1867af50 100644 --- a/packages/shorebird_cli/test/src/platform/linux_test.dart +++ b/packages/shorebird_cli/test/src/platform/linux_test.dart @@ -8,10 +8,7 @@ void main() { group('linux', () { group('minimumSupportedLinuxFlutterVersion', () { test('is 3.27.4', () { - expect( - minimumSupportedLinuxFlutterVersion, - equals(Version(3, 27, 4)), - ); + expect(minimumSupportedLinuxFlutterVersion, equals(Version(3, 27, 4))); }); }); diff --git a/packages/shorebird_cli/test/src/pubspec_editor_test.dart b/packages/shorebird_cli/test/src/pubspec_editor_test.dart index b7e1506f..ce88971e 100644 --- a/packages/shorebird_cli/test/src/pubspec_editor_test.dart +++ b/packages/shorebird_cli/test/src/pubspec_editor_test.dart @@ -19,9 +19,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( () => body(), - values: { - shorebirdEnvRef.overrideWith(() => shorebirdEnv), - }, + values: {shorebirdEnvRef.overrideWith(() => shorebirdEnv)}, ); } @@ -44,9 +42,8 @@ void main() { test('does nothing', () { expect( - () => runWithOverrides( - pubspecEditor.addShorebirdYamlToPubspecAssets, - ), + () => + runWithOverrides(pubspecEditor.addShorebirdYamlToPubspecAssets), returnsNormally, ); verifyNever(() => shorebirdEnv.getFlutterProjectRoot()); @@ -132,42 +129,42 @@ flutter: ); expect( pubspecFile.readAsStringSync(), - equals( - ''' + equals(''' $basePubspecContents flutter: assets: - shorebird.yaml -''', - ), +'''), ); }); - test('creates assets and adds shorebird.yaml (non-empty flutter)', - () { - pubspecFile - ..createSync() - ..writeAsStringSync(''' + test( + 'creates assets and adds shorebird.yaml (non-empty flutter)', + () { + pubspecFile + ..createSync() + ..writeAsStringSync(''' $basePubspecContents flutter: uses-material-design: true '''); - IOOverrides.runZoned( - () => runWithOverrides( - pubspecEditor.addShorebirdYamlToPubspecAssets, - ), - getCurrentDirectory: () => tempDir, - ); - expect( - pubspecFile.readAsStringSync(), - equals(''' + IOOverrides.runZoned( + () => runWithOverrides( + pubspecEditor.addShorebirdYamlToPubspecAssets, + ), + getCurrentDirectory: () => tempDir, + ); + expect( + pubspecFile.readAsStringSync(), + equals(''' $basePubspecContents flutter: uses-material-design: true assets: - shorebird.yaml '''), - ); - }); + ); + }, + ); test('adds shorebird.yaml to assets (existing assets)', () { pubspecFile ..createSync() diff --git a/packages/shorebird_cli/test/src/release_type_test.dart b/packages/shorebird_cli/test/src/release_type_test.dart index a6cd6359..674cc976 100644 --- a/packages/shorebird_cli/test/src/release_type_test.dart +++ b/packages/shorebird_cli/test/src/release_type_test.dart @@ -24,19 +24,16 @@ void main() { group('releaseTypes', () { late ArgParser parser; setUp(() { - parser = ArgParser() - ..addMultiOption( - 'platforms', - allowed: ReleaseType.values.map((e) => e.cliName), - ); + parser = + ArgParser()..addMultiOption( + 'platforms', + allowed: ReleaseType.values.map((e) => e.cliName), + ); }); group('when nothing is provided', () { test('parses and return empty', () { - expect( - parser.parse([]).releaseTypes.toList(), - isEmpty, - ); + expect(parser.parse([]).releaseTypes.toList(), isEmpty); }); }); @@ -46,10 +43,9 @@ void main() { parser.parse(['--platforms', 'android']).releaseTypes.toList(), [ReleaseType.android], ); - expect( - parser.parse(['--platforms', 'ios']).releaseTypes.toList(), - [ReleaseType.ios], - ); + expect(parser.parse(['--platforms', 'ios']).releaseTypes.toList(), [ + ReleaseType.ios, + ]); expect( parser .parse(['--platforms', 'ios-framework']) @@ -57,10 +53,9 @@ void main() { .toList(), [ReleaseType.iosFramework], ); - expect( - parser.parse(['--platforms', 'aar']).releaseTypes.toList(), - [ReleaseType.aar], - ); + expect(parser.parse(['--platforms', 'aar']).releaseTypes.toList(), [ + ReleaseType.aar, + ]); }); }); @@ -73,22 +68,18 @@ void main() { }); test('parses the release types', () { - expect( - parser.parse(['android', 'foo']).releaseTypes.toList(), - [ReleaseType.android], - ); - expect( - parser.parse(['ios', 'foo']).releaseTypes.toList(), - [ReleaseType.ios], - ); - expect( - parser.parse(['ios-framework', 'foo']).releaseTypes.toList(), - [ReleaseType.iosFramework], - ); - expect( - parser.parse(['aar', 'foo']).releaseTypes.toList(), - [ReleaseType.aar], - ); + expect(parser.parse(['android', 'foo']).releaseTypes.toList(), [ + ReleaseType.android, + ]); + expect(parser.parse(['ios', 'foo']).releaseTypes.toList(), [ + ReleaseType.ios, + ]); + expect(parser.parse(['ios-framework', 'foo']).releaseTypes.toList(), [ + ReleaseType.iosFramework, + ]); + expect(parser.parse(['aar', 'foo']).releaseTypes.toList(), [ + ReleaseType.aar, + ]); }); }); }); diff --git a/packages/shorebird_cli/test/src/shorebird_android_artifacts_test.dart b/packages/shorebird_cli/test/src/shorebird_android_artifacts_test.dart index e94bfddd..402a5942 100644 --- a/packages/shorebird_cli/test/src/shorebird_android_artifacts_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_android_artifacts_test.dart @@ -43,8 +43,9 @@ void main() { shorebirdEnv = MockShorebirdEnv(); when(() => cache.updateAll()).thenAnswer((_) async {}); - when(() => shorebirdEnv.getShorebirdProjectRoot()) - .thenReturn(projectRoot); + when( + () => shorebirdEnv.getShorebirdProjectRoot(), + ).thenReturn(projectRoot); shorebirdAndroidArtifacts = ShorebirdAndroidArtifacts(); }); @@ -62,13 +63,7 @@ void main() { test('throws ArtifactNotFoundException for apks', () { final buildDir = Directory( - p.join( - projectRoot.path, - 'build', - 'app', - 'outputs', - 'flutter-apk', - ), + p.join(projectRoot.path, 'build', 'app', 'outputs', 'flutter-apk'), ); expect( () => shorebirdAndroidArtifacts.findApk( @@ -115,13 +110,7 @@ void main() { test('throws ArtifactNotFoundException for apks', () { Directory( - p.join( - projectRoot.path, - 'build', - 'app', - 'outputs', - 'flutter-apk', - ), + p.join(projectRoot.path, 'build', 'app', 'outputs', 'flutter-apk'), ).createSync(recursive: true); expect( () => shorebirdAndroidArtifacts.findApk( @@ -143,16 +132,12 @@ void main() { 'release', 'app-release.aab', ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); expect( shorebirdAndroidArtifacts - .findAab( - project: projectRoot, - flavor: null, - ) + .findAab(project: projectRoot, flavor: null) .path, equals(artifact.path), ); @@ -166,16 +151,12 @@ void main() { 'flutter-apk', 'app-release.apk', ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); expect( shorebirdAndroidArtifacts - .findApk( - project: projectRoot, - flavor: null, - ) + .findApk(project: projectRoot, flavor: null) .path, equals(artifact.path), ); @@ -192,18 +173,14 @@ void main() { 'internalRelease', 'app-internal-release.aab', ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); const flavor = 'internal'; expect( shorebirdAndroidArtifacts - .findAab( - project: projectRoot, - flavor: flavor, - ) + .findAab(project: projectRoot, flavor: flavor) .path, equals(artifact.path), ); @@ -217,18 +194,14 @@ void main() { 'flutter-apk', 'app-internal-release.apk', ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); const flavor = 'internal'; expect( shorebirdAndroidArtifacts - .findApk( - project: projectRoot, - flavor: flavor, - ) + .findApk(project: projectRoot, flavor: flavor) .path, equals(artifact.path), ); @@ -245,18 +218,14 @@ void main() { 'stableGlobalRelease', 'app-stable-global-release.aab', ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); const flavor = 'stableGlobal'; expect( shorebirdAndroidArtifacts - .findAab( - project: projectRoot, - flavor: flavor, - ) + .findAab(project: projectRoot, flavor: flavor) .path, equals(artifact.path), ); @@ -270,77 +239,67 @@ void main() { 'flutter-apk', 'app-stableglobal-release.apk', ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); const flavor = 'stableGlobal'; expect( shorebirdAndroidArtifacts - .findApk( - project: projectRoot, - flavor: flavor, - ) + .findApk(project: projectRoot, flavor: flavor) .path, equals(artifact.path), ); }); }); - group('when using multi-dimensional flavors and multi-word flavor name', - () { - test('finds the app bundle', () { - final artifactPath = p.join( - 'build', - 'app', - 'outputs', - 'bundle', - 'stablePlayStoreRelease', - 'app-stable-playStore-release.aab', - ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + group( + 'when using multi-dimensional flavors and multi-word flavor name', + () { + test('finds the app bundle', () { + final artifactPath = p.join( + 'build', + 'app', + 'outputs', + 'bundle', + 'stablePlayStoreRelease', + 'app-stable-playStore-release.aab', + ); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); - const flavor = 'stablePlayStore'; + const flavor = 'stablePlayStore'; - expect( - shorebirdAndroidArtifacts - .findAab( - project: projectRoot, - flavor: flavor, - ) - .path, - equals(artifact.path), - ); - }); + expect( + shorebirdAndroidArtifacts + .findAab(project: projectRoot, flavor: flavor) + .path, + equals(artifact.path), + ); + }); - test('finds the apk', () { - final artifactPath = p.join( - 'build', - 'app', - 'outputs', - 'flutter-apk', - 'app-stableplaystore-release.apk', - ); - final artifact = File( - p.join(projectRoot.path, artifactPath), - )..createSync(recursive: true); + test('finds the apk', () { + final artifactPath = p.join( + 'build', + 'app', + 'outputs', + 'flutter-apk', + 'app-stableplaystore-release.apk', + ); + final artifact = File(p.join(projectRoot.path, artifactPath)) + ..createSync(recursive: true); - const flavor = 'stablePlayStore'; + const flavor = 'stablePlayStore'; - expect( - shorebirdAndroidArtifacts - .findApk( - project: projectRoot, - flavor: flavor, - ) - .path, - equals(artifact.path), - ); - }); - }); + expect( + shorebirdAndroidArtifacts + .findApk(project: projectRoot, flavor: flavor) + .path, + equals(artifact.path), + ); + }); + }, + ); group('when multiple files are found', () { test('throws MultipleArtifactsFoundException when looking for aab', () { @@ -381,8 +340,10 @@ void main() { isA().having( (exception) => exception.toString(), 'message', - equals('Multiple artifacts found in ${buildDir.path}: ' - '($duplicatedArtifactPath, $artifactPath)'), + equals( + 'Multiple artifacts found in ${buildDir.path}: ' + '($duplicatedArtifactPath, $artifactPath)', + ), ), ), ); @@ -390,13 +351,7 @@ void main() { test('throws MultipleArtifactsFoundException when looking for apk', () { final buildDir = Directory( - p.join( - projectRoot.path, - 'build', - 'app', - 'outputs', - 'flutter-apk', - ), + p.join(projectRoot.path, 'build', 'app', 'outputs', 'flutter-apk'), ); final duplicatedArtifactPath = p.join( buildDir.path, @@ -425,8 +380,10 @@ void main() { isA().having( (exception) => exception.toString(), 'message', - equals('Multiple artifacts found in ${buildDir.path}: ' - '($duplicatedArtifactPath, $artifactPath)'), + equals( + 'Multiple artifacts found in ${buildDir.path}: ' + '($duplicatedArtifactPath, $artifactPath)', + ), ), ), ); @@ -437,15 +394,7 @@ void main() { test('returns path to AAR library', () { expect( runWithOverrides(() => ShorebirdAndroidArtifacts.aarLibraryPath), - equals( - p.join( - projectRoot.path, - 'build', - 'host', - 'outputs', - 'repo', - ), - ), + equals(p.join(projectRoot.path, 'build', 'host', 'outputs', 'repo')), ); }); }); @@ -507,18 +456,21 @@ void main() { group('extractReleaseVersionFromAppBundle', () { setUp(() { - when(() => bundletool.getVersionName(any())) - .thenAnswer((_) async => '1.2.3'); - when(() => bundletool.getVersionCode(any())) - .thenAnswer((_) async => '4'); + when( + () => bundletool.getVersionName(any()), + ).thenAnswer((_) async => '1.2.3'); + when( + () => bundletool.getVersionCode(any()), + ).thenAnswer((_) async => '4'); }); test('returns version name and code from app bundle', () async { const appBundlePath = 'path/to/appbundle'; expect( await runWithOverrides( - () => shorebirdAndroidArtifacts - .extractReleaseVersionFromAppBundle(appBundlePath), + () => shorebirdAndroidArtifacts.extractReleaseVersionFromAppBundle( + appBundlePath, + ), ), equals('1.2.3+4'), ); diff --git a/packages/shorebird_cli/test/src/shorebird_artifacts_test.dart b/packages/shorebird_cli/test/src/shorebird_artifacts_test.dart index bc10b238..1d83aff4 100644 --- a/packages/shorebird_cli/test/src/shorebird_artifacts_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_artifacts_test.dart @@ -44,8 +44,9 @@ void main() { () => cache.getArtifactDirectory(any()), ).thenReturn(artifactDirectory); when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory); - when(() => shorebirdEnv.shorebirdEngineRevision) - .thenReturn(engineRevision); + when( + () => shorebirdEnv.shorebirdEngineRevision, + ).thenReturn(engineRevision); }); group('getArtifactPath', () { @@ -220,9 +221,7 @@ void main() { R runWithOverrides(R Function() body) { return runScoped( () => body(), - values: { - engineConfigRef.overrideWith(() => engineConfig), - }, + values: {engineConfigRef.overrideWith(() => engineConfig)}, ); } @@ -235,18 +234,15 @@ void main() { when( () => engineConfig.localEngineSrcPath, ).thenReturn(localEngineSrcPath); - when( - () => engineConfig.localEngine, - ).thenReturn(localEngine); + when(() => engineConfig.localEngine).thenReturn(localEngine); }); group('getArtifactPath', () { test('returns correct path for aot tools', () { expect( runWithOverrides( - () => artifacts.getArtifactPath( - artifact: ShorebirdArtifact.aotTools, - ), + () => + artifacts.getArtifactPath(artifact: ShorebirdArtifact.aotTools), ), equals( p.join( diff --git a/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart b/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart index 7cae552b..78adcc8d 100644 --- a/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart @@ -52,9 +52,9 @@ void main() { shorebirdVersion = MockShorebirdVersion(); when(() => logger.level).thenReturn(Level.info); final logFile = MockFile(); - when(() => shorebirdEnv.logsDirectory).thenReturn( - Directory.systemTemp.createTempSync(), - ); + when( + () => shorebirdEnv.logsDirectory, + ).thenReturn(Directory.systemTemp.createTempSync()); when(() => logFile.absolute).thenReturn(logFile); when(() => logFile.path).thenReturn('test.log'); when( @@ -137,11 +137,7 @@ void main() { ).called(1); verify( () => logger.info( - any( - that: contains( - 'Usage: shorebird [arguments]', - ), - ), + any(that: contains('Usage: shorebird [arguments]')), ), ).called(1); }); @@ -164,14 +160,12 @@ void main() { expect(result, equals(ExitCode.usage.code)); verify(() => logger.err(exception.message)).called(1); verify( - () => logger.err( - ''' + () => logger.err(''' To proxy an option to the flutter command, use the -- --