root/trunk/tango/scrapple/util/Test.d

Revision 39, 14.6 kB (checked in by flithm, 8 months ago)

Fixes for Tango 0.99.4

Line 
1 /++
2     copyright:  Copyright (c) 2007 Darryl Bleau. All rights reserved.
3     license:    BSD style.
4     version:    Oct 2007
5     author:     Darryl B
6     
7     Test is a highly configurable module that allows for detailed code test output for specified TestCases. Output may be customized on a
8     per-Test basis, or disabled entirely. Test results may be stepped through after run for output that isn't provided. The Test is also
9     fairly customizable with regards to language output. Examples of some possible Test output.
10     
11     Example:
12     ---
13     Test of Test
14       -Test of Failure
15         1 :: Warning [1.0041s]
16         2 :: Failure [1.0039s]
17         3 :: Failure [1.0042s]
18       -Test of Exception :: Exception [0.0001s]
19       -Test of Warning :: Warning [1.0036s]
20       -Test of Success :: Success [1.0039s]
21     [5.0196 Seconds]
22     ---
23     
24     Example:
25     ---
26     Test of Test (5.0196 Seconds)
27      [Warning] (1.0041 Seconds) :: Test of Failure [1] (Uh Oh!)
28      [Failure] (1.0039 Seconds) :: Test of Failure [2] (Yikes!)
29      [Failure] (1.0042 Seconds) :: Test of Failure [3] (Oops!, Eeek!, Yikes!)
30      [Warning] (1.0036 Seconds) :: Test of Warning (Erm..., Um...)
31      [Success] (1.0039 Seconds) :: Test of Success (Yay!)
32      [Exception] (0.0001 Seconds) :: Test of Exception (exception)   
33     ---
34     
35     The basic use of Test is to create functions (or delegates) which perform the code test, tell Test about them, and then tell it to run.
36     The test functions must return a Test.Status and accept as a parameter a string array to which the test function can append
37     any messages that it may want to have included in the report.
38     
39     Example:
40     ---
41     Test.Status myTestFunction(char[][] messages) { return Test.Success; }
42     auto myTest = new Test("Test Name");
43     myTest["My Test Case"] = &myTestFunction;
44     myTest.run;
45     ---
46     
47     The Test can be configured to alter it's output via the config struct.
48     Example:
49     ---
50     myTest.config.incremental = false;
51     myTest.config.summary = true;   
52     myTest.config.testName = false;
53     myTest.config.timing.precision = 6;
54     myTest.config.timing.postfix = " Seconds";
55     myTest.config.status.output[Test.Status.Success] = false;
56     myTest.config.status.test[Test.Status.Failure] = "FAILED!!!";
57     ---
58     This would tell the Test not to output during run (incremental), to output a summary, and not to output the name of the test.
59     The Test will also output all timings at 6 decimal precision, and postfix times with "Seconds".
60     Additionally, the Test will not output any TestCases which were successful, and will output "FAILED!!!" for any TestCases which were failures.
61 +/
62
63 module tango.scrapple.util.Test;
64
65 private import tango.io.Stdout;
66 private import tango.text.Util;
67 private import tango.time.StopWatch;
68 private import tango.core.Array;
69 private import Float = tango.text.convert.Float;
70 private import tango.text.Ascii;
71 private import tango.core.Memory;
72
73 /++
74     Provides the Test functionality. A Test may contain multiple TestCases. A TestCase may be added as a function via index:
75     ---
76     myTest["My Test Case"] = &myTestFunction;
77     ---
78     Where the index represents the name of the TestCase. A TestCase may then be retrieved from the same index:
79     ---
80     TestCase myTestCase = myTest["My Test Case"];
81     ---
82     Delegates may be added to a Test in the same fashion:
83     ---
84     myTest["My Test Case 2"] = &myClass.myFunction;
85     ---
86     TestCase iterations may also be specified by using .add (in this case, each running of the Test will run this test function 4 times):
87     ---
88     myTest.add(&myFunction, "My Test", 4);
89     ---
90     Alternatively, a TestCase may be created and then added to a Test in the same way:
91     ---
92     TestCase myTestCase = new TestCase(&myFunction);
93     myTest["My TestCase"] = myTestCase;
94     ---
95     TestCases may also be added via .add:
96     ---
97     myTest.add(myTestCase, "My TestCase");
98     ---
99 +/
100 public class Test
101 {
102     /// Test status indicators
103     enum Status
104     {
105         Untested, /// Test has not yet been run.
106         Success, /// Test indicates success.
107         Warning, /// Test did not fail, but has a warning.
108         Exception, /// Test threw an exception.
109         Failure /// Test indicates failure.
110     }   
111    
112     /// Provides output-related configuration for the Test
113     struct Config
114     {
115         bool message = true; /// Output all TestCase messages.
116         bool testTime = false; /// Output timing result for the entire Test.
117         bool caseTime = false; /// Output timing results for each TestCase.
118         bool testName = true; /// Output Test name.
119         bool caseName = true; /// Output each TestCase name.
120         bool incremental = true; /// Output Test results during run.
121         bool summary = false; /// Output Test results summary after run.
122         bool sortName = true; /// Sorts output according to TestCase name. Takes precedence over sortTime.
123         bool sortTime = false; /// Sorts output according to TestCase time. Takes precedence over sortStatus. For multiple iteration tests, the first time is used.
124         bool sortStatus = false; /// Sorts output according to TestCase status. For multiple iteration tests, the first Status is used.
125         /// Status related configuration.
126         struct Status
127         {
128             bool[] output =
129                 [ Test.Status.Untested : true,
130                   Test.Status.Success : true,
131                   Test.Status.Warning : true,
132                   Test.Status.Exception : true,
133                   Test.Status.Failure : true ]; /// Boolean array indexed by Test.Status indicating whether TestCases with that status should be output.
134             char[][] text =
135                 [ Test.Status.Untested : "Untested",
136                   Test.Status.Success : "Success",
137                   Test.Status.Warning : "Warning",
138                   Test.Status.Exception : "Exception",
139                   Test.Status.Failure : "Failure" ]; /// Char array indexed by Test.Status containing the text to be output for that Test.Status.
140         }
141         /// provides status.
142         Status status;
143         /// Timing related configuration.
144         struct Timing
145         {
146             int precision = 3; /// Decimal precision of timing output.
147             char[] postfix = "s"; /// String to append to timing output.
148         }
149         /// provides timing.
150         Timing timing;
151     }
152     /// provides config.
153     Config config;
154    
155     private TestCase[char[]] _tests;
156     private char[] _name;
157     char[] name() { return _name; }
158    
159     void opIndexAssign(testFunction tf, char[] name) { _tests[name] = new TestCase(tf); }
160     void opIndexAssign(testDelegate td, char[] name) { _tests[name] = new TestCase(td); }
161     void opIndexAssign(TestCase tc, char[] name) { _tests[name] = tc; }
162     void opIndexAssign(int priority, char[] name)
163     {
164         TestCase tc = _tests[name];
165         if (tc !is null)
166             tc.priority = priority;
167     }
168     void add(testFunction tf, char[] name, int iterations = 1) { _tests[name] = new TestCase(tf, iterations); }
169     void add(testDelegate td, char[] name, int iterations = 1) { _tests[name] = new TestCase(td, iterations); }
170     void add(TestCase tc, char[] name) { _tests[name] = tc; }
171     TestCase opIndex(char[] name) { return _tests[name]; } 
172
173     int opApply(int delegate(inout TestCase tc, inout char[] name) dg)
174     {
175         int result;
176         foreach (char[] name, TestCase tc; _tests)
177             if ((result = dg(tc, name)) != 0)
178                 break;
179         return result;
180     }
181
182     int opApply(int delegate(inout TestCase tc) dg)
183     {
184         int result;
185         foreach (TestCase tc; _tests)
186             if ((result = dg(tc)) != 0)
187                 break;
188         return result;
189     }
190    
191     private void _outputLevel(int level)
192     {
193         for (int i=0; i < level; i++)
194             Stdout("  ");
195     }
196    
197     /// Performs the Test, outputting results as per Test.config, and tracks all TestCase results.
198     void run()
199     {
200         int outLevel = 0;
201         float totalTime = 0;
202         StopWatch timer;
203         if (config.incremental && config.testName)
204         {
205             Stdout.format("{}", this._name).newline;
206             outLevel++;
207         }
208         char[][] tcSort;
209         foreach(char[] name, TestCase tc; _tests)
210             tcSort ~= name;
211         tcSort.sort((char[] lhs, char[] rhs) { return (_tests[lhs]._priority < _tests[rhs]._priority); });
212        
213         foreach (char[] name; tcSort)
214         {
215             TestCase tc = _tests[name];
216             if (config.incremental && config.caseName)
217             {
218                 _outputLevel(outLevel);
219                 Stdout.format("-{}", name);
220                 if (tc._iterations > 1)
221                 {
222                     outLevel++;
223                     Stdout.newline;
224                 }
225             }
226             float totalCaseTime = 0;
227             for (int i = 0; i < tc._iterations; i++)
228             {
229                 if (config.incremental)
230                 {
231                     if (config.caseName && (tc._iterations > 1))
232                     {
233                         _outputLevel(outLevel);
234                         Stdout.format("{}", i+1);
235                     }
236                     Stdout("... ").flush;
237                 }
238                
239                 GC.disable;
240                 timer.start;
241                 try
242                 {
243                     if (tc._testFunction !is null)
244                         tc._status[i] = tc._testFunction(tc._messages[i]);
245                     else
246                         tc._status[i] = tc._testDelegate(tc._messages[i]);
247                 }
248                 catch (Exception ex)
249                 {
250                     tc._status[i] = Status.Exception;
251                     tc._messages[i] ~= ex.msg;
252                 }
253                 totalTime += tc._time[i] = timer.stop;
254                 totalCaseTime += tc._time[i];
255                 GC.enable;
256                 GC.collect;
257
258                 if (config.incremental)
259                 {
260                     Stdout("\b\b\b\b");
261                     if (config.caseName)
262                         Stdout(" ::");
263                     if (config.status.output[tc._status[i]])
264                         Stdout.format(" {}", config.status.text[tc._status[i]]);
265                     if (config.message && (tc._messages[i] !is null))
266                         Stdout.format(" ({})", join(tc._messages[i], ", "));
267                     if (config.caseTime)
268                         Stdout.format(" [{}{}]", Float.toString(tc._time[i], config.timing.precision), config.timing.postfix);
269                     Stdout.newline;
270                 }
271             }
272             if (config.incremental && config.caseName && (tc._iterations > 1))
273             {
274                 if (config.caseTime)
275                 {
276                     _outputLevel(outLevel);
277                     Stdout.format("-Avg: [{}{}], Total: [{}{}]", Float.toString((totalCaseTime / tc._iterations), config.timing.precision), config.timing.postfix, Float.toString(totalCaseTime, config.timing.precision), config.timing.postfix).newline;
278                 }
279                 outLevel--;
280             }
281         }
282         if (config.incremental && config.testTime)
283             Stdout.format("[{}{}]", Float.toString(totalTime, config.timing.precision), config.timing.postfix).newline;
284         if (config.incremental)
285             Stdout.newline;
286         if (config.summary)
287             this.output;
288     }
289
290     /// Outputs the results of the Test via Stdout as per the Test.config.
291     void output()
292     {
293         char[][] tcSort;
294         float totalTime = 0;
295         foreach(char[] name, TestCase tc; _tests)
296         {
297             tcSort ~= name;
298             for (int i = 0; i < tc._iterations; i++)
299                 totalTime += tc._time[i];
300         }
301        
302         if (config.testName)
303             Stdout.format("{}", this._name);
304         if (config.testTime)
305             Stdout.format(" ({}{})", Float.toString(totalTime, config.timing.precision), config.timing.postfix);
306         if (config.testName || config.testTime)
307             Stdout.newline;
308        
309         if (config.sortName)
310             tcSort.sort;
311         else if (config.sortTime)
312             tcSort.sort((char[] lhs, char[] rhs) { return (_tests[lhs]._time[0] < _tests[rhs]._time[0]); });   
313         else if (config.sortStatus)
314             tcSort.sort((char[] lhs, char[] rhs) { return (_tests[lhs]._status[0] < _tests[rhs]._status[0]); });
315         foreach(char[] name; tcSort)
316         {
317             TestCase tc = _tests[name];
318             for (int i = 0; i < tc._iterations; i++)
319             {
320                 if (config.status.output[tc._status[i]])
321                     Stdout.format(" [{}]", config.status.text[tc._status[i]]);
322                 if (config.caseTime)
323                     Stdout.format(" ({}{})", Float.toString(tc._time[i], config.timing.precision), config.timing.postfix);
324                 if (config.caseName)
325                 {
326                     Stdout.format(" :: {}", name);
327                     if (tc._iterations > 1)
328                         Stdout.format(" [{}]", i+1);
329                 }
330                 if (config.message)
331                     if (tc._messages[i] !is null)
332                         Stdout.format(" ({})", join(tc._messages[i], ", "));
333                 Stdout.newline;
334             }
335         }
336         Stdout.newline;
337     }
338    
339     this(char[] name)
340     {
341         this._name = name; 
342     }
343 }
344
345 alias Test.Status function(inout char[][] messages) testFunction;
346 alias Test.Status delegate(inout char[][] messages) testDelegate;
347
348 /// An individual TestCase. These may be used for iterating through Test results (for customized output, perhaps).
349 public class TestCase
350 {
351     private Test.Status[] _status;
352     private char[][][] _messages;
353     private float[] _time;
354     private testFunction _testFunction;
355     private testDelegate _testDelegate;
356     private int _iterations;
357     private int _priority;
358    
359     /// Status result for this TestCase. An index may be specified to retrieve the result for a particular iteration.
360     Test.Status status(int index = 0) { return _status[index]; }
361     /// Messaages relating to this TestCase. An index may be specified to retrieve the messages for a particular iteration.
362     char[][] messages(int index = 0) { return _messages[index]; }
363     /// Timing results for this TestCase. An index may be specified to retrieve the timing for a particular iteration.
364     float time(int index = 0) { return _time[index]; }
365     /// The number of iterations specified for this TestCase.
366     int iterations() { return _iterations; }
367     /// The processing priority for this TestCase, 0 is default, lower numbers are higher priority (run first).
368     int priority() { return _priority; }
369     void priority(int priority) { this._priority = priority; }
370    
371     private void _init(int iterations)
372     {
373         this._iterations = iterations;
374         _status = new Test.Status[iterations];
375         _messages = new char[][][iterations];
376         _time = new float[iterations];
377     }
378    
379     this(testFunction tf, int iterations = 1)
380     {
381         this._testFunction = tf;
382         _init(iterations);
383     }
384    
385     this(testDelegate td, int iterations = 1)
386     {
387         this._testDelegate = td;
388         _init(iterations);
389     }
390 }
391
392 debug(Test)
393 {
394     private import tango.core.Thread;
395    
396     Test.Status successFunction(inout char[][] messages)
397     {
398         Thread.sleep(1);
399         messages ~= "Yay!";
400         return Test.Status.Success;
401     }
402
403     class Warning
404     {
405         Test.Status warningFunction(inout char[][] messages)
406         {
407             Thread.sleep(1);
408             messages ~= "Erm...";
409             messages ~= "Um...";
410             return Test.Status.Warning;
411         }
412     }
413    
414     class Failure
415     {
416         bool warn = true;
417         bool moreMessages = false;
418         Test.Status failureFunction(inout char[][] messages)
419         {
420             Thread.sleep(1);
421             if (warn)
422             {
423                 messages ~= "Uh Oh!";
424                 warn = false;
425                 return Test.Status.Warning;
426             }
427             if (moreMessages)
428             {
429                 messages ~= "Oops!";
430                 messages ~= "Eeek!";
431             }
432             else
433                 moreMessages = true;
434             messages ~= "Yikes!";
435             return Test.Status.Failure;
436         }
437     }
438    
439     Test.Status exceptionFunction(inout char[][] messages)
440     {
441         throw new Exception("exception");
442     }
443    
444     void main()
445     {
446         auto myWarning = new Warning;
447         auto myFailure = new Failure;
448         auto myTest = new Test("Test of Test");
449         myTest.config.timing.postfix = " Seconds";
450         myTest.config.timing.precision = 4;
451         myTest.config.summary = true;
452         myTest.config.caseTime = true;
453         myTest.config.testTime = true;
454         myTest.config.testName = true;
455         myTest.config.caseName = true;
456         myTest.config.message = true;
457         myTest.config.incremental = true;
458         myTest.config.status.output[Test.Status.Success] = true;
459         myTest.config.status.output[Test.Status.Warning] = true;
460         myTest.config.status.output[Test.Status.Failure] = true;
461         myTest.config.status.text[Test.Status.Failure] = "Failure";
462         myTest.config.sortName = false;
463         myTest.config.sortTime = true;
464         myTest.config.sortStatus = false;
465         TestCase failureCase = new TestCase(&myFailure.failureFunction, 3);
466         myTest["Test of Success"] = &successFunction;
467         myTest["Test of Warning"] = &myWarning.warningFunction;
468         myTest["Test of Failure"] = failureCase;
469         myTest["Test of Exception"] = &exceptionFunction;
470         myTest.run;
471     }
472 }
Note: See TracBrowser for help on using the browser.