Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/shorebird_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ jobs:
distribution: "zulu"
java-version: "17"

- name: 📦 Cache Gradle
if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }}
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: 🐦 Run Flutter Tools Tests
# TODO(eseidel): Find a nice way to run this on windows.
if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }}
Expand Down
2 changes: 2 additions & 0 deletions packages/shorebird_tests/test/android_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:test/test.dart';
import 'shorebird_tests.dart';

void main() {
setUpAll(warmUpTemplateProject);

group('shorebird android projects', () {
testWithShorebirdProject('can build an apk', (projectDirectory) async {
await projectDirectory.runFlutterBuildApk();
Expand Down
2 changes: 2 additions & 0 deletions packages/shorebird_tests/test/base_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:test/test.dart';
import 'shorebird_tests.dart';

void main() {
setUpAll(warmUpTemplateProject);

group('shorebird helpers', () {
testWithShorebirdProject('can build a base project',
(projectDirectory) async {
Expand Down
2 changes: 2 additions & 0 deletions packages/shorebird_tests/test/ios_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:test/test.dart';
import 'shorebird_tests.dart';

void main() {
setUpAll(warmUpTemplateProject);

group(
'shorebird ios projects',
() {
Expand Down
108 changes: 83 additions & 25 deletions packages/shorebird_tests/test/shorebird_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,46 +91,104 @@ Future<void> _createFlutterProject(Directory projectDirectory) async {
}
}

@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();
/// Cached template project directory, created once and reused across tests.
///
/// This avoids running `flutter create` for every test, which saves
/// significant time (especially the first Gradle/SDK download).
Directory? _templateProject;

try {
await _createFlutterProject(projectDirectory);
/// Creates (or returns the cached) template Flutter project with
/// shorebird.yaml configured. The first call runs `flutter create` and
/// `flutter build apk` to warm up Gradle caches.
///
/// Call this from `setUpAll` so the expensive setup runs outside per-test
/// timeouts.
Future<void> warmUpTemplateProject() => _getTemplateProject();

Future<Directory> _getTemplateProject() async {
if (_templateProject != null) {
return _templateProject!;
}

final Directory templateDir = Directory(
path.join(Directory.systemTemp.createTempSync().path, 'shorebird_template'),
)..createSync();

projectDirectory.pubspecFile.writeAsStringSync('''
${projectDirectory.pubspecFile.readAsStringSync()}
await _createFlutterProject(templateDir);

templateDir.pubspecFile.writeAsStringSync('''
${templateDir.pubspecFile.readAsStringSync()}
assets:
- shorebird.yaml
''');

File(
path.join(
projectDirectory.path,
'shorebird.yaml',
),
).writeAsStringSync('''
File(
path.join(templateDir.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: "123"
''');

// Warm up the Gradle cache with a throwaway build so subsequent
// per-test builds are fast and don't hit the per-test timeout.
// Skip if Gradle cache is already populated (e.g., from GHA cache restore).
final Directory gradleCache = Directory(
path.join(Platform.environment['HOME'] ?? '', '.gradle', 'caches'),
);
final bool hasGradleCache =
gradleCache.existsSync() && gradleCache.listSync().isNotEmpty;
if (hasGradleCache) {
print('[warmup] Gradle cache exists, skipping warm-up build');
} else {
await _runFlutterCommand(
['build', 'apk'],
workingDirectory: templateDir,
);
}

_templateProject = templateDir;
return templateDir;
}

/// Copies the template project to a fresh directory for test isolation.
Future<Directory> _copyTemplateProject() async {
final Directory template = await _getTemplateProject();
final Directory testDir = Directory(
path.join(Directory.systemTemp.createTempSync().path, 'shorebird_test'),
);

// Use platform copy to preserve the full directory tree efficiently.
if (Platform.isWindows) {
await Process.run('xcopy', [
template.path,
testDir.path,
'/E',
'/I',
'/Q',
]);
} else {
await Process.run('cp', ['-R', template.path, testDir.path]);
}

return testDir;
}

@isTest
Future<void> testWithShorebirdProject(String name,
FutureOr<void> Function(Directory projectDirectory) testFn) async {
test(
name,
() async {
final Directory projectDirectory = await _copyTemplateProject();

try {
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.
// Per-test timeout can be shorter now since the template project
// creation and Gradle warm-up happen outside the test timeout.
Duration(minutes: 6),
),
);
Expand Down