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