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