Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library test_reflective_loader; | 5 library test_reflective_loader; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 @MirrorsUsed(metaTargets: 'ReflectiveTest') | 8 @MirrorsUsed(metaTargets: 'ReflectiveTest') |
| 9 import 'dart:mirrors'; | 9 import 'dart:mirrors'; |
| 10 | 10 |
| 11 import 'package:unittest/unittest.dart'; | 11 import 'package:test/test.dart' as test_package; |
| 12 | 12 |
| 13 /** | 13 /** |
| 14 * A marker annotation used to annotate overridden test methods (so we cannot | 14 * A marker annotation used to annotate overridden test methods (so we cannot |
| 15 * rename them to `fail_`) which are expected to fail at `assert` in the | 15 * rename them to `fail_`) which are expected to fail at `assert` in the |
| 16 * checked mode. | 16 * checked mode. |
| 17 */ | 17 */ |
| 18 const _AssertFailingTest assertFailingTest = const _AssertFailingTest(); | 18 const _AssertFailingTest assertFailingTest = const _AssertFailingTest(); |
| 19 | 19 |
| 20 /** | 20 /** |
| 21 * A marker annotation used to annotate overridden test methods (so we cannot | 21 * A marker annotation used to annotate overridden test methods (so we cannot |
| 22 * rename them to `fail_`) which are expected to fail. | 22 * rename them to `fail_`) which are expected to fail. |
| 23 */ | 23 */ |
| 24 const _FailingTest failingTest = const _FailingTest(); | 24 const _FailingTest failingTest = const _FailingTest(); |
| 25 | 25 |
| 26 /** | 26 /** |
| 27 * A marker annotation used to instruct dart2js to keep reflection information | 27 * A marker annotation used to instruct dart2js to keep reflection information |
| 28 * for the annotated classes. | 28 * for the annotated classes. |
| 29 */ | 29 */ |
| 30 const ReflectiveTest reflectiveTest = const ReflectiveTest(); | 30 const ReflectiveTest reflectiveTest = const ReflectiveTest(); |
| 31 | 31 |
| 32 /** | 32 /** |
| 33 * Test classes annotated with this annotation are run using [solo_group]. | 33 * A marker annotation used to annotate "solo" groups and tests. |
| 34 */ | 34 */ |
| 35 const _SoloTest soloTest = const _SoloTest(); | 35 const _SoloTest soloTest = const _SoloTest(); |
| 36 | 36 |
| 37 final List<_Group> _currentGroups = <_Group>[]; | |
| 38 int _currentSuiteLevel = 0; | |
| 39 String _currentSuiteName = null; | |
| 40 | |
| 37 /** | 41 /** |
| 38 * Is `true` the application is running in the checked mode. | 42 * Is `true` the application is running in the checked mode. |
| 39 */ | 43 */ |
| 40 final bool _isCheckedMode = () { | 44 final bool _isCheckedMode = () { |
| 41 try { | 45 try { |
| 42 assert(false); | 46 assert(false); |
| 43 return false; | 47 return false; |
| 44 } catch (_) { | 48 } catch (_) { |
| 45 return true; | 49 return true; |
| 46 } | 50 } |
| 47 }(); | 51 }(); |
| 48 | 52 |
| 49 /** | 53 /** |
| 54 * Run the [define] function parameter that calls [defineReflectiveTests] to | |
| 55 * add normal and "solo" tests, and also calls [defineReflectiveSuite] to | |
| 56 * create embedded suites. If the current suite is the top-level one, perform | |
| 57 * check for "solo" groups and tests, and run all or only "solo" items. | |
| 58 */ | |
| 59 void defineReflectiveSuite(void define(), {String name}) { | |
| 60 String groupName = _currentSuiteName; | |
| 61 _currentSuiteLevel++; | |
| 62 try { | |
| 63 _currentSuiteName = _combineNames(_currentSuiteName, name); | |
| 64 define(); | |
| 65 } finally { | |
| 66 _currentSuiteName = groupName; | |
| 67 _currentSuiteLevel--; | |
| 68 } | |
| 69 if (_currentSuiteLevel == 0) { | |
| 70 void runTests({bool allGroups, bool allTests}) { | |
| 71 for (_Group group in _currentGroups) { | |
| 72 if (allGroups || group.isSolo) { | |
| 73 for (_Test test in group.tests) { | |
| 74 if (allTests || test.isSolo) { | |
| 75 test_package.test(test.name, test.function); | |
| 76 } | |
| 77 } | |
| 78 } | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 if (_currentGroups.any((g) => g.hasSoloTest)) { | |
| 83 runTests(allGroups: true, allTests: false); | |
| 84 } else if (_currentGroups.any((g) => g.isSolo)) { | |
| 85 runTests(allGroups: false, allTests: true); | |
| 86 } else { | |
| 87 runTests(allGroups: true, allTests: true); | |
| 88 } | |
| 89 _currentGroups.clear(); | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 /** | |
| 50 * Runs test methods existing in the given [type]. | 94 * Runs test methods existing in the given [type]. |
| 51 * | 95 * |
| 52 * Methods with names starting with `test` are run using [test] function. | 96 * If there is a "solo" test method in the top-level suite, it only "solo" |
|
Paul Berry
2016/10/04 19:19:12
Drop the word "it"
scheglov
2016/10/04 19:30:11
Done.
| |
| 53 * Methods with names starting with `solo_test` are run using [solo_test] functi on. | 97 * methods are run. |
| 98 * | |
| 99 * If there is a "solo" test type, only its test methods are run. | |
| 100 * | |
| 101 * Otherwise all tests methods of all test types are run. | |
| 54 * | 102 * |
| 55 * Each method is run with a new instance of [type]. | 103 * Each method is run with a new instance of [type]. |
| 56 * So, [type] should have a default constructor. | 104 * So, [type] should have a default constructor. |
| 57 * | 105 * |
| 58 * If [type] declares method `setUp`, it methods will be invoked before any test | 106 * If [type] declares method `setUp`, it methods will be invoked before any test |
| 59 * method invocation. | 107 * method invocation. |
| 60 * | 108 * |
| 61 * If [type] declares method `tearDown`, it will be invoked after any test | 109 * If [type] declares method `tearDown`, it will be invoked after any test |
| 62 * method invocation. If method returns [Future] to test some asynchronous | 110 * method invocation. If method returns [Future] to test some asynchronous |
| 63 * behavior, then `tearDown` will be invoked in `Future.complete`. | 111 * behavior, then `tearDown` will be invoked in `Future.complete`. |
| 64 */ | 112 */ |
| 65 void defineReflectiveTests(Type type) { | 113 void defineReflectiveTests(Type type) { |
| 66 ClassMirror classMirror = reflectClass(type); | 114 ClassMirror classMirror = reflectClass(type); |
| 67 if (!classMirror.metadata.any((InstanceMirror annotation) => | 115 if (!classMirror.metadata.any((InstanceMirror annotation) => |
| 68 annotation.type.reflectedType == ReflectiveTest)) { | 116 annotation.type.reflectedType == ReflectiveTest)) { |
| 69 String name = MirrorSystem.getName(classMirror.qualifiedName); | 117 String name = MirrorSystem.getName(classMirror.qualifiedName); |
| 70 throw new Exception('Class $name must have annotation "@reflectiveTest" ' | 118 throw new Exception('Class $name must have annotation "@reflectiveTest" ' |
| 71 'in order to be run by runReflectiveTests.'); | 119 'in order to be run by runReflectiveTests.'); |
| 72 } | 120 } |
| 73 void runMembers() { | 121 |
| 74 classMirror.instanceMembers | 122 _Group group; |
| 75 .forEach((Symbol symbol, MethodMirror memberMirror) { | 123 { |
| 76 // we need only methods | 124 bool isSolo = _hasAnnotationInstance(classMirror, soloTest); |
| 77 if (memberMirror is! MethodMirror || !memberMirror.isRegularMethod) { | 125 String className = MirrorSystem.getName(classMirror.simpleName); |
| 78 return; | 126 group = new _Group(isSolo, _combineNames(_currentSuiteName, className)); |
| 79 } | 127 _currentGroups.add(group); |
| 80 String memberName = MirrorSystem.getName(symbol); | 128 } |
| 81 // test_ | 129 |
| 82 if (memberName.startsWith('test_')) { | 130 classMirror.instanceMembers |
| 83 test(memberName, () { | 131 .forEach((Symbol symbol, MethodMirror memberMirror) { |
| 84 if (_hasFailingTestAnnotation(memberMirror) || | 132 // we need only methods |
| 85 _isCheckedMode && _hasAssertFailingTestAnnotation(memberMirror)) { | 133 if (memberMirror is! MethodMirror || !memberMirror.isRegularMethod) { |
| 86 return _runFailingTest(classMirror, symbol); | 134 return; |
| 87 } else { | 135 } |
| 88 return _runTest(classMirror, symbol); | 136 // prepare information about the method |
| 89 } | 137 String memberName = MirrorSystem.getName(symbol); |
| 90 }); | 138 bool isSolo = memberName.startsWith('solo_') || |
| 91 return; | 139 _hasAnnotationInstance(memberMirror, soloTest); |
| 92 } | 140 // test_ |
| 93 // solo_test_ | 141 if (memberName.startsWith('test_')) { |
| 94 if (memberName.startsWith('solo_test_')) { | 142 group.addTest(isSolo, memberName, () { |
| 95 solo_test(memberName, () { | 143 if (_hasFailingTestAnnotation(memberMirror) || |
| 144 _isCheckedMode && _hasAssertFailingTestAnnotation(memberMirror)) { | |
| 145 return _runFailingTest(classMirror, symbol); | |
| 146 } else { | |
| 96 return _runTest(classMirror, symbol); | 147 return _runTest(classMirror, symbol); |
| 97 }); | 148 } |
| 98 } | 149 }); |
| 99 // fail_test_ | 150 return; |
| 100 if (memberName.startsWith('fail_')) { | 151 } |
| 101 test(memberName, () { | 152 // solo_test_ |
| 102 return _runFailingTest(classMirror, symbol); | 153 if (memberName.startsWith('solo_test_')) { |
| 103 }); | 154 group.addTest(true, memberName, () { |
| 104 } | 155 return _runTest(classMirror, symbol); |
| 105 // solo_fail_test_ | 156 }); |
| 106 if (memberName.startsWith('solo_fail_')) { | 157 } |
| 107 solo_test(memberName, () { | 158 // fail_test_ |
| 108 return _runFailingTest(classMirror, symbol); | 159 if (memberName.startsWith('fail_')) { |
| 109 }); | 160 group.addTest(isSolo, memberName, () { |
| 110 } | 161 return _runFailingTest(classMirror, symbol); |
| 111 }); | 162 }); |
| 112 } | 163 } |
| 113 String className = MirrorSystem.getName(classMirror.simpleName); | 164 // solo_fail_test_ |
| 114 if (_hasAnnotationInstance(classMirror, soloTest)) { | 165 if (memberName.startsWith('solo_fail_')) { |
| 115 solo_group(className, runMembers); | 166 group.addTest(true, memberName, () { |
| 167 return _runFailingTest(classMirror, symbol); | |
| 168 }); | |
| 169 } | |
| 170 }); | |
| 171 } | |
|
Paul Berry
2016/10/04 19:19:12
AFAICT, if defineReflectiveTests() is called witho
scheglov
2016/10/04 19:30:11
Good idea.
Done.
Thanks.
| |
| 172 | |
| 173 /** | |
| 174 * Return the combination of the [base] and [addition] names. | |
| 175 * If any other two is `null`, then the other one is returned. | |
| 176 */ | |
| 177 String _combineNames(String base, String addition) { | |
| 178 if (base == null) { | |
| 179 return addition; | |
| 180 } else if (addition == null) { | |
| 181 return base; | |
| 116 } else { | 182 } else { |
| 117 group(className, runMembers); | 183 return '$base | $addition'; |
| 118 } | 184 } |
| 119 } | 185 } |
| 120 | 186 |
| 121 bool _hasAnnotationInstance(DeclarationMirror declaration, instance) => | 187 bool _hasAnnotationInstance(DeclarationMirror declaration, instance) => |
| 122 declaration.metadata.any((InstanceMirror annotation) => | 188 declaration.metadata.any((InstanceMirror annotation) => |
| 123 identical(annotation.reflectee, instance)); | 189 identical(annotation.reflectee, instance)); |
| 124 | 190 |
| 125 bool _hasAssertFailingTestAnnotation(MethodMirror method) => | 191 bool _hasAssertFailingTestAnnotation(MethodMirror method) => |
| 126 _hasAnnotationInstance(method, assertFailingTest); | 192 _hasAnnotationInstance(method, assertFailingTest); |
| 127 | 193 |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 146 * | 212 * |
| 147 * This properly handles the following cases: | 213 * This properly handles the following cases: |
| 148 * - The test fails by throwing an exception | 214 * - The test fails by throwing an exception |
| 149 * - The test returns a future which completes with an error. | 215 * - The test returns a future which completes with an error. |
| 150 * | 216 * |
| 151 * However, it does not handle the case where the test creates an asynchronous | 217 * However, it does not handle the case where the test creates an asynchronous |
| 152 * callback using expectAsync(), and that callback generates a failure. | 218 * callback using expectAsync(), and that callback generates a failure. |
| 153 */ | 219 */ |
| 154 Future _runFailingTest(ClassMirror classMirror, Symbol symbol) { | 220 Future _runFailingTest(ClassMirror classMirror, Symbol symbol) { |
| 155 return new Future(() => _runTest(classMirror, symbol)).then((_) { | 221 return new Future(() => _runTest(classMirror, symbol)).then((_) { |
| 156 fail('Test passed - expected to fail.'); | 222 test_package.fail('Test passed - expected to fail.'); |
| 157 }, onError: (_) {}); | 223 }, onError: (_) {}); |
| 158 } | 224 } |
| 159 | 225 |
| 160 _runTest(ClassMirror classMirror, Symbol symbol) { | 226 _runTest(ClassMirror classMirror, Symbol symbol) { |
| 161 InstanceMirror instanceMirror = classMirror.newInstance(new Symbol(''), []); | 227 InstanceMirror instanceMirror = classMirror.newInstance(new Symbol(''), []); |
| 162 return _invokeSymbolIfExists(instanceMirror, #setUp) | 228 return _invokeSymbolIfExists(instanceMirror, #setUp) |
| 163 .then((_) => instanceMirror.invoke(symbol, []).reflectee) | 229 .then((_) => instanceMirror.invoke(symbol, []).reflectee) |
| 164 .whenComplete(() => _invokeSymbolIfExists(instanceMirror, #tearDown)); | 230 .whenComplete(() => _invokeSymbolIfExists(instanceMirror, #tearDown)); |
| 165 } | 231 } |
| 166 | 232 |
| 233 typedef void _TestFunction(); | |
|
Paul Berry
2016/10/04 19:19:12
Return type needs to be `dynamic`, since some test
scheglov
2016/10/04 19:30:11
Done.
| |
| 234 | |
| 167 /** | 235 /** |
| 168 * A marker annotation used to instruct dart2js to keep reflection information | 236 * A marker annotation used to instruct dart2js to keep reflection information |
| 169 * for the annotated classes. | 237 * for the annotated classes. |
| 170 */ | 238 */ |
| 171 class ReflectiveTest { | 239 class ReflectiveTest { |
| 172 const ReflectiveTest(); | 240 const ReflectiveTest(); |
| 173 } | 241 } |
| 174 | 242 |
| 175 /** | 243 /** |
| 176 * A marker annotation used to annotate overridden test methods (so we cannot | 244 * A marker annotation used to annotate overridden test methods (so we cannot |
| 177 * rename them to `fail_`) which are expected to fail at `assert` in the | 245 * rename them to `fail_`) which are expected to fail at `assert` in the |
| 178 * checked mode. | 246 * checked mode. |
| 179 */ | 247 */ |
| 180 class _AssertFailingTest { | 248 class _AssertFailingTest { |
| 181 const _AssertFailingTest(); | 249 const _AssertFailingTest(); |
| 182 } | 250 } |
| 183 | 251 |
| 184 /** | 252 /** |
| 185 * A marker annotation used to annotate overridden test methods (so we cannot | 253 * A marker annotation used to annotate overridden test methods (so we cannot |
| 186 * rename them to `fail_`) which are expected to fail. | 254 * rename them to `fail_`) which are expected to fail. |
| 187 */ | 255 */ |
| 188 class _FailingTest { | 256 class _FailingTest { |
| 189 const _FailingTest(); | 257 const _FailingTest(); |
| 190 } | 258 } |
| 191 | 259 |
| 192 /** | 260 /** |
| 193 * A marker annotation used to annotate a test class to run it using | 261 * Information about a type based test group. |
| 194 * [solo_group]. | 262 */ |
| 263 class _Group { | |
| 264 final bool isSolo; | |
| 265 final String name; | |
| 266 final List<_Test> tests = <_Test>[]; | |
| 267 | |
| 268 _Group(this.isSolo, this.name); | |
| 269 | |
| 270 bool get hasSoloTest => tests.any((test) => test.isSolo); | |
| 271 | |
| 272 void addTest(bool isSolo, String name, _TestFunction function) { | |
| 273 String fullName = _combineNames(this.name, name); | |
| 274 tests.add(new _Test(isSolo, fullName, function)); | |
| 275 } | |
| 276 } | |
| 277 | |
| 278 /** | |
| 279 * A marker annotation used to annotate "solo" groups and tests. | |
| 195 */ | 280 */ |
| 196 class _SoloTest { | 281 class _SoloTest { |
| 197 const _SoloTest(); | 282 const _SoloTest(); |
| 198 } | 283 } |
| 284 | |
| 285 /** | |
| 286 * Information about a test. | |
| 287 */ | |
| 288 class _Test { | |
| 289 final bool isSolo; | |
| 290 final String name; | |
| 291 final _TestFunction function; | |
| 292 | |
| 293 _Test(this.isSolo, this.name, this.function); | |
| 294 } | |
| OLD | NEW |