1 module dmocks.repository;
2 
3 import dmocks.util;
4 import dmocks.model;
5 import dmocks.arguments;
6 
7 import std.stdio;
8 import std.conv;
9 import std.traits;
10 
11 import dmocks.call;
12 import dmocks.expectation;
13 import dmocks.action;
14 
15 package:
16 
17 class MockRepository
18 {
19     // TODO: split this up somehow!
20     private bool _allowDefaults = false;
21     private bool _recording = true;
22     private bool _ordered = false;
23     private bool _allowUnexpected = false;
24 
25     private Call[] _calls = [];
26     private Call[] _unexpectedCalls = [];
27     private GroupExpectation _rootGroupExpectation;
28     private CallExpectation _lastRecordedCallExpectation; // stores last call added to _lastGroupExpectation
29     private Call _lastRecordedCall; // stores last call with which _lastRecordedCallExpectation was created
30     private GroupExpectation _lastGroupExpectation; // stores last group added to _rootGroupExpectation
31 
32     private void CheckLastCallSetup ()
33     {
34         if (_allowDefaults || _lastRecordedCallExpectation is null || _lastRecordedCallExpectation.action.hasAction)
35         {
36             return;
37         }
38 
39         throw new MocksSetupException(
40                 "Last expectation: if you do not specify the AllowDefaults option, you need to return a value, throw an exception, execute a delegate, or pass through to base function. The expectation is: " ~ _lastRecordedCallExpectation.toString());
41     }
42 
43     this()
44     {   
45         _rootGroupExpectation = createGroupExpectation(false);
46         Ordered(false);
47     }
48 
49 
50     void AllowDefaults (bool value)
51     {
52         _allowDefaults = value;
53     }
54 
55     void AllowUnexpected(bool value)
56     {
57         _allowUnexpected = value;
58     }
59 
60     bool AllowUnexpected()
61     {
62         return _allowUnexpected;
63     }
64 
65     bool Recording ()
66     {
67         return _recording;
68     }
69 
70     bool Ordered ()
71     {
72         return _ordered;
73     }
74 
75     void Replay ()
76     {
77         CheckLastCallSetup();
78         _recording = false;
79     }
80 
81     void BackToRecord ()
82     {
83         _recording = true;
84     }
85 
86     void Ordered(bool value)
87     {
88         debugLog("SETTING ORDERED: %s", value);
89         _ordered = value;
90         _lastGroupExpectation = createGroupExpectation(_ordered);
91         _rootGroupExpectation.addExpectation(_lastGroupExpectation);
92     }
93 
94     void Record(CallExpectation expectation, Call call)
95     {
96         CheckLastCallSetup();
97         _lastGroupExpectation.addExpectation(expectation);
98         _lastRecordedCallExpectation = expectation;
99         _lastRecordedCall = call;
100     }
101 
102     @trusted public auto MethodCall (alias METHOD, ARGS...) (MockId mocked, string name, ARGS args)
103     {
104         alias ReturnType!(FunctionTypeOf!(METHOD)) TReturn;
105 
106         ReturnOrPass!(TReturn) rope;
107         auto call = createCall!METHOD(mocked, name, args);
108         debugLog("checking Recording...");
109         if (Recording)
110         {
111             auto expectation = createExpectation!(METHOD)(mocked, name, args);
112             Record(expectation, call);
113             return rope;
114         }
115 
116         debugLog("checking for matching expectation...");
117         auto expectation = Match(call);
118 
119         debugLog("checking if expectation is null...");
120         if (expectation is null)
121         {
122             if (AllowUnexpected())
123                 return rope;
124             throw new ExpectationViolationException("Unexpected call to method: " ~ call.toString());
125         }
126 
127         rope = expectation.action.getActor().act!(TReturn, ARGS)(args);
128         debugLog("returning...");
129         return rope;
130     }
131 
132     CallExpectation Match(Call call)
133     {
134         _calls ~= call;
135         auto exp = _rootGroupExpectation.match(call);
136         if (exp is null)
137             _unexpectedCalls ~= _calls;
138         return exp;
139     }
140 
141     CallExpectation LastRecordedCallExpectation ()
142     {
143         return _lastRecordedCallExpectation;
144     }
145 
146     Call LastRecordedCall ()
147     {
148         return _lastRecordedCall;
149     }
150 
151     void Verify (bool checkUnmatchedExpectations, bool checkUnexpectedCalls)
152     {
153         string expectationError = "";
154         if (checkUnmatchedExpectations && !_rootGroupExpectation.satisfied)
155             expectationError~="\n" ~ _rootGroupExpectation.toString();
156                 
157         if (checkUnexpectedCalls && _unexpectedCalls.length > 0)
158             expectationError~="\n" ~ UnexpectedCallsReport;
159         if (expectationError != "")
160             throw new ExpectationViolationException(expectationError);
161     }
162 
163     string UnexpectedCallsReport()
164     {
165         import std.array;
166         auto apndr = appender!(string);
167         apndr.put("Unexpected calls(calls):\n");
168         foreach(Call ev; _unexpectedCalls)
169         {
170             apndr.put(ev.toString());
171             apndr.put("\n");
172         }
173         return apndr.data;
174     }
175 
176     version (DMocksTest)
177     {
178         unittest {
179             mixin(test!("repository record/replay"));
180 
181             MockRepository r = new MockRepository();
182             assert (r.Recording());
183             r.Replay();
184             assert (!r.Recording());
185             r.BackToRecord();
186             assert (r.Recording());
187         }
188 
189         
190         // test for correctly formulated template
191         unittest {
192             class A
193             {
194                 public void a()
195                 {
196                 }
197             }
198             auto a = new A;
199             auto c = new MockRepository();
200             auto mid = new MockId;
201             //c.Call!(a.a)(mid, "a");
202             static assert(__traits(compiles, c.MethodCall!(a.a)(mid, "a")));
203         }
204     }
205 }