-
Notifications
You must be signed in to change notification settings - Fork 10
feat: adding shorebird specific tests #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
628270a
261daf5
efecbc1
0dda0a0
5e2b84a
2b52f3a
d540320
79f2341
54229b3
0ddd735
64c0924
f14ef85
4da3647
a5f636b
d1a8316
aff3d70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
name: shorebird_ci | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
cancel-in-progress: true | ||
|
||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
test: | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
os: [ubuntu-latest, macos-latest, windows-latest] | ||
|
||
runs-on: ${{ matrix.os }} | ||
|
||
name: 🐦 Shorebird Test | ||
|
||
env: | ||
FLUTTER_STORAGE_BASE_URL: https://download.shorebird.dev | ||
|
||
steps: | ||
- name: 📚 Git Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: 🎯 Setup Dart | ||
uses: dart-lang/setup-dart@v1 | ||
|
||
- uses: actions/setup-java@v4 | ||
with: | ||
distribution: 'zulu' | ||
java-version: '11' | ||
|
||
- name: 🐦 Run Shorebird Tests | ||
run: dart test | ||
working-directory: packages/shorebird_tests |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# https://dart.dev/guides/libraries/private-files | ||
# Created by `dart pub` | ||
.dart_tool/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
A dart project that includes tests that perfoma asserts in the modifications made on the Flutter framework by the Shorebird team. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# This file configures the static analysis results for your project (errors, | ||
include: package:lints/recommended.yaml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
name: shorebird_tests | ||
description: Shorebird's Flutter customizations tests | ||
version: 1.0.0 | ||
|
||
environment: | ||
sdk: ^3.3.4 | ||
|
||
dependencies: | ||
archive: ^3.5.1 | ||
path: ^1.9.0 | ||
|
||
dev_dependencies: | ||
lints: ^3.0.0 | ||
meta: ^1.15.0 | ||
test: ^1.24.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import 'package:test/test.dart'; | ||
|
||
import 'shorebird_tests.dart'; | ||
|
||
void main() { | ||
group('shorebird android projects', () { | ||
testWithShorebirdProject('can build an apk', (projectDirectory) async { | ||
await projectDirectory.runFlutterBuildApk(); | ||
|
||
expect(projectDirectory.apkFile().existsSync(), isTrue); | ||
expect(projectDirectory.shorebirdFile.existsSync(), isTrue); | ||
}); | ||
|
||
testWithShorebirdProject( | ||
'adds the public key when passed through the environment variable', | ||
(projectDirectory) async { | ||
const base64PublicKey = 'public_123'; | ||
await projectDirectory.runFlutterBuildApk( | ||
environment: { | ||
'SHOREBIRD_PUBLIC_KEY': base64PublicKey, | ||
}, | ||
); | ||
|
||
final generatedYaml = | ||
await projectDirectory.getGeneratedShorebirdYaml(); | ||
|
||
expect(generatedYaml, contains('patch_public_key: $base64PublicKey')); | ||
}, | ||
); | ||
|
||
group('when building with a flavor', () { | ||
testWithShorebirdProject( | ||
'correctly changes the app id', | ||
(projectDirectory) async { | ||
projectDirectory.addAndroidFlavors(); | ||
projectDirectory.addShorebirdFlavors(); | ||
|
||
await projectDirectory.runFlutterBuildApk(flavor: 'internal'); | ||
|
||
final generatedYaml = | ||
await projectDirectory.getGeneratedShorebirdYaml( | ||
flavor: 'internal', | ||
); | ||
|
||
expect(generatedYaml, contains('app_id: internal_123')); | ||
}, | ||
); | ||
|
||
testWithShorebirdProject( | ||
'correctly changes the app id and adds the public key when passed through the environment variable', | ||
(projectDirectory) async { | ||
const base64PublicKey = 'public_123'; | ||
projectDirectory.addAndroidFlavors(); | ||
projectDirectory.addShorebirdFlavors(); | ||
|
||
await projectDirectory.runFlutterBuildApk( | ||
flavor: 'internal', | ||
environment: { | ||
'SHOREBIRD_PUBLIC_KEY': base64PublicKey, | ||
}, | ||
); | ||
|
||
final generatedYaml = | ||
await projectDirectory.getGeneratedShorebirdYaml( | ||
flavor: 'internal', | ||
); | ||
|
||
expect(generatedYaml, contains('app_id: internal_123')); | ||
expect(generatedYaml, contains('patch_public_key: $base64PublicKey')); | ||
}, | ||
); | ||
}); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import 'package:test/test.dart'; | ||
|
||
import 'shorebird_tests.dart'; | ||
|
||
void main() { | ||
group('shorebird helpers', () { | ||
testWithShorebirdProject('can build a base project', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we even need this test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, in theory we don't, but I kind like having basic ones like that cause it makes it easier to track down a test that broke in an "earlier" layer. Happy to remove them if you think they just "fill up space" |
||
(projectDirectory) async { | ||
expect(projectDirectory.existsSync(), isTrue); | ||
|
||
expect(projectDirectory.pubspecFile.existsSync(), isTrue); | ||
expect(projectDirectory.shorebirdFile.existsSync(), isTrue); | ||
}); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
import 'dart:async'; | ||
import 'dart:io'; | ||
|
||
import 'package:archive/archive_io.dart'; | ||
import 'package:path/path.dart' as path; | ||
|
||
import 'package:meta/meta.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
/// This will be the path to the flutter binary housed in this flutter repository. | ||
/// | ||
/// Which since we are running the tests from this inner package , we need to go up two directories | ||
/// in order to find the flutter binary in the bin folder. | ||
File get _flutterBinaryFile => File( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doc comments throughout this file would be useful. Here: "this assumes we're running from (path)" or similar |
||
path.join( | ||
Directory.current.path, | ||
'..', | ||
'..', | ||
'bin', | ||
'flutter${Platform.isWindows ? '.bat' : ''}', | ||
), | ||
); | ||
|
||
/// Runs a flutter command using the correct binary ([_flutterBinaryFile]) with the given arguments. | ||
Future<ProcessResult> _runFlutterCommand( | ||
List<String> arguments, { | ||
required Directory workingDirectory, | ||
Map<String, String>? environment, | ||
}) async { | ||
return Process.run( | ||
_flutterBinaryFile.absolute.path, | ||
arguments, | ||
workingDirectory: workingDirectory.path, | ||
environment: { | ||
'FLUTTER_STORAGE_BASE_URL': 'https://download.shorebird.dev', | ||
if (environment != null) ...environment, | ||
}, | ||
); | ||
} | ||
|
||
Future<void> _createFlutterProject(Directory projectDirectory) async { | ||
final result = await _runFlutterCommand( | ||
['create', '--empty', '.'], | ||
workingDirectory: projectDirectory, | ||
); | ||
if (result.exitCode != 0) { | ||
throw Exception('Failed to create Flutter project: ${result.stderr}'); | ||
} | ||
} | ||
|
||
@isTest | ||
Future<void> testWithShorebirdProject(String name, | ||
FutureOr<void> Function(Directory projectDirectory) testFn) async { | ||
test( | ||
name, | ||
() async { | ||
final parentDirectory = Directory.systemTemp.createTempSync(); | ||
final projectDirectory = Directory( | ||
path.join( | ||
parentDirectory.path, | ||
'shorebird_test', | ||
), | ||
)..createSync(); | ||
|
||
File( | ||
path.join( | ||
projectDirectory.path, | ||
'shorebird.yaml', | ||
), | ||
).writeAsStringSync(''' | ||
app_id: 123 | ||
'''); | ||
|
||
try { | ||
await _createFlutterProject(projectDirectory); | ||
|
||
projectDirectory.pubspecFile.writeAsStringSync(''' | ||
${projectDirectory.pubspecFile.readAsStringSync()} | ||
|
||
assets: | ||
- shorebird.yaml | ||
'''); | ||
await testFn(projectDirectory); | ||
} finally { | ||
projectDirectory.deleteSync(recursive: true); | ||
} | ||
}, | ||
timeout: Timeout( | ||
// These tests usually run flutter create, flutter build, etc, which can take a while, | ||
// specially in CI, so setting from the default of 30 seconds to 6 minutes. | ||
Duration(minutes: 6), | ||
), | ||
); | ||
} | ||
|
||
extension ShorebirdProjectDirectoryOnDirectory on Directory { | ||
File get pubspecFile => File( | ||
path.join(this.path, 'pubspec.yaml'), | ||
); | ||
|
||
File get shorebirdFile => File( | ||
path.join(this.path, 'shorebird.yaml'), | ||
); | ||
|
||
File get appGradleFile => File( | ||
path.join(this.path, 'android', 'app', 'build.gradle'), | ||
); | ||
|
||
void addAndroidFlavors() { | ||
// TODO(erickzanardo): Maybe in the future make this more dynamic | ||
// and allow the user to pass the flavors, but it is good for now. | ||
const flavors = ''' | ||
flavorDimensions "track" | ||
productFlavors { | ||
playStore { | ||
dimension "track" | ||
applicationIdSuffix ".ps" | ||
} | ||
internal { | ||
dimension "track" | ||
applicationIdSuffix ".internal" | ||
} | ||
global { | ||
dimension "track" | ||
applicationIdSuffix ".global" | ||
} | ||
} | ||
'''; | ||
|
||
final currentGradleContent = appGradleFile.readAsStringSync(); | ||
appGradleFile.writeAsStringSync( | ||
''' | ||
${currentGradleContent.replaceFirst( | ||
' buildTypes {', | ||
' $flavors\n buildTypes {', | ||
)} | ||
''', | ||
); | ||
} | ||
|
||
void addShorebirdFlavors() { | ||
const flavors = ''' | ||
flavors: | ||
global: global_123 | ||
internal: internal_123 | ||
playStore: playStore_123 | ||
'''; | ||
|
||
final currentShorebirdContent = shorebirdFile.readAsStringSync(); | ||
shorebirdFile.writeAsStringSync( | ||
''' | ||
$currentShorebirdContent | ||
$flavors | ||
''', | ||
); | ||
} | ||
|
||
Future<void> runFlutterBuildApk({ | ||
String? flavor, | ||
Map<String, String>? environment, | ||
}) async { | ||
final result = await _runFlutterCommand( | ||
[ | ||
'build', | ||
'apk', | ||
if (flavor != null) '--flavor=$flavor', | ||
], | ||
workingDirectory: this, | ||
environment: environment, | ||
); | ||
if (result.exitCode != 0) { | ||
throw Exception('Failed to run `flutter build apk`: ${result.stderr}'); | ||
} | ||
} | ||
|
||
File apkFile({String? flavor}) => File( | ||
path.join( | ||
this.path, | ||
'build', | ||
'app', | ||
'outputs', | ||
'flutter-apk', | ||
'app-${flavor != null ? '$flavor-' : ''}release.apk', | ||
), | ||
); | ||
|
||
Future<String> getGeneratedShorebirdYaml({String? flavor}) async { | ||
final decodedBytes = | ||
ZipDecoder().decodeBytes(apkFile(flavor: flavor).readAsBytesSync()); | ||
|
||
await extractArchiveToDisk( | ||
decodedBytes, path.join(this.path, 'apk-extracted')); | ||
|
||
return File( | ||
path.join( | ||
this.path, | ||
'apk-extracted', | ||
'assets', | ||
'flutter_assets', | ||
'shorebird.yaml', | ||
), | ||
).readAsStringSync(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need these dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah,
path is used here for example: https://github.com/shorebirdtech/flutter/pull/45/files#diff-f89684ff9eb7b28bb4625302b31d2c083d33414ed9bde6331f7991098896b412R15
and archive is used here: https://github.com/shorebirdtech/flutter/pull/45/files#diff-f89684ff9eb7b28bb4625302b31d2c083d33414ed9bde6331f7991098896b412R189