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