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