1 /** 2 This module is an attempt to alleviate compile times by including the bare 3 minimum. The idea is that while the reporting usually done by unit-threaded 4 is welcome, it only really matters when tests fail. Otherwise, no news is 5 good news. 6 7 Likewise, naming and selecting tests are features used when certain tests 8 fail. The usual way to run tests is to run all of them and be happy if 9 they all pass. 10 11 This module makes it so that unit-threaded gets out of the way, and if 12 needed the full features can be turned on at the cost of compiling 13 much more slowly. 14 15 There aren't even any template constraints on the `should` functions 16 to avoid imports as much as possible. 17 */ 18 module unit_threaded.light; 19 20 alias UnitTestException = Exception; 21 22 /** 23 Dummy version of runTests so "normal" code compiles. 24 */ 25 int runTests(T...)(in string[] args) { 26 return runTestsImpl; 27 } 28 29 /// ditto 30 int runTests(T)(string[] args, T testData) { 31 return runTestsImpl; 32 } 33 34 int runTestsImpl() { 35 import core.runtime: Runtime; 36 import core.stdc.stdio: printf; 37 38 try { 39 40 Runtime.moduleUnitTester(); 41 42 printf("\n"); 43 version(Posix) 44 printf("\033[32;1mOk\033[0;;m"); 45 else 46 printf("Ok"); 47 48 printf(": All tests passed\n\n"); 49 50 return 0; 51 } catch(Throwable _) 52 return 1; 53 } 54 55 /** 56 Dummy version so "normal" code compiles 57 */ 58 int[] allTestData(T...)() { 59 return []; 60 } 61 62 /** 63 No-op version of writelnUt 64 */ 65 void writelnUt(T...)(auto ref T args) { 66 67 } 68 69 /** 70 Same as unit_threaded.property.check 71 */ 72 void check(alias F)(int numFuncCalls = 100, 73 in string file = __FILE__, in size_t line = __LINE__) @trusted { 74 import unit_threaded.property: utCheck = check; 75 utCheck!F(numFuncCalls, file, line); 76 } 77 78 /** 79 Same as unit_threaded.property.checkCustom 80 */ 81 void checkCustom(alias Generator, alias Predicate) 82 (int numFuncCalls = 100, in string file = __FILE__, in size_t line = __LINE__) @trusted { 83 import unit_threaded.property: utCheckCustom = checkCustom; 84 utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line); 85 } 86 87 88 /** 89 Generic output interface 90 */ 91 interface Output { 92 void send(in string output) @safe; 93 void flush() @safe; 94 } 95 96 /** 97 Dummy version of unit_threaded.testcase.TestCase 98 */ 99 class TestCase { 100 abstract void test(); 101 void setup() {} 102 void shutdown() {} 103 static TestCase currentTest() { return new class TestCase { override void test() {}}; } 104 Output getWriter() { return new class Output { override void send(in string output) {} override void flush() {}}; } 105 } 106 107 108 /** 109 Same as unit_threaded.mock.mock 110 */ 111 auto mock(T)() { 112 import unit_threaded.mock: utMock = mock; 113 return utMock!T; 114 } 115 116 /** 117 Same as unit_threaded.mock.mockStruct 118 */ 119 auto mockStruct(T...)(auto ref T returns) { 120 import unit_threaded.mock: utMockStruct = mockStruct; 121 return utMockStruct(returns); 122 } 123 124 /** 125 Throw if condition is not true. 126 */ 127 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 128 assert_(cast(bool)condition(), file, line); 129 } 130 131 /// Throw if condition not false. 132 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 133 assert_(!cast(bool)condition(), file, line); 134 } 135 136 /// Assert value is equal to expected 137 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 138 139 void checkInputRange(T)(auto ref const(T) _) @trusted { 140 auto obj = cast(T)_; 141 bool e = obj.empty; 142 auto f = obj.front; 143 obj.popFront; 144 } 145 enum isInputRange(T) = is(T: Elt[], Elt) || is(typeof(checkInputRange(T.init))); 146 147 static if(is(V == class)) { 148 assert_(value.tupleof == expected.tupleof, file, line); 149 } else static if(isInputRange!V && isInputRange!E) { 150 auto ref unqual(T)(auto ref const(T) obj) @trusted { 151 static if(is(T == void[])) 152 return cast(ubyte[])obj; 153 else 154 return cast(T)obj; 155 } 156 import std.algorithm: equal; 157 assert_(equal(unqual(value), unqual(expected)), file, line); 158 } else { 159 assert_(cast(const)value == cast(const)expected, file, line); 160 } 161 } 162 163 /// Assert value is not equal to expected. 164 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 165 assert_(value != expected, file, line); 166 } 167 168 /// Assert value is null. 169 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 170 assert_(value is null, file, line); 171 } 172 173 /// Assert value is not null 174 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 175 assert_(value !is null, file, line); 176 } 177 178 enum isLikeAssociativeArray(T, K) = is(typeof({ 179 if(K.init in T) { } 180 if(K.init !in T) { } 181 })); 182 static assert(isLikeAssociativeArray!(string[string], string)); 183 static assert(!isLikeAssociativeArray!(string[string], int)); 184 185 186 /// Assert that value is in container. 187 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__) 188 if(isLikeAssociativeArray!(U, T)) { 189 assert_(cast(bool)(value in container), file, line); 190 } 191 192 /// ditto. 193 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__) 194 if (!isLikeAssociativeArray!(U, T)) 195 { 196 import std.algorithm: find; 197 import std.array: empty; 198 assert_(!find(container, value).empty, file, line); 199 } 200 201 /// Assert value is not in container. 202 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__) 203 if(isLikeAssociativeArray!U) { 204 assert_(!cast(bool)(value in container), file, line); 205 } 206 207 /// ditto. 208 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__) 209 if (!isLikeAssociativeArray!(U, T)) 210 { 211 import std.algorithm: find; 212 import std.array: empty; 213 assert_(find(container, value).empty, file, line); 214 } 215 216 /// Assert that expr throws. 217 void shouldThrow(T : Throwable = Exception, E) 218 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) { 219 auto threw = false; 220 () @trusted { 221 try { 222 expr(); 223 } catch(T _) { 224 threw = true; 225 } 226 }(); 227 assert_(threw, file, line); 228 } 229 230 /// Assert that expr throws an Exception that must have the type E, derived types won't do. 231 void shouldThrowExactly(T : Throwable = Exception, E) 232 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) 233 { 234 T throwable = null; 235 236 () @trusted { 237 try { 238 expr(); 239 assert_(false, file, line); 240 } catch(T t) { 241 throwable = t; 242 } 243 }(); 244 245 //Object.opEquals is @system and impure 246 const sameType = () @trusted { return throwable !is null && typeid(throwable) == typeid(T); }(); 247 assert_(sameType, file, line); 248 249 } 250 251 /// Assert that expr doesn't throw 252 void shouldNotThrow(T: Throwable = Exception, E) 253 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) { 254 () @trusted { 255 try 256 expr(); 257 catch(T _) 258 assert_(false, file, line); 259 }(); 260 } 261 262 /// Assert that expr throws and the exception message is msg. 263 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, 264 string msg, 265 string file = __FILE__, 266 size_t line = __LINE__) { 267 T throwable = null; 268 269 () @trusted { 270 try { 271 expr(); 272 } catch(T ex) { 273 throwable = ex; 274 } 275 }(); 276 277 assert_(throwable !is null && throwable.msg == msg, file, line); 278 } 279 280 /// Assert that value is approximately equal to expected. 281 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, size_t line = __LINE__) { 282 import std.math: approxEqual; 283 assert_(approxEqual(value, expected), file, line); 284 } 285 286 /// assert that rng is empty. 287 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 288 import std.range: isInputRange; 289 import std.traits: isAssociativeArray; 290 import std.array; 291 292 static if(isInputRange!R) 293 assert_(rng.empty, file, line); 294 else static if(isAssociativeArray!R) 295 () @trusted { assert_(rng.keys.empty, file, line); }(); 296 else 297 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 298 } 299 300 /// Assert that rng is not empty. 301 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 302 import std.range: isInputRange; 303 import std.traits: isAssociativeArray; 304 import std.array; 305 306 static if(isInputRange!R) 307 assert_(!rnd.empty, file, line); 308 else static if(isAssociativeArray!R) 309 () @trusted { assert_(!rng.keys.empty, file, line); }(); 310 else 311 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 312 } 313 314 /// Assert that t should be greater than u. 315 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u, 316 in string file = __FILE__, in size_t line = __LINE__) 317 { 318 assert_(t > u, file, line); 319 } 320 321 /// Assert that t should be smaller than u. 322 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u, 323 in string file = __FILE__, in size_t line = __LINE__) 324 { 325 assert_(t < u, file, line); 326 } 327 328 /// Assert that value is the same set as expected (i.e. order doesn't matter) 329 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 330 assert_(isSameSet(value, expected), file, line); 331 } 332 333 /// Assert that value is not the same set as expected. 334 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 335 assert_(!isSameSet(value, expected), file, line); 336 } 337 338 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) { 339 import std.array: array; 340 import std.algorithm: canFind; 341 342 //sort makes the element types have to implement opCmp 343 //instead, try one by one 344 auto ta = t.array; 345 auto ua = u.array; 346 if (ta.length != ua.length) return false; 347 foreach(element; ta) 348 { 349 if (!ua.canFind(element)) return false; 350 } 351 352 return true; 353 } 354 355 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter) 356 void shouldBeSameJsonAs(in string actual, 357 in string expected, 358 in string file = __FILE__, 359 in size_t line = __LINE__) 360 @trusted // not @safe pure due to parseJSON 361 { 362 import std.json: parseJSON, JSONException; 363 364 auto parse(in string str) { 365 try 366 return str.parseJSON; 367 catch(JSONException ex) { 368 assert_(false, "Failed to parse " ~ str, file, line); 369 } 370 assert(0); 371 } 372 373 assert_(parse(actual) == parse(expected), file, line); 374 } 375 376 377 private void assert_(in bool value, in string file, in size_t line) @safe pure { 378 assert_(value, "Assertion failure", file, line); 379 } 380 381 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure { 382 if(!value) 383 throw new Exception(message, file, line); 384 } 385 386 void fail(in string output, in string file, in size_t line) @safe pure { 387 assert_(false, output, file, line); 388 }