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 }