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 }