1 % Copyright (C) 2019 Alaskan Emily, Transnat Games.
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.
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:
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.
16 % 2. Altered source versions must be plainly marked as such, and must not
17 % be misrepresented as being the original software.
19 % 3. This notice may not be removed or altered from any source distribution.
22 :- module transunit.mock.
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.
28 %=============================================================================%
30 :- import_module list.
35 %-----------------------------------------------------------------------------%
36 % A mock represents a list of results that can be given as output.
37 % It is operationally equivalent to a list.
40 %-----------------------------------------------------------------------------%
41 % Determines if a mock will repeat its contents or not.
42 :- type mock_option ---> repeat ; oneshot.
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.
50 %-----------------------------------------------------------------------------%
51 % state_mock's use IO instead of updates to change their inputs.
52 :- type state_mock(T).
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)).
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)).
64 %-----------------------------------------------------------------------------%
65 % Creates a oneshot mock from a list of inputs.
66 :- func init_mock(list(T)) = mock(T).
68 %-----------------------------------------------------------------------------%
69 % Creates a mock from a list of inputs.
70 :- func init_mock(mock_option, list(T)) = mock(T).
72 %-----------------------------------------------------------------------------%
73 % Creates a oneshot mock from an array of inputs.
74 :- func init_mock_from_array(array.array(T)) = mock(T).
76 %-----------------------------------------------------------------------------%
77 % Creates a mock from an array of inputs.
78 :- func init_mock_from_array(mock_option, array.array(T)) = mock(T).
80 %-----------------------------------------------------------------------------%
81 % Creates a oneshot state_mock from a list of inputs.
82 :- pred init_state_mock(state_option,
86 :- mode init_state_mock(in, in, out, di, uo) is det.
88 %-----------------------------------------------------------------------------%
89 % Creates a state_mock from a list of inputs.
90 :- pred init_state_mock(state_option,
95 :- mode init_state_mock(in, in, in, out, di, uo) is det.
97 %-----------------------------------------------------------------------------%
98 % Creates a state_mock from an array of inputs.
99 :- pred init_state_mock_from_array(state_option,
104 :- mode init_state_mock_from_array(in, in, in, out, di, uo) is det.
106 %-----------------------------------------------------------------------------%
107 % Creates a oneshot state_mock from an array of inputs.
108 :- pred init_state_mock_from_array(state_option,
112 :- mode init_state_mock_from_array(in, in, out, di, uo) is det.
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.
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.
129 %-----------------------------------------------------------------------------%
130 % Removes the first item from the mock, updating the mock. Fails if the mock
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.
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.
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.
151 %=============================================================================%
153 %=============================================================================%
155 :- use_module exception.
157 :- use_module thread.
158 :- use_module std_util.
159 :- use_module thread.mvar.
161 %-----------------------------------------------------------------------------%
164 repeat(array.array(T), int) ;
167 :- type state_mock(T) ---> state_mock(thread.mvar.mvar(mock(T)), state_option).
169 %-----------------------------------------------------------------------------%
171 init_mock(List) = oneshot(List).
173 %-----------------------------------------------------------------------------%
175 init_mock(oneshot, List) = oneshot(List).
176 init_mock(repeat, []) = oneshot([]).
177 init_mock(repeat, List) = repeat(array.array(List), 0) :- List = [_|_].
179 %-----------------------------------------------------------------------------%
181 init_mock_from_array(A) = init_mock(array.to_list(A)).
183 %-----------------------------------------------------------------------------%
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),
190 Cmp = (=), Result = oneshot([])
192 Cmp = (<), Result = repeat(array.copy(A), Low)
195 exception.throw(exception.software_error("Invalid array bounds"))
198 %-----------------------------------------------------------------------------%
200 init_state_mock(Option, List, state_mock(Mvar, Option), !IO) :-
201 thread.mvar.init(init_mock(List), Mvar, !IO).
203 %-----------------------------------------------------------------------------%
205 init_state_mock(Option, MockOption, List, state_mock(Mvar, Option), !IO) :-
206 thread.mvar.init(init_mock(MockOption, List), Mvar, !IO).
208 %-----------------------------------------------------------------------------%
210 init_state_mock_from_array(Option, A, state_mock(Mvar, Option), !IO) :-
211 thread.mvar.init(init_mock_from_array(A), Mvar, !IO).
213 %-----------------------------------------------------------------------------%
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).
218 %-----------------------------------------------------------------------------%
220 :- pred repeat_mock_retrieve(array.array(T), int, T, mock(T)).
221 :- mode repeat_mock_retrieve(in, in, out, out) is det.
223 repeat_mock_retrieve(A, I, T, repeat(A, N)) :-
224 array.unsafe_lookup(A, I, T),
225 array.bounds(A, Low, High),
227 builtin.compare(Cmp, M, High),
234 exception.throw(exception.software_error("Invalid repeat index"))
237 %-----------------------------------------------------------------------------%
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).
244 %-----------------------------------------------------------------------------%
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).
251 %-----------------------------------------------------------------------------%
253 mock_try_retrieve(T, oneshot([T|List]), oneshot(List)).
254 mock_try_retrieve(T, In, Out) :-
257 repeat_mock_retrieve(A, I, T, Out)
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.
267 get_mock(state_mock(Mvar, not_threadsafe), Mvar, Mock, !IO) :-
268 thread.mvar.try_take(Mvar, MaybeMock, !IO),
270 MaybeMock = maybe.yes(Mock)
272 % This indicates a horrible mistake was made somewhere.
273 MaybeMock = maybe.no,
274 exception.throw(exception.software_error("Invalid state in state_mock"))
276 get_mock(state_mock(Mvar, threadsafe), Mvar, Mock, !IO) :-
277 thread.mvar.take(Mvar, Mock, !IO).
279 %-----------------------------------------------------------------------------%
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).
286 %-----------------------------------------------------------------------------%
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).