diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89f7747 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.buildlog +.DS_Store +.idea +.pub/ +.settings/ +build/ +packages +pubspec.lock diff --git a/.status b/.status new file mode 100644 index 0000000..364ca4b --- /dev/null +++ b/.status @@ -0,0 +1,4 @@ +# Copyright (c) 2015, 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. + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0b1c48a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. scheglov@google.com diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2a2d63c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.0.1 + +- Initial version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6f5e0ea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Want to contribute? Great! First, read this page (including the small print at +the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. + +### File headers +All files in the project must start with the following header. + + // Copyright (c) 2015, 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. + +### The small print +Contributions made by corporations are covered by a different agreement than the +one above, the +[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..de31e1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cbc7e63 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# test_reflective_loader + +Support for discovering tests and test suites using reflection. + +It follows the xUnit style where each class is a test suite, and each method +with the name prefix "test_" is a single text. + +Methods with names starting with "test_" are are run using test() function with +the corresponding name. If the class defines methods setUp() or tearDown(), +they are executed before / after each test correspondingly, even the test fails. + +Methods with names starting with "solo_test_" are run using solo_test() function. + +Methods with names starting with "fail_" are expected to fail. + +Methods with names starting with "solo_fail_" are run using solo_test() function +and expected to fail. + +Method returning Future class instances are asynchronous, so tearDown() is +executed after the returned Future completes. + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: https://github.com/dart-lang/test_reflective_loader/issues diff --git a/lib/test_reflective_loader.dart b/lib/test_reflective_loader.dart new file mode 100644 index 0000000..6ea195a --- /dev/null +++ b/lib/test_reflective_loader.dart @@ -0,0 +1,121 @@ +// Copyright (c) 2015, 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 test_reflective_loader; + +@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(); diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..b741dfc --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,11 @@ +name: test_reflective_loader +version: 0.0.1 +description: Support for discovering tests and test suites using reflection. +author: Dart Team +homepage: https://github.com/dart-lang/test_reflective_loader + +environment: + sdk: '>=1.0.0 <2.0.0' + +dev_dependencies: + unittest: '>=0.9.0 <0.12.0'