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