blob: 8180cf7800bd85737844932ba1fbdd051637b7fc [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library reflective_tests;
@MirrorsUsed(metaTargets: 'ReflectiveTest')
import 'dart:mirrors';
import 'dart:async';
import 'package:unittest/unittest.dart';
/**
* Runs test methods existing in the given [type].
*
* Methods with names starting with `test` are run using [test] function.
* Methods with names starting with `solo_test` are run using [solo_test] function.
*
* Each method is run with a new instance of [type].
* So, [type] should have a default constructor.
*
* If [type] declares method `setUp`, it methods will be invoked before any test
* method invocation.
*
* If [type] declares method `tearDown`, it will be invoked after any test
* method invocation. If method returns [Future] to test some asyncronous
* behavior, then `tearDown` will be invoked in `Future.complete`.
*/
void runReflectiveTests(Type type) {
ClassMirror classMirror = reflectClass(type);
if (!classMirror.metadata.any((InstanceMirror annotation) =>
annotation.type.reflectedType == ReflectiveTest)) {
String name = MirrorSystem.getName(classMirror.qualifiedName);
throw new Exception('Class $name must have annotation "@reflectiveTest" '
'in order to be run by runReflectiveTests.');
}
String className = MirrorSystem.getName(classMirror.simpleName);
group(className, () {
classMirror.instanceMembers.forEach((symbol, memberMirror) {
// we need only methods
if (memberMirror is! MethodMirror || !memberMirror.isRegularMethod) {
return;
}
String memberName = MirrorSystem.getName(symbol);
// test_
if (memberName.startsWith('test_')) {
test(memberName, () {
return _runTest(classMirror, symbol);
});
return;
}
// solo_test_
if (memberName.startsWith('solo_test_')) {
solo_test(memberName, () {
return _runTest(classMirror, symbol);
});
}
// fail_test_
if (memberName.startsWith('fail_')) {
test(memberName, () {
return _runFailingTest(classMirror, symbol);
});
}
// solo_fail_test_
if (memberName.startsWith('solo_fail_')) {
solo_test(memberName, () {
return _runFailingTest(classMirror, symbol);
});
}
});
});
}
Future _invokeSymbolIfExists(InstanceMirror instanceMirror, Symbol symbol) {
var invocationResult = null;
try {
invocationResult = instanceMirror.invoke(symbol, []).reflectee;
} on NoSuchMethodError {}
if (invocationResult is Future) {
return invocationResult;
} else {
return new Future.value(invocationResult);
}
}
/**
* Run a test that is expected to fail, and confirm that it fails.
*
* This properly handles the following cases:
* - The test fails by throwing an exception
* - The test returns a future which completes with an error.
*
* However, it does not handle the case where the test creates an asynchronous
* callback using expectAsync(), and that callback generates a failure.
*/
Future _runFailingTest(ClassMirror classMirror, Symbol symbol) {
return new Future(() => _runTest(classMirror, symbol)).then((_) {
fail('Test passed - expected to fail.');
}, onError: (_) {});
}
_runTest(ClassMirror classMirror, Symbol symbol) {
InstanceMirror instanceMirror = classMirror.newInstance(new Symbol(''), []);
return _invokeSymbolIfExists(instanceMirror, #setUp)
.then((_) => instanceMirror.invoke(symbol, []).reflectee)
.whenComplete(() => _invokeSymbolIfExists(instanceMirror, #tearDown));
}
/**
* A marker annotation used to instruct dart2js to keep reflection information
* for the annotated classes.
*/
class ReflectiveTest {
const ReflectiveTest();
}
/**
* A marker annotation used to instruct dart2js to keep reflection information
* for the annotated classes.
*/
const ReflectiveTest reflectiveTest = const ReflectiveTest();