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 in string file = __FILE__, in 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, in string file = __FILE__, in 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, in string file = __FILE__, in 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, in string file = __FILE__, in 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, in string file = __FILE__, in 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)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 207 assert_(value != expected, file, line); 208 } 209 210 /// Assert value is null. 211 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 212 assert_(value is null, file, line); 213 } 214 215 /// Assert value is not null 216 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in 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)(in auto ref T value, in auto ref U container, in string file = __FILE__, in 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)(in auto ref T value, U container, in string file = __FILE__, in 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)(in auto ref T value, in auto ref U container, in string file = __FILE__, in 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)(in auto ref T value, U container, in string file = __FILE__, in 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, in string file = __FILE__, in size_t line = __LINE__) { 261 import std.traits: isSafe, isUnsafe; 262 263 auto threw = false; 264 265 static if(isUnsafe!expr) 266 void callExpr() @system { expr(); } 267 else 268 void callExpr() @safe { expr(); } 269 270 bool impl() { 271 try { 272 callExpr; 273 return false; 274 } catch(T _) { 275 return true; 276 } 277 } 278 279 static if(isSafe!callExpr) 280 threw = () @trusted { return impl; }(); 281 else 282 threw = impl; 283 284 assert_(threw, file, line); 285 } 286 287 /// Assert that expr throws an Exception that must have the type E, derived types won't do. 288 void shouldThrowExactly(T : Throwable = Exception, E) 289 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) 290 { 291 import std.traits: isSafe, isUnsafe; 292 293 T throwable = null; 294 295 static if(isUnsafe!expr) 296 void callExpr() @system { expr(); } 297 else 298 void callExpr() @safe { expr(); } 299 300 void impl() { 301 try 302 callExpr; 303 catch(T t) { 304 throwable = t; 305 } 306 } 307 308 static if(isSafe!callExpr) 309 () @trusted { return impl; }(); 310 else 311 impl; 312 313 //Object.opEquals is @system and impure 314 const sameType = () @trusted { return throwable !is null && typeid(throwable) == typeid(T); }(); 315 assert_(sameType, file, line); 316 } 317 318 319 /// Assert that expr doesn't throw 320 void shouldNotThrow(T: Throwable = Exception, E) 321 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) { 322 323 import std.traits: isSafe, isUnsafe; 324 325 static if(isUnsafe!expr) 326 void callExpr() @system { expr(); } 327 else 328 void callExpr() @safe { expr(); } 329 330 void impl() { 331 try 332 callExpr; 333 catch(T t) { 334 assert_(false, file, line); 335 } 336 } 337 338 static if(isSafe!callExpr) 339 () @trusted { return impl; }(); 340 else 341 impl; 342 } 343 344 /// Assert that expr throws and the exception message is msg. 345 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, 346 string msg, 347 string file = __FILE__, 348 size_t line = __LINE__) { 349 import std.traits: isSafe, isUnsafe; 350 351 T throwable = null; 352 353 static if(isUnsafe!expr) 354 void callExpr() @system { expr(); } 355 else 356 void callExpr() @safe { expr(); } 357 358 void impl() { 359 try 360 callExpr; 361 catch(T t) { 362 throwable = t; 363 } 364 } 365 366 static if(isSafe!callExpr) 367 () @trusted { return impl; }(); 368 else 369 impl; 370 371 assert_(throwable !is null && throwable.msg == msg, file, line); 372 } 373 374 /// Assert that value is approximately equal to expected. 375 void shouldApproxEqual(V, E) 376 (in V value, in E expected, double maxRelDiff = 1e-2, double maxAbsDiff = 1e-5, string file = __FILE__, size_t line = __LINE__) 377 { 378 import std.math: approxEqual; 379 assert_(approxEqual(value, expected, maxRelDiff, maxAbsDiff), file, line); 380 } 381 382 /// assert that rng is empty. 383 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 384 import std.range: isInputRange; 385 import std.traits: isAssociativeArray; 386 import std.array; 387 388 static if(isInputRange!R) 389 assert_(rng.empty, file, line); 390 else static if(isAssociativeArray!R) 391 () @trusted { assert_(rng.keys.empty, file, line); }(); 392 else 393 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 394 } 395 396 /// Assert that rng is not empty. 397 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 398 import std.range: isInputRange; 399 import std.traits: isAssociativeArray; 400 import std.array; 401 402 static if(isInputRange!R) 403 assert_(!rnd.empty, file, line); 404 else static if(isAssociativeArray!R) 405 () @trusted { assert_(!rng.keys.empty, file, line); }(); 406 else 407 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 408 } 409 410 /// Assert that t should be greater than u. 411 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u, 412 in string file = __FILE__, in size_t line = __LINE__) 413 { 414 assert_(t > u, file, line); 415 } 416 417 /// Assert that t should be smaller than u. 418 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u, 419 in string file = __FILE__, in size_t line = __LINE__) 420 { 421 assert_(t < u, file, line); 422 } 423 424 /// Assert that value is the same set as expected (i.e. order doesn't matter) 425 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 426 assert_(isSameSet(value, expected), file, line); 427 } 428 429 /// Assert that value is not the same set as expected. 430 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 431 assert_(!isSameSet(value, expected), file, line); 432 } 433 434 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) { 435 import std.array: array; 436 import std.algorithm: canFind; 437 438 //sort makes the element types have to implement opCmp 439 //instead, try one by one 440 auto ta = t.array; 441 auto ua = u.array; 442 if (ta.length != ua.length) return false; 443 foreach(element; ta) 444 { 445 if (!ua.canFind(element)) return false; 446 } 447 448 return true; 449 } 450 451 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter) 452 void shouldBeSameJsonAs(in string actual, 453 in string expected, 454 in string file = __FILE__, 455 in size_t line = __LINE__) 456 @trusted // not @safe pure due to parseJSON 457 { 458 import std.json: parseJSON, JSONException; 459 460 auto parse(in string str) { 461 try 462 return str.parseJSON; 463 catch(JSONException ex) { 464 assert_(false, "Failed to parse " ~ str, file, line); 465 } 466 assert(0); 467 } 468 469 assert_(parse(actual) == parse(expected), file, line); 470 } 471 472 473 private void assert_(in bool value, in string file, in size_t line) @safe pure { 474 assert_(value, "Assertion failure", file, line); 475 } 476 477 private void assert_(bool value, in string message, in string file, in size_t line) @safe pure { 478 if(!value) 479 throw new Exception(message, file, line); 480 } 481 482 void fail(in string output, in string file, in size_t line) @safe pure { 483 assert_(false, output, file, line); 484 } 485 486 487 auto should(E)(lazy E expr) { 488 489 struct Should { 490 491 bool opEquals(U)(auto ref U other, 492 in string file = __FILE__, 493 in size_t line = __LINE__) 494 { 495 expr.shouldEqual(other, file, line); 496 return true; 497 } 498 } 499 500 return Should(); 501 }