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