| 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.backend.declarer; | 5 library test.backend.declarer; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import '../frontend/timeout.dart'; | 9 import '../frontend/timeout.dart'; |
| 10 import '../utils.dart'; | 10 import '../utils.dart'; |
| 11 import 'group.dart'; | 11 import 'group.dart'; |
| 12 import 'group_entry.dart'; |
| 12 import 'invoker.dart'; | 13 import 'invoker.dart'; |
| 13 import 'metadata.dart'; | 14 import 'metadata.dart'; |
| 14 import 'group_entry.dart'; | 15 import 'test.dart'; |
| 15 | 16 |
| 16 /// A class that manages the state of tests as they're declared. | 17 /// A class that manages the state of tests as they're declared. |
| 17 /// | 18 /// |
| 18 /// A nested tree of Declarers tracks the current group, set-up, and tear-down | 19 /// A nested tree of Declarers tracks the current group, set-up, and tear-down |
| 19 /// functions. Each Declarer in the tree corresponds to a group. This tree is | 20 /// functions. Each Declarer in the tree corresponds to a group. This tree is |
| 20 /// tracked by a zone-scoped "current" Declarer; the current declarer can be set | 21 /// tracked by a zone-scoped "current" Declarer; the current declarer can be set |
| 21 /// for a block using [Declarer.declare], and it can be accessed using | 22 /// for a block using [Declarer.declare], and it can be accessed using |
| 22 /// [Declarer.current]. | 23 /// [Declarer.current]. |
| 23 class Declarer { | 24 class Declarer { |
| 24 /// The parent declarer, or `null` if this corresponds to the root group. | 25 /// The parent declarer, or `null` if this corresponds to the root group. |
| 25 final Declarer _parent; | 26 final Declarer _parent; |
| 26 | 27 |
| 27 /// The name of the current test group, including the name of any parent | 28 /// The name of the current test group, including the name of any parent |
| 28 /// groups. | 29 /// groups. |
| 29 /// | 30 /// |
| 30 /// This is `null` if this is the root group. | 31 /// This is `null` if this is the root group. |
| 31 final String _name; | 32 final String _name; |
| 32 | 33 |
| 33 /// The metadata for this group, including the metadata of any parent groups | 34 /// The metadata for this group, including the metadata of any parent groups |
| 34 /// and of the test suite. | 35 /// and of the test suite. |
| 35 final Metadata _metadata; | 36 final Metadata _metadata; |
| 36 | 37 |
| 37 /// The set-up functions for this group. | 38 /// The set-up functions to run for each test in this group. |
| 38 final _setUps = new List<AsyncFunction>(); | 39 final _setUps = new List<AsyncFunction>(); |
| 39 | 40 |
| 40 /// The tear-down functions for this group. | 41 /// The tear-down functions to run for each test in this group. |
| 41 final _tearDowns = new List<AsyncFunction>(); | 42 final _tearDowns = new List<AsyncFunction>(); |
| 42 | 43 |
| 44 /// The set-up functions to run once for this group. |
| 45 final _setUpAlls = new List<AsyncFunction>(); |
| 46 |
| 47 /// The tear-down functions to run once for this group. |
| 48 final _tearDownAlls = new List<AsyncFunction>(); |
| 49 |
| 43 /// The children of this group, either tests or sub-groups. | 50 /// The children of this group, either tests or sub-groups. |
| 44 final _entries = new List<GroupEntry>(); | 51 final _entries = new List<GroupEntry>(); |
| 45 | 52 |
| 46 /// Whether [build] has been called for this declarer. | 53 /// Whether [build] has been called for this declarer. |
| 47 bool _built = false; | 54 bool _built = false; |
| 48 | 55 |
| 49 /// The current zone-scoped declarer. | 56 /// The current zone-scoped declarer. |
| 50 static Declarer get current => Zone.current[#test.declarer]; | 57 static Declarer get current => Zone.current[#test.declarer]; |
| 51 | 58 |
| 52 /// Creates a new declarer for the root group. | 59 /// Creates a new declarer for the root group. |
| 53 /// | 60 /// |
| 54 /// This is the implicit group that exists outside of any calls to `group()`. | 61 /// This is the implicit group that exists outside of any calls to `group()`. |
| 55 /// If [metadata] is passed, it's used as the metadata for the implicit root | 62 /// If [metadata] is passed, it's used as the metadata for the implicit root |
| 56 /// group. | 63 /// group. |
| 57 Declarer([Metadata metadata]) | 64 Declarer([Metadata metadata]) |
| 58 : this._(null, null, metadata == null ? new Metadata() : metadata); | 65 : this._(null, null, metadata == null ? new Metadata() : metadata); |
| 59 | 66 |
| 60 Declarer._(this._parent, this._name, this._metadata); | 67 Declarer._(this._parent, this._name, this._metadata); |
| 61 | 68 |
| 62 /// Runs [body] with this declarer as [Declarer.current]. | 69 /// Runs [body] with this declarer as [Declarer.current]. |
| 63 /// | 70 /// |
| 64 /// Returns the return value of [body]. | 71 /// Returns the return value of [body]. |
| 65 declare(body()) => runZoned(body, zoneValues: {#test.declarer: this}); | 72 declare(body()) => runZoned(body, zoneValues: {#test.declarer: this}); |
| 66 | 73 |
| 67 /// Defines a test case with the given name and body. | 74 /// Defines a test case with the given name and body. |
| 68 void test(String name, body(), {String testOn, Timeout timeout, skip, | 75 void test(String name, body(), {String testOn, Timeout timeout, skip, |
| 69 Map<String, dynamic> onPlatform}) { | 76 Map<String, dynamic> onPlatform}) { |
| 70 if (_built) { | 77 _checkNotBuilt("test"); |
| 71 throw new StateError("Can't call test() once tests have begun running."); | |
| 72 } | |
| 73 | 78 |
| 74 var metadata = _metadata.merge(new Metadata.parse( | 79 var metadata = _metadata.merge(new Metadata.parse( |
| 75 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform)); | 80 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform)); |
| 76 | 81 |
| 77 _entries.add(new LocalTest(_prefix(name), metadata, () { | 82 _entries.add(new LocalTest(_prefix(name), metadata, () { |
| 78 // TODO(nweiz): It might be useful to throw an error here if a test starts | 83 // TODO(nweiz): It might be useful to throw an error here if a test starts |
| 79 // running while other tests from the same declarer are also running, | 84 // running while other tests from the same declarer are also running, |
| 80 // since they might share closurized state. | 85 // since they might share closurized state. |
| 81 | 86 |
| 82 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in | 87 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in |
| 83 // two stable versions. | 88 // two stable versions. |
| 84 return Invoker.current.waitForOutstandingCallbacks(() { | 89 return Invoker.current.waitForOutstandingCallbacks(() { |
| 85 return _runSetUps().then((_) => body()); | 90 return _runSetUps().then((_) => body()); |
| 86 }).then((_) => _runTearDowns()); | 91 }).then((_) => _runTearDowns()); |
| 87 })); | 92 })); |
| 88 } | 93 } |
| 89 | 94 |
| 90 /// Creates a group of tests. | 95 /// Creates a group of tests. |
| 91 void group(String name, void body(), {String testOn, Timeout timeout, skip, | 96 void group(String name, void body(), {String testOn, Timeout timeout, skip, |
| 92 Map<String, dynamic> onPlatform}) { | 97 Map<String, dynamic> onPlatform}) { |
| 93 if (_built) { | 98 _checkNotBuilt("group"); |
| 94 throw new StateError("Can't call group() once tests have begun running."); | |
| 95 } | |
| 96 | 99 |
| 97 var metadata = _metadata.merge(new Metadata.parse( | 100 var metadata = _metadata.merge(new Metadata.parse( |
| 98 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform)); | 101 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform)); |
| 99 | 102 |
| 100 // Don't load the tests for a skipped group. | 103 // Don't load the tests for a skipped group. |
| 101 if (metadata.skip) { | 104 if (metadata.skip) { |
| 102 _entries.add(new Group(name, [], metadata: metadata)); | 105 _entries.add(new Group(name, [], metadata: metadata)); |
| 103 return; | 106 return; |
| 104 } | 107 } |
| 105 | 108 |
| 106 var declarer = new Declarer._(this, _prefix(name), metadata); | 109 var declarer = new Declarer._(this, _prefix(name), metadata); |
| 107 declarer.declare(body); | 110 declarer.declare(body); |
| 108 _entries.add(declarer.build()); | 111 _entries.add(declarer.build()); |
| 109 } | 112 } |
| 110 | 113 |
| 111 /// Returns [name] prefixed with this declarer's group name. | 114 /// Returns [name] prefixed with this declarer's group name. |
| 112 String _prefix(String name) => _name == null ? name : "$_name $name"; | 115 String _prefix(String name) => _name == null ? name : "$_name $name"; |
| 113 | 116 |
| 114 /// Registers a function to be run before each test in this group. | 117 /// Registers a function to be run before each test in this group. |
| 115 void setUp(callback()) { | 118 void setUp(callback()) { |
| 116 if (_built) { | 119 _checkNotBuilt("setUp"); |
| 117 throw new StateError("Can't call setUp() once tests have begun running."); | |
| 118 } | |
| 119 | |
| 120 _setUps.add(callback); | 120 _setUps.add(callback); |
| 121 } | 121 } |
| 122 | 122 |
| 123 /// Registers a function to be run after each test in this group. | 123 /// Registers a function to be run after each test in this group. |
| 124 void tearDown(callback()) { | 124 void tearDown(callback()) { |
| 125 if (_built) { | 125 _checkNotBuilt("tearDown"); |
| 126 throw new StateError( | 126 _tearDowns.add(callback); |
| 127 "Can't call tearDown() once tests have begun running."); | 127 } |
| 128 } | |
| 129 | 128 |
| 130 _tearDowns.add(callback); | 129 /// Registers a function to be run once before all tests. |
| 130 void setUpAll(callback()) { |
| 131 _checkNotBuilt("setUpAll"); |
| 132 _setUpAlls.add(callback); |
| 133 } |
| 134 |
| 135 /// Registers a function to be run once after all tests. |
| 136 void tearDownAll(callback()) { |
| 137 _checkNotBuilt("tearDownAll"); |
| 138 _tearDownAlls.add(callback); |
| 131 } | 139 } |
| 132 | 140 |
| 133 /// Finalizes and returns the group being declared. | 141 /// Finalizes and returns the group being declared. |
| 134 Group build() { | 142 Group build() { |
| 135 if (_built) { | 143 _checkNotBuilt("build"); |
| 136 throw new StateError("Can't call Declarer.build() more than once."); | |
| 137 } | |
| 138 | 144 |
| 139 _built = true; | 145 _built = true; |
| 140 return new Group(_name, _entries.toList(), metadata: _metadata); | 146 return new Group(_name, _entries.toList(), |
| 147 metadata: _metadata, |
| 148 setUpAll: _setUpAll, |
| 149 tearDownAll: _tearDownAll); |
| 150 } |
| 151 |
| 152 /// Throws a [StateError] if [build] has been called. |
| 153 /// |
| 154 /// [name] should be the name of the method being called. |
| 155 void _checkNotBuilt(String name) { |
| 156 if (!_built) return; |
| 157 throw new StateError("Can't call $name() once tests have begun running."); |
| 141 } | 158 } |
| 142 | 159 |
| 143 /// Run the set-up functions for this and any parent groups. | 160 /// Run the set-up functions for this and any parent groups. |
| 144 /// | 161 /// |
| 145 /// If no set-up functions are declared, this returns a [Future] that | 162 /// If no set-up functions are declared, this returns a [Future] that |
| 146 /// completes immediately. | 163 /// completes immediately. |
| 147 Future _runSetUps() { | 164 Future _runSetUps() { |
| 148 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two | 165 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two |
| 149 // stable versions. | 166 // stable versions. |
| 150 if (_parent != null) { | 167 if (_parent != null) { |
| (...skipping 15 matching lines...) Expand all Loading... |
| 166 return Invoker.current.unclosable(() { | 183 return Invoker.current.unclosable(() { |
| 167 var tearDowns = []; | 184 var tearDowns = []; |
| 168 for (var declarer = this; declarer != null; declarer = declarer._parent) { | 185 for (var declarer = this; declarer != null; declarer = declarer._parent) { |
| 169 tearDowns.addAll(declarer._tearDowns.reversed); | 186 tearDowns.addAll(declarer._tearDowns.reversed); |
| 170 } | 187 } |
| 171 | 188 |
| 172 return Future.forEach(tearDowns, _errorsDontStopTest); | 189 return Future.forEach(tearDowns, _errorsDontStopTest); |
| 173 }); | 190 }); |
| 174 } | 191 } |
| 175 | 192 |
| 193 /// Returns a [Test] that runs the callbacks in [_setUpAll]. |
| 194 Test get _setUpAll { |
| 195 if (_setUpAlls.isEmpty) return null; |
| 196 |
| 197 return new LocalTest(_prefix("(setUpAll)"), _metadata, () { |
| 198 return Future.forEach(_setUpAlls, (setUp) => setUp()); |
| 199 }); |
| 200 } |
| 201 |
| 202 /// Returns a [Test] that runs the callbacks in [_tearDownAll]. |
| 203 Test get _tearDownAll { |
| 204 if (_tearDownAlls.isEmpty) return null; |
| 205 |
| 206 return new LocalTest(_prefix("(tearDownAll)"), _metadata, () { |
| 207 return Invoker.current.unclosable(() { |
| 208 return Future.forEach(_tearDownAlls.reversed, _errorsDontStopTest); |
| 209 }); |
| 210 }); |
| 211 } |
| 212 |
| 176 /// Runs [body] with special error-handling behavior. | 213 /// Runs [body] with special error-handling behavior. |
| 177 /// | 214 /// |
| 178 /// Errors emitted [body] will still cause the current test to fail, but they | 215 /// Errors emitted [body] will still cause the current test to fail, but they |
| 179 /// won't cause it to *stop*. In particular, they won't remove any outstanding | 216 /// won't cause it to *stop*. In particular, they won't remove any outstanding |
| 180 /// callbacks registered outside of [body]. | 217 /// callbacks registered outside of [body]. |
| 181 Future _errorsDontStopTest(body()) { | 218 Future _errorsDontStopTest(body()) { |
| 182 var completer = new Completer(); | 219 var completer = new Completer(); |
| 183 | 220 |
| 184 Invoker.current.addOutstandingCallback(); | 221 Invoker.current.addOutstandingCallback(); |
| 185 Invoker.current.waitForOutstandingCallbacks(() { | 222 Invoker.current.waitForOutstandingCallbacks(() { |
| 186 new Future.sync(body).whenComplete(completer.complete); | 223 new Future.sync(body).whenComplete(completer.complete); |
| 187 }).then((_) => Invoker.current.removeOutstandingCallback()); | 224 }).then((_) => Invoker.current.removeOutstandingCallback()); |
| 188 | 225 |
| 189 return completer.future; | 226 return completer.future; |
| 190 } | 227 } |
| 191 } | 228 } |
| OLD | NEW |