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 }