1 /**
2  * Implementations of $(D TestCase) child classes.
3  */
4 
5 module unit_threaded.testcase;
6 
7 import unit_threaded.should;
8 import unit_threaded.io : addToOutput, utWrite;
9 import unit_threaded.reflection : TestData, TestFunction;
10 
11 import std.exception;
12 import std.algorithm;
13 
14 /**
15  * Class from which other test cases derive
16  */
17 class TestCase
18 {
19     /**
20      * The name of the test case.
21      */
22     string name() @safe const pure nothrow
23     {
24         return this.classinfo.name;
25     }
26 
27     /**
28      * Executes the test.
29      * Returns: An array of failures
30      */
31     string[] opCall()
32     {
33         utWrite(collectOutput());
34         return _failed ? [name] : [];
35     }
36 
37     /**
38      * Collect this test's output so as to not interleave with output from
39      * other tests.
40      * Returns: the output of running this test.
41      */
42     final string collectOutput()
43     {
44         print(name ~ ":\n");
45         try
46         {
47             test();
48         }
49         catch (UnitTestException ex)
50         {
51             fail(ex.toString());
52         }
53         catch (Throwable t)
54         {
55             fail("\n    " ~ t.toString() ~ "\n");
56         }
57 
58         if (_failed)
59             print("\n\n");
60         return _output;
61     }
62 
63     /**
64      * Run the test case.
65      */
66     abstract void test();
67 
68     /**
69      * The number of tests to run.
70      */
71     ulong numTestsRun() @property @safe const pure nothrow
72     {
73         return 1;
74     }
75 
76 private:
77     bool _failed;
78     string _output;
79 
80     void fail(in string msg) @safe
81     {
82         _failed = true;
83         print(msg);
84     }
85 
86     void print(in string msg) @safe
87     {
88         addToOutput(_output, msg);
89     }
90 }
91 
92 /**
93  * A test case that is a simple function.
94  */
95 class FunctionTestCase : TestCase
96 {
97     this(immutable TestData data) @safe pure nothrow
98     {
99         _name = data.name;
100         _func = data.testFunction;
101     }
102 
103     override void test()
104     {
105         _func();
106     }
107 
108     override string name() @safe const pure nothrow
109     {
110         return _name;
111     }
112 
113     private string _name;
114     private TestFunction _func;
115 }
116 
117 /**
118  * A test case that should fail.
119  */
120 class ShouldFailTestCase : TestCase
121 {
122     this(TestCase testCase) @safe pure nothrow
123     {
124         this.testCase = testCase;
125     }
126 
127     override string name() @safe const pure nothrow
128     {
129         return this.testCase.name;
130     }
131 
132     override void test()
133     {
134         const ex = collectException!Exception(testCase.test());
135         if (ex is null)
136         {
137             throw new Exception("Test " ~ testCase.name ~ " was expected to fail but did not");
138         }
139     }
140 
141 private:
142 
143     TestCase testCase;
144 }
145 
146 /**
147  * A test case that contains other test cases.
148  */
149 class CompositeTestCase : TestCase
150 {
151     void add(TestCase t) @safe nothrow
152     {
153         _tests ~= t;
154     }
155 
156     void opOpAssign(string op : "~")(TestCase t) @safe nothrow
157     {
158         add(t);
159     }
160 
161     override string[] opCall()
162     {
163         return _tests.map!(a => a()).reduce!((a, b) => a ~ b);
164     }
165 
166     override void test()
167     {
168         assert(false, "CompositeTestCase.test should never be called");
169     }
170 
171     override ulong numTestsRun() @safe const pure nothrow
172     {
173         return _tests.length;
174     }
175 
176 private:
177 
178     TestCase[] _tests;
179 }