Skip to content

Commit

Permalink
feat: add test optimizer brick (#639)
Browse files Browse the repository at this point in the history
  • Loading branch information
renancaraujo committed Feb 13, 2023
1 parent 3bef875 commit 3f8434e
Show file tree
Hide file tree
Showing 16 changed files with 441 additions and 64 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/test_optimizer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: test_optimizer_ci

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
paths:
- .github/workflows/test_optimizer.yaml
- bricks/test_optimizer/**"
branches:
- main
pull_request:
paths:
- .github/workflows/test_optimizer.yaml
- bricks/test_optimizer/**"
branches:
- main

jobs:
build_hooks:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
with:
dart_sdk: 2.19.0
working_directory: bricks/test_optimizer/hooks

verify_bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0

- uses: dart-lang/setup-dart@v1

- name: Install mason
run: dart pub global activate mason_cli

- name: Run bundle generate
run: tool/generate_test_optimizer_bundle.sh

- name: Check for unbundled changes
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; }
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ doc/api/
# Files generated during tests
.test_coverage.dart
coverage/
.test_runner.dart
.test_optimizer.dart
!bricks/test_optimizer/__brick__/test/.test_optimizer.dart

# Android studio and IntelliJ
.idea
Expand Down
38 changes: 38 additions & 0 deletions bricks/test_optimizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# test_optimizer

[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason)

A brick that generates a single entrypoint for Dart tests.

_Generated by [mason][1] 🧱_

## Getting Started 🚀

```sh
mason make test_optimizer --package-root ./path/to/package --on-conflict overwrite
```

The above command will generate a `.test_optimizer.dart` in the `test` directory that imports and executes all tests

```dart
// GENERATED CODE - DO NOT MODIFY BY HAND
// Consider adding this file to your .gitignore.
import 'app/view/app_test.dart' as app_view_app_test_dart;
import 'counter/cubit/counter_cubit_test.dart' as counter_cubit_counter_cubit_test_dart;
import 'counter/view/counter_page_test.dart' as counter_view_counter_page_test_dart;
void main() {
app_view_app_test_dart.main();
counter_cubit_counter_cubit_test_dart.main();
counter_view_counter_page_test_dart.main();
}
```

[1]: https://github.com/felangel/mason

---

### Note for maintainers

After changing this brick, make sure to run `./tools/generate_test_optimizer_bundle.sh` from the root of the repository to update the bundle.
45 changes: 45 additions & 0 deletions bricks/test_optimizer/__brick__/test/.test_optimizer.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions bricks/test_optimizer/brick.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: test_optimizer
description: A brick that generates a single entrypoint for Dart tests.
version: 0.1.0+1

environment:
mason: ">=0.1.0-dev.41 <0.1.0"

vars:
package-root:
type: string
default: "."
description: The path to the package root.
prompt: Please enter the path to the package root.
4 changes: 4 additions & 0 deletions bricks/test_optimizer/hooks/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
linter:
rules:
public_member_api_docs: false
48 changes: 48 additions & 0 deletions bricks/test_optimizer/hooks/lib/pre_gen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// ignore_for_file: public_member_api_docs

import 'dart:io';

import 'package:mason/mason.dart';
import 'package:path/path.dart' as path;

typedef ExitFn = Never Function(int code);

ExitFn exitFn = exit;

Future<void> run(HookContext context) async {
final packageRoot = context.vars['package-root'] as String;
final testDir = Directory(path.join(packageRoot, 'test'));

if (!testDir.existsSync()) {
context.logger.err('Could not find directory ${testDir.path}');
exitFn(1);
}

final pubspec = File(path.join(packageRoot, 'pubspec.yaml'));
if (!pubspec.existsSync()) {
context.logger.err('Could not find pubspec.yaml at ${testDir.path}');
exitFn(1);
}

final pubspecContents = await pubspec.readAsString();
final flutterSdkRegExp = RegExp(r'sdk:\s*flutter$', multiLine: true);
final isFlutter = flutterSdkRegExp.hasMatch(pubspecContents);

final tests = testDir
.listSync(recursive: true)
.where((entity) => entity.isTest)
.map(
(entity) => path
.relative(entity.path, from: testDir.path)
.replaceAll(r'\', '/'),
)
.toList();

context.vars = {'tests': tests, 'isFlutter': isFlutter};
}

extension on FileSystemEntity {
bool get isTest {
return this is File && path.basename(this.path).endsWith('_test.dart');
}
}
7 changes: 7 additions & 0 deletions bricks/test_optimizer/hooks/pre_gen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:mason/mason.dart';

import 'lib/pre_gen.dart' as pre_gen;

Future<void> run(HookContext context) async {
await pre_gen.run(context);
}
14 changes: 14 additions & 0 deletions bricks/test_optimizer/hooks/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: hooks
publish_to: none

environment:
sdk: ">=2.19.0 <3.0.0"

dependencies:
mason: ">=0.1.0-dev.41 <0.1.0"
path: ^1.8.1

dev_dependencies:
mocktail: ^0.3.0
test: ^1.22.2
very_good_analysis: ^4.0.0
150 changes: 150 additions & 0 deletions bricks/test_optimizer/hooks/test/pre_gen_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import 'dart:io';

import 'package:hooks/pre_gen.dart' as pre_gen;
import 'package:mason/mason.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

class MockProgress extends Mock implements Progress {}

class MockLogger extends Mock implements Logger {}

class FakeContext extends Fake implements HookContext {
@override
final logger = MockLogger();

@override
Map<String, Object?> vars = {};
}

void main() {
late HookContext context;

setUp(() {
context = FakeContext();
});

group('Pre gen hook', () {
group('Completes', () {
test('with test files list', () async {
final packageRoot =
Directory.systemTemp.createTempSync('test_optimizer');
File(path.join(packageRoot.path, 'pubspec.yaml')).createSync();

final testDir = Directory(path.join(packageRoot.path, 'test'))
..createSync();
File(path.join(testDir.path, 'test1_test.dart')).createSync();
File(path.join(testDir.path, 'test2_test.dart')).createSync();
File(path.join(testDir.path, 'no_test_here.dart')).createSync();

context.vars['package-root'] = packageRoot.absolute.path;

await pre_gen.run(context);

final tests = context.vars['tests'] as List<String>;

expect(
tests,
containsAll([
'test2_test.dart',
'test1_test.dart',
]),
);
expect(test, isNot(contains('no_test_here.dart')));
expect(context.vars['isFlutter'], false);
});

test('with proper isFlutter identification', () async {
final packageRoot =
Directory.systemTemp.createTempSync('test_optimizer');

File(path.join(packageRoot.path, 'pubspec.yaml'))
..createSync()
..writeAsStringSync('''
dependencies:
flutter:
sdk: flutter''');

Directory(path.join(packageRoot.path, 'test')).createSync();

context.vars['package-root'] = packageRoot.absolute.path;

await pre_gen.run(context);

expect(context.vars['isFlutter'], true);
});
});
group('Fails', () {
setUp(() {
pre_gen.exitFn = (code) {
throw ProcessException('exit', [code.toString()]);
};
});

tearDown(() {
pre_gen.exitFn = exit;
});

test('when target test dir does not exist', () async {
final packageRoot =
Directory.systemTemp.createTempSync('test_optimizer');
File(path.join(packageRoot.path, 'pubspec.yaml')).createSync();

final testDir = Directory(path.join(packageRoot.path, 'test'));

context.vars['package-root'] = packageRoot.absolute.path;

await expectLater(
() => pre_gen.run(context),
throwsA(
isA<ProcessException>().having(
(ex) => ex.arguments.first,
'error code',
equals('1'),
),
),
);

verify(
() => context.logger.err('Could not find directory ${testDir.path}'),
).called(1);

expect(context.vars['tests'], isNull);
expect(context.vars['isFlutter'], isNull);
});
test('when target dir does not contain a pubspec.yaml', () async {
final packageRoot =
Directory.systemTemp.createTempSync('test_optimizer');

final testDir = Directory(path.join(packageRoot.path, 'test'))
..createSync();
File(path.join(testDir.path, 'test1_test.dart')).createSync();
File(path.join(testDir.path, 'test2_test.dart')).createSync();
File(path.join(testDir.path, 'no_test_here.dart')).createSync();

context.vars['package-root'] = packageRoot.absolute.path;

await expectLater(
() => pre_gen.run(context),
throwsA(
isA<ProcessException>().having(
(ex) => ex.arguments.first,
'error code',
equals('1'),
),
),
);

verify(
() => context.logger.err(
'Could not find pubspec.yaml at ${testDir.path}',
),
).called(1);

expect(context.vars['tests'], isNull);
expect(context.vars['isFlutter'], isNull);
});
});
});
}
4 changes: 3 additions & 1 deletion lib/src/cli/cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import 'package:path/path.dart' as p;
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:universal_io/io.dart';
import 'package:very_good_cli/src/commands/test/templates/test_runner_bundle.dart';
import 'package:very_good_cli/src/commands/test/templates/test_optimizer_bundle.dart';
import 'package:very_good_test_runner/very_good_test_runner.dart';

part 'dart_cli.dart';

part 'flutter_cli.dart';

part 'git_cli.dart';

const _asyncRunZoned = runZoned;
Expand Down
Loading

0 comments on commit 3f8434e

Please sign in to comment.