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 }