1 module unit_threaded.testcase;
2 
3 import unit_threaded.check;
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         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 private:
54     bool _failed;
55     string _output;
56 
57     bool check(E)(lazy E expression) {
58         try {
59             expression();
60         } catch(UnitTestException ex) {
61             fail(ex.msg);
62         } catch(Exception ex) {
63             fail("\n    " ~ ex.toString() ~ "\n");
64         }
65 
66         return !_failed;
67     }
68 
69     void fail(in string msg) {
70         _failed = true;
71         print(msg);
72     }
73 
74     void print(in string msg) {
75         addToOutput(_output, msg);
76     }
77 }
78 
79 class CompositeTestCase: TestCase {
80     void add(TestCase t) { _tests ~= t;}
81 
82     void opOpAssign(string op : "~")(TestCase t) {
83         add(t);
84     }
85 
86     override string[] opCall() {
87         return _tests.map!(a => a()).reduce!((a, b) => a ~ b);
88     }
89 
90     override void test() { assert(false, "CompositeTestCase.test should never be called"); }
91 
92     override ulong numTestsRun() const {
93         return _tests.length;
94     }
95 
96 private:
97 
98     TestCase[] _tests;
99 }
100 
101 
102 class ShouldFailTestCase: TestCase {
103     this(TestCase testCase) {
104         this.testCase = testCase;
105     }
106 
107     override string getPath() const pure nothrow {
108         return this.testCase.getPath;
109     }
110 
111     override void test() {
112         const ex = collectException!Exception(testCase.test());
113         if(ex is null) {
114             throw new Exception("Test " ~ testCase.getPath() ~ " was expected to fail but did not");
115         }
116     }
117 
118 private:
119 
120     TestCase testCase;
121 }
122 
123 class FunctionTestCase: TestCase {
124     this(immutable TestData data) pure nothrow {
125         _name = data.name;
126         _func = data.testFunction;
127     }
128 
129     override void test() {
130         _func();
131     }
132 
133     override string getPath() const pure nothrow {
134         return _name;
135     }
136 
137     private string _name;
138     private TestFunction _func;
139 }
140 
141 class BuiltinTestCase: FunctionTestCase {
142     this(immutable TestData data) pure nothrow {
143         super(data);
144     }
145 
146     override void test() {
147         try {
148             super.test();
149         } catch(Throwable t) {
150             utFail(t.msg, t.file, t.line);
151         }
152     }
153 }