Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(436)

Side by Side Diff: pkg/metatest/lib/metatest.dart

Issue 524153002: Sharing metatest logic between unittest and scheduled_test (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: more cl tweaks Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/metatest/LICENSE ('k') | pkg/metatest/lib/src/utils.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, 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 /// A test library for testing test libraries? We must go deeper. 5 /// A test library for testing test libraries? We must go deeper.
6 /// 6 ///
7 /// Since unit testing code tends to use a lot of global state, it can be tough 7 /// Since unit testing code tends to use a lot of global state, it can be tough
8 /// to test. This library manages it by running each test case in a child 8 /// to test. This library manages it by running each test case in a child
9 /// isolate, then reporting the results back to the parent isolate. 9 /// isolate, then reporting the results back to the parent isolate.
10 library metatest; 10 library metatest;
11 11
12 import 'dart:async'; 12 import 'dart:async';
13 import 'dart:io';
14 import 'dart:isolate'; 13 import 'dart:isolate';
15 14
16 import 'package:path/path.dart' as path;
17 import 'package:unittest/unittest.dart'; 15 import 'package:unittest/unittest.dart';
18 import 'package:scheduled_test/scheduled_test.dart' as scheduled_test;
19 16
20 import 'utils.dart'; 17 import 'src/utils.dart';
18
19 /// Whether or not we're running in a child isolate that's supposed to run a
20 /// test.
21 bool _inChildIsolate;
22
23 /// The port with which the child isolate should communicate with the parent
24 /// isolate.
25 ///
26 /// `null` in the parent isolate.
27 SendPort _replyTo;
28
29 /// The only value of the configuration used in metatest.
30 final _metaConfiguration = new _MetaConfiguration();
31
32 /// The function holding the tests to be run.
33 Function _testBody;
34
35 /// The description of the test to run in the child isolate.
36 ///
37 /// `null` in the parent isolate.
38 String _testToRun;
39
40 /// Stores the optional timeout used to override the default unittest timeout.
41 Duration _timeoutOverride;
42
43 /// Runs [setUpFn] before every metatest.
44 ///
45 /// Note that [setUpFn] will be overwritten if the test itself calls [setUp].
46 void metaSetUp(void setUpFn()) {
47 if (_inChildIsolate) setUp(setUpFn);
48 }
49
50 /// Runs a set of tests defined in `body` and checks the result by comparing
51 /// with values in `expectedResults`.
52 ///
53 /// [expectedResults] is a list which should have a [Map] value for each test
54 /// that is run. Each [Map] key corresponds to values from a completed test
55 /// case: "description", "message", "result", and "stackTrace".
56 ///
57 /// The value of "result" can be one of: 'pass', 'fail', or 'error'
nweiz 2014/09/18 22:16:03 Add a period at the end of the sentence.
58 ///
59 /// The value for "stackTrace" is the [String] 'null' if the property is `null`
60 /// on the source test case. Otherwise, it is the output of `toString`.
nweiz 2014/09/18 22:16:03 "the output of [toString]" is very unclear. What's
61 ///
62 /// Here's an example of a `expectedResults` value for two tests, where the
63 /// where the first fails and the second passes.
64 ///
65 /// ```dart
66 /// [{
67 /// 'description': 'test',
68 /// 'message': 'Caught error!',
69 /// 'result': 'fail',
70 /// }, {
71 /// 'description': 'follow up',
72 /// 'result': 'pass',
73 /// }]
74 /// ```
75 void expectTestResults(String description, void body(),
76 List<Map> expectedResults) {
77 _setUpTest(description, body, (resultsMap) {
78 var list = resultsMap['results'];
79 expect(list, hasLength(expectedResults.length),
80 reason: 'The number of tests run does not match the number of expected'
81 ' results.');
82
83 for (var i = 0; i < list.length; i++) {
84 var expectedMap = expectedResults[i];
85 var map = list[i];
86
87 expectedMap.forEach((key, value) {
88 expect(map, containsPair(key, value), reason: 'A test did not match the'
89 ' expected value for "$key" at index $i.');
90 });
91 }
92 });
93 }
21 94
22 /// Declares a test with the given [description] and [body]. [body] corresponds 95 /// Declares a test with the given [description] and [body]. [body] corresponds
23 /// to the `main` method of a test file, and will be run in an isolate. By 96 /// to the `main` method of a test file, and will be run in an isolate. By
24 /// default, this expects that all tests defined in [body] pass, but if 97 /// default, this expects that all tests defined in [body] pass, but if
25 /// [passing] is passed, only tests listed there are expected to pass. 98 /// [passing] is passed, only tests listed there are expected to pass.
26 void expectTestsPass(String description, void body(), {List<String> passing}) { 99 void expectTestsPass(String description, void body(), {List<String> passing}) {
27 _setUpTest(description, body, (results) { 100 _setUpTest(description, body, (results) {
28 if (_hasError(results)) { 101 if (_hasError(results)) {
29 throw 'Expected all tests to pass, but got error(s):\n' 102 fail('Expected all tests to pass, but got error(s):\n'
30 '${_summarizeTests(results)}'; 103 '${_summarizeTests(results)}');
31 } else if (passing == null) { 104 } else if (passing == null) {
32 if (results['failed'] != 0) { 105 if (results['failed'] != 0) {
33 throw 'Expected all tests to pass, but some failed:\n' 106 fail('Expected all tests to pass, but some failed:\n'
34 '${_summarizeTests(results)}'; 107 '${_summarizeTests(results)}');
35 } 108 }
36 } else { 109 } else {
37 var shouldPass = new Set.from(passing); 110 var shouldPass = new Set.from(passing);
38 var didPass = new Set.from(results['results'] 111 var didPass = new Set.from(results['results']
39 .where((t) => t['result'] == 'pass') 112 .where((t) => t['result'] == 'pass')
40 .map((t) => t['description'])); 113 .map((t) => t['description']));
41 114
42 if (!shouldPass.containsAll(didPass) || 115 if (!shouldPass.containsAll(didPass) ||
43 !didPass.containsAll(shouldPass)) { 116 !didPass.containsAll(shouldPass)) {
44 String stringify(Set<String> tests) => 117 String stringify(Set<String> tests) =>
45 '{${tests.map((t) => '"$t"').join(', ')}}'; 118 '{${tests.map((t) => '"$t"').join(', ')}}';
46 119
47 fail('Expected exactly ${stringify(shouldPass)} to pass, but ' 120 fail('Expected exactly ${stringify(shouldPass)} to pass, but '
48 '${stringify(didPass)} passed.\n' 121 '${stringify(didPass)} passed.\n'
49 '${_summarizeTests(results)}'); 122 '${_summarizeTests(results)}');
50 } 123 }
51 } 124 }
52 }); 125 });
53 } 126 }
54 127
55 /// Declares a test with the given [description] and [body].
56 ///
57 /// [body] corresponds
58 /// to the `main` method of a test file, and will be run in an isolate.
59 ///
60 /// All tests must have an expected result in [expectedResults].
61 void expectTests(String description, void body(),
62 Map<String, String> expectedResults) {
63 _setUpTest(description, body, (results) {
64 expectedResults = new Map.from(expectedResults);
65
66 for (var testResult in results['results']) {
67 var description = testResult['description'];
68
69 expect(expectedResults, contains(description),
70 reason: '"$description" did not have an expected result set.\n'
71 '${_summarizeTests(results)}');
72
73 var result = testResult['result'];
74
75 expect(expectedResults, containsPair(description, result),
76 reason: 'The test "$description" not not have the expected result.\n'
77 '${_summarizeTests(results)}');
78
79 expectedResults.remove(description);
80 }
81 expect(expectedResults, isEmpty,
82 reason: 'Unexpected additional test results\n'
83 '${_summarizeTests(results)}');
84 });
85 }
86
87 /// Declares a test with the given [description] and [body]. [body] corresponds 128 /// Declares a test with the given [description] and [body]. [body] corresponds
88 /// to the `main` method of a test file, and will be run in an isolate. Expects 129 /// to the `main` method of a test file, and will be run in an isolate. Expects
89 /// all tests defined by [body] to fail. 130 /// all tests defined by [body] to fail.
90 void expectTestsFail(String description, void body()) { 131 void expectTestsFail(String description, void body()) {
91 _setUpTest(description, body, (results) { 132 _setUpTest(description, body, (results) {
92 if (_hasError(results)) { 133 if (_hasError(results)) {
93 throw 'Expected all tests to fail, but got error(s):\n' 134 throw 'Expected all tests to fail, but got error(s):\n'
94 '${_summarizeTests(results)}'; 135 '${_summarizeTests(results)}';
95 } else if (results['passed'] != 0) { 136 } else if (results['passed'] != 0) {
96 throw 'Expected all tests to fail, but some passed:\n' 137 throw 'Expected all tests to fail, but some passed:\n'
97 '${_summarizeTests(results)}'; 138 '${_summarizeTests(results)}';
98 } 139 }
99 }); 140 });
100 } 141 }
101 142
102 /// Runs [setUpFn] before every metatest. Note that [setUpFn] will be
103 /// overwritten if the test itself calls [setUp].
104 void metaSetUp(void setUpFn()) {
105 if (_inChildIsolate) scheduled_test.setUp(setUpFn);
106 }
107
108 /// Sets up a test with the given [description] and [body]. After the test runs, 143 /// Sets up a test with the given [description] and [body]. After the test runs,
109 /// calls [validate] with the result map. 144 /// calls [validate] with the result map.
110 void _setUpTest(String description, void body(), void validate(Map)) { 145 void _setUpTest(String description, void body(), void validate(Map map)) {
111 if (_inChildIsolate) { 146 if (_inChildIsolate) {
112 _ensureInitialized(); 147 _ensureInitialized();
113 if (_testToRun == description) body(); 148 if (_testToRun == description) body();
114 } else { 149 } else {
115 test(description, () { 150 test(description, () {
116 expect(_runInIsolate(description).then(validate), completes); 151 return _runInIsolate(description).then(validate);
117 }); 152 });
118 } 153 }
119 } 154 }
120 155
121 /// The description of the test to run in the child isolate.
122 ///
123 /// `null` in the parent isolate.
124 String _testToRun;
125
126 /// The port with which the child isolate should communicate with the parent
127 /// isolate.
128 ///
129 /// `null` in the parent isolate.
130 SendPort _replyTo;
131
132 /// Whether or not we're running in a child isolate that's supposed to run a
133 /// test.
134 bool _inChildIsolate;
135
136 /// Initialize metatest. 156 /// Initialize metatest.
137 /// 157 ///
138 /// [message] should be the second argument to [main]. It's used to determine 158 /// [message] should be the second argument to [main]. It's used to determine
139 /// whether this test is in the parent isolate or a child isolate. 159 /// whether this test is in the parent isolate or a child isolate.
140 void initMetatest(message) { 160 ///
161 /// [timeout], when specified, overrides the default timeout for unittest.
162 void initMetatest(message, {Duration timeout}) {
163 _timeoutOverride = timeout;
141 if (message == null) { 164 if (message == null) {
142 _inChildIsolate = false; 165 _inChildIsolate = false;
143 } else { 166 } else {
144 _testToRun = message['testToRun']; 167 _testToRun = message['testToRun'];
145 _replyTo = message['replyTo']; 168 _replyTo = message['replyTo'];
146 _inChildIsolate = true; 169 _inChildIsolate = true;
147 } 170 }
148 } 171 }
149 172
150 /// Runs the test described by [description] in its own isolate. Returns a map 173 // TODO(kevmoo) We need to capture the main method to allow running in an
151 /// describing the results of that test run. 174 // isolate. There is no mechanism to capture the current executing URI between
175 // browser and vm. Issue 1145 and/or Issue 8440
176 void initTests(void testBody(message)) {
177 _testBody = testBody;
178 _testBody(null);
179 }
180
181 /// Runs the test described by [description] in its own isolate.
182 ///
183 /// Returns a map describing the results of that test run.
152 Future<Map> _runInIsolate(String description) { 184 Future<Map> _runInIsolate(String description) {
185 if (_testBody == null) {
186 throw new StateError('initTests was not called.');
187 }
188
153 var replyPort = new ReceivePort(); 189 var replyPort = new ReceivePort();
154 // TODO(nweiz): Don't use path here once issue 8440 is fixed. 190 return Isolate.spawn(_testBody, {
155 var uri = path.toUri(path.absolute(path.fromUri(Platform.script)));
156 return Isolate.spawnUri(uri, [], {
157 'testToRun': description, 191 'testToRun': description,
158 'replyTo': replyPort.sendPort 192 'replyTo': replyPort.sendPort
159 }).then((_) { 193 }).then((_) => replyPort.first);
160 // TODO(nweiz): Remove this timeout once issue 8875 is fixed and we can
161 // capture top-level exceptions.
162 return timeout(replyPort.first, 30 * 1000, () {
163 throw 'Timed out waiting for test to complete.';
164 });
165 });
166 } 194 }
167 195
168 /// Returns whether [results] (a test result map) describes a test run in which 196 /// Returns whether [results] (a test result map) describes a test run in which
169 /// an error occurred. 197 /// an error occurred.
170 bool _hasError(Map results) { 198 bool _hasError(Map results) {
171 return results['errors'] > 0 || results['uncaughtError'] != null || 199 return results['errors'] > 0 || results['uncaughtError'] != null ||
172 (results['passed'] == 0 && results['failed'] == 0); 200 (results['passed'] == 0 && results['failed'] == 0);
173 } 201 }
174 202
175 /// Returns a string description of the test run descibed by [results]. 203 /// Returns a string description of the test run descibed by [results].
(...skipping 28 matching lines...) Expand all
204 return prefixLines(buffer.toString()); 232 return prefixLines(buffer.toString());
205 } 233 }
206 234
207 /// Indents each line of [str] by two spaces. 235 /// Indents each line of [str] by two spaces.
208 String _indent(String str) { 236 String _indent(String str) {
209 return str.replaceAll(new RegExp("^", multiLine: true), " "); 237 return str.replaceAll(new RegExp("^", multiLine: true), " ");
210 } 238 }
211 239
212 /// Ensure that the metatest configuration is loaded. 240 /// Ensure that the metatest configuration is loaded.
213 void _ensureInitialized() { 241 void _ensureInitialized() {
214 unittestConfiguration = _singleton; 242 unittestConfiguration = _metaConfiguration;
243 if (_timeoutOverride != null) {
244 unittestConfiguration.timeout = _timeoutOverride;
245 }
215 } 246 }
216 247
217 final _singleton = new _MetaConfiguration();
218
219 /// Special test configuration for use within the child isolates. This hides all 248 /// Special test configuration for use within the child isolates. This hides all
220 /// output and reports data back to the parent isolate. 249 /// output and reports data back to the parent isolate.
221 class _MetaConfiguration extends Configuration { 250 class _MetaConfiguration extends Configuration {
222 251
223 _MetaConfiguration() : super.blank(); 252 _MetaConfiguration() : super.blank();
224 253
225 void onSummary(int passed, int failed, int errors, List<TestCase> results, 254 void onSummary(int passed, int failed, int errors, List<TestCase> results,
226 String uncaughtError) { 255 String uncaughtError) {
227 _replyTo.send({ 256 _replyTo.send({
228 "passed": passed, 257 "passed": passed,
229 "failed": failed, 258 "failed": failed,
230 "errors": errors, 259 "errors": errors,
231 "uncaughtError": uncaughtError, 260 "uncaughtError": uncaughtError,
232 "results": results.map((testCase) => { 261 "results": results.map((testCase) => {
233 "description": testCase.description, 262 "description": testCase.description,
234 "message": testCase.message, 263 "message": testCase.message,
235 "result": testCase.result, 264 "result": testCase.result,
236 "stackTrace": testCase.stackTrace.toString() 265 "stackTrace": testCase.stackTrace.toString()
237 }).toList() 266 }).toList()
238 }); 267 });
239 } 268 }
240 } 269 }
OLDNEW
« no previous file with comments | « pkg/metatest/LICENSE ('k') | pkg/metatest/lib/src/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698