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