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