1 // Copyright Juan Manuel Cabo 2012. 2 // Copyright Mario Kröplin 2017. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 module dunit.assertion; 8 9 import core.thread; 10 import core.time; 11 import std.algorithm; 12 import std.array; 13 import std.conv; 14 import std.range; 15 import std..string; 16 import std.traits; 17 18 /** 19 * Thrown on an assertion failure. 20 */ 21 class AssertException : Exception 22 { 23 @safe pure nothrow this(string msg, 24 string file = __FILE__, 25 size_t line = __LINE__, 26 Throwable next = null) 27 { 28 super(msg.empty ? "Assertion failure" : msg, file, line, next); 29 } 30 } 31 32 /** 33 * Thrown on an assertion failure. 34 */ 35 class AssertAllException : AssertException 36 { 37 private AssertException[] exceptions; 38 39 @safe pure nothrow this(AssertException[] exceptions, 40 string file = __FILE__, 41 size_t line = __LINE__, 42 Throwable next = null) 43 { 44 string msg = heading(exceptions.length); 45 46 exceptions.each!(exception => msg ~= '\n' ~ exception.description); 47 this.exceptions = exceptions; 48 super(msg, file, line, next); 49 } 50 51 private @safe pure nothrow static string heading(size_t count) 52 { 53 if (count == 1) 54 return "1 assertion failure:"; 55 else 56 return text(count, " assertion failures:"); 57 } 58 } 59 60 /** 61 * Returns a description of the throwable. 62 */ 63 @safe pure nothrow string description(Throwable throwable) 64 { 65 with (throwable) 66 { 67 if (file.empty) 68 return text(typeid(throwable).name, ": ", msg); 69 else 70 return text(typeid(throwable).name, "@", file, "(", line, "): ", msg); 71 } 72 } 73 74 /** 75 * Asserts that a condition is true. 76 * Throws: AssertException otherwise 77 */ 78 void assertTrue(T)(T condition, lazy string msg = null, 79 string file = __FILE__, 80 size_t line = __LINE__) 81 { 82 if (cast(bool) condition) 83 return; 84 85 fail(msg, file, line); 86 } 87 88 /// 89 @safe pure unittest 90 { 91 assertTrue(true); 92 assertTrue("foo" in ["foo": "bar"]); 93 94 auto exception = expectThrows!AssertException(assertTrue(false)); 95 96 assertEquals("Assertion failure", exception.msg); 97 } 98 99 /** 100 * Asserts that a condition is false. 101 * Throws: AssertException otherwise 102 */ 103 void assertFalse(T)(T condition, lazy string msg = null, 104 string file = __FILE__, 105 size_t line = __LINE__) 106 { 107 if (!cast(bool) condition) 108 return; 109 110 fail(msg, file, line); 111 } 112 113 /// 114 @safe pure unittest 115 { 116 assertFalse(false); 117 assertFalse("foo" in ["bar": "foo"]); 118 119 auto exception = expectThrows!AssertException(assertFalse(true)); 120 121 assertEquals("Assertion failure", exception.msg); 122 } 123 124 /** 125 * Asserts that the string values are equal. 126 * Throws: AssertException otherwise 127 */ 128 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 129 string file = __FILE__, 130 size_t line = __LINE__) 131 if (isSomeString!T) 132 { 133 import dunit.diff : description; 134 135 if (expected == actual) 136 return; 137 138 string header = (msg.empty) ? null : msg ~ "; "; 139 140 fail(header ~ description(expected.to!string, actual.to!string), 141 file, line); 142 } 143 144 /// 145 @safe pure unittest 146 { 147 assertEquals("foo", "foo"); 148 149 auto exception = expectThrows!AssertException(assertEquals("bar", "baz")); 150 151 assertEquals("expected: <ba<r>> but was: <ba<z>>", exception.msg); 152 } 153 154 /** 155 * Asserts that the floating-point values are approximately equal. 156 * Throws: AssertException otherwise 157 */ 158 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 159 string file = __FILE__, 160 size_t line = __LINE__) 161 if (isFloatingPoint!T || isFloatingPoint!U) 162 { 163 import std.math : approxEqual; 164 165 if (approxEqual(expected, actual)) 166 return; 167 168 string header = (msg.empty) ? null : msg ~ "; "; 169 170 fail(header ~ format("expected: <%s> but was: <%s>", expected, actual), 171 file, line); 172 } 173 174 /// 175 @safe /*pure*/ unittest // format is impure for floating point values 176 { 177 assertEquals(1, 1.01); 178 179 auto exception = expectThrows!AssertException(assertEquals(1, 1.1)); 180 181 assertEquals("expected: <1> but was: <1.1>", exception.msg); 182 } 183 184 /** 185 * Asserts that the values are equal. 186 * Throws: AssertException otherwise 187 */ 188 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 189 string file = __FILE__, 190 size_t line = __LINE__) 191 if (!isSomeString!T && !isFloatingPoint!T && !isFloatingPoint!U) 192 { 193 if (expected == actual) 194 return; 195 196 string header = (msg.empty) ? null : msg ~ "; "; 197 198 fail(header ~ format("expected: <%s> but was: <%s>", expected, actual), 199 file, line); 200 } 201 202 /// 203 @safe pure unittest 204 { 205 assertEquals(42, 42); 206 207 auto exception = expectThrows!AssertException(assertEquals(42, 24)); 208 209 assertEquals("expected: <42> but was: <24>", exception.msg); 210 } 211 212 /// 213 unittest // Object.opEquals is impure 214 { 215 Object foo = new Object(); 216 Object bar = null; 217 218 assertEquals(foo, foo); 219 assertEquals(bar, bar); 220 221 auto exception = expectThrows!AssertException(assertEquals(foo, bar)); 222 223 assertEquals("expected: <object.Object> but was: <null>", exception.msg); 224 } 225 226 /** 227 * Asserts that the arrays are equal. 228 * Throws: AssertException otherwise 229 */ 230 void assertArrayEquals(T, U)(in T[] expected, in U[] actual, lazy string msg = null, 231 string file = __FILE__, 232 size_t line = __LINE__) 233 { 234 assertRangeEquals(expected, actual, 235 msg, 236 file, line); 237 } 238 239 /** 240 * Asserts that the associative arrays are equal. 241 * Throws: AssertException otherwise 242 */ 243 void assertArrayEquals(T, U, V)(in T[V] expected, in U[V] actual, lazy string msg = null, 244 string file = __FILE__, 245 size_t line = __LINE__) 246 { 247 string header = (msg.empty) ? null : msg ~ "; "; 248 249 foreach (key; expected.byKey) 250 if (key in actual) 251 { 252 assertEquals(expected[key], actual[key], 253 format(header ~ "mismatch at key %s", key.repr), 254 file, line); 255 } 256 257 auto difference = setSymmetricDifference(expected.keys.sort(), actual.keys.sort()); 258 259 assertEmpty(difference, 260 format("key mismatch; difference: %(%s, %)", difference), 261 file, line); 262 } 263 264 /// 265 pure unittest // keys, values, byKey, byValue not usable in @safe context 266 { 267 int[string] expected = ["foo": 1, "bar": 2]; 268 269 assertArrayEquals(expected, ["foo": 1, "bar": 2]); 270 271 AssertException exception; 272 273 exception = expectThrows!AssertException(assertArrayEquals(expected, ["foo": 2])); 274 assertEquals(`mismatch at key "foo"; expected: <1> but was: <2>`, exception.msg); 275 exception = expectThrows!AssertException(assertArrayEquals(expected, ["foo": 1])); 276 assertEquals(`key mismatch; difference: "bar"`, exception.msg); 277 } 278 279 /** 280 * Asserts that the ranges are equal. 281 * Throws: AssertException otherwise 282 */ 283 void assertRangeEquals(R1, R2)(R1 expected, R2 actual, lazy string msg = null, 284 string file = __FILE__, 285 size_t line = __LINE__) 286 if (isInputRange!R1 && isInputRange!R2 && is(typeof(expected.front == actual.front))) 287 { 288 string header = (msg.empty) ? null : msg ~ "; "; 289 size_t index = 0; 290 291 for (; !expected.empty && ! actual.empty; ++index, expected.popFront, actual.popFront) 292 { 293 assertEquals(expected.front, actual.front, 294 header ~ format("mismatch at index %s", index), 295 file, line); 296 } 297 assertEmpty(expected, 298 header ~ format("length mismatch at index %s; ", index) ~ 299 format("expected: <%s> but was: empty", expected.front), 300 file, line); 301 assertEmpty(actual, 302 header ~ format("length mismatch at index %s; ", index) ~ 303 format("expected: empty but was: <%s>", actual.front), 304 file, line); 305 } 306 307 /// 308 @safe pure unittest 309 { 310 int[] expected = [0, 1]; 311 312 assertRangeEquals(expected, [0, 1]); 313 314 AssertException exception; 315 316 exception = expectThrows!AssertException(assertRangeEquals(expected, [0])); 317 assertEquals("length mismatch at index 1; expected: <1> but was: empty", exception.msg); 318 exception = expectThrows!AssertException(assertRangeEquals(expected, [0, 1, 2])); 319 assertEquals("length mismatch at index 2; expected: empty but was: <2>", exception.msg); 320 exception = expectThrows!AssertException(assertArrayEquals("bar", "baz")); 321 assertEquals("mismatch at index 2; expected: <r> but was: <z>", exception.msg); 322 } 323 324 /** 325 * Asserts that the value is empty. 326 * Throws: AssertException otherwise 327 */ 328 void assertEmpty(T)(T actual, lazy string msg = null, 329 string file = __FILE__, 330 size_t line = __LINE__) 331 { 332 if (actual.empty) 333 return; 334 335 fail(msg, file, line); 336 } 337 338 /// 339 @safe pure unittest 340 { 341 assertEmpty([]); 342 343 auto exception = expectThrows!AssertException(assertEmpty([1, 2, 3])); 344 345 assertEquals("Assertion failure", exception.msg); 346 } 347 348 /** 349 * Asserts that the value is not empty. 350 * Throws: AssertException otherwise 351 */ 352 void assertNotEmpty(T)(T actual, lazy string msg = null, 353 string file = __FILE__, 354 size_t line = __LINE__) 355 { 356 if (!actual.empty) 357 return; 358 359 fail(msg, file, line); 360 } 361 362 /// 363 @safe pure unittest 364 { 365 assertNotEmpty([1, 2, 3]); 366 367 auto exception = expectThrows!AssertException(assertNotEmpty([])); 368 369 assertEquals("Assertion failure", exception.msg); 370 } 371 372 /** 373 * Asserts that the value is null. 374 * Throws: AssertException otherwise 375 */ 376 void assertNull(T)(T actual, lazy string msg = null, 377 string file = __FILE__, 378 size_t line = __LINE__) 379 { 380 if (actual is null) 381 return; 382 383 fail(msg, file, line); 384 } 385 386 /// 387 @safe pure unittest 388 { 389 Object foo = new Object(); 390 391 assertNull(null); 392 393 auto exception = expectThrows!AssertException(assertNull(foo)); 394 395 assertEquals("Assertion failure", exception.msg); 396 } 397 398 /** 399 * Asserts that the value is not null. 400 * Throws: AssertException otherwise 401 */ 402 void assertNotNull(T)(T actual, lazy string msg = null, 403 string file = __FILE__, 404 size_t line = __LINE__) 405 { 406 if (actual !is null) 407 return; 408 409 fail(msg, file, line); 410 } 411 412 /// 413 @safe pure unittest 414 { 415 Object foo = new Object(); 416 417 assertNotNull(foo); 418 419 auto exception = expectThrows!AssertException(assertNotNull(null)); 420 421 assertEquals("Assertion failure", exception.msg); 422 } 423 424 /** 425 * Asserts that the values are the same. 426 * Throws: AssertException otherwise 427 */ 428 void assertSame(T, U)(T expected, U actual, lazy string msg = null, 429 string file = __FILE__, 430 size_t line = __LINE__) 431 { 432 if (expected is actual) 433 return; 434 435 string header = (msg.empty) ? null : msg ~ "; "; 436 437 fail(header ~ format("expected same: <%s> was not: <%s>", expected, actual), 438 file, line); 439 } 440 441 /// 442 unittest // format is impure and not safe for Object 443 { 444 Object foo = new Object(); 445 Object bar = new Object(); 446 447 assertSame(foo, foo); 448 449 auto exception = expectThrows!AssertException(assertSame(foo, bar)); 450 451 assertEquals("expected same: <object.Object> was not: <object.Object>", exception.msg); 452 } 453 454 /** 455 * Asserts that the values are not the same. 456 * Throws: AssertException otherwise 457 */ 458 void assertNotSame(T, U)(T expected, U actual, lazy string msg = null, 459 string file = __FILE__, 460 size_t line = __LINE__) 461 { 462 if (expected !is actual) 463 return; 464 465 string header = (msg.empty) ? null : msg ~ "; "; 466 467 fail(header ~ "expected not same", 468 file, line); 469 } 470 471 /// 472 @safe pure unittest 473 { 474 Object foo = new Object(); 475 Object bar = new Object(); 476 477 assertNotSame(foo, bar); 478 479 auto exception = expectThrows!AssertException(assertNotSame(foo, foo)); 480 481 assertEquals("expected not same", exception.msg); 482 } 483 484 /** 485 * Asserts that all assertions pass. 486 * Throws: AssertAllException otherwise 487 */ 488 void assertAll(void delegate() @safe [] assertions ...) @safe 489 { 490 AssertException[] exceptions = null; 491 492 foreach (assertion; assertions) 493 try 494 assertion(); 495 catch (AssertException exception) 496 exceptions ~= exception; 497 if (!exceptions.empty) 498 { 499 // [Issue 16345] IFTI fails with lazy variadic function in some cases 500 const file = null; 501 const line = 0; 502 503 throw new AssertAllException(exceptions, file, line); 504 } 505 } 506 507 /// 508 @safe unittest 509 { 510 assertAll( 511 assertTrue(true), 512 assertFalse(false), 513 ); 514 515 auto exception = expectThrows!AssertException(assertAll( 516 assertTrue(false), 517 assertFalse(true), 518 )); 519 520 assertTrue(exception.msg.canFind("2 assertion failures"), exception.msg); 521 } 522 523 /** 524 * Asserts that the expression throws the specified throwable. 525 * Throws: AssertException otherwise 526 * Returns: the caught throwable 527 */ 528 T expectThrows(T : Throwable = Exception, E)(lazy E expression, lazy string msg = null, 529 string file = __FILE__, 530 size_t line = __LINE__) 531 { 532 try 533 expression(); 534 catch (T throwable) 535 return throwable; 536 537 string header = (msg.empty) ? null : msg ~ "; "; 538 539 fail(header ~ format("expected <%s> was not thrown", T.stringof), 540 file, line); 541 assert(0); 542 } 543 544 /// 545 @safe pure unittest 546 { 547 import std.exception : enforce; 548 549 auto exception = expectThrows(enforce(false)); 550 551 assertEquals("Enforcement failed", exception.msg); 552 } 553 554 /// 555 @safe pure unittest 556 { 557 auto exception = expectThrows!AssertException(expectThrows(42)); 558 559 assertEquals("expected <Exception> was not thrown", exception.msg); 560 } 561 562 /** 563 * Fails a test. 564 * Throws: AssertException 565 */ 566 void fail(string msg = null, 567 string file = __FILE__, 568 size_t line = __LINE__) @safe pure 569 { 570 throw new AssertException(msg, file, line); 571 } 572 573 /// 574 @safe pure unittest 575 { 576 auto exception = expectThrows!AssertException(fail()); 577 578 assertEquals("Assertion failure", exception.msg); 579 } 580 581 alias assertGreaterThan = assertOp!">"; 582 alias assertGreaterThanOrEqual = assertOp!">="; 583 alias assertLessThan = assertOp!"<"; 584 alias assertLessThanOrEqual = assertOp!"<="; 585 alias assertIn = assertOp!"in"; 586 alias assertNotIn = assertOp!"!in"; 587 588 /** 589 * Asserts that the condition (lhs op rhs) is satisfied. 590 * Throws: AssertException otherwise 591 * See_Also: http://d.puremagic.com/issues/show_bug.cgi?id=4653 592 */ 593 template assertOp(string op) 594 { 595 void assertOp(T, U)(T lhs, U rhs, lazy string msg = null, 596 string file = __FILE__, 597 size_t line = __LINE__) 598 { 599 mixin("if (lhs " ~ op ~ " rhs) return;"); 600 601 string header = (msg.empty) ? null : msg ~ "; "; 602 603 fail(format("%scondition (%s %s %s) not satisfied", 604 header, lhs.repr, op, rhs.repr), 605 file, line); 606 } 607 } 608 609 /// 610 @safe pure unittest 611 { 612 assertLessThan(2, 3); 613 614 auto exception = expectThrows!AssertException(assertGreaterThanOrEqual(2, 3)); 615 616 assertEquals("condition (2 >= 3) not satisfied", exception.msg); 617 } 618 619 /// 620 @safe pure unittest 621 { 622 assertIn("foo", ["foo": "bar"]); 623 624 auto exception = expectThrows!AssertException(assertNotIn("foo", ["foo": "bar"])); 625 626 assertEquals(`condition ("foo" !in ["foo":"bar"]) not satisfied`, exception.msg); 627 } 628 629 /** 630 * Checks a probe until the timeout expires. The assert error is produced 631 * if the probe fails to return 'true' before the timeout. 632 * 633 * The parameter timeout determines the maximum timeout to wait before 634 * asserting a failure (default is 500ms). 635 * 636 * The parameter delay determines how often the predicate will be 637 * checked (default is 10ms). 638 * 639 * This kind of assertion is very useful to check on code that runs in another 640 * thread. For instance, the thread that listens to a socket. 641 * 642 * Throws: AssertException when the probe fails to become true before timeout 643 */ 644 public static void assertEventually(bool delegate() probe, 645 Duration timeout = 500.msecs, Duration delay = 10.msecs, 646 lazy string msg = null, 647 string file = __FILE__, 648 size_t line = __LINE__) 649 { 650 const startTime = TickDuration.currSystemTick(); 651 652 while (!probe()) 653 { 654 const elapsedTime = cast(Duration)(TickDuration.currSystemTick() - startTime); 655 656 if (elapsedTime >= timeout) 657 fail(msg.empty ? "timed out" : msg, file, line); 658 659 Thread.sleep(delay); 660 } 661 } 662 663 /// 664 unittest 665 { 666 assertEventually({ static count = 0; return ++count > 23; }); 667 668 auto exception = expectThrows!AssertException(assertEventually({ return false; })); 669 670 assertEquals("timed out", exception.msg); 671 } 672 673 private string repr(T)(T value) 674 { 675 // format string key with double quotes 676 return format("%(%s%)", value.only); 677 }