Merge lp:~christof-mroz/hipl/mock-functions into lp:hipl
- mock-functions
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~christof-mroz/hipl/mock-functions |
Merge into: | lp:hipl |
Diff against target: |
258 lines (+237/-1) 3 files modified
Makefile.am (+1/-1) test/mocks.c (+196/-0) test/mocks.h (+40/-0) |
To merge this branch: | bzr merge lp:~christof-mroz/hipl/mock-functions |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Diego Biurrun | Abstain | ||
Miika Komu | Needs Information | ||
Review via email: mp+56090@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-04-04.
Commit message
Description of the change
A framework for mock functions (library function overrides) in unit tests.
Refer to the documentation in test/mocks.c for motivation and usage details.
The hipfw-timeout and hipfw-esp-speedup branches depend on this.
Lectures start again next week... if there are objections, please consider branching and fixing them yourself: I will be available for review but probably not for coding and testing for quite some time.
René Hummen (rene-hummen) wrote : | # |
review needs-fixing
On 04.04.2011, at 00:12, Christof Mroz wrote:
> Christof Mroz has proposed merging lp:~christof-mroz/hipl/mock-functions into lp:hipl.
>
> Requested reviews:
> HIPL core team (hipl-core)
>
> For more details, see:
> https:/
>
> A framework for mock functions (library function overrides) in unit tests.
> Refer to the documentation in test/mocks.c for motivation and usage details.
>
> The hipfw-timeout and hipfw-esp-speedup branches depend on this.
>
> Lectures start again next week... if there are objections, please consider branching and fixing them yourself: I will be available for review but probably not for coding and testing for quite some time.
> --
> https:/
> Your team HIPL core team is requested to review the proposed merge of lp:~christof-mroz/hipl/mock-functions into lp:hipl.
> === modified file 'Makefile.am'
> --- Makefile.am 2011-03-25 17:51:40 +0000
> +++ Makefile.am 2011-04-03 22:12:27 +0000
> @@ -23,7 +23,7 @@
>
> ACLOCAL_AMFLAGS = -I m4
>
> -HIPL_HEADER_
> +HIPL_HEADER_
Use test/*.h to prepare for future headers.
> +
> +/*** time(2) ***/
> +
Why do you need to overload time()? Contrary to system(), this should not have side-effects when executed from a unit-test.
> +bool mock_time = false; /**< time(2) mock enabled? */
> +time_t mock_time_next; /**< value returned on next invocation of time(2) mock */
> +
> +/**
> + * time(2) mock function. Controlled by the ::mock_time flag.
> + * Returns the preset value ::mock_time_next, if enabled.
> + *
> + * @param t If non-NULL, the return value is also stored in the memory pointed
> + * to by @a t.
> + * @return The current value of ::mock_time_next.
> + */
> +time_t time(time_t *t) {
> + if (!mock_time) {
> + time_t (*original)
> + return original(t);
> + }
> +
> +
> + if (t) {
> + *t = mock_time_next;
> + }
> +
> + return mock_time_next;
> +}
> +
> +/*** system(3) ***/
> +
> +bool mock_system = false;
> +char *mock_system_last = NULL;
> +int mock_system_exit = EXIT_SUCCESS;
> +
> +/**
> + * system(3) mock function. Controlled by the ::mock_system flag.
> + * Stores a copy of @a command in ::mock_system_last, if enabled.
mock_system_exit seems to be more interesting to talk about here.
> + * @param command A copy of this string will be stored in ::mock_system_last of
> + * non-NULL. Otherwise, ::mock_system_last is set to NULL.
> + * @return The value of ::mock_system_exit if @a command was non-NULL, -1
> + * otherwise.
> + */
The usage is unclear to me. Why do I need mock_system_last?
> === added file 'test/mocks.o'
> Binary files test/mocks.o 1970-01-01 00:00:00 +0000 and test/mocks.o 2011-04-03 22:12:27 +0000 differ
I'm quite sure, you don't want to add a binary to the repository :)
René
Diego Biurrun (diego-biurrun) wrote : | # |
review abstain
On Sun, Apr 03, 2011 at 10:12:32PM +0000, Christof Mroz wrote:
>
> --- Makefile.am 2011-03-25 17:51:40 +0000
> +++ Makefile.am 2011-04-03 22:12:27 +0000
> @@ -23,7 +23,7 @@
>
> -HIPL_HEADER_
> +HIPL_HEADER_
> HIPL_HEADER_LIST = $(wildcard $(addprefix $(srcdir)
As Rene noted, just test/*.h will be more future-proof.
> --- test/mocks.c 1970-01-01 00:00:00 +0000
> +++ test/mocks.c 2011-04-03 22:12:27 +0000
> @@ -0,0 +1,188 @@
> + * This also implies that each other suite can not define an own implementation
cannot, its own
> + * Remember that each test runs in a process of its own, so the flags must be
> + * set at the beginning of each.
each what? process or test?
In general I
> +// RTLD_NEXT is optional in POSIX, and unit tests relying on mock functions will
> +// be silently ignored on systems that don't support it. See get_original().
> +//
> +// Some libc implementations (e.g. uclibc) export RTLD_NEXT by default, if
> +// supported. glibc considers this a GNU extension, so we pass _GNU_SOURCE
> +// (which should not affect non-glibc systems).
> +//
> +#define _GNU_SOURCE
http://
tells me that RTLD_NEXT is reserved for future use
nit: We generally use /* */ for multiline comments.
> +#include "test/mocks.h"
Just mocks.h should be enough for headers in the same directory.
> + * @param name Symbolic (i.e. exported) name, of the original function. It will
s/,//
> +/*** time(2) ***/
> +
> +time_t time(time_t *t) {
K&R places the { on the next line for function declarations.
> + if (!mock_time) {
> + time_t (*original)
> + return original(t);
> + }
> +
> +
> + if (t) {
> + *t = mock_time_next;
> + }
nit: two emply lines seem excessive
> +int system(const char *command) {
see above
> --- test/mocks.h 1970-01-01 00:00:00 +0000
> +++ test/mocks.h 2011-04-03 22:12:27 +0000
> @@ -0,0 +1,40 @@
> +/*
> + * Copyright (c) 2010 Aalto University and RWTH Aachen University.
Happy new year to you :)
...same for the other file...
> +#ifndef HIP_TEST_MOCKS_H
> +#define HIP_TEST_MOCKS_H
> +
> +#endif /* HIP_TEST_
The comment is wrongly copy + pasted.
> === added file 'test/mocks.o'
> Binary files test/mocks.o 1970-01-01 00:00:00 +0000 and test/mocks.o 2011-04-03 22:12:27 +0000 differ
We don't want that one.
Diego
Christof Mroz (christof-mroz) wrote : | # |
> Is the word "mock" or "mock up" function?
AFAIK it's the usual unit test terminology, see e.g.
http://
> Just by reading the file I could not get the big picture behind it. Is the
> idea to overload some functions during the runtime of hipfw and force it to
> execute some unit tests?
The overloading applies to unit tests only; test/mocks.c is not built for hipd, hipfw etc. at all, just for the check programs.
No unit test in this branch relies on the feature yes, but some newly added tests in hipfw-timeout and hipfw-esp-speedup do... please take a look at them if still in doubt.
Christof Mroz (christof-mroz) wrote : | # |
> > === modified file 'Makefile.am'
> > --- Makefile.am 2011-03-25 17:51:40 +0000
> > +++ Makefile.am 2011-04-03 22:12:27 +0000
> > @@ -23,7 +23,7 @@
> >
> > ACLOCAL_AMFLAGS = -I m4
> >
> > -HIPL_HEADER_
> test/*/*.h test/*/*/*.h
> > +HIPL_HEADER_
> test/*/*.h test/*/*/*.h test/mocks.h
>
> Use test/*.h to prepare for future headers.
OK (my idea was to prevent accidental inclusion of a stray header: test/ is currently not supposed to contain other headers than mocks.h)
> > +
> > +/*** time(2) ***/
> > +
>
> Why do you need to overload time()? Contrary to system(), this should not have
> side-effects when executed from a unit-test.
To simulate clock skews, for example (which are handled by periodic cleanup).
> > +/*** system(3) ***/
> > +
> > +bool mock_system = false;
> > +char *mock_system_last = NULL;
> > +int mock_system_exit = EXIT_SUCCESS;
> > +
> > +/**
> > + * system(3) mock function. Controlled by the ::mock_system flag.
> > + * Stores a copy of @a command in ::mock_system_last, if enabled.
>
> mock_system_exit seems to be more interesting to talk about here.
Oops, forgot to document the globals...
> > + * @param command A copy of this string will be stored in
> ::mock_system_last of
> > + * non-NULL. Otherwise, ::mock_system_last is set to NULL.
> > + * @return The value of ::mock_system_exit if @a command was non-
> NULL, -1
> > + * otherwise.
> > + */
>
> The usage is unclear to me. Why do I need mock_system_last?
See the hipfw-esp-speedup branch for an example: There, we expect a certain command line to be built, which can be checked by comparing the last command executed.
Christof Mroz (christof-mroz) wrote : | # |
> > +// RTLD_NEXT is optional in POSIX, and unit tests relying on mock functions
> will
> > +// be silently ignored on systems that don't support it. See
> get_original().
> > +//
> > +// Some libc implementations (e.g. uclibc) export RTLD_NEXT by default, if
> > +// supported. glibc considers this a GNU extension, so we pass _GNU_SOURCE
> > +// (which should not affect non-glibc systems).
> > +//
> > +#define _GNU_SOURCE
>
> http://
>
> tells me that RTLD_NEXT is reserved for future use
OK, I'll be more specific... just trying to take the scariness out of RTLD_NEXT, because it actually seems to be widely supported.
> > --- test/mocks.h 1970-01-01 00:00:00 +0000
> > +++ test/mocks.h 2011-04-03 22:12:27 +0000
> > @@ -0,0 +1,40 @@
> > +/*
> > + * Copyright (c) 2010 Aalto University and RWTH Aachen University.
>
> Happy new year to you :)
Heh, good catch.
- 5818. By Christof Mroz
-
Add test/*.h to header locations, rather than just test/mocks.h.
- 5819. By Christof Mroz
-
Don't version test/mocks.c object file.
- 5820. By Christof Mroz
-
Files were created in 2011; update license boilerplate accordingly.
- 5821. By Christof Mroz
-
Documentation fixes and clarifications.
- 5822. By Christof Mroz
-
Use relative path for header file in same directory.
- 5823. By Christof Mroz
-
Uncrustified test/mocks.c.
- 5824. By Christof Mroz
-
Fix header file guard comment.
- 5825. By Christof Mroz
-
Merged trunk revision 5824.
- 5826. By Christof Mroz
-
Clarify that mock functions apply to unit testing only.
- 5827. By Christof Mroz
-
Documentation fixes in test/mocks.c.
Unmerged revisions
Preview Diff
1 | === modified file 'Makefile.am' |
2 | --- Makefile.am 2011-03-25 17:51:40 +0000 |
3 | +++ Makefile.am 2011-04-04 15:57:28 +0000 |
4 | @@ -23,7 +23,7 @@ |
5 | |
6 | ACLOCAL_AMFLAGS = -I m4 |
7 | |
8 | -HIPL_HEADER_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h test/*/*.h test/*/*/*.h |
9 | +HIPL_HEADER_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h test/*/*.h test/*/*/*.h test/*.h |
10 | HIPL_HEADER_LIST = $(wildcard $(addprefix $(srcdir)/,$(HIPL_HEADER_LOCATIONS))) |
11 | |
12 | # For "make dist" |
13 | |
14 | === added file 'test/mocks.c' |
15 | --- test/mocks.c 1970-01-01 00:00:00 +0000 |
16 | +++ test/mocks.c 2011-04-04 15:57:28 +0000 |
17 | @@ -0,0 +1,196 @@ |
18 | +/* |
19 | + * Copyright (c) 2011 Aalto University and RWTH Aachen University. |
20 | + * |
21 | + * Permission is hereby granted, free of charge, to any person |
22 | + * obtaining a copy of this software and associated documentation |
23 | + * files (the "Software"), to deal in the Software without |
24 | + * restriction, including without limitation the rights to use, |
25 | + * copy, modify, merge, publish, distribute, sublicense, and/or sell |
26 | + * copies of the Software, and to permit persons to whom the |
27 | + * Software is furnished to do so, subject to the following |
28 | + * conditions: |
29 | + * |
30 | + * The above copyright notice and this permission notice shall be |
31 | + * included in all copies or substantial portions of the Software. |
32 | + * |
33 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
34 | + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
35 | + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
36 | + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
37 | + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
38 | + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
39 | + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
40 | + * OTHER DEALINGS IN THE SOFTWARE. |
41 | + */ |
42 | + |
43 | +/** |
44 | + * @file |
45 | + * @brief Mock functions for unit tests. |
46 | + * |
47 | + * Because unit tests should be self-contained and predictable, testing |
48 | + * functions that rely on external state can be tricky. Sometimes, the easiest |
49 | + * solution lies in overloading certain library functions with so-called |
50 | + * <i>mock functions</i> that simulate those calls and produce user-supplied |
51 | + * fake output for the duration of the test. Nothing is overridden outside of |
52 | + * unit tests, of course. |
53 | + * |
54 | + * <h2>Short tutorial</h2> |
55 | + * - In Makefile.am |
56 | + * - add test/mock.c to your check program's _SOURCE, |
57 | + * - add -ldl to your check program's _LDFLAGS. |
58 | + * - Copy & Paste one of the implementations below. |
59 | + * |
60 | + * |
61 | + * <h2>Background</h2> |
62 | + * |
63 | + * Mock functions in HIPL are implemented using the fact that the linker tries |
64 | + * to satisfy declarations by using statically defined symbols first, before |
65 | + * resorting to dynamic libraries. |
66 | + * In other words: providing an implementation with the exact same prototype as |
67 | + * the library function is all you need, first of all. |
68 | + * |
69 | + * Mocks can be defined in the file they're needed in, but beware: |
70 | + * The overriding implementation's definition cannot be static: a compiler error |
71 | + * will be emitted. On the other hand, a non-static definition will be picked up |
72 | + * by other object files in the same test suite, i.e. they will invariably use |
73 | + * your mock function which may not be intended. |
74 | + * This also implies that each other suite cannot define its own implementation |
75 | + * due to "duplicate symbol" errors. |
76 | + * |
77 | + * For this reason, and to encourage modularity, mock functions should be |
78 | + * defined in this file. They should be opt-in by enabling a global boolean |
79 | + * flag, in order not to disrupt other unit tests. |
80 | + * Remember that each test runs in a process of its own, so the flags must be |
81 | + * set at the beginning of each test. |
82 | + * |
83 | + * @note The documentation implies that argments of disabled mock functions are |
84 | + * passed to the original implementation. |
85 | + * |
86 | + * @author Christof Mroz <christof.mroz@rwth-aachen.de> |
87 | + */ |
88 | + |
89 | +/* |
90 | + * RTLD_NEXT is just "reserved for future use" in POSIX but not mandatory, so it |
91 | + * may not be available. |
92 | + * Unit tests relying on mock functions will be silently ignored on systems that |
93 | + * don't support it. See get_original(). |
94 | + * |
95 | + * Some libc implementations (e.g. uclibc) export RTLD_NEXT by default, if |
96 | + * supported. glibc considers this a GNU extension, so we pass _GNU_SOURCE |
97 | + * (which should not affect non-glibc systems). |
98 | + */ |
99 | +#define _GNU_SOURCE |
100 | + |
101 | +#include <check.h> |
102 | +#include <dlfcn.h> |
103 | +#include <stdbool.h> |
104 | +#include <stdint.h> |
105 | +#include <stdio.h> |
106 | +#include <stdlib.h> |
107 | +#include <time.h> |
108 | + |
109 | +#include "mocks.h" |
110 | + |
111 | + |
112 | +// A NULL argument to dlsym() is allowed as per POSIX, and may do the trick, |
113 | +// even though the chances are slim. We try it anyway. |
114 | +#ifndef RTLD_NEXT |
115 | +#define RTLD_NEXT NULL |
116 | +#endif |
117 | + |
118 | +/** |
119 | + * Retrieve a pointer to the real implementation of the library function |
120 | + * named by symbol @a name. |
121 | + * Exits with status 0 if it cannot be retrieved. |
122 | + * |
123 | + * @param mock A pointer to the mock function. This is used to guard against |
124 | + * returning a pointer to the mock rather than the real function |
125 | + * instead, which would in turn be called by the mock itself and so |
126 | + * on, thereby triggering infinite recursion. |
127 | + * @param name Symbolic (i.e. exported) name,of the original function. It will |
128 | + * be searched in default shared object load order and the first |
129 | + * occurence retrieved. |
130 | + * @return Function pointer to import symbol @a name. |
131 | + * Does not return on failure. |
132 | + */ |
133 | +static void *get_original(const void *const mock, const char *const name) |
134 | +{ |
135 | + void *ret = dlsym(RTLD_NEXT, name); |
136 | + |
137 | + fail_if(!ret, "dlsym(\"%s\"): %s\n", name, dlerror()); |
138 | + fail_if(!mock); |
139 | + |
140 | + // Avoid infinite recursion. This can happen if RTLD_NEXT was aliased to |
141 | + // NULL above. |
142 | + if (ret == mock) { |
143 | + fprintf(stderr, |
144 | + "Skipping check: function chaining not supported by lib\n"); |
145 | + exit(EXIT_SUCCESS); |
146 | + } |
147 | + |
148 | + return ret; |
149 | +} |
150 | + |
151 | +/*** time(2) ***/ |
152 | + |
153 | +bool mock_time = false; /**< time(2) mock enabled? */ |
154 | +time_t mock_time_next; /**< value returned on next invocation of time(2) mock */ |
155 | + |
156 | +/** |
157 | + * time(2) mock function. Controlled by the ::mock_time flag. |
158 | + * Returns the preset value ::mock_time_next, if enabled. |
159 | + * |
160 | + * @param t If non-NULL, the return value is also stored in the memory pointed |
161 | + * to by @a t. |
162 | + * @return The current value of ::mock_time_next. |
163 | + */ |
164 | +time_t time(time_t *t) |
165 | +{ |
166 | + if (!mock_time) { |
167 | + time_t (*original)(time_t *) = get_original(time, "time"); |
168 | + return original(t); |
169 | + } |
170 | + |
171 | + if (t) { |
172 | + *t = mock_time_next; |
173 | + } |
174 | + |
175 | + return mock_time_next; |
176 | +} |
177 | + |
178 | +/*** system(3) ***/ |
179 | + |
180 | +bool mock_system = false; /**< system(3) mock enabled? */ |
181 | +char *mock_system_last = NULL; /**< copy of last argument passed to |
182 | + * system(3) mock, or NULL if mock was |
183 | + * not called yet. */ |
184 | +int mock_system_exit = EXIT_SUCCESS; /**< value that will be returned by |
185 | + * system(3) mock if a non-NULL |
186 | + * argument is supplied to it.*/ |
187 | + |
188 | +/** |
189 | + * system(3) mock function. Controlled by the ::mock_system flag. |
190 | + * Stores a copy of @a command in ::mock_system_last, if enabled. |
191 | + * |
192 | + * @param command A copy of this string will be stored in ::mock_system_last of |
193 | + * non-NULL. Otherwise, ::mock_system_last is set to NULL. |
194 | + * @return The value of ::mock_system_exit if @a command was non-NULL, -1 |
195 | + * otherwise. |
196 | + */ |
197 | +int system(const char *command) |
198 | +{ |
199 | + if (!mock_system) { |
200 | + int (*original)(const char *) = get_original(system, "system"); |
201 | + return original(command); |
202 | + } |
203 | + |
204 | + free(mock_system_last); |
205 | + if (command) { |
206 | + mock_system_last = strdup(command); |
207 | + } else { |
208 | + mock_system_last = NULL; |
209 | + return -1; |
210 | + } |
211 | + |
212 | + return mock_system_exit; |
213 | +} |
214 | |
215 | === added file 'test/mocks.h' |
216 | --- test/mocks.h 1970-01-01 00:00:00 +0000 |
217 | +++ test/mocks.h 2011-04-04 15:57:28 +0000 |
218 | @@ -0,0 +1,40 @@ |
219 | +/* |
220 | + * Copyright (c) 2011 Aalto University and RWTH Aachen University. |
221 | + * |
222 | + * Permission is hereby granted, free of charge, to any person |
223 | + * obtaining a copy of this software and associated documentation |
224 | + * files (the "Software"), to deal in the Software without |
225 | + * restriction, including without limitation the rights to use, |
226 | + * copy, modify, merge, publish, distribute, sublicense, and/or sell |
227 | + * copies of the Software, and to permit persons to whom the |
228 | + * Software is furnished to do so, subject to the following |
229 | + * conditions: |
230 | + * |
231 | + * The above copyright notice and this permission notice shall be |
232 | + * included in all copies or substantial portions of the Software. |
233 | + * |
234 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
235 | + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
236 | + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
237 | + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
238 | + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
239 | + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
240 | + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
241 | + * OTHER DEALINGS IN THE SOFTWARE. |
242 | + */ |
243 | + |
244 | +#ifndef HIP_TEST_MOCKS_H |
245 | +#define HIP_TEST_MOCKS_H |
246 | + |
247 | +#include <stdbool.h> |
248 | +#include <time.h> |
249 | + |
250 | + |
251 | +extern bool mock_time; |
252 | +extern time_t mock_time_next; |
253 | + |
254 | +extern bool mock_system; |
255 | +extern char *mock_system_last; |
256 | +extern int mock_system_exit; |
257 | + |
258 | +#endif /* HIP_TEST_MOCKS_H */ |
Is the word "mock" or "mock up" function?
Just by reading the file I could not get the big picture behind it. Is the idea to overload some functions during the runtime of hipfw and force it to execute some unit tests?