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