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 _addTestsIfTopLevelSuite(); |
| 70 } |
| 71 |
| 72 /** |
50 * Runs test methods existing in the given [type]. | 73 * Runs test methods existing in the given [type]. |
51 * | 74 * |
52 * Methods with names starting with `test` are run using [test] function. | 75 * If there is a "solo" test method in the top-level suite, only "solo" methods |
53 * Methods with names starting with `solo_test` are run using [solo_test] functi
on. | 76 * are run. |
| 77 * |
| 78 * If there is a "solo" test type, only its test methods are run. |
| 79 * |
| 80 * Otherwise all tests methods of all test types are run. |
54 * | 81 * |
55 * Each method is run with a new instance of [type]. | 82 * Each method is run with a new instance of [type]. |
56 * So, [type] should have a default constructor. | 83 * So, [type] should have a default constructor. |
57 * | 84 * |
58 * If [type] declares method `setUp`, it methods will be invoked before any test | 85 * If [type] declares method `setUp`, it methods will be invoked before any test |
59 * method invocation. | 86 * method invocation. |
60 * | 87 * |
61 * If [type] declares method `tearDown`, it will be invoked after any test | 88 * If [type] declares method `tearDown`, it will be invoked after any test |
62 * method invocation. If method returns [Future] to test some asynchronous | 89 * method invocation. If method returns [Future] to test some asynchronous |
63 * behavior, then `tearDown` will be invoked in `Future.complete`. | 90 * behavior, then `tearDown` will be invoked in `Future.complete`. |
64 */ | 91 */ |
65 void defineReflectiveTests(Type type) { | 92 void defineReflectiveTests(Type type) { |
66 ClassMirror classMirror = reflectClass(type); | 93 ClassMirror classMirror = reflectClass(type); |
67 if (!classMirror.metadata.any((InstanceMirror annotation) => | 94 if (!classMirror.metadata.any((InstanceMirror annotation) => |
68 annotation.type.reflectedType == ReflectiveTest)) { | 95 annotation.type.reflectedType == ReflectiveTest)) { |
69 String name = MirrorSystem.getName(classMirror.qualifiedName); | 96 String name = MirrorSystem.getName(classMirror.qualifiedName); |
70 throw new Exception('Class $name must have annotation "@reflectiveTest" ' | 97 throw new Exception('Class $name must have annotation "@reflectiveTest" ' |
71 'in order to be run by runReflectiveTests.'); | 98 'in order to be run by runReflectiveTests.'); |
72 } | 99 } |
73 void runMembers() { | 100 |
74 classMirror.instanceMembers | 101 _Group group; |
75 .forEach((Symbol symbol, MethodMirror memberMirror) { | 102 { |
76 // we need only methods | 103 bool isSolo = _hasAnnotationInstance(classMirror, soloTest); |
77 if (memberMirror is! MethodMirror || !memberMirror.isRegularMethod) { | 104 String className = MirrorSystem.getName(classMirror.simpleName); |
78 return; | 105 group = new _Group(isSolo, _combineNames(_currentSuiteName, className)); |
| 106 _currentGroups.add(group); |
| 107 } |
| 108 |
| 109 classMirror.instanceMembers |
| 110 .forEach((Symbol symbol, MethodMirror memberMirror) { |
| 111 // we need only methods |
| 112 if (memberMirror is! MethodMirror || !memberMirror.isRegularMethod) { |
| 113 return; |
| 114 } |
| 115 // prepare information about the method |
| 116 String memberName = MirrorSystem.getName(symbol); |
| 117 bool isSolo = memberName.startsWith('solo_') || |
| 118 _hasAnnotationInstance(memberMirror, soloTest); |
| 119 // test_ |
| 120 if (memberName.startsWith('test_')) { |
| 121 group.addTest(isSolo, memberName, () { |
| 122 if (_hasFailingTestAnnotation(memberMirror) || |
| 123 _isCheckedMode && _hasAssertFailingTestAnnotation(memberMirror)) { |
| 124 return _runFailingTest(classMirror, symbol); |
| 125 } else { |
| 126 return _runTest(classMirror, symbol); |
| 127 } |
| 128 }); |
| 129 return; |
| 130 } |
| 131 // solo_test_ |
| 132 if (memberName.startsWith('solo_test_')) { |
| 133 group.addTest(true, memberName, () { |
| 134 return _runTest(classMirror, symbol); |
| 135 }); |
| 136 } |
| 137 // fail_test_ |
| 138 if (memberName.startsWith('fail_')) { |
| 139 group.addTest(isSolo, memberName, () { |
| 140 return _runFailingTest(classMirror, symbol); |
| 141 }); |
| 142 } |
| 143 // solo_fail_test_ |
| 144 if (memberName.startsWith('solo_fail_')) { |
| 145 group.addTest(true, memberName, () { |
| 146 return _runFailingTest(classMirror, symbol); |
| 147 }); |
| 148 } |
| 149 }); |
| 150 |
| 151 // Support for the case of missing enclosing [defineReflectiveSuite]. |
| 152 _addTestsIfTopLevelSuite(); |
| 153 } |
| 154 |
| 155 /** |
| 156 * If the current suite is the top-level one, add tests to the `test` package. |
| 157 */ |
| 158 void _addTestsIfTopLevelSuite() { |
| 159 if (_currentSuiteLevel == 0) { |
| 160 void runTests({bool allGroups, bool allTests}) { |
| 161 for (_Group group in _currentGroups) { |
| 162 if (allGroups || group.isSolo) { |
| 163 for (_Test test in group.tests) { |
| 164 if (allTests || test.isSolo) { |
| 165 test_package.test(test.name, test.function); |
| 166 } |
| 167 } |
| 168 } |
79 } | 169 } |
80 String memberName = MirrorSystem.getName(symbol); | 170 } |
81 // test_ | 171 |
82 if (memberName.startsWith('test_')) { | 172 if (_currentGroups.any((g) => g.hasSoloTest)) { |
83 test(memberName, () { | 173 runTests(allGroups: true, allTests: false); |
84 if (_hasFailingTestAnnotation(memberMirror) || | 174 } else if (_currentGroups.any((g) => g.isSolo)) { |
85 _isCheckedMode && _hasAssertFailingTestAnnotation(memberMirror)) { | 175 runTests(allGroups: false, allTests: true); |
86 return _runFailingTest(classMirror, symbol); | 176 } else { |
87 } else { | 177 runTests(allGroups: true, allTests: true); |
88 return _runTest(classMirror, symbol); | 178 } |
89 } | 179 _currentGroups.clear(); |
90 }); | |
91 return; | |
92 } | |
93 // solo_test_ | |
94 if (memberName.startsWith('solo_test_')) { | |
95 solo_test(memberName, () { | |
96 return _runTest(classMirror, symbol); | |
97 }); | |
98 } | |
99 // fail_test_ | |
100 if (memberName.startsWith('fail_')) { | |
101 test(memberName, () { | |
102 return _runFailingTest(classMirror, symbol); | |
103 }); | |
104 } | |
105 // solo_fail_test_ | |
106 if (memberName.startsWith('solo_fail_')) { | |
107 solo_test(memberName, () { | |
108 return _runFailingTest(classMirror, symbol); | |
109 }); | |
110 } | |
111 }); | |
112 } | |
113 String className = MirrorSystem.getName(classMirror.simpleName); | |
114 if (_hasAnnotationInstance(classMirror, soloTest)) { | |
115 solo_group(className, runMembers); | |
116 } else { | |
117 group(className, runMembers); | |
118 } | 180 } |
119 } | 181 } |
120 | 182 |
| 183 /** |
| 184 * Return the combination of the [base] and [addition] names. |
| 185 * If any other two is `null`, then the other one is returned. |
| 186 */ |
| 187 String _combineNames(String base, String addition) { |
| 188 if (base == null) { |
| 189 return addition; |
| 190 } else if (addition == null) { |
| 191 return base; |
| 192 } else { |
| 193 return '$base | $addition'; |
| 194 } |
| 195 } |
| 196 |
121 bool _hasAnnotationInstance(DeclarationMirror declaration, instance) => | 197 bool _hasAnnotationInstance(DeclarationMirror declaration, instance) => |
122 declaration.metadata.any((InstanceMirror annotation) => | 198 declaration.metadata.any((InstanceMirror annotation) => |
123 identical(annotation.reflectee, instance)); | 199 identical(annotation.reflectee, instance)); |
124 | 200 |
125 bool _hasAssertFailingTestAnnotation(MethodMirror method) => | 201 bool _hasAssertFailingTestAnnotation(MethodMirror method) => |
126 _hasAnnotationInstance(method, assertFailingTest); | 202 _hasAnnotationInstance(method, assertFailingTest); |
127 | 203 |
128 bool _hasFailingTestAnnotation(MethodMirror method) => | 204 bool _hasFailingTestAnnotation(MethodMirror method) => |
129 _hasAnnotationInstance(method, failingTest); | 205 _hasAnnotationInstance(method, failingTest); |
130 | 206 |
(...skipping 15 matching lines...) Expand all Loading... |
146 * | 222 * |
147 * This properly handles the following cases: | 223 * This properly handles the following cases: |
148 * - The test fails by throwing an exception | 224 * - The test fails by throwing an exception |
149 * - The test returns a future which completes with an error. | 225 * - The test returns a future which completes with an error. |
150 * | 226 * |
151 * However, it does not handle the case where the test creates an asynchronous | 227 * However, it does not handle the case where the test creates an asynchronous |
152 * callback using expectAsync(), and that callback generates a failure. | 228 * callback using expectAsync(), and that callback generates a failure. |
153 */ | 229 */ |
154 Future _runFailingTest(ClassMirror classMirror, Symbol symbol) { | 230 Future _runFailingTest(ClassMirror classMirror, Symbol symbol) { |
155 return new Future(() => _runTest(classMirror, symbol)).then((_) { | 231 return new Future(() => _runTest(classMirror, symbol)).then((_) { |
156 fail('Test passed - expected to fail.'); | 232 test_package.fail('Test passed - expected to fail.'); |
157 }, onError: (_) {}); | 233 }, onError: (_) {}); |
158 } | 234 } |
159 | 235 |
160 _runTest(ClassMirror classMirror, Symbol symbol) { | 236 _runTest(ClassMirror classMirror, Symbol symbol) { |
161 InstanceMirror instanceMirror = classMirror.newInstance(new Symbol(''), []); | 237 InstanceMirror instanceMirror = classMirror.newInstance(new Symbol(''), []); |
162 return _invokeSymbolIfExists(instanceMirror, #setUp) | 238 return _invokeSymbolIfExists(instanceMirror, #setUp) |
163 .then((_) => instanceMirror.invoke(symbol, []).reflectee) | 239 .then((_) => instanceMirror.invoke(symbol, []).reflectee) |
164 .whenComplete(() => _invokeSymbolIfExists(instanceMirror, #tearDown)); | 240 .whenComplete(() => _invokeSymbolIfExists(instanceMirror, #tearDown)); |
165 } | 241 } |
166 | 242 |
| 243 typedef _TestFunction(); |
| 244 |
167 /** | 245 /** |
168 * A marker annotation used to instruct dart2js to keep reflection information | 246 * A marker annotation used to instruct dart2js to keep reflection information |
169 * for the annotated classes. | 247 * for the annotated classes. |
170 */ | 248 */ |
171 class ReflectiveTest { | 249 class ReflectiveTest { |
172 const ReflectiveTest(); | 250 const ReflectiveTest(); |
173 } | 251 } |
174 | 252 |
175 /** | 253 /** |
176 * A marker annotation used to annotate overridden test methods (so we cannot | 254 * 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 | 255 * rename them to `fail_`) which are expected to fail at `assert` in the |
178 * checked mode. | 256 * checked mode. |
179 */ | 257 */ |
180 class _AssertFailingTest { | 258 class _AssertFailingTest { |
181 const _AssertFailingTest(); | 259 const _AssertFailingTest(); |
182 } | 260 } |
183 | 261 |
184 /** | 262 /** |
185 * A marker annotation used to annotate overridden test methods (so we cannot | 263 * A marker annotation used to annotate overridden test methods (so we cannot |
186 * rename them to `fail_`) which are expected to fail. | 264 * rename them to `fail_`) which are expected to fail. |
187 */ | 265 */ |
188 class _FailingTest { | 266 class _FailingTest { |
189 const _FailingTest(); | 267 const _FailingTest(); |
190 } | 268 } |
191 | 269 |
192 /** | 270 /** |
193 * A marker annotation used to annotate a test class to run it using | 271 * Information about a type based test group. |
194 * [solo_group]. | 272 */ |
| 273 class _Group { |
| 274 final bool isSolo; |
| 275 final String name; |
| 276 final List<_Test> tests = <_Test>[]; |
| 277 |
| 278 _Group(this.isSolo, this.name); |
| 279 |
| 280 bool get hasSoloTest => tests.any((test) => test.isSolo); |
| 281 |
| 282 void addTest(bool isSolo, String name, _TestFunction function) { |
| 283 String fullName = _combineNames(this.name, name); |
| 284 tests.add(new _Test(isSolo, fullName, function)); |
| 285 } |
| 286 } |
| 287 |
| 288 /** |
| 289 * A marker annotation used to annotate "solo" groups and tests. |
195 */ | 290 */ |
196 class _SoloTest { | 291 class _SoloTest { |
197 const _SoloTest(); | 292 const _SoloTest(); |
198 } | 293 } |
| 294 |
| 295 /** |
| 296 * Information about a test. |
| 297 */ |
| 298 class _Test { |
| 299 final bool isSolo; |
| 300 final String name; |
| 301 final _TestFunction function; |
| 302 |
| 303 _Test(this.isSolo, this.name, this.function); |
| 304 } |
OLD | NEW |