OSDN Git Service

Add infinitely repeating producing mock
[transunit/transunit.git] / transunit.mock.m
1 % Copyright (C) 2019 Alaskan Emily, Transnat Games.
2 %
3 % This software is provided 'as-is', without any express or implied warranty.
4 % In no event will the authors be held liable for any damages arising from
5 % the use of this software.
6 %
7 % Permission is granted to anyone to use this software for any purpose,
8 % including commercial applications, and to alter it and redistribute it
9 % freely, subject to the following restrictions:
10 %
11 %   1. The origin of this software must not be misrepresented; you must not
12 %      claim that you wrote the original software. If you use this software
13 %      in a product, an acknowledgment in the product documentation would be
14 %      appreciated but is not required.
15 %
16 %   2. Altered source versions must be plainly marked as such, and must not
17 %      be misrepresented as being the original software.
18 %
19 %   3. This notice may not be removed or altered from any source distribution.
20 %
21
22 :- module transunit.mock.
23
24 %=============================================================================%
25 % Implementation of a basic mock type. This is useful for implementing a
26 % typeclass you want to test, giving known results for typeclass preds/funcs.
27 :- interface.
28 %=============================================================================%
29
30 :- import_module list.
31 :- use_module array.
32 :- use_module maybe.
33 :- use_module io.
34
35 %-----------------------------------------------------------------------------%
36 % A mock represents a list of results that can be given as output.
37 % It is operationally equivalent to a list.
38 :- type mock(T).
39
40 %-----------------------------------------------------------------------------%
41 % Determines if a mock will repeat its contents or not.
42 :- type mock_option ---> repeat ; oneshot.
43
44 %-----------------------------------------------------------------------------%
45 % Determines if the mock considers any form of contention to be a fatal error.
46 % not_threadsafe will avoid a deadlock in incorrect usage of state_mocks, but
47 % is not suitable for multi-threaded use.
48 :- type state_option ---> threadsafe ; not_threadsafe.
49
50 %-----------------------------------------------------------------------------%
51 % state_mock's use IO instead of updates to change their inputs.
52 :- type state_mock(T).
53
54 %-----------------------------------------------------------------------------%
55 % maybe_mock's hold maybe's of values. Unlike a regular mock which always
56 % yields results until it is empty, a maybe mock will fail to yield when the
57 % item it would yield is maybe.no. This also unwraps the maybe's.
58 :- type maybe_mock(T) == mock(maybe.maybe(T)).
59
60 %-----------------------------------------------------------------------------%
61 % maybe_state_mock are a combination of state_mock and maybe_mock.
62 :- type maybe_state_mock(T) == state_mock(maybe.maybe(T)).
63
64 %-----------------------------------------------------------------------------%
65 % Creates a oneshot mock from a list of inputs.
66 :- func init_mock(list(T)) = mock(T).
67
68 %-----------------------------------------------------------------------------%
69 % Creates a mock from a list of inputs.
70 :- func init_mock(mock_option, list(T)) = mock(T).
71
72 %-----------------------------------------------------------------------------%
73 % Creates a oneshot mock from an array of inputs.
74 :- func init_mock_from_array(array.array(T)) = mock(T).
75
76 %-----------------------------------------------------------------------------%
77 % Creates a mock from an array of inputs.
78 :- func init_mock_from_array(mock_option, array.array(T)) = mock(T).
79
80 %-----------------------------------------------------------------------------%
81 % Creates a oneshot state_mock from a list of inputs.
82 :- pred init_state_mock(state_option,
83     list(T),
84     state_mock(T),
85     io.io, io.io).
86 :- mode init_state_mock(in, in, out, di, uo) is det.
87
88 %-----------------------------------------------------------------------------%
89 % Creates a state_mock from a list of inputs.
90 :- pred init_state_mock(state_option,
91     mock_option,
92     list(T),
93     state_mock(T),
94     io.io, io.io).
95 :- mode init_state_mock(in, in, in, out, di, uo) is det.
96
97 %-----------------------------------------------------------------------------%
98 % Creates a state_mock from an array of inputs.
99 :- pred init_state_mock_from_array(state_option,
100     mock_option,
101     array.array(T),
102     state_mock(T),
103     io.io, io.io).
104 :- mode init_state_mock_from_array(in, in, in, out, di, uo) is det.
105
106 %-----------------------------------------------------------------------------%
107 % Creates a oneshot state_mock from an array of inputs.
108 :- pred init_state_mock_from_array(state_option,
109     array.array(T),
110     state_mock(T),
111     io.io, io.io).
112 :- mode init_state_mock_from_array(in, in, out, di, uo) is det.
113
114 %-----------------------------------------------------------------------------%
115 % Removes the first item from the mock, updating the mock. If the mock was
116 % empty then it is left as-is.
117 % Yields maybe.no if no items remain. Otherwise yields maybe.yes of the item.
118 :- pred mock_retrieve(maybe.maybe(T), mock(T), mock(T)).
119 :- mode mock_retrieve(out, in, out) is det.
120
121 %-----------------------------------------------------------------------------%
122 % Removes the first item from the mock, updating the mock. If the mock was
123 % empty then it is left as-is.
124 % Yields maybe.no if no items remain or the item was maybe.no. Yields the item
125 % if it is a maybe.yes.
126 :- pred maybe_mock_retrieve(maybe.maybe(T), maybe_mock(T), maybe_mock(T)).
127 :- mode maybe_mock_retrieve(out, in, out) is det.
128
129 %-----------------------------------------------------------------------------%
130 % Removes the first item from the mock, updating the mock. Fails if the mock
131 % was empty.
132 % Yields maybe.no if no items remain. Otherwise yields maybe.yes of the item.
133 :- pred mock_try_retrieve(T, mock(T), mock(T)).
134 :- mode mock_try_retrieve(out, in, out) is semidet.
135
136 %-----------------------------------------------------------------------------%
137 % Removes the first item from the mock, updating the mock. If the mock was
138 % empty then it is left as-is.
139 % Yields maybe.no if no items remain. Otherwise yields maybe.yes of the item.
140 :- pred state_mock_retrieve(state_mock(T), maybe.maybe(T), io.io, io.io).
141 :- mode state_mock_retrieve(in, out, di, uo) is det.
142
143 %-----------------------------------------------------------------------------%
144 % Removes the first item from the mock, updating the mock with IO state. If the
145 % mock was empty then it is left as-is.
146 % Yields maybe.no if no items remain or the item was maybe.no. Yields the item
147 % if it is a maybe.yes.
148 :- pred maybe_state_mock_retrieve(maybe_state_mock(T), maybe.maybe(T), io.io, io.io).
149 :- mode maybe_state_mock_retrieve(in, out, di, uo) is det.
150
151 %=============================================================================%
152 :- implementation.
153 %=============================================================================%
154
155 :- use_module exception.
156 :- use_module int.
157 :- use_module thread.
158 :- use_module std_util.
159 :- use_module thread.mvar.
160
161 %-----------------------------------------------------------------------------%
162
163 :- type mock(T) --->
164     repeat(array.array(T), int) ;
165     oneshot(list(T)).
166
167 :- type state_mock(T) ---> state_mock(thread.mvar.mvar(mock(T)), state_option).
168
169 %-----------------------------------------------------------------------------%
170
171 init_mock(List) = oneshot(List).
172
173 %-----------------------------------------------------------------------------%
174
175 init_mock(oneshot, List) = oneshot(List).
176 init_mock(repeat, []) = oneshot([]).
177 init_mock(repeat, List) = repeat(array.array(List), 0) :- List = [_|_].
178
179 %-----------------------------------------------------------------------------%
180
181 init_mock_from_array(A) = init_mock(array.to_list(A)).
182
183 %-----------------------------------------------------------------------------%
184
185 init_mock_from_array(oneshot, A) = oneshot(array.to_list(A)).
186 init_mock_from_array(repeat, A) = Result :-
187     array.bounds(A, Low, High),
188     builtin.compare(Cmp, Low, High),
189     (
190         Cmp = (=), Result = oneshot([])
191     ;
192         Cmp = (<), Result = repeat(array.copy(A), Low)
193     ;
194         Cmp = (>),
195         exception.throw(exception.software_error("Invalid array bounds"))
196     ).
197
198 %-----------------------------------------------------------------------------%
199
200 init_state_mock(Option, List, state_mock(Mvar, Option), !IO) :-
201     thread.mvar.init(init_mock(List), Mvar, !IO).
202
203 %-----------------------------------------------------------------------------%
204
205 init_state_mock(Option, MockOption, List, state_mock(Mvar, Option), !IO) :-
206     thread.mvar.init(init_mock(MockOption, List), Mvar, !IO).
207
208 %-----------------------------------------------------------------------------%
209
210 init_state_mock_from_array(Option, A, state_mock(Mvar, Option), !IO) :-
211     thread.mvar.init(init_mock_from_array(A), Mvar, !IO).
212
213 %-----------------------------------------------------------------------------%
214
215 init_state_mock_from_array(Option, MockOption, A, state_mock(Mvar, Option), !IO) :-
216     thread.mvar.init(init_mock_from_array(MockOption, A), Mvar, !IO).
217
218 %-----------------------------------------------------------------------------%
219
220 :- pred repeat_mock_retrieve(array.array(T), int, T, mock(T)).
221 :- mode repeat_mock_retrieve(in, in, out, out) is det.
222
223 repeat_mock_retrieve(A, I, T, repeat(A, N)) :-
224     array.unsafe_lookup(A, I, T),
225     array.bounds(A, Low, High),
226     M = int.plus(I, 1),
227     builtin.compare(Cmp, M, High),
228     (
229         Cmp = (=), N = Low
230     ;
231         Cmp = (<), N = M
232     ;
233         Cmp = (>),
234         exception.throw(exception.software_error("Invalid repeat index"))
235     ).
236
237 %-----------------------------------------------------------------------------%
238
239 mock_retrieve(maybe.no, oneshot([]), oneshot([])).
240 mock_retrieve(maybe.yes(T), oneshot([T|List]), oneshot(List)).
241 mock_retrieve(maybe.yes(T), repeat(A, I), Out) :-
242     repeat_mock_retrieve(A, I, T, Out).
243
244 %-----------------------------------------------------------------------------%
245
246 maybe_mock_retrieve(maybe.no, oneshot([]), oneshot([])).
247 maybe_mock_retrieve(T, oneshot([T|List]), oneshot(List)).
248 maybe_mock_retrieve(T, repeat(A, I), Out) :-
249     repeat_mock_retrieve(A, I, T, Out).
250
251 %-----------------------------------------------------------------------------%
252
253 mock_try_retrieve(T, oneshot([T|List]), oneshot(List)).
254 mock_try_retrieve(T, In, Out) :-
255     require_det (
256         In = repeat(A, I),
257         repeat_mock_retrieve(A, I, T, Out)
258     ).
259
260 %-----------------------------------------------------------------------------%
261 % Gets the mock from a state_mock.
262 % - In threadsafe mode this will block for the mock to be available.
263 % - In not-threadsafe mode this will error if the mock is not available.
264 :- pred get_mock(state_mock(T), thread.mvar.mvar(mock(T)), mock(T), io.io, io.io).
265 :- mode get_mock(in, out, out, di, uo) is det.
266
267 get_mock(state_mock(Mvar, not_threadsafe), Mvar, Mock, !IO) :-
268     thread.mvar.try_take(Mvar, MaybeMock, !IO),
269     (
270         MaybeMock = maybe.yes(Mock)
271     ;
272         % This indicates a horrible mistake was made somewhere.
273         MaybeMock = maybe.no,
274         exception.throw(exception.software_error("Invalid state in state_mock"))
275     ).
276 get_mock(state_mock(Mvar, threadsafe), Mvar, Mock, !IO) :-
277     thread.mvar.take(Mvar, Mock, !IO).
278
279 %-----------------------------------------------------------------------------%
280
281 state_mock_retrieve(StateMock, Out, !IO) :-
282     get_mock(StateMock, Mvar, MockIn, !IO),
283     mock_retrieve(Out, MockIn, MockOut),
284     thread.mvar.put(Mvar, MockOut, !IO).
285
286 %-----------------------------------------------------------------------------%
287
288 maybe_state_mock_retrieve(StateMock, Out, !IO) :-
289     get_mock(StateMock, Mvar, MockIn, !IO),
290     maybe_mock_retrieve(Out, MockIn, MockOut),
291     thread.mvar.put(Mvar, MockOut, !IO).