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         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.toString());
62         } catch(Throwable 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 class ShouldFailTestCase: TestCase {
102     this(TestCase testCase) {
103         this.testCase = testCase;
104     }
105 
106     override string getPath() const pure nothrow {
107         return this.testCase.getPath;
108     }
109 
110     override void test() {
111         const ex = collectException!Exception(testCase.test());
112         if(ex is null) {
113             throw new Exception("Test " ~ testCase.getPath() ~ " was expected to fail but did not");
114         }
115     }
116 
117 private:
118 
119     TestCase testCase;
120 }
121 
122 class FunctionTestCase: TestCase {
123     this(immutable TestData data) pure nothrow {
124         _name = data.name;
125         _func = data.testFunction;
126 
127         if(data.suffix) {
128             _name ~= "." ~ data.suffix;
129         }
130     }
131 
132     override void test() {
133         _func();
134     }
135 
136     override string getPath() const pure nothrow {
137         return _name;
138     }
139 
140     private string _name;
141     private TestFunction _func;
142 }
143 
144 class BuiltinTestCase: FunctionTestCase {
145     this(immutable TestData data) pure nothrow {
146         super(data);
147     }
148 
149     override void test() {
150         try {
151             super.test();
152         } catch(Throwable t) {
153             utFail(t.msg, t.file, t.line);
154         }
155     }
156 }