1 module fluentasserts.core.callable;
2 
3 public import fluentasserts.core.base;
4 import std..string;
5 import std.datetime;
6 import std.conv;
7 import std.traits;
8 
9 import fluentasserts.core.results;
10 
11 @safe:
12 ///
13 struct ShouldCallable(T) {
14   private {
15     T callable;
16   }
17 
18   mixin ShouldCommons;
19   mixin ShouldThrowableCommons;
20 
21   ///
22   this(lazy T callable) {
23     auto result = callable.evaluate;
24 
25     valueEvaluation = result.evaluation;
26     this.callable = result.value;
27   }
28 
29   ///
30   auto haveExecutionTime(string file = __FILE__, size_t line = __LINE__) {
31     validateException;
32 
33     auto tmpShould = ShouldBaseType!Duration(evaluate(valueEvaluation.duration)).forceMessage(" have execution time");
34 
35     return tmpShould;
36   }
37 
38   ///
39   auto beNull(string file = __FILE__, size_t line = __LINE__) {
40     validateException;
41 
42     addMessage(" be ");
43     addValue("null");
44     beginCheck;
45 
46     bool isNull = callable is null;
47 
48     string expected;
49 
50     static if(isDelegate!callable) {
51       string actual = callable.ptr.to!string;
52     } else {
53       string actual = (cast(void*)callable).to!string;
54     }
55 
56     if(expectedValue) {
57       expected = "null";
58     } else {
59       expected = "not null";
60     }
61 
62     return result(isNull, [], new ExpectedActualResult(expected, actual), file, line);
63   }
64 }
65 
66 /// Should be able to catch any exception
67 unittest {
68   ({
69     throw new Exception("test");
70   }).should.throwAnyException.msg.should.equal("test");
71 }
72 
73 /// Should be able to catch any assert
74 unittest {
75   ({
76     assert(false, "test");
77   }).should.throwSomething.withMessage.equal("test");
78 }
79 
80 /// Should be able to use with message without a custom assert
81 unittest {
82   ({
83     assert(false, "test");
84   }).should.throwSomething.withMessage("test");
85 }
86 
87 /// Should be able to catch a certain exception type
88 unittest {
89   class CustomException : Exception {
90     this(string msg, string fileName = "", size_t line = 0, Throwable next = null) {
91       super(msg, fileName, line, next);
92     }
93   }
94 
95   ({
96     throw new CustomException("test");
97   }).should.throwException!CustomException.withMessage("test");
98 
99   bool hasException;
100   try {
101     ({
102       throw new Exception("test");
103     }).should.throwException!CustomException.withMessage("test");
104   } catch(TestException t) {
105     hasException = true;
106     t.msg.should.contain("    }) should throw exception with message equal \"test\". `object.Exception` saying `test` was thrown.");
107   }
108   hasException.should.equal(true).because("we want to catch a CustomException not an Exception");
109 }
110 
111 /// Should be able to retrieve a typed version of a custom exception
112 unittest {
113   class CustomException : Exception {
114     int data;
115     this(int data, string msg, string fileName = "", size_t line = 0, Throwable next = null) {
116       super(msg, fileName, line, next);
117 
118       this.data = data;
119     }
120   }
121 
122   auto thrown = ({
123     throw new CustomException(2, "test");
124   }).should.throwException!CustomException.thrown;
125 
126   thrown.should.not.beNull;
127   thrown.msg.should.equal("test");
128   (cast(CustomException) thrown).data.should.equal(2);
129 }
130 
131 /// Should fail if an exception is not thrown
132 unittest {
133   auto thrown = false;
134   try {
135     ({  }).should.throwAnyException;
136   } catch(TestException e) {
137     thrown = true;
138     e.msg.split("\n")[0].should.equal("({  }) should throw any exception. No exception was thrown.");
139   }
140 
141   thrown.should.equal(true);
142 }
143 
144 /// Should fail if an exception is not expected
145 unittest {
146   auto thrown = false;
147   try {
148     ({
149       throw new Exception("test");
150     }).should.not.throwAnyException;
151   } catch(TestException e) {
152     thrown = true;
153     e.msg.split("\n")[2].should.equal("    }) should not throw any exception. `object.Exception` saying `test` was thrown.");
154   }
155 
156   thrown.should.equal(true);
157 }
158 
159 /// Should be able to benchmark some code
160 unittest {
161   ({
162 
163   }).should.haveExecutionTime.lessThan(1.seconds);
164 }
165 
166 /// Should fail on benchmark timeout
167 unittest {
168   import core.thread;
169 
170   TestException exception = null;
171 
172   try {
173     ({
174       Thread.sleep(2.msecs);
175     }).should.haveExecutionTime.lessThan(1.msecs);
176   } catch(TestException e) {
177     exception = e;
178   }
179 
180   exception.should.not.beNull.because("we wait 20 milliseconds");
181   exception.msg.should.startWith("({\n      Thread.sleep(2.msecs);\n    }) should have execution time less than 1 ms.");
182 }
183 
184 /// It should check if a delegate is null
185 unittest {
186   void delegate() action;
187   action.should.beNull;
188 
189   ({ }).should.not.beNull;
190 
191   auto msg = ({
192     action.should.not.beNull;
193   }).should.throwException!TestException.msg;
194 
195   msg.should.startWith("action should not be null.");
196   msg.should.contain("Expected:not null");
197   msg.should.contain("Actual:null");
198 
199   msg = ({
200     ({ }).should.beNull;
201   }).should.throwException!TestException.msg;
202 
203   msg.should.startWith("({ }) should be null.");
204   msg.should.contain("Expected:null\n");
205   msg.should.not.contain("Actual:null\n");
206 }