OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library unittest.internal_test_case; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import '../unittest.dart'; | |
10 import 'test_environment.dart'; | |
11 import 'utils.dart'; | |
12 | |
13 /// An implementation of [TestCase] that exposes internal properties for other | |
14 /// unittest use. | |
15 class InternalTestCase implements TestCase { | |
16 final int id; | |
17 final String description; | |
18 | |
19 /// The setup function to call before the test, if any. | |
20 Function _setUp; | |
21 | |
22 /// The teardown function to call after the test, if any. | |
23 Function _tearDown; | |
24 | |
25 /// The body of the test case. | |
26 TestFunction _testFunction; | |
27 | |
28 /// Remaining number of callback functions that must reach a 'done' state | |
29 /// before the test completes. | |
30 int callbackFunctionsOutstanding = 0; | |
31 | |
32 /// The error or failure message for the tests. | |
33 /// | |
34 /// Initially an empty string. | |
35 String message = ''; | |
36 | |
37 /// The result of the test case. | |
38 /// | |
39 /// If the test case has is completed, this will be one of [PASS], [FAIL], or | |
40 /// [ERROR]. Otherwise, it will be `null`. | |
41 String result; | |
42 | |
43 /// Returns whether this test case passed. | |
44 bool get passed => result == PASS; | |
45 | |
46 /// The stack trace for the error that caused this test case to fail, or | |
47 /// `null` if it succeeded. | |
48 StackTrace stackTrace; | |
49 | |
50 /// The name of the group within which this test is running. | |
51 final String currentGroup; | |
52 | |
53 /// The time the test case started running. | |
54 /// | |
55 /// `null` if the test hasn't yet begun running. | |
56 DateTime get startTime => _startTime; | |
57 DateTime _startTime; | |
58 | |
59 /// The amount of time the test case took. | |
60 /// | |
61 /// `null` if the test hasn't finished running. | |
62 Duration get runningTime => _runningTime; | |
63 Duration _runningTime; | |
64 | |
65 /// Whether this test is enabled. | |
66 /// | |
67 /// Disabled tests won't be run. | |
68 bool enabled = true; | |
69 | |
70 /// A completer that will complete when the test is finished. | |
71 /// | |
72 /// This is only non-`null` when outstanding callbacks exist. | |
73 Completer _testComplete; | |
74 | |
75 /// Whether this test case has finished running. | |
76 bool get isComplete => !enabled || result != null; | |
77 | |
78 InternalTestCase(this.id, this.description, this._testFunction) | |
79 : currentGroup = environment.currentContext.fullName, | |
80 _setUp = environment.currentContext.testSetUp, | |
81 _tearDown = environment.currentContext.testTearDown; | |
82 | |
83 /// A function that returns another function to handle errors from [Future]s. | |
84 /// | |
85 /// [stage] is a string description of the stage of testing that failed. | |
86 Function _errorHandler(String stage) => (e, stack) { | |
87 if (stack == null && e is Error) { | |
88 stack = e.stackTrace; | |
89 } | |
90 if (result == null || result == PASS) { | |
91 if (e is TestFailure) { | |
92 fail("$e", stack); | |
93 } else { | |
94 error("$stage failed: Caught $e", stack); | |
95 } | |
96 } | |
97 }; | |
98 | |
99 /// Performs any associated [_setUp] function and runs the test. | |
100 /// | |
101 /// Returns a [Future] that can be used to schedule the next test. If the test | |
102 /// runs to completion synchronously, or is disabled, null is returned, to | |
103 /// tell unittest to schedule the next test immediately. | |
104 Future run() { | |
105 if (!enabled) return new Future.value(); | |
106 | |
107 result = stackTrace = null; | |
108 message = ''; | |
109 | |
110 // Avoid calling [new Future] to avoid issue 11911. | |
111 return new Future.value().then((_) { | |
112 if (_setUp != null) return _setUp(); | |
113 }).catchError(_errorHandler('Setup')).then((_) { | |
114 // Skip the test if setup failed. | |
115 if (result != null) return new Future.value(); | |
116 config.onTestStart(this); | |
117 _startTime = new DateTime.now(); | |
118 _runningTime = null; | |
119 callbackFunctionsOutstanding++; | |
120 var testReturn = _testFunction(); | |
121 // If _testFunction() returned a future, we want to wait for it like we | |
122 // would a callback, so if a failure occurs while waiting, we can abort. | |
123 if (testReturn is Future) { | |
124 callbackFunctionsOutstanding++; | |
125 testReturn | |
126 .catchError(_errorHandler('Test')) | |
127 .whenComplete(markCallbackComplete); | |
128 } | |
129 }).catchError(_errorHandler('Test')).then((_) { | |
130 markCallbackComplete(); | |
131 if (result == null) { | |
132 // Outstanding callbacks exist; we need to return a Future. | |
133 _testComplete = new Completer(); | |
134 return _testComplete.future.whenComplete(() { | |
135 if (_tearDown != null) { | |
136 return _tearDown(); | |
137 } | |
138 }).catchError(_errorHandler('Teardown')); | |
139 } else if (_tearDown != null) { | |
140 return _tearDown(); | |
141 } | |
142 }).catchError(_errorHandler('Teardown')).whenComplete(() { | |
143 _setUp = null; | |
144 _tearDown = null; | |
145 _testFunction = null; | |
146 }); | |
147 } | |
148 | |
149 /// Marks the test as having completed with [testResult], which should be one | |
150 /// of [PASS], [FAIL], or [ERROR]. | |
151 void _complete(String testResult, | |
152 [String messageText = '', StackTrace stack]) { | |
153 if (runningTime == null) { | |
154 // The startTime can be `null` if an error happened during setup. In this | |
155 // case we simply report a running time of 0. | |
156 if (startTime != null) { | |
157 _runningTime = new DateTime.now().difference(startTime); | |
158 } else { | |
159 _runningTime = const Duration(seconds: 0); | |
160 } | |
161 } | |
162 _setResult(testResult, messageText, stack); | |
163 if (_testComplete != null) { | |
164 var t = _testComplete; | |
165 _testComplete = null; | |
166 t.complete(this); | |
167 } | |
168 } | |
169 | |
170 // Sets [this]'s fields to reflect the test result, and notifies the current | |
171 // configuration that the test has completed. | |
172 // | |
173 // Returns true if this is the first time the result has been set. | |
174 void _setResult(String testResult, String messageText, StackTrace stack) { | |
175 message = messageText; | |
176 stackTrace = getTrace(stack, formatStacks, filterStacks); | |
177 if (stackTrace == null) stackTrace = stack; | |
178 if (result == null) { | |
179 result = testResult; | |
180 config.onTestResult(this); | |
181 } else { | |
182 result = testResult; | |
183 config.onTestResultChanged(this); | |
184 } | |
185 } | |
186 | |
187 /// Marks the test as having passed. | |
188 void pass() { | |
189 _complete(PASS); | |
190 } | |
191 | |
192 void registerException(error, [StackTrace stackTrace]) { | |
193 var message = error is TestFailure ? error.message : 'Caught $error'; | |
194 if (result == null) { | |
195 fail(message, stackTrace); | |
196 } else { | |
197 this.error(message, stackTrace); | |
198 } | |
199 } | |
200 | |
201 /// Marks the test as having failed. | |
202 void fail(String messageText, [StackTrace stack]) { | |
203 if (result != null) { | |
204 var newMessage = result == PASS | |
205 ? 'Test failed after initially passing: $messageText' | |
206 : 'Test failed more than once: $messageText'; | |
207 // TODO(gram): Should we combine the stack with the old one? | |
208 _complete(ERROR, newMessage, stack); | |
209 } else { | |
210 _complete(FAIL, messageText, stack); | |
211 } | |
212 } | |
213 | |
214 /// Marks the test as having had an unexpected error. | |
215 void error(String messageText, [StackTrace stack]) { | |
216 _complete(ERROR, messageText, stack); | |
217 } | |
218 | |
219 /// Indicates that an asynchronous callback has completed, and marks the test | |
220 /// as passing if all outstanding callbacks are complete. | |
221 void markCallbackComplete() { | |
222 callbackFunctionsOutstanding--; | |
223 if (callbackFunctionsOutstanding == 0 && !isComplete) pass(); | |
224 } | |
225 | |
226 String toString() => result != null ? "$description: $result" : description; | |
227 } | |
OLD | NEW |