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 /** 24 Dummy version so "normal" code compiles 25 */ 26 mixin template runTestsMain(Modules...) if(Modules.length > 0) { 27 int main() { 28 import unit_threaded.light: runTestsImpl; 29 return runTestsImpl; 30 } 31 } 32 33 /** 34 Dummy version of runTests so "normal" code compiles. 35 */ 36 int runTests(T...)(in string[] args) { 37 return runTestsImpl; 38 } 39 40 /// ditto 41 int runTests(T)(string[] args, T testData) { 42 return runTestsImpl; 43 } 44 45 int runTestsImpl() { 46 import core.runtime: Runtime; 47 import core.stdc.stdio: printf; 48 49 version(Posix) 50 printf("\033[32;1mOk\033[0;;m"); 51 else 52 printf("Ok"); 53 54 printf(": All tests passed\n\n"); 55 56 return 0; 57 } 58 59 60 /** 61 Dummy version so "normal" code compiles 62 */ 63 int[] allTestData(T...)() { 64 return []; 65 } 66 67 /** 68 No-op version of writelnUt 69 */ 70 void writelnUt(T...)(auto ref T args) { 71 72 } 73 74 /** 75 Same as unit_threaded.property.check 76 */ 77 void check(alias F)(int numFuncCalls = 100, 78 string file = __FILE__, size_t line = __LINE__) { 79 import unit_threaded.property: utCheck = check; 80 utCheck!F(numFuncCalls, file, line); 81 } 82 83 /** 84 Same as unit_threaded.property.checkCustom 85 */ 86 void checkCustom(alias Generator, alias Predicate) 87 (int numFuncCalls = 100, string file = __FILE__, size_t line = __LINE__) { 88 import unit_threaded.property: utCheckCustom = checkCustom; 89 utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line); 90 } 91 92 93 /** 94 Generic output interface 95 */ 96 interface Output { 97 void send(in string output) @safe; 98 void flush() @safe; 99 } 100 101 /** 102 Dummy version of unit_threaded.testcase.TestCase 103 */ 104 class TestCase { 105 abstract void test(); 106 void setup() {} 107 void shutdown() {} 108 static TestCase currentTest() { return new class TestCase { override void test() {}}; } 109 Output getWriter() { return new class Output { override void send(in string output) {} override void flush() {}}; } 110 } 111 112 113 /** 114 Same as unit_threaded.mock.mock 115 */ 116 auto mock(T)() { 117 import unit_threaded.mock: utMock = mock; 118 return utMock!T; 119 } 120 121 /** 122 Same as unit_threaded.mock.mockStruct 123 */ 124 auto mockStruct(T...)(auto ref T returns) { 125 import unit_threaded.mock: utMockStruct = mockStruct; 126 return utMockStruct(returns); 127 } 128 129 /** 130 Throw if condition is not true. 131 */ 132 void shouldBeTrue(E)(lazy E condition, string file = __FILE__, size_t line = __LINE__) { 133 assert_(cast(bool)condition(), file, line); 134 } 135 136 /// Throw if condition not false. 137 void shouldBeFalse(E)(lazy E condition, string file = __FILE__, size_t line = __LINE__) { 138 assert_(!cast(bool)condition(), file, line); 139 } 140 141 /// Assert value is equal to expected 142 void shouldEqual(V, E)(auto ref V value, auto ref E expected, string file = __FILE__, size_t line = __LINE__) { 143 144 void checkInputRange(T)(auto ref const(T) _) @trusted { 145 auto obj = cast(T) _; 146 bool e = obj.empty; 147 auto f = obj.front; 148 obj.popFront; 149 } 150 enum isInputRange(T) = is(T: Elt[], Elt) || is(typeof(checkInputRange(T.init))); 151 152 static if(is(V == class)) { 153 154 import unit_threaded.should: isEqual; 155 assert_(isEqual(value, expected), file, line); 156 157 } else static if(isInputRange!V && isInputRange!E) { 158 159 auto ref unqual(OriginalType)(auto ref OriginalType obj) @trusted { 160 161 // copied from std.traits 162 template Unqual(T) { 163 static if (is(T U == immutable U)) alias Unqual = U; 164 else static if (is(T U == shared inout const U)) alias Unqual = U; 165 else static if (is(T U == shared inout U)) alias Unqual = U; 166 else static if (is(T U == shared const U)) alias Unqual = U; 167 else static if (is(T U == shared U)) alias Unqual = U; 168 else static if (is(T U == inout const U)) alias Unqual = U; 169 else static if (is(T U == inout U)) alias Unqual = U; 170 else static if (is(T U == const U)) alias Unqual = U; 171 else alias Unqual = T; 172 } 173 174 static if(__traits(compiles, obj[])) { 175 static if(!is(typeof(obj[]) == OriginalType)) { 176 return unqual(obj[]); 177 } else static if(__traits(compiles, cast(Unqual!OriginalType) obj)) { 178 return cast(Unqual!OriginalType) obj; 179 } else { 180 return obj; 181 } 182 } else static if(__traits(compiles, cast(Unqual!OriginalType) obj)) { 183 return cast(Unqual!OriginalType) obj; 184 } else 185 return obj; 186 } 187 188 auto ref unvoid(OriginalType)(auto ref OriginalType obj) @trusted { 189 static if(is(OriginalType == void[])) 190 return cast(ubyte[]) obj; 191 else 192 return obj; 193 } 194 195 import std.algorithm: equal; 196 assert_(equal(unvoid(unqual(value)), unvoid(unqual(expected))), file, line); 197 198 } else { 199 assert_(value == expected, file, line); 200 } 201 } 202 203 204 205 /// Assert value is not equal to expected. 206 void shouldNotEqual(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) { 207 assert_(value != expected, file, line); 208 } 209 210 /// Assert value is null. 211 void shouldBeNull(T)(const scope auto ref T value, string file = __FILE__, size_t line = __LINE__) { 212 assert_(value is null, file, line); 213 } 214 215 /// Assert value is not null 216 void shouldNotBeNull(T)(const scope auto ref T value, string file = __FILE__, size_t line = __LINE__) { 217 assert_(value !is null, file, line); 218 } 219 220 enum isLikeAssociativeArray(T, K) = is(typeof({ 221 if(K.init in T) { } 222 if(K.init !in T) { } 223 })); 224 static assert(isLikeAssociativeArray!(string[string], string)); 225 static assert(!isLikeAssociativeArray!(string[string], int)); 226 227 228 /// Assert that value is in container. 229 void shouldBeIn(T, U)(const scope auto ref T value, const scope auto ref U container, string file = __FILE__, size_t line = __LINE__) 230 if(isLikeAssociativeArray!(U, T)) { 231 assert_(cast(bool)(value in container), file, line); 232 } 233 234 /// ditto. 235 void shouldBeIn(T, U)(const scope auto ref T value, U container, string file = __FILE__, size_t line = __LINE__) 236 if (!isLikeAssociativeArray!(U, T)) 237 { 238 import std.algorithm: find; 239 import std.array: empty; 240 assert_(!find(container, value).empty, file, line); 241 } 242 243 /// Assert value is not in container. 244 void shouldNotBeIn(T, U)(const scope auto ref T value, const scope auto ref U container, string file = __FILE__, size_t line = __LINE__) 245 if(isLikeAssociativeArray!U) { 246 assert_(!cast(bool)(value in container), file, line); 247 } 248 249 /// ditto. 250 void shouldNotBeIn(T, U)(const scope auto ref T value, U container, string file = __FILE__, size_t line = __LINE__) 251 if (!isLikeAssociativeArray!(U, T)) 252 { 253 import std.algorithm: find; 254 import std.array: empty; 255 assert_(find(container, value).empty, file, line); 256 } 257 258 /// Assert that expr throws. 259 void shouldThrow(T : Throwable = Exception, E) 260 (lazy E expr, string file = __FILE__, size_t line = __LINE__) { 261 import std.traits: isSafe, isUnsafe; 262 263 static if(isUnsafe!expr) 264 void callExpr() @system { expr(); } 265 else 266 void callExpr() @safe { expr(); } 267 268 bool impl() { 269 try { 270 callExpr; 271 return false; 272 } catch(T _) { 273 return true; 274 } 275 } 276 277 static if(isSafe!callExpr) 278 const threw = () @trusted { return impl; }(); 279 else 280 const threw = impl; 281 282 assert_(threw, file, line); 283 } 284 285 /// Assert that expr throws an Exception that must have the type E, derived types won't do. 286 void shouldThrowExactly(T : Throwable = Exception, E) 287 (lazy E expr, string file = __FILE__, size_t line = __LINE__) 288 { 289 import std.traits: isSafe, isUnsafe; 290 291 TypeInfo typeInfo = null; 292 293 static if(isUnsafe!expr) 294 void callExpr() @system { expr(); } 295 else 296 void callExpr() @safe { expr(); } 297 298 void impl() { 299 try 300 callExpr; 301 catch(T t) { 302 typeInfo = typeid(t); 303 } 304 } 305 306 static if(isSafe!callExpr) 307 () @trusted { return impl; }(); 308 else 309 impl; 310 311 // Object.opEquals is @system and impure 312 const sameType = () @trusted { return typeInfo !is null && typeInfo == typeid(T); }(); 313 assert_(sameType, file, line); 314 } 315 316 317 /// Assert that expr doesn't throw 318 void shouldNotThrow(T: Throwable = Exception, E) 319 (lazy E expr, string file = __FILE__, size_t line = __LINE__) { 320 321 import std.traits: isSafe, isUnsafe; 322 323 static if(isUnsafe!expr) 324 void callExpr() @system { expr(); } 325 else 326 void callExpr() @safe { expr(); } 327 328 void impl() { 329 try 330 callExpr; 331 catch(T t) { 332 assert_(false, file, line); 333 } 334 } 335 336 static if(isSafe!callExpr) 337 () @trusted { return impl; }(); 338 else 339 impl; 340 } 341 342 /// Assert that expr throws and the exception message is msg. 343 void shouldThrowWithMessage(T : Throwable = Exception, E) 344 (lazy E expr, 345 string msg, 346 string file = __FILE__, 347 size_t line = __LINE__) 348 in(msg != "") 349 do 350 { 351 import std.traits: isSafe, isUnsafe; 352 353 string thrownMsg; 354 355 static if(isUnsafe!expr) 356 void callExpr() @system { expr(); } 357 else 358 void callExpr() @safe { expr(); } 359 360 void impl() { 361 try 362 callExpr; 363 catch(T t) { 364 thrownMsg = t.msg; 365 } 366 } 367 368 static if(isSafe!callExpr) 369 () @trusted { return impl; }(); 370 else 371 impl; 372 373 assert_(thrownMsg == msg, file, line); 374 } 375 376 /// Assert that value is approximately equal to expected. 377 void shouldApproxEqual(V, E) 378 (in V value, in E expected, double maxRelDiff = 1e-2, double maxAbsDiff = 1e-5, string file = __FILE__, size_t line = __LINE__) 379 { 380 import std.math: approxEqual; 381 assert_(approxEqual(value, expected, maxRelDiff, maxAbsDiff), file, line); 382 } 383 384 /// assert that rng is empty. 385 void shouldBeEmpty(R)(const scope auto ref R rng, string file = __FILE__, size_t line = __LINE__) { 386 import std.range: isInputRange; 387 import std.traits: isAssociativeArray; 388 import std.array; 389 390 static if(isInputRange!R) 391 assert_(rng.empty, file, line); 392 else static if(isAssociativeArray!R) 393 () @trusted { assert_(rng.keys.empty, file, line); }(); 394 else 395 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 396 } 397 398 /// Assert that rng is not empty. 399 void shouldNotBeEmpty(R)(const scope auto ref R rng, string file = __FILE__, size_t line = __LINE__) { 400 import std.range: isInputRange; 401 import std.traits: isAssociativeArray; 402 import std.array; 403 404 static if(isInputRange!R) 405 assert_(!rnd.empty, file, line); 406 else static if(isAssociativeArray!R) 407 () @trusted { assert_(!rng.keys.empty, file, line); }(); 408 else 409 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 410 } 411 412 /// Assert that t should be greater than u. 413 void shouldBeGreaterThan(T, U)(const scope auto ref T t, const scope auto ref U u, 414 string file = __FILE__, size_t line = __LINE__) 415 { 416 assert_(t > u, file, line); 417 } 418 419 /// Assert that t should be smaller than u. 420 void shouldBeSmallerThan(T, U)(const scope auto ref T t, const scope auto ref U u, 421 string file = __FILE__, size_t line = __LINE__) 422 { 423 assert_(t < u, file, line); 424 } 425 426 /// Assert that value is the same set as expected (i.e. order doesn't matter) 427 void shouldBeSameSetAs(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) { 428 assert_(isSameSet(value, expected), file, line); 429 } 430 431 /// Assert that value is not the same set as expected. 432 void shouldNotBeSameSetAs(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) { 433 assert_(!isSameSet(value, expected), file, line); 434 } 435 436 private bool isSameSet(T, U)(const scope auto ref T t, const scope auto ref U u) { 437 import std.array: array; 438 import std.algorithm: canFind; 439 440 //sort makes the element types have to implement opCmp 441 //instead, try one by one 442 auto ta = t.array; 443 auto ua = u.array; 444 if (ta.length != ua.length) return false; 445 foreach(element; ta) 446 { 447 if (!ua.canFind(element)) return false; 448 } 449 450 return true; 451 } 452 453 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter) 454 void shouldBeSameJsonAs(in string actual, 455 in string expected, 456 string file = __FILE__, 457 size_t line = __LINE__) 458 @trusted // not @safe pure due to parseJSON 459 { 460 import std.json: parseJSON, JSONException; 461 462 auto parse(in string str) { 463 try 464 return str.parseJSON; 465 catch(JSONException ex) { 466 assert_(false, "Failed to parse " ~ str, file, line); 467 } 468 assert(0); 469 } 470 471 assert_(parse(actual) == parse(expected), file, line); 472 } 473 474 475 private void assert_(in bool value, string file, size_t line) @safe pure { 476 assert_(value, "Assertion failure", file, line); 477 } 478 479 private void assert_(bool value, string message, string file, size_t line) @safe pure { 480 if(!value) 481 throw new Exception(message, file, line); 482 } 483 484 void fail(in string output, string file, size_t line) @safe pure { 485 assert_(false, output, file, line); 486 } 487 488 489 auto should(E)(lazy E expr) { 490 491 struct Should { 492 493 bool opEquals(U)(auto ref U other, 494 string file = __FILE__, 495 size_t line = __LINE__) 496 { 497 expr.shouldEqual(other, file, line); 498 return true; 499 } 500 } 501 502 return Should(); 503 }