OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 // TODO(jmesserly): replace this with the real package:test. | |
6 // Not possible yet due to various bugs we still have. | |
7 library minitest; | |
8 | |
9 import 'dart:async'; | |
10 import 'dart:js'; | |
11 import 'package:matcher/matcher.dart'; | |
12 export 'package:matcher/matcher.dart'; | |
13 | |
14 // from matcher/throws_matcher.dart | |
15 | |
16 Function _wrapAsync = (Function f, [id]) => f; | |
17 | |
18 /// This can be used to match two kinds of objects: | |
19 /// | |
20 /// * A [Function] that throws an exception when called. The function cannot | |
21 /// take any arguments. If you want to test that a function expecting | |
22 /// arguments throws, wrap it in another zero-argument function that calls | |
23 /// the one you want to test. | |
24 /// | |
25 /// * A [Future] that completes with an exception. Note that this creates an | |
26 /// asynchronous expectation. The call to `expect()` that includes this will | |
27 /// return immediately and execution will continue. Later, when the future | |
28 /// completes, the actual expectation will run. | |
29 const Matcher throws = const Throws(); | |
30 | |
31 /// This can be used to match two kinds of objects: | |
32 /// | |
33 /// * A [Function] that throws an exception when called. The function cannot | |
34 /// take any arguments. If you want to test that a function expecting | |
35 /// arguments throws, wrap it in another zero-argument function that calls | |
36 /// the one you want to test. | |
37 /// | |
38 /// * A [Future] that completes with an exception. Note that this creates an | |
39 /// asynchronous expectation. The call to `expect()` that includes this will | |
40 /// return immediately and execution will continue. Later, when the future | |
41 /// completes, the actual expectation will run. | |
42 /// | |
43 /// In both cases, when an exception is thrown, this will test that the exceptio
n | |
44 /// object matches [matcher]. If [matcher] is not an instance of [Matcher], it | |
45 /// will implicitly be treated as `equals(matcher)`. | |
46 Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher)); | |
47 | |
48 class Throws extends Matcher { | |
49 final Matcher _matcher; | |
50 | |
51 const Throws([Matcher matcher]) : this._matcher = matcher; | |
52 | |
53 bool matches(item, Map matchState) { | |
54 if (item is! Function && item is! Future) return false; | |
55 if (item is Future) { | |
56 var done = _wrapAsync((fn) => fn()); | |
57 | |
58 // Queue up an asynchronous expectation that validates when the future | |
59 // completes. | |
60 item.then((value) { | |
61 done(() { | |
62 fail("Expected future to fail, but succeeded with '$value'."); | |
63 }); | |
64 }, onError: (error, trace) { | |
65 done(() { | |
66 if (_matcher == null) return; | |
67 var reason; | |
68 if (trace != null) { | |
69 var stackTrace = trace.toString(); | |
70 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}"; | |
71 reason = "Actual exception trace:\n$stackTrace"; | |
72 } | |
73 expect(error, _matcher, reason: reason); | |
74 }); | |
75 }); | |
76 // It hasn't failed yet. | |
77 return true; | |
78 } | |
79 | |
80 try { | |
81 item(); | |
82 return false; | |
83 } catch (e, s) { | |
84 if (_matcher == null || _matcher.matches(e, matchState)) { | |
85 return true; | |
86 } else { | |
87 addStateInfo(matchState, {'exception': e, 'stack': s}); | |
88 return false; | |
89 } | |
90 } | |
91 } | |
92 | |
93 Description describe(Description description) { | |
94 if (_matcher == null) { | |
95 return description.add("throws"); | |
96 } else { | |
97 return description.add('throws ').addDescriptionOf(_matcher); | |
98 } | |
99 } | |
100 | |
101 Description describeMismatch( | |
102 item, Description mismatchDescription, Map matchState, bool verbose) { | |
103 if (item is! Function && item is! Future) { | |
104 return mismatchDescription.add('is not a Function or Future'); | |
105 } else if (_matcher == null || matchState['exception'] == null) { | |
106 return mismatchDescription.add('did not throw'); | |
107 } else { | |
108 mismatchDescription | |
109 .add('threw ') | |
110 .addDescriptionOf(matchState['exception']); | |
111 if (verbose) { | |
112 mismatchDescription.add(' at ').add(matchState['stack'].toString()); | |
113 } | |
114 return mismatchDescription; | |
115 } | |
116 } | |
117 } | |
118 | |
119 // End of matcher/throws_matcher.dart | |
120 | |
121 void group(String name, void body()) => context.callMethod('suite', [name, body]
); | |
122 | |
123 void test(String name, body(), {String skip}) { | |
124 if (skip != null) { | |
125 print('SKIP $name: $skip'); | |
126 return; | |
127 } | |
128 JsObject result = context.callMethod('test', [name, (JsFunction done) { | |
129 _finishTest(f) { | |
130 if (f is Future) { | |
131 f.then(_finishTest); | |
132 } else { | |
133 done.apply([]); | |
134 } | |
135 } | |
136 _finishTest(body()); | |
137 }]); | |
138 result['async'] = 1; | |
139 } | |
140 | |
141 // TODO(jmesserly): everything below this was stolen from | |
142 // package:test/src/frontend/expect.dart | |
143 | |
144 /// An exception thrown when a test assertion fails. | |
145 class TestFailure { | |
146 final String message; | |
147 | |
148 TestFailure(this.message); | |
149 | |
150 String toString() => message; | |
151 } | |
152 | |
153 /// An individual unit test. | |
154 abstract class TestCase { | |
155 /// A unique numeric identifier for this test case. | |
156 int get id; | |
157 | |
158 /// A description of what the test is specifying. | |
159 String get description; | |
160 | |
161 /// The error or failure message for the tests. | |
162 /// | |
163 /// Initially an empty string. | |
164 String get message; | |
165 | |
166 /// The result of the test case. | |
167 /// | |
168 /// If the test case has is completed, this will be one of [PASS], [FAIL], or | |
169 /// [ERROR]. Otherwise, it will be `null`. | |
170 String get result; | |
171 | |
172 /// Returns whether this test case passed. | |
173 bool get passed; | |
174 | |
175 /// The stack trace for the error that caused this test case to fail, or | |
176 /// `null` if it succeeded. | |
177 StackTrace get stackTrace; | |
178 | |
179 /// The name of the group within which this test is running. | |
180 String get currentGroup; | |
181 | |
182 /// The time the test case started running. | |
183 /// | |
184 /// `null` if the test hasn't yet begun running. | |
185 DateTime get startTime; | |
186 | |
187 /// The amount of time the test case took. | |
188 /// | |
189 /// `null` if the test hasn't finished running. | |
190 Duration get runningTime; | |
191 | |
192 /// Whether this test is enabled. | |
193 /// | |
194 /// Disabled tests won't be run. | |
195 bool get enabled; | |
196 | |
197 /// Whether this test case has finished running. | |
198 bool get isComplete => !enabled || result != null; | |
199 } | |
200 | |
201 /// The type used for functions that can be used to build up error reports | |
202 /// upon failures in [expect]. | |
203 typedef String ErrorFormatter( | |
204 actual, Matcher matcher, String reason, Map matchState, bool verbose); | |
205 | |
206 /// Assert that [actual] matches [matcher]. | |
207 /// | |
208 /// This is the main assertion function. [reason] is optional and is typically | |
209 /// not supplied, as a reason is generated from [matcher]; if [reason] | |
210 /// is included it is appended to the reason generated by the matcher. | |
211 /// | |
212 /// [matcher] can be a value in which case it will be wrapped in an | |
213 /// [equals] matcher. | |
214 /// | |
215 /// If the assertion fails a [TestFailure] is thrown. | |
216 /// | |
217 /// In some cases extra diagnostic info can be produced on failure (for | |
218 /// example, stack traces on mismatched exceptions). To enable these, | |
219 /// [verbose] should be specified as `true`. | |
220 void expect(actual, matcher, | |
221 {String reason, bool verbose: false, ErrorFormatter formatter}) { | |
222 | |
223 matcher = wrapMatcher(matcher); | |
224 var matchState = {}; | |
225 try { | |
226 if (matcher.matches(actual, matchState)) return; | |
227 } catch (e, trace) { | |
228 if (reason == null) { | |
229 reason = '${(e is String) ? e : e.toString()} at $trace'; | |
230 } | |
231 } | |
232 if (formatter == null) formatter = _defaultFailFormatter; | |
233 fail(formatter(actual, matcher, reason, matchState, verbose)); | |
234 } | |
235 | |
236 /// Convenience method for throwing a new [TestFailure] with the provided | |
237 /// [message]. | |
238 void fail(String message) => throw new TestFailure(message); | |
239 | |
240 // The default error formatter. | |
241 String _defaultFailFormatter( | |
242 actual, Matcher matcher, String reason, Map matchState, bool verbose) { | |
243 var description = new StringDescription(); | |
244 description.add('Expected: ').addDescriptionOf(matcher).add('\n'); | |
245 description.add(' Actual: ').addDescriptionOf(actual).add('\n'); | |
246 | |
247 var mismatchDescription = new StringDescription(); | |
248 matcher.describeMismatch(actual, mismatchDescription, matchState, verbose); | |
249 | |
250 if (mismatchDescription.length > 0) { | |
251 description.add(' Which: ${mismatchDescription}\n'); | |
252 } | |
253 if (reason != null) description.add(reason).add('\n'); | |
254 return description.toString(); | |
255 } | |
256 | |
257 // from html_configuration | |
258 void useHtmlConfiguration([bool isLayoutTest = false]) { } | |
OLD | NEW |