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 |