~sircmpwn/2828743

# mgen.md -rw-r--r-- 3.5 KiB View raw

mgen

Mock generator for C.

Usage

mgen \
  [-P <function prototype> ...] \
  [-W <function prototype> ...] \
  [-H <path to header> ...] \
  mocks.c mocks.h

Generates code and supplementary headers which provide mocks of the given function prototypes. You then compile & link to mocks.c instead of the code or library which is being mocked, and include mocks.h in your test harness to customize the behavior of the mocked functions.

-P : generate mocks for the given prototype.

-H : parse the specified header and generate mocks for each prototype therein.

-W : generate mocks for the specified wrapped function, and print the necessary linker flags on stdout. These flags must be passed to the linker for linking the test executable.

Configuring mock behavior

The following functions are generated which allow you to configure the behavior of mocked function <fn> during testing:

/**
 * When called for the nth time, raise SIGABRT.
 */
void m_<fn>_abort(int n);

/**
 * When called for the nth time (or -1 to set the default), the function will
 * return the given rval. If left unconfigured, the default return value is the
 * zero value of <rtype>. <rtype> is generated as the return type of the mocked
 * function prototype.
 */
void m_<fn>_return(int n, <rtype> rval);

/**
 * When called for the nth time (or -1 to configure the default), the function
 * will set errno to the specified value.
 */
void m_<fn>_errno(int n, int errno);

/**
 * Pass through to the wrapped function on the nth call (or -1 to configure the
 * default). This is only generated for wrapped functions (-W).
 */
void m_<fn>_passthrough(int n);

/**
 * When called for the nth time (or -1 to configure the default), invoke
 * callback. The function signature of callback matches the mocked function
 * prototype. The value returned from the callback is returned to the caller,
 * unless m_<fn>_return has been configured for this call.
 */
void m_<fn>_callback(int n, <callback>);

Setting expectations

The following functions allow you to SIGABRT if the tested code does not invoke the mocked function in the expected way:

/**
 * If the nth call to <fn> (or -1 to configure the default) does not provide
 * ...params, raise SIGABRT. Note, this is not a variadic function - the
 * parameters in the mocked function's prototype are used in the generated
 * prototype.
 */
void m_<fn>_expect(int n, ...params);

/**
 * Adjusts the behavior of m_<fn>_expect for the nth call (or -1 to configure
 * the default) to disregard the pth parameter. It is a programming error to
 * call m_<fn>_ignore for the nth call before calling m_<fn>_expect for that
 * call.
 */
void m_<fn>_ignore(int n, int p);

Retrospective testing

The following functions can be used after the test session to understand how the tested code used your mocked function.

/**
 * Returns the number of times <fn> was invoked.
 */
int m_<fn>_ncalls();

struct m_<fn>_call {
/**
  * Defined with one field per parameter, in order, named after the parameters
  * in the function prototype.
  */
};

/**
 * Returns the call record for the nth call, or NULL if it wasn't called n
 * times.
 */
struct m_<fn>_call *mgen_<fn>_call(int n);

Miscellaneous generated functions

/**
 * Discards all call records and declarative configurations for the mocked
 * function.
 */
void m_<fn>_reset();

/**
 * Resets all mocked functions.
 */
void mgen_reset();

/**
 * Returns a unique (and invalid) pointer.
 */
void *mgen_uniqueptr();
# tested.c -rw-r--r-- 170 bytes View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
int read_to_end(FILE *f, char *buf) {
    int r = 0, l;
    while (!feof(f)) {
        l = fread(buf, 1, 256, f);
        r += l;
        buf += l;
    }
    return r;
}
# test.c -rw-r--r-- 546 bytes View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "mocks.h"

static const char *hello = "hello world!";

static int fread_write_buffer(
        void *ptr, size_t size, size_t nmemb, FILE *f) {
    char *buf = ptr;
    strcpy(buf, hello);
    return sizeof(hello);
}

void test_read_to_end() {
    char buf[256];
    FILE *f = mgen_uniqueptr();

    m_feof_return(0, false);
    m_feof_return(1, false);
    m_feof_abort(2);

    m_fread_callback(0, fread_write_buffer);
    m_fread_abort(1);
    
    assert(sizeof(hello) == read_to_end(f, &buf));
    assert(strcmp(buf, hello) == 0);
}
# Makefile -rw-r--r-- 367 bytes View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Untested and probably wrong in multiple ways, just to give you
# an idea of how it would work

read_to_end_mocks.c:
	mgen \
		-W 'int feof(FILE *f)' \
		-W 'int fread(void *ptr, size_t size, size_t nmemb, FILE *f)' \
		mocks.c mocks.h > mocks.ld

test_read_to_end: read_to_end.o mocks.o
	$(LD) -o $< $$(cat mocks.ld) $@

check: test_read_to_end
	./test_read_to_end