1 module dmocks.expectation;
2 
3 import dmocks.action;
4 import dmocks.arguments;
5 import dmocks.call;
6 import dmocks.model;
7 import dmocks.name_match;
8 import dmocks.qualifiers;
9 import dmocks.util;
10 import std.conv;
11 import std.range;
12 import std.traits;
13 
14 package:
15 
16 class CallExpectation : Expectation
17 {
18     this(MockId object, NameMatch name, QualifierMatch qualifiers, ArgumentsMatch args, TypeInfo returnType)
19     {
20         this.object = object;
21         this.name = name;
22         this.repeatInterval = Interval(1, 1);
23         this.arguments = args;
24         this.qualifiers = qualifiers;
25         this.action = new Action(returnType);
26         _matchedCalls = [];
27     }
28 
29     MockId object;
30     NameMatch name;
31     QualifierMatch qualifiers;
32     ArgumentsMatch arguments;
33     private Call[] _matchedCalls;
34     Interval repeatInterval;
35 
36     Action action;
37 
38     override string toString(string indentation)
39     {
40         auto apndr = appender!(string);
41         apndr.put(indentation);
42         apndr.put("Expectation: ");
43         bool details = !satisfied;
44 
45         if (!details)
46             apndr.put("satisfied, ");
47         else
48             apndr.put("not satisfied, ");
49 
50         apndr.put("Method: ");
51         apndr.put(name.toString());
52         apndr.put(" ");
53         apndr.put(arguments.toString());
54         apndr.put(" ");
55         apndr.put(qualifiers.toString());
56         apndr.put("\n");
57         apndr.put(indentation);
58         apndr.put("ExpectedCalls: ");
59         apndr.put(repeatInterval.toString());
60 
61         if (details)
62         {
63             apndr.put("\n" ~ indentation ~ "Calls: " ~ _matchedCalls.length.to!string);
64             foreach (Call call; _matchedCalls)
65             {
66                 apndr.put("\n" ~ indentation ~ "  " ~ call.toString());
67             }
68         }
69         return apndr.data;
70     }
71 
72     string diffToString(Call call)
73     in (name.matches(call.name), "logic error: trying to format expectation diff to call that doesn't match it")
74     {
75         string result;
76 
77         if (object != call.object)
78         {
79             result ~= yellow("(different object).");
80         }
81         result ~= call.name;
82         result ~= " ";
83         result ~= arguments.diffToString(call.arguments);
84         result ~= " ";
85         result ~= qualifiers.diffToString(call.qualifiers);
86         if (_matchedCalls.length >= repeatInterval.Max)
87         {
88             result ~= " (called too many times)";
89         }
90         result ~= "\n";
91         return result;
92     }
93 
94     override string toString()
95     {
96         return toString("");
97     }
98 
99     CallExpectation match(Call call)
100     {
101         debugLog("Expectation.match:");
102         if (object != call.object)
103         {
104             debugLog("object doesn't match");
105             return null;
106         }
107         if (!name.matches(call.name))
108         {
109             debugLog("name doesn't match");
110             return null;
111         }
112         if (!qualifiers.matches(call.qualifiers))
113         {
114             debugLog("qualifiers don't match");
115             return null;
116         }
117         if (!arguments.matches(call.arguments))
118         {
119             debugLog("arguments don't match");
120             return null;
121         }
122         if (_matchedCalls.length >= repeatInterval.Max)
123         {
124             debugLog("repeat interval desn't match");
125             return null;
126         }
127         debugLog("full match");
128         _matchedCalls ~= call;
129         return this;
130     }
131 
132     bool satisfied()
133     {
134         return _matchedCalls.length <= repeatInterval.Max && _matchedCalls.length >= repeatInterval.Min;
135     }
136 }
137 
138 class GroupExpectation : Expectation
139 {
140     this()
141     {
142         repeatInterval = Interval(1, 1);
143         expectations = [];
144     }
145 
146     Expectation[] expectations;
147     bool ordered;
148     Interval repeatInterval;
149 
150     CallExpectation match(Call call)
151     {
152         // match call to expectation
153         foreach (Expectation expectation; expectations)
154         {
155             CallExpectation e = expectation.match(call);
156             if (e !is null)
157                 return e;
158             if (ordered && !expectation.satisfied())
159                 return null;
160         }
161         return null;
162     }
163 
164     void addExpectation(Expectation expectation)
165     {
166         expectations ~= expectation;
167     }
168 
169     bool satisfied()
170     {
171         foreach (Expectation expectation; expectations)
172         {
173             if (!expectation.satisfied())
174                 return false;
175         }
176         return true;
177     }
178 
179     override string toString(string indentation)
180     {
181         auto apndr = appender!(string);
182         apndr.put(indentation);
183         apndr.put("GroupExpectation: ");
184         bool details = !satisfied;
185 
186         if (!details)
187             apndr.put("satisfied, ");
188         else
189             apndr.put("not satisfied, ");
190 
191         if (ordered)
192             apndr.put("ordered, ");
193         else
194             apndr.put("unordered, ");
195 
196         apndr.put("Interval: ");
197         apndr.put(repeatInterval.toString());
198 
199         if (details)
200         {
201             foreach (Expectation expectation; expectations)
202             {
203                 apndr.put("\n");
204                 apndr.put(expectation.toString(indentation ~ "  "));
205             }
206         }
207         return apndr.data;
208     }
209 
210     override string toString()
211     {
212         return toString("");
213     }
214 }
215 
216 GroupExpectation createGroupExpectation(bool ordered)
217 {
218     auto ret = new GroupExpectation();
219     ret.ordered = ordered;
220     return ret;
221 }
222 
223 // composite design pattern
224 interface Expectation
225 {
226     CallExpectation match(Call call);
227     bool satisfied();
228     string toString(string indentation);
229     string toString();
230 }
231 
232 CallExpectation createExpectation(alias METHOD, ARGS...)(MockId object, string name, ARGS args)
233 {
234     auto ret = new CallExpectation(object, new NameMatchText(name), qualifierMatch!METHOD,
235             new StrictArgumentsMatch(arguments(args)), typeid(ReturnType!(FunctionTypeOf!(METHOD))));
236     return ret;
237 }