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

Side by Side Diff: pkg/scheduled_test/test/metatest.dart

Issue 12209073: Add a scheduled test library. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Make everything play nicely with the outside world. Created 7 years, 10 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
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
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.
4
5 /// A test library for testing test libraries? We must go deeper.
6 ///
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
9 /// isolate, then reporting the results back to the parent isolate.
10 library metatest;
11
12 import 'dart:async';
13 import 'dart:isolate';
14
15 import '../../../pkg/path/lib/path.dart' as path;
16 import 'package:scheduled_test/src/utils.dart';
17 import 'package:unittest/unittest.dart';
18
19 /// Declares a test with the given [description] and [body]. [body] corresponds
20 /// to the `main` method of a test file, and will be run in an isolate. By
21 /// default, this expects that all tests defined in [body] pass, but if
22 /// [passing] is passed, only tests listed there are expected to pass.
23 void expectTestsPass(String description, void body(), {List<String> passing}) {
24 _setUpTest(description, body, (results) {
25 if (_hasError(results)) {
26 throw 'Expected all tests to pass, but got error(s):\n'
27 '${_summarizeTests(results)}';
28 } else if (passing == null) {
29 if (results['failed'] != 0) {
30 throw 'Expected all tests to pass, but some failed:\n'
31 '${_summarizeTests(results)}';
32 }
33 } else {
34 var shouldPass = new Set.from(passing);
35 var didPass = new Set.from(results['results']
36 .where((t) => t['result'] == 'pass')
37 .map((t) => t['description']));
38
39 if (!shouldPass.containsAll(didPass) ||
40 !didPass.containsAll(shouldPass)) {
41 String stringify(Set<String> tests) =>
42 '{${tests.map((t) => '"$t"').join(', ')}}';
43
44 fail('Expected exactly ${stringify(shouldPass)} to pass, but '
45 '${stringify(didPass)} passed.\n'
46 '${_summarizeTests(results)}');
47 }
48 }
49 });
50 }
51
52 /// Declares a test with the given [description] and [body]. [body] corresponds
53 /// to the `main` method of a test file, and will be run in an isolate. Expects
54 /// all tests defined by [body] to fail.
55 void expectTestsFail(String description, void body()) {
56 _setUpTest(description, body, (results) {
57 if (_hasError(results)) {
58 throw 'Expected all tests to fail, but got error(s):\n'
59 '${_summarizeTests(results)}';
60 } else if (results['passed'] != 0) {
61 throw 'Expected all tests to fail, but some passed:\n'
62 '${_summarizeTests(results)}';
63 }
64 });
65 }
66
67 /// Sets up a test with the given [description] and [body]. After the test runs,
68 /// calls [validate] with the result map.
69 void _setUpTest(String description, void body(), void validate(Map)) {
70 _inChildIsolate.then((inIsolate) {
71 if (inIsolate) {
72 _ensureInitialized();
73 if (_testToRun == description) body();
74 } else {
75 test(description, () {
76 expect(_runInIsolate(description).then(validate), completes);
77 });
78 }
79 });
80 }
81
82 /// The description of the test to run in the child isolate. `null` in the
83 /// parent isolate. Not set until [_inChildIsolate] completes.
84 String _testToRun;
85
86 /// The port with which the child isolate should communicate with the parent
87 /// isolate. `null` in the parent isolate. Not set until [_inChildIsolate]
88 /// completes.
89 SendPort _replyTo;
90
91 /// The cached [Future] for [_inChildIsolate].
92 Future<bool> _inChildIsolateFuture;
93
94 /// Returns whether or not we're running in a child isolate that's supposed to
95 /// run a test.
96 Future<bool> get _inChildIsolate {
97 if (_inChildIsolateFuture != null) return _inChildIsolateFuture;
98
99 var completer = new Completer();
100 port.receive((message, replyTo) {
101 _testToRun = message;
102 _replyTo = replyTo;
103 port.close();
104 completer.complete(true);
105 });
106
107 // TODO(nweiz): don't use a timeout here once issue 8416 is fixed.
108 _inChildIsolateFuture = timeout(completer.future, 500, () {
109 port.close();
110 return false;
111 });
112 return _inChildIsolateFuture;
113 }
114
115 /// Runs the test described by [description] in its own isolate. Returns a map
116 /// describing the results of that test run.
117 Future<Map> _runInIsolate(String description) {
118 var future = spawnUri(path.join(path.current, new Options().script))
119 .call(description);
120 // TODO(nweiz): Remove this timeout once issue 8417 is fixed and we can
121 // capture top-level exceptions.
122 return timeout(future, 30 * 1000, () {
123 throw 'Timed out waiting for test to complete.';
124 });
125 }
126
127 /// Returns whether [results] (a test result map) describes a test run in which
128 /// an error occurred.
129 bool _hasError(Map results) {
130 return results['errors'] > 0 || results['uncaughtError'] != null ||
131 (results['passed'] == 0 && results['failed'] == 0);
132 }
133
134 /// Returns a string description of the test run descibed by [results].
135 String _summarizeTests(Map results) {
136 var buffer = new StringBuffer();
137 for (var t in results["results"]) {
138 buffer.add("${t['result'].toUpperCase()}: ${t['description']}\n");
139 if (t['message'] != '') buffer.add("${_indent(t['message'])}\n");
140 if (t['stackTrace'] != null && t['stackTrace'] != '') {
141 buffer.add("${_indent(t['stackTrace'])}\n");
142 }
143 }
144
145 buffer.add("\n");
146
147 var success = false;
148 if (results['passed'] == 0 && results['failed'] == 0 &&
149 results['errors'] == 0 && results['uncaughtError'] == null) {
150 buffer.add('No tests found.');
151 // This is considered a failure too.
152 } else if (results['failed'] == 0 && results['errors'] == 0 &&
153 results['uncaughtError'] == null) {
154 buffer.add('All ${results['passed']} tests passed.');
155 success = true;
156 } else {
157 if (results['uncaughtError'] != null) {
158 buffer.add('Top-level uncaught error: ${results['uncaughtError']}');
159 }
160 buffer.add('${results['passed']} PASSED, ${results['failed']} FAILED, '
161 '${results['errors']} ERRORS');
162 }
163 return prefixLines(buffer.toString());
164 }
165
166 /// Indents each line of [str] by two spaces.
167 String _indent(String str) {
168 // TODO(nweiz): Use this simpler code once issue 2980 is fixed.
169 // return str.replaceAll(new RegExp("^", multiLine: true), " ");
170
171 return Strings.join(str.split("\n").map((line) => " $line"), "\n");
172 }
173
174 /// Ensure that the metatest configuration is loaded.
175 void _ensureInitialized() {
176 if (config is! _MetaConfiguration) configure(new _MetaConfiguration());
177 }
178
179 /// Special test configuration for use within the child isolates. This hides all
180 /// output and reports data back to the parent isolate.
181 class _MetaConfiguration extends Configuration {
182 final name = "MetaConfiguration";
183
184 void logTestCaseMesssage(TestCase testCase, String message) {}
185
186 void onSummary(int passed, int failed, int errors, List<TestCase> results,
187 String uncaughtError) {
188 _replyTo.send({
189 "passed": passed,
190 "failed": failed,
191 "errors": errors,
192 "uncaughtError": uncaughtError,
193 "results": results.map((testCase) => {
194 "description": testCase.description,
195 "message": testCase.message,
196 "result": testCase.result,
197 "stackTrace": testCase.stackTrace
198 }).toList()
199 });
200 }
201
202 void onInit() {}
203 void onDone(bool success) {}
204 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698