Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

root/orange/test/UnitTester.d

Revision 35:511d1ef4e299, 10.0 kB (checked in by Jacob Carlborg <doob@me.com>, 13 years ago)

Now all unit tests pass on latest DMD2 compiler.

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