Mercurial > projects > orange
comparison orange/test/UnitTester.d @ 27:fc315d786f24 experimental
Added unit testing.
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Fri, 19 Nov 2010 11:14:55 +0100 |
parents | 78e5fef4bbf2 |
children | 4fea56a5849f |
comparison
equal
deleted
inserted
replaced
26:78e5fef4bbf2 | 27:fc315d786f24 |
---|---|
4 * Version: Initial created: Oct 17, 2010 | 4 * Version: Initial created: Oct 17, 2010 |
5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) | 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) |
6 */ | 6 */ |
7 module orange.test.UnitTester; | 7 module orange.test.UnitTester; |
8 | 8 |
9 version (Tango) | |
10 { | |
11 import tango.io.device.File; | |
12 import tango.io.stream.Lines; | |
13 import tango.util.Convert; | |
14 import tango.sys.Environment; | |
15 } | |
16 | |
17 | |
18 else | |
19 import std.conv; | |
20 | |
9 import tango.core.Exception; | 21 import tango.core.Exception; |
22 import tango.io.FilePath; | |
10 import orange.core._; | 23 import orange.core._; |
11 import orange.util._; | 24 import orange.util._; |
12 | 25 |
26 Use!(void delegate (), string) describe (string message) | |
27 { | |
28 return UnitTester.instance.describe(message); | |
29 } | |
30 | |
31 Use!(void delegate (), string) it (string message) | |
32 { | |
33 return UnitTester.instance.test(message); | |
34 } | |
35 | |
36 void delegate () before () | |
37 { | |
38 return UnitTester.instance.before; | |
39 } | |
40 | |
41 void delegate () before (void delegate () before) | |
42 { | |
43 return UnitTester.instance.before = before; | |
44 } | |
45 | |
46 void delegate () after () | |
47 { | |
48 return UnitTester.instance.after; | |
49 } | |
50 | |
51 void delegate () after (void delegate () after) | |
52 { | |
53 return UnitTester.instance.after = after; | |
54 } | |
55 | |
56 void run () | |
57 { | |
58 UnitTester.instance.run; | |
59 } | |
60 | |
61 private: | |
62 | |
13 class UnitTester | 63 class UnitTester |
14 { | 64 { |
15 private | 65 private: |
16 { | 66 |
17 struct Test | 67 struct DescriptionManager |
18 { | 68 { |
19 void delegate () test; | 69 Description[] descriptions; |
70 size_t lastIndex = size_t.max; | |
71 | |
72 void opCatAssign (Description description) | |
73 { | |
74 descriptions ~= description; | |
75 lastIndex++; | |
76 } | |
77 | |
78 void opCatAssign (Test test) | |
79 { | |
80 last.tests ~= test; | |
81 } | |
82 | |
83 Description opIndex (size_t i) | |
84 { | |
85 return descriptions[i]; | |
86 } | |
87 | |
88 Description last () | |
89 { | |
90 return descriptions[$ - 1]; | |
91 } | |
92 | |
93 Description first () | |
94 { | |
95 return descriptions[0]; | |
96 } | |
97 | |
98 int opApply (int delegate(ref Description) dg) | |
99 { | |
100 int result = 0; | |
101 | |
102 foreach (desc ; descriptions) | |
103 { | |
104 result = dg(desc); | |
105 | |
106 if (result) | |
107 return result; | |
108 } | |
109 | |
110 return result; | |
111 } | |
112 | |
113 size_t length () | |
114 { | |
115 return descriptions.length; | |
116 } | |
117 } | |
118 | |
119 class Description | |
120 { | |
121 private | |
122 { | |
123 DescriptionManager descriptions; | |
124 Test[] tests; | |
125 Test[] failures; | |
126 Test[] pending; | |
127 size_t lastIndex = size_t.max; | |
20 string message; | 128 string message; |
21 AssertException exception; | 129 void delegate () description; |
22 | 130 } |
23 bool failed () | 131 |
24 { | 132 this (string message) |
25 return !succeeded; | 133 { |
26 } | 134 this.message = message; |
27 | 135 } |
28 bool succeeded () | 136 |
29 { | 137 void run () |
30 return !exception; | 138 { |
31 } | 139 if (shouldRun) |
32 | 140 description(); |
33 void run () | 141 } |
34 { | 142 |
35 if (!isPending) | 143 bool shouldRun () |
36 test(); | 144 { |
37 } | 145 return description !is null; |
38 | 146 } |
39 bool isPending () | 147 } |
40 { | 148 |
41 return test is null; | 149 struct Test |
42 } | 150 { |
43 } | 151 void delegate () test; |
44 | 152 string message; |
45 Test[] tests; | 153 AssertException exception; |
46 AssertException[] exceptions; | 154 |
47 void delegate () pre_; | 155 bool failed () |
48 void delegate () post_; | 156 { |
49 size_t failures; | 157 return !succeeded; |
50 size_t pending; | 158 } |
51 size_t lastIndex = size_t.max; | 159 |
52 } | 160 bool succeeded () |
161 { | |
162 if (exception is null) | |
163 return true; | |
164 | |
165 return false; | |
166 } | |
167 | |
168 void run () | |
169 { | |
170 if (!isPending) | |
171 test(); | |
172 } | |
173 | |
174 bool isPending () | |
175 { | |
176 return test is null; | |
177 } | |
178 } | |
179 | |
180 static UnitTester instance_; | |
181 | |
182 DescriptionManager descriptions; | |
183 Description currentDescription; | |
184 | |
185 void delegate () before_; | |
186 void delegate () after_; | |
187 | |
188 size_t numberOfFailures; | |
189 size_t numberOfPending; | |
190 size_t numberOfTests; | |
191 size_t failureId; | |
192 | |
193 string defaultIndentation = " "; | |
194 string indentation; | |
195 | |
196 static UnitTester instance () | |
197 { | |
198 if (instance_) | |
199 return instance_; | |
200 | |
201 return instance_ = new UnitTester; | |
202 } | |
203 | |
204 Use!(void delegate (), string) describe (string message) | |
205 { | |
206 addDescription(message); | |
207 Use!(void delegate (), string) use; | |
208 | |
209 use.args[0] = &internalDescribe; | |
210 use.args[1] = message; | |
211 | |
212 return use; | |
213 } | |
53 | 214 |
54 Use!(void delegate (), string) test (string message) | 215 Use!(void delegate (), string) test (string message) |
55 { | 216 { |
56 tests ~= Test(null, message); | 217 addTest(message); |
57 lastIndex++; | |
58 | |
59 Use!(void delegate (), string) use; | 218 Use!(void delegate (), string) use; |
60 | 219 |
61 use.args[0] = &internalTest; | 220 use.args[0] = &internalTest; |
62 use.args[1] = message; | 221 use.args[1] = message; |
63 | 222 |
64 return use; | 223 return use; |
65 } | 224 } |
66 | 225 |
67 void run () | 226 void run () |
68 { | 227 { |
69 foreach (test ; tests) | 228 foreach (description ; descriptions) |
70 { | 229 runDescription(description); |
71 if (test.isPending) | 230 |
72 pending++; | 231 printResult; |
73 | 232 } |
74 try | 233 |
234 void runDescription (Description description) | |
235 { | |
236 restore(currentDescription) in { | |
237 currentDescription = description; | |
238 description.run; | |
239 | |
240 foreach (desc ; description.descriptions) | |
241 runDescription(desc); | |
242 | |
243 foreach (ref test ; description.tests) | |
75 { | 244 { |
76 execute in { | 245 if (test.isPending) |
77 test.run(); | 246 addPendingTest(description, test); |
78 }; | 247 |
79 } | 248 try |
80 | 249 { |
81 catch (AssertException e) | 250 execute in { |
82 { | 251 test.run(); |
83 exceptions ~= e; | 252 }; |
84 failures++; | 253 } |
85 } | 254 |
86 } | 255 catch (AssertException e) |
87 | 256 handleFailure(description, test, e); |
88 printResult; | 257 } |
89 } | 258 }; |
90 | 259 } |
91 void delegate () pre () | 260 |
92 { | 261 void delegate () before () |
93 return pre_; | 262 { |
94 } | 263 return before_; |
95 | 264 } |
96 void delegate () pre (void delegate () pre) | 265 |
97 { | 266 void delegate () before (void delegate () before) |
98 return pre_ = pre; | 267 { |
99 } | 268 return before_ = before; |
100 | 269 } |
101 void delegate () post () | 270 |
102 { | 271 void delegate () after () |
103 return post_; | 272 { |
104 } | 273 return after_; |
105 | 274 } |
106 void delegate () post (void delegate () post) | 275 |
107 { | 276 void delegate () after (void delegate () after) |
108 return post_ = post; | 277 { |
109 } | 278 return after_ = after; |
110 | 279 } |
111 private void internalTest (void delegate () dg, string message) | 280 |
112 { | 281 void addTest (string message) |
113 tests[lastIndex] = Test(dg, message); | 282 { |
114 } | 283 numberOfTests++; |
115 | 284 currentDescription.tests ~= Test(null, message); |
116 private void printResult () | 285 } |
286 | |
287 void addDescription (string message) | |
288 { | |
289 if (currentDescription) | |
290 currentDescription.descriptions ~= new Description(message); | |
291 | |
292 else | |
293 descriptions ~= new Description(message); | |
294 } | |
295 | |
296 void addPendingTest (Description description, ref Test test) | |
297 { | |
298 numberOfPending++; | |
299 description.pending ~= test; | |
300 } | |
301 | |
302 void handleFailure (Description description, ref Test test, AssertException exception) | |
303 { | |
304 numberOfFailures++; | |
305 test.exception = exception; | |
306 description.failures ~= test; | |
307 } | |
308 | |
309 void internalDescribe (void delegate () dg, string message) | |
310 { | |
311 if (currentDescription) | |
312 currentDescription.descriptions.last.description = dg; | |
313 | |
314 else | |
315 descriptions.last.description = dg; | |
316 } | |
317 | |
318 void internalTest (void delegate () dg, string message) | |
319 { | |
320 currentDescription.tests[$ - 1] = Test(dg, message); | |
321 } | |
322 | |
323 void printResult () | |
117 { | 324 { |
118 if (isAllTestsSuccessful) | 325 if (isAllTestsSuccessful) |
119 return printSuccess(); | 326 return printSuccess(); |
120 | 327 |
121 foreach (test ; tests) | 328 foreach (description ; descriptions) |
122 { | 329 { |
123 print("- ", test.message); | 330 printDescription(description); |
124 | 331 printResultImpl(description.descriptions); |
125 if (test.isPending) | 332 } |
126 print(" ", "(PENDING: Not Yet Implemented)"); | 333 |
127 | 334 failureId = 0; |
335 | |
336 printPending; | |
337 printFailures; | |
338 | |
339 print("\n", numberOfTests, " ", "test".pluralize(numberOfTests),", ", numberOfFailures, " ", "failure".pluralize(numberOfFailures)); | |
340 printNumberOfPending; | |
341 println(); | |
342 } | |
343 | |
344 void printResultImpl (DescriptionManager descriptions) | |
345 { | |
346 restore(indentation) in { | |
347 indentation ~= defaultIndentation; | |
348 | |
349 foreach (description ; descriptions) | |
350 { | |
351 printDescription(description); | |
352 printResultImpl(description.descriptions); | |
353 } | |
354 }; | |
355 } | |
356 | |
357 void printDescription (Description description) | |
358 { | |
359 println(indentation, description.message); | |
360 | |
361 restore(indentation) in { | |
362 indentation ~= defaultIndentation; | |
363 | |
364 foreach (i, ref test ; description.tests) | |
365 { | |
366 print(indentation, "- ", test.message); | |
367 | |
368 if (test.isPending) | |
369 print(" (PENDING: Not Yet Implemented)"); | |
370 | |
371 if (test.failed) | |
372 print(" (FAILED - ", ++failureId, ')'); | |
373 | |
374 println(); | |
375 } | |
376 }; | |
377 } | |
378 | |
379 void printPending () | |
380 { | |
381 if (!hasPending) | |
382 return; | |
383 | |
384 println("\nPending:"); | |
385 | |
386 restore(indentation) in { | |
387 indentation ~= defaultIndentation; | |
388 | |
389 foreach (description ; descriptions) | |
390 { | |
391 printPendingDescription(description); | |
392 printPendingImpl(description.descriptions); | |
393 } | |
394 }; | |
395 } | |
396 | |
397 void printPendingImpl (DescriptionManager descriptions) | |
398 { | |
399 foreach (description ; descriptions) | |
400 { | |
401 printPendingDescription(description); | |
402 printPendingImpl(description.descriptions); | |
403 } | |
404 } | |
405 | |
406 void printPendingDescription (Description description) | |
407 { | |
408 foreach (test ; description.pending) | |
409 println(indentation, description.message, " ", test.message, "\n", indentation, indentation, "# Not Yet Implemented"); | |
410 } | |
411 | |
412 void printFailures () | |
413 { | |
414 if (!hasFailures) | |
415 return; | |
416 | |
417 println("\nFailures:"); | |
418 | |
419 restore(indentation) in { | |
420 indentation ~= defaultIndentation; | |
421 | |
422 foreach (description ; descriptions) | |
423 { | |
424 printFailuresDescription(description); | |
425 printFailuresImpl(description.descriptions); | |
426 } | |
427 }; | |
428 } | |
429 | |
430 void printFailuresImpl (DescriptionManager descriptions) | |
431 { | |
432 foreach (description ; descriptions) | |
433 { | |
434 printFailuresDescription(description); | |
435 printFailuresImpl(description.descriptions); | |
436 } | |
437 } | |
438 | |
439 void printFailuresDescription (Description description) | |
440 { | |
441 foreach (test ; description.failures) | |
442 { | |
443 auto str = indentation ~ to!(string)(++failureId) ~ ") "; | |
444 auto whitespace = toWhitespace(str.length); | |
445 | |
446 println(str, description.message, " ", test.message); | |
447 println(whitespace, "# ", test.exception.file, ".d:", test.exception.line); | |
448 println(whitespace, "Stack trace:"); | |
449 print(whitespace); | |
450 test.exception.writeOut(&printStackTrace); | |
128 println(); | 451 println(); |
129 } | 452 //println(readFailedTest(test, 0)); |
130 | 453 } |
131 print("\n", tests.length, " test, ", failures, " failures"); | 454 } |
132 printPending(); | 455 |
133 println(); | 456 void printStackTrace (string str) |
134 } | 457 { |
135 | 458 return print(str); |
136 private void printPending () | 459 |
460 /*if (str.find("start") < size_t.max || | |
461 str.find("main") < size_t.max || | |
462 str.find("rt.compiler.") < size_t.max || | |
463 str.find("orange.") || | |
464 str.find(":0") || | |
465 str.find("_d_assert") || | |
466 str.find("onAssertError") || | |
467 str.find("tango.core.Exception.AssertException._ctor ") || | |
468 str.find("object.") || | |
469 str.find("tango.core.tools.")) | |
470 return;*/ | |
471 } | |
472 | |
473 string readFailedTest (ref Test test, int numberOfSurroundingLines = 3) | |
474 { | |
475 auto filename = test.exception.file.dup.replace('.', '/'); | |
476 | |
477 filename ~= ".d"; | |
478 filename = Environment.toAbsolute(filename); | |
479 auto lineNumber = test.exception.line; | |
480 string str; | |
481 auto file = new File(filename); | |
482 | |
483 foreach (i, line ; new Lines!(char)(file)) | |
484 if (i >= (lineNumber - 1) - numberOfSurroundingLines && i <= (lineNumber - 1) + numberOfSurroundingLines) | |
485 str ~= line ~ '\n'; | |
486 | |
487 file.close; | |
488 | |
489 return str; | |
490 } | |
491 | |
492 void printNumberOfPending () | |
137 { | 493 { |
138 if (hasPending) | 494 if (hasPending) |
139 print(", ", pending, " pending"); | 495 print(", ", numberOfPending, " pending"); |
140 } | 496 } |
141 | 497 |
142 private void printSuccess () | 498 void printSuccess () |
143 { | 499 { |
144 println("All ", tests.length, " tests passed successfully."); | 500 println("All ", numberOfTests, " test".pluralize(numberOfTests), " passed successfully."); |
145 } | 501 } |
146 | 502 |
147 private bool isAllTestsSuccessful () | 503 bool isAllTestsSuccessful () |
148 { | 504 { |
149 return !hasPending && !hasFailures; | 505 return !hasPending && !hasFailures; |
150 } | 506 } |
151 | 507 |
152 private bool hasPending () | 508 bool hasPending () |
153 { | 509 { |
154 return pending > 0; | 510 return numberOfPending > 0; |
155 } | 511 } |
156 | 512 |
157 private bool hasFailures () | 513 bool hasFailures () |
158 { | 514 { |
159 return failures > 0; | 515 return numberOfFailures > 0; |
160 } | 516 } |
161 | 517 |
162 private Use!(void delegate ()) execute () | 518 Use!(void delegate ()) execute () |
163 { | 519 { |
164 Use!(void delegate ()) use; | 520 Use!(void delegate ()) use; |
165 | 521 |
166 use.args[0] = &executeImpl; | 522 use.args[0] = &executeImpl; |
167 | 523 |
168 return use; | 524 return use; |
169 } | 525 } |
170 | 526 |
171 private void executeImpl (void delegate () dg) | 527 void executeImpl (void delegate () dg) |
172 { | 528 { |
173 if (pre) pre(); | 529 auto before = this.before; |
530 auto after = this.after; | |
531 | |
532 if (before) before(); | |
174 if (dg) dg(); | 533 if (dg) dg(); |
175 if (post) post(); | 534 if (after) after(); |
176 } | 535 } |
177 } | 536 |
537 string toWhitespace (size_t value) | |
538 { | |
539 string str; | |
540 | |
541 for (size_t i = 0; i < value; i++) | |
542 str ~= ' '; | |
543 | |
544 return str; | |
545 } | |
546 | |
547 string pluralize (string str, int value) | |
548 { | |
549 if (value == 1) | |
550 return str; | |
551 | |
552 return str ~ "s"; | |
553 } | |
554 } |