1 /** 2 * Implementations of $(D TestCase) child classes. 3 */ 4 5 module unit_threaded.testcase; 6 7 import unit_threaded.should; 8 import unit_threaded.io : addToOutput, utWrite; 9 import unit_threaded.reflection : TestData, TestFunction; 10 11 import std.exception; 12 import std.algorithm; 13 14 /** 15 * Class from which other test cases derive 16 */ 17 class TestCase 18 { 19 /** 20 * The name of the test case. 21 */ 22 string name() @safe const pure nothrow 23 { 24 return this.classinfo.name; 25 } 26 27 /** 28 * Executes the test. 29 * Returns: An array of failures 30 */ 31 string[] opCall() 32 { 33 utWrite(collectOutput()); 34 return _failed ? [name] : []; 35 } 36 37 /** 38 * Collect this test's output so as to not interleave with output from 39 * other tests. 40 * Returns: the output of running this test. 41 */ 42 final string collectOutput() 43 { 44 print(name ~ ":\n"); 45 try 46 { 47 test(); 48 } 49 catch (UnitTestException ex) 50 { 51 fail(ex.toString()); 52 } 53 catch (Throwable t) 54 { 55 fail("\n " ~ t.toString() ~ "\n"); 56 } 57 58 if (_failed) 59 print("\n\n"); 60 return _output; 61 } 62 63 /** 64 * Run the test case. 65 */ 66 abstract void test(); 67 68 /** 69 * The number of tests to run. 70 */ 71 ulong numTestsRun() @property @safe const pure nothrow 72 { 73 return 1; 74 } 75 76 private: 77 bool _failed; 78 string _output; 79 80 void fail(in string msg) @safe 81 { 82 _failed = true; 83 print(msg); 84 } 85 86 void print(in string msg) @safe 87 { 88 addToOutput(_output, msg); 89 } 90 } 91 92 /** 93 * A test case that is a simple function. 94 */ 95 class FunctionTestCase : TestCase 96 { 97 this(immutable TestData data) @safe pure nothrow 98 { 99 _name = data.name; 100 _func = data.testFunction; 101 } 102 103 override void test() 104 { 105 _func(); 106 } 107 108 override string name() @safe const pure nothrow 109 { 110 return _name; 111 } 112 113 private string _name; 114 private TestFunction _func; 115 } 116 117 /** 118 * A test case that should fail. 119 */ 120 class ShouldFailTestCase : TestCase 121 { 122 this(TestCase testCase) @safe pure nothrow 123 { 124 this.testCase = testCase; 125 } 126 127 override string name() @safe const pure nothrow 128 { 129 return this.testCase.name; 130 } 131 132 override void test() 133 { 134 const ex = collectException!Exception(testCase.test()); 135 if (ex is null) 136 { 137 throw new Exception("Test " ~ testCase.name ~ " was expected to fail but did not"); 138 } 139 } 140 141 private: 142 143 TestCase testCase; 144 } 145 146 /** 147 * A test case that contains other test cases. 148 */ 149 class CompositeTestCase : TestCase 150 { 151 void add(TestCase t) @safe nothrow 152 { 153 _tests ~= t; 154 } 155 156 void opOpAssign(string op : "~")(TestCase t) @safe nothrow 157 { 158 add(t); 159 } 160 161 override string[] opCall() 162 { 163 return _tests.map!(a => a()).reduce!((a, b) => a ~ b); 164 } 165 166 override void test() 167 { 168 assert(false, "CompositeTestCase.test should never be called"); 169 } 170 171 override ulong numTestsRun() @safe const pure nothrow 172 { 173 return _tests.length; 174 } 175 176 private: 177 178 TestCase[] _tests; 179 }