I'm new to testing and just finished writing (what I hope to be) tests for an existing argument parser in C. Please have a look.
First the Unit Under Test:
args.h
#ifndef ARGS_H
#define ARGS_H
#include <argp.h>
struct args
{
int count, silent, verbose;
char *output_file;
};
error_t parse_opt(int, char*, struct argp_state*);
int parse_args(int, char**);
#endif /* ARGS_H */
args.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <argp.h>
#include <errno.h>
#include <libeternity/args.h>
#include <config.h>
/* argp requires these to be global */
const char *argp_program_version = PACKAGE_VERSION;
const char *argp_program_bug_address = PACKAGE_BUGREPORT;
error_t parse_opt(int key, char *arg, struct argp_state *state)
{
struct args *args = state->input;
switch (key)
{
case 'q': case 's':
args->silent = 1;
break;
case 'v':
args->verbose = 1;
break;
case 'o':
args->output_file = arg;
break;
case ARGP_KEY_ARG:
if (args->count-- < 0)
break;
break;
case ARGP_KEY_END:
if (state->arg_num < 1)
{
argp_usage(state);
return 0;
}
break;
default:
return ARGP_ERR_UNKNOWN;
break;
}
return 0;
}
int parse_args(int argc, char **argv)
{
int rv;
const char* doc = "Eternity";
char args_doc[] = "file1.jpg file2.png file3.bmp ...";
struct argp_option options[] = {
{"verbose",'v', 0, 0, "Produce verbose output", 0},
{"quiet", 'q', 0, 0, "Don't produce any output", 0},
{"silent", 's', 0, OPTION_ALIAS, NULL, 0},
{"output", 'o', "FILE", 0, "Output to FILE instead of std output", 0},
{0}
};
struct argp argp = {options, parse_opt, args_doc, doc, NULL, 0, NULL};
struct args args;
args.count = argc;
args.silent = args.verbose = 0;
args.output_file = "-";
rv = argp_parse(&argp, argc, argv, ARGP_NO_EXIT, NULL, &args);
if (rv > 0)
{
errno = rv;
perror("argp_parse");
return -1;
}
if (rv < 0)
return -1;
return 0;
}
And the test itself (compiled with -Wl,--wrap=argp_parse,--wrap=argp_usage for mocking purposes):
test_args.c
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <argp.h>
#include <libeternity/args.h>
int __wrap_argp_parse(const struct argp *argp,
int argc, char **argv, unsigned flags,
int *arg_index, void *input)
{
check_expected(argc);
check_expected(argv);
return mock_type(int);
}
void __wrap_argp_usage(struct argp_state *state)
{
assert_non_null(state);
}
void parseArgs_argpSucceeds_return0(void **state)
{
int rv, argc = 2;
char *argv[argc];
argv[0] = "eternity";
argv[1] = "--help";
expect_value(__wrap_argp_parse, argc, 2);
expect_memory(__wrap_argp_parse, argv, &argv, sizeof(argv));
will_return(__wrap_argp_parse, 0);
rv = parse_args(argc, argv);
assert_int_equal(rv, 0);
}
void parseArgs_argpFails_returnNeg1(void **state)
{
int rv, argc = 2;
char *argv[argc];
argv[0] = "eternity";
argv[1] = "--help";
expect_value(__wrap_argp_parse, argc, 2);
expect_memory(__wrap_argp_parse, argv, &argv, sizeof(argv));
will_return(__wrap_argp_parse, -1);
rv = parse_args(argc, argv);
assert_int_equal(rv, -1);
}
void parseOpt_qEncountered_setSilentFlag(void **state)
{
int rv;
struct argp_state argp_state;
struct args args;
args.silent = 0;
struct args *argsp = &args;
argp_state.input = (void *) argsp;
rv = parse_opt((int) 'q', 0, &argp_state);
argsp = (struct args*) argp_state.input;
assert_int_equal(argsp->silent, 1);
assert_int_equal(rv, 0);
}
void parseOpt_sEncountered_setSilentFlag(void **state)
{
int rv;
struct argp_state argp_state;
struct args args;
args.silent = 0;
struct args *argsp = &args;
argp_state.input = (void *) argsp;
rv = parse_opt((int) 's', 0, &argp_state);
argsp = (struct args*) argp_state.input;
assert_int_equal(argsp->silent, 1);
assert_int_equal(rv, 0);
}
void parseOpt_vEncountered_setVerboseFlag(void **state)
{
int rv;
struct argp_state argp_state;
struct args args;
args.verbose = 0;
struct args *argsp = &args;
argp_state.input = (void *) argsp;
rv = parse_opt((int) 'v', 0, &argp_state);
argsp = (struct args*) argp_state.input;
assert_int_equal(argsp->verbose, 1);
assert_int_equal(rv, 0);
}
void parseOpt_oEncountered_setOutputFile(void **state)
{
int rv;
struct argp_state argp_state;
struct args args;
args.output_file = NULL;
struct args *argsp = &args;
argp_state.input = (void *) argsp;
rv = parse_opt((int) 'o', "/tmp/libeternity.out", &argp_state);
argsp = (struct args*) argp_state.input;
assert_non_null(argsp->output_file);
assert_int_equal(rv, 0);
}
void parseOpt_keyArgEncountered_decrementArgCount(void **state)
{
int rv;
struct argp_state argp_state;
struct args args;
args.count = 4;
struct args *argsp = &args;
argp_state.input = (void *) argsp;
rv = parse_opt((int) ARGP_KEY_ARG, 0, &argp_state);
argsp = (struct args*) argp_state.input;
assert_int_equal(argsp->count, 3);
assert_int_equal(rv, 0);
}
int main(void)
{
const struct CMUnitTest tests[] =
{
cmocka_unit_test(parseArgs_argpSucceeds_return0),
cmocka_unit_test(parseArgs_argpFails_returnNeg1),
cmocka_unit_test(parseOpt_qEncountered_setSilentFlag),
cmocka_unit_test(parseOpt_sEncountered_setSilentFlag),
cmocka_unit_test(parseOpt_vEncountered_setVerboseFlag),
cmocka_unit_test(parseOpt_oEncountered_setOutputFile),
cmocka_unit_test(parseOpt_keyArgEncountered_decrementArgCount),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
Am I approaching this right? How is my code coverage, and how can it be improved?