Merge lp:~christof-mroz/hipl/mock-functions into lp:hipl

Proposed by Christof Mroz
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
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.

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.

To post a comment you must log in.
Revision history for this message
Miika Komu (miika-iki) wrote :

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?

review: Needs Information
Revision history for this message
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://code.launchpad.net/~christof-mroz/hipl/mock-functions/+merge/56090
>
> 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://code.launchpad.net/~christof-mroz/hipl/mock-functions/+merge/56090
> 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_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h test/*/*.h test/*/*/*.h
> +HIPL_HEADER_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h test/*/*.h test/*/*/*.h test/mocks.h

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)(time_t*) = get_original(time, "time");
> + 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é

Revision history for this message
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_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h test/*/*.h test/*/*/*.h
> +HIPL_HEADER_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h test/*/*.h test/*/*/*.h test/mocks.h
> HIPL_HEADER_LIST = $(wildcard $(addprefix $(srcdir)/,$(HIPL_HEADER_LOCATIONS)))

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://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html

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)(time_t*) = get_original(time, "time");
> + 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_FIREWALL_TEST_SUITES_H */

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

review: Abstain
Revision history for this message
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://www.lastcraft.com/mock_callbacks.php

> 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.

Revision history for this message
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_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h
> test/*/*.h test/*/*/*.h
> > +HIPL_HEADER_LOCATIONS = firewall/*.h hipd/*.h lib/*/*.h modules/*/*/*.h
> 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.

Revision history for this message
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://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html
>
> 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 */

Subscribers

People subscribed via source and target branches

to all changes: