Skip to content

Commit 3f8434e

Browse files
authored
feat: add test optimizer brick (#639)
1 parent 3bef875 commit 3f8434e

File tree

16 files changed

+441
-64
lines changed

16 files changed

+441
-64
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: test_optimizer_ci
2+
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}
5+
cancel-in-progress: true
6+
7+
on:
8+
push:
9+
paths:
10+
- .github/workflows/test_optimizer.yaml
11+
- bricks/test_optimizer/**"
12+
branches:
13+
- main
14+
pull_request:
15+
paths:
16+
- .github/workflows/test_optimizer.yaml
17+
- bricks/test_optimizer/**"
18+
branches:
19+
- main
20+
21+
jobs:
22+
build_hooks:
23+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
24+
with:
25+
dart_sdk: 2.19.0
26+
working_directory: bricks/test_optimizer/hooks
27+
28+
verify_bundle:
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v3.3.0
32+
33+
- uses: dart-lang/setup-dart@v1
34+
35+
- name: Install mason
36+
run: dart pub global activate mason_cli
37+
38+
- name: Run bundle generate
39+
run: tool/generate_test_optimizer_bundle.sh
40+
41+
- name: Check for unbundled changes
42+
run: git diff --exit-code --quiet || { echo "::error::Changes detected on the test_opitimizer brick. Please run tools/generate_test_optimizer_bundle.sh to bundle these changes"; exit 1; }

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ doc/api/
1515
# Files generated during tests
1616
.test_coverage.dart
1717
coverage/
18-
.test_runner.dart
18+
.test_optimizer.dart
19+
!bricks/test_optimizer/__brick__/test/.test_optimizer.dart
1920

2021
# Android studio and IntelliJ
2122
.idea

bricks/test_optimizer/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# test_optimizer
2+
3+
[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason)
4+
5+
A brick that generates a single entrypoint for Dart tests.
6+
7+
_Generated by [mason][1] 🧱_
8+
9+
## Getting Started 🚀
10+
11+
```sh
12+
mason make test_optimizer --package-root ./path/to/package --on-conflict overwrite
13+
```
14+
15+
The above command will generate a `.test_optimizer.dart` in the `test` directory that imports and executes all tests
16+
17+
```dart
18+
// GENERATED CODE - DO NOT MODIFY BY HAND
19+
// Consider adding this file to your .gitignore.
20+
21+
import 'app/view/app_test.dart' as app_view_app_test_dart;
22+
import 'counter/cubit/counter_cubit_test.dart' as counter_cubit_counter_cubit_test_dart;
23+
import 'counter/view/counter_page_test.dart' as counter_view_counter_page_test_dart;
24+
25+
void main() {
26+
app_view_app_test_dart.main();
27+
counter_cubit_counter_cubit_test_dart.main();
28+
counter_view_counter_page_test_dart.main();
29+
}
30+
```
31+
32+
[1]: https://github.com/felangel/mason
33+
34+
---
35+
36+
### Note for maintainers
37+
38+
After changing this brick, make sure to run `./tools/generate_test_optimizer_bundle.sh` from the root of the repository to update the bundle.

bricks/test_optimizer/__brick__/test/.test_optimizer.dart

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bricks/test_optimizer/brick.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: test_optimizer
2+
description: A brick that generates a single entrypoint for Dart tests.
3+
version: 0.1.0+1
4+
5+
environment:
6+
mason: ">=0.1.0-dev.41 <0.1.0"
7+
8+
vars:
9+
package-root:
10+
type: string
11+
default: "."
12+
description: The path to the package root.
13+
prompt: Please enter the path to the package root.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include: package:very_good_analysis/analysis_options.4.0.0.yaml
2+
linter:
3+
rules:
4+
public_member_api_docs: false
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// ignore_for_file: public_member_api_docs
2+
3+
import 'dart:io';
4+
5+
import 'package:mason/mason.dart';
6+
import 'package:path/path.dart' as path;
7+
8+
typedef ExitFn = Never Function(int code);
9+
10+
ExitFn exitFn = exit;
11+
12+
Future<void> run(HookContext context) async {
13+
final packageRoot = context.vars['package-root'] as String;
14+
final testDir = Directory(path.join(packageRoot, 'test'));
15+
16+
if (!testDir.existsSync()) {
17+
context.logger.err('Could not find directory ${testDir.path}');
18+
exitFn(1);
19+
}
20+
21+
final pubspec = File(path.join(packageRoot, 'pubspec.yaml'));
22+
if (!pubspec.existsSync()) {
23+
context.logger.err('Could not find pubspec.yaml at ${testDir.path}');
24+
exitFn(1);
25+
}
26+
27+
final pubspecContents = await pubspec.readAsString();
28+
final flutterSdkRegExp = RegExp(r'sdk:\s*flutter$', multiLine: true);
29+
final isFlutter = flutterSdkRegExp.hasMatch(pubspecContents);
30+
31+
final tests = testDir
32+
.listSync(recursive: true)
33+
.where((entity) => entity.isTest)
34+
.map(
35+
(entity) => path
36+
.relative(entity.path, from: testDir.path)
37+
.replaceAll(r'\', '/'),
38+
)
39+
.toList();
40+
41+
context.vars = {'tests': tests, 'isFlutter': isFlutter};
42+
}
43+
44+
extension on FileSystemEntity {
45+
bool get isTest {
46+
return this is File && path.basename(this.path).endsWith('_test.dart');
47+
}
48+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'package:mason/mason.dart';
2+
3+
import 'lib/pre_gen.dart' as pre_gen;
4+
5+
Future<void> run(HookContext context) async {
6+
await pre_gen.run(context);
7+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: hooks
2+
publish_to: none
3+
4+
environment:
5+
sdk: ">=2.19.0 <3.0.0"
6+
7+
dependencies:
8+
mason: ">=0.1.0-dev.41 <0.1.0"
9+
path: ^1.8.1
10+
11+
dev_dependencies:
12+
mocktail: ^0.3.0
13+
test: ^1.22.2
14+
very_good_analysis: ^4.0.0
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import 'dart:io';
2+
3+
import 'package:hooks/pre_gen.dart' as pre_gen;
4+
import 'package:mason/mason.dart';
5+
import 'package:mocktail/mocktail.dart';
6+
import 'package:path/path.dart' as path;
7+
import 'package:test/test.dart';
8+
9+
class MockProgress extends Mock implements Progress {}
10+
11+
class MockLogger extends Mock implements Logger {}
12+
13+
class FakeContext extends Fake implements HookContext {
14+
@override
15+
final logger = MockLogger();
16+
17+
@override
18+
Map<String, Object?> vars = {};
19+
}
20+
21+
void main() {
22+
late HookContext context;
23+
24+
setUp(() {
25+
context = FakeContext();
26+
});
27+
28+
group('Pre gen hook', () {
29+
group('Completes', () {
30+
test('with test files list', () async {
31+
final packageRoot =
32+
Directory.systemTemp.createTempSync('test_optimizer');
33+
File(path.join(packageRoot.path, 'pubspec.yaml')).createSync();
34+
35+
final testDir = Directory(path.join(packageRoot.path, 'test'))
36+
..createSync();
37+
File(path.join(testDir.path, 'test1_test.dart')).createSync();
38+
File(path.join(testDir.path, 'test2_test.dart')).createSync();
39+
File(path.join(testDir.path, 'no_test_here.dart')).createSync();
40+
41+
context.vars['package-root'] = packageRoot.absolute.path;
42+
43+
await pre_gen.run(context);
44+
45+
final tests = context.vars['tests'] as List<String>;
46+
47+
expect(
48+
tests,
49+
containsAll([
50+
'test2_test.dart',
51+
'test1_test.dart',
52+
]),
53+
);
54+
expect(test, isNot(contains('no_test_here.dart')));
55+
expect(context.vars['isFlutter'], false);
56+
});
57+
58+
test('with proper isFlutter identification', () async {
59+
final packageRoot =
60+
Directory.systemTemp.createTempSync('test_optimizer');
61+
62+
File(path.join(packageRoot.path, 'pubspec.yaml'))
63+
..createSync()
64+
..writeAsStringSync('''
65+
dependencies:
66+
flutter:
67+
sdk: flutter''');
68+
69+
Directory(path.join(packageRoot.path, 'test')).createSync();
70+
71+
context.vars['package-root'] = packageRoot.absolute.path;
72+
73+
await pre_gen.run(context);
74+
75+
expect(context.vars['isFlutter'], true);
76+
});
77+
});
78+
group('Fails', () {
79+
setUp(() {
80+
pre_gen.exitFn = (code) {
81+
throw ProcessException('exit', [code.toString()]);
82+
};
83+
});
84+
85+
tearDown(() {
86+
pre_gen.exitFn = exit;
87+
});
88+
89+
test('when target test dir does not exist', () async {
90+
final packageRoot =
91+
Directory.systemTemp.createTempSync('test_optimizer');
92+
File(path.join(packageRoot.path, 'pubspec.yaml')).createSync();
93+
94+
final testDir = Directory(path.join(packageRoot.path, 'test'));
95+
96+
context.vars['package-root'] = packageRoot.absolute.path;
97+
98+
await expectLater(
99+
() => pre_gen.run(context),
100+
throwsA(
101+
isA<ProcessException>().having(
102+
(ex) => ex.arguments.first,
103+
'error code',
104+
equals('1'),
105+
),
106+
),
107+
);
108+
109+
verify(
110+
() => context.logger.err('Could not find directory ${testDir.path}'),
111+
).called(1);
112+
113+
expect(context.vars['tests'], isNull);
114+
expect(context.vars['isFlutter'], isNull);
115+
});
116+
test('when target dir does not contain a pubspec.yaml', () async {
117+
final packageRoot =
118+
Directory.systemTemp.createTempSync('test_optimizer');
119+
120+
final testDir = Directory(path.join(packageRoot.path, 'test'))
121+
..createSync();
122+
File(path.join(testDir.path, 'test1_test.dart')).createSync();
123+
File(path.join(testDir.path, 'test2_test.dart')).createSync();
124+
File(path.join(testDir.path, 'no_test_here.dart')).createSync();
125+
126+
context.vars['package-root'] = packageRoot.absolute.path;
127+
128+
await expectLater(
129+
() => pre_gen.run(context),
130+
throwsA(
131+
isA<ProcessException>().having(
132+
(ex) => ex.arguments.first,
133+
'error code',
134+
equals('1'),
135+
),
136+
),
137+
);
138+
139+
verify(
140+
() => context.logger.err(
141+
'Could not find pubspec.yaml at ${testDir.path}',
142+
),
143+
).called(1);
144+
145+
expect(context.vars['tests'], isNull);
146+
expect(context.vars['isFlutter'], isNull);
147+
});
148+
});
149+
});
150+
}

0 commit comments

Comments
 (0)