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
5 changes: 5 additions & 0 deletions pkgs/test_reflective_loader/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.4.0

- Add support for one-time set up and teardown in test classes via static
`setUpClass` and `tearDownClass` methods

## 0.3.0

- Require Dart `^3.5.0`.
Expand Down
46 changes: 38 additions & 8 deletions pkgs/test_reflective_loader/lib/test_reflective_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ void defineReflectiveTests(Type type) {
{
var isSolo = _hasAnnotationInstance(classMirror, soloTest);
var className = MirrorSystem.getName(classMirror.simpleName);
group = _Group(isSolo, _combineNames(_currentSuiteName, className),
classMirror.testLocation);
group = _Group(
isSolo, _combineNames(_currentSuiteName, className), classMirror);
_currentGroups.add(group);
}

Expand Down Expand Up @@ -151,10 +151,24 @@ void _addTestsIfTopLevelSuite() {
if (_currentSuiteLevel == 0) {
void runTests({required bool allGroups, required bool allTests}) {
for (var group in _currentGroups) {
var runTestCount = 0;
if (allGroups || group.isSolo) {
for (var test in group.tests) {
if (allTests || test.isSolo) {
test_package.test(test.name, test.function,
if (!test.isSkipped) {
runTestCount += 1;
}
test_package.test(test.name, () async {
await group.ensureSetUpClass();
try {
await test.function();
} finally {
runTestCount -= 1;
if (runTestCount == 0) {
group.tearDownClass();
}
}
},
timeout: test.timeout,
skip: test.isSkipped,
location: test.location);
Expand Down Expand Up @@ -210,14 +224,14 @@ bool _hasSkippedTestAnnotation(MethodMirror method) =>
_hasAnnotationInstance(method, skippedTest);

Future<Object?> _invokeSymbolIfExists(
InstanceMirror instanceMirror, Symbol symbol) {
ObjectMirror objectMirror, Symbol symbol) {
Object? invocationResult;
InstanceMirror? closure;
try {
closure = instanceMirror.getField(symbol);
closure = objectMirror.getField(symbol);
// ignore: avoid_catching_errors
} on NoSuchMethodError {
// ignore
// ignore: empty_catches
}

if (closure is ClosureMirror) {
Expand Down Expand Up @@ -307,10 +321,13 @@ class _AssertFailingTest {
class _Group {
final bool isSolo;
final String name;
final test_package.TestLocation? location;
final List<_Test> tests = <_Test>[];

_Group(this.isSolo, this.name, this.location);
/// For static group-wide operations eg `setUpClass` and `tearDownClass`.
final ClassMirror _classMirror;
Future<Object?>? _setUpCompletion;

_Group(this.isSolo, this.name, this._classMirror);

bool get hasSoloTest => tests.any((test) => test.isSolo);

Expand All @@ -327,6 +344,19 @@ class _Group {
tests.add(_Test(isSolo, fullName, function, timeout?._timeout,
memberMirror.testLocation));
}

/// Runs group-wide setup if it has not been started yet,
/// ensuring it only runs once for a group. Set up runs and
/// completes before any test of the group runs
Future<Object?> ensureSetUpClass() =>
_setUpCompletion ??= _invokeSymbolIfExists(_classMirror, #setUpClass);

/// Runs group-wide tear down iff [ensureSetUpClass] was called at least once.
/// Must be called once and only called after all tests of the group have
/// completed
void tearDownClass() => _setUpCompletion != null
? _invokeSymbolIfExists(_classMirror, #tearDownClass)
: null;
}

/// A marker annotation used to instruct dart2js to keep reflection information
Expand Down
2 changes: 1 addition & 1 deletion pkgs/test_reflective_loader/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: test_reflective_loader
version: 0.3.0
version: 0.4.0
description: Support for discovering tests and test suites using reflection.
repository: https://github.com/dart-lang/tools/tree/main/pkgs/test_reflective_loader
issue_tracker: https://github.com/dart-lang/tools/labels/package%3Atest_reflective_loader
Expand Down
5 changes: 5 additions & 0 deletions pkgs/test_reflective_loader/test/location_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ void main() {
// the source code to ensure the locations match up.
name = name.split('|').last.trim();

// Skip "tearDownAll" or "setUpAll", it never has a location.
if (name case '(tearDownAll)' || '(setUpAll)') {
continue;
}

// Expect locations for all remaining fields.
var url = test['url'] as String;
var line = test['line'] as int;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,62 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';

void main() {
defineReflectiveSuite(() {
// ensure set-ups and tear-downs are not prematurely called ie before any
// tests actually execute
setUpAll(() {
expect(TestReflectiveLoaderTest.didSetUpClass, false);
expect(TestReflectiveLoaderTest.didTearDownClass, false);
expect(SecondTest.didSetUpClass, false);
expect(SecondTest.didTearDownClass, false);
});
defineReflectiveTests(TestReflectiveLoaderTest);
defineReflectiveTests(SecondTest);
tearDownAll(() {
expect(TestReflectiveLoaderTest.didSetUpClass, true);
expect(TestReflectiveLoaderTest.didTearDownClass, true);
expect(SecondTest.didSetUpClass, true);
expect(SecondTest.didTearDownClass, true);
});
});
}

@reflectiveTest
class TestReflectiveLoaderTest {
void test_passes() {
expect(true, true);
static bool didSetUpClass = false;
static bool didTearDownClass = false;

// TODO(scheglov): Linter was updated to automatically ignore
// this but needs time before it is actually used. Remove this
// ignore and others like it in this file once the linter
// change is active in this project:
// ignore: unreachable_from_main
static void setUpClass() {
expect(didSetUpClass, false);
didSetUpClass = true;
expect(didTearDownClass, false);
}

// TODO(scheglov): See comment directly above
// "TestReflectiveLoaderTest.setUpClass" for info about this ignore:
// ignore: unreachable_from_main
static void tearDownClass() {
expect(didSetUpClass, true);
expect(didTearDownClass, false);
didTearDownClass = true;
}

void test_classwide_state() {
expect(didSetUpClass, true);
expect(didTearDownClass, false);
}

@failingTest
void test_fails() {
expect(false, true);
}

@failingTest
void test_fails_throws_sync() {
@skippedTest
void test_fails_but_skipped() {
throw StateError('foo');
}

Expand All @@ -36,13 +75,46 @@ class TestReflectiveLoaderTest {
return Future.error('foo');
}

@skippedTest
void test_fails_but_skipped() {
@failingTest
void test_fails_throws_sync() {
throw StateError('foo');
}

void test_passes() {
expect(true, true);
}

@skippedTest
void test_times_out_but_skipped() {
while (true) {}
}
}

@reflectiveTest
class SecondTest {
static bool didSetUpClass = false;
static bool didTearDownClass = false;

// TODO(scheglov): See comment directly above
// "TestReflectiveLoaderTest.setUpClass" for info about this ignore:
// ignore: unreachable_from_main
static void setUpClass() {
expect(didSetUpClass, false);
didSetUpClass = true;
expect(didTearDownClass, false);
}

// TODO(scheglov): See comment directly above
// "TestReflectiveLoaderTest.setUpClass" for info about this ignore:
// ignore: unreachable_from_main
static void tearDownClass() {
expect(didSetUpClass, true);
expect(didTearDownClass, false);
didTearDownClass = true;
}

void test_classwide_state() {
expect(didSetUpClass, true);
expect(didTearDownClass, false);
}
}