xtest init function to parse arguments and provide functionality to specify output format, list tests and only run/list tests that match a filter
This commit is contained in:
parent
9131218783
commit
ec262ba62f
207
xtest.c
207
xtest.c
|
@ -1,6 +1,8 @@
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
#include <getopt.h>
|
||||||
#include "xtest.h"
|
#include "xtest.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
struct xtest_assert_info {
|
struct xtest_assert_info {
|
||||||
const char *file;
|
const char *file;
|
||||||
|
@ -11,7 +13,6 @@ struct xtest_assert_info {
|
||||||
|
|
||||||
struct xtest_assert_info assertion_info;
|
struct xtest_assert_info assertion_info;
|
||||||
|
|
||||||
|
|
||||||
#ifndef XTEST_MAX_GROUP_NESTING
|
#ifndef XTEST_MAX_GROUP_NESTING
|
||||||
#define XTEST_MAX_GROUP_NESTING 16
|
#define XTEST_MAX_GROUP_NESTING 16
|
||||||
#endif
|
#endif
|
||||||
|
@ -24,6 +25,16 @@ struct xtest_assert_info assertion_info;
|
||||||
#define XTEST_INDENT 2
|
#define XTEST_INDENT 2
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
enum output_format {
|
||||||
|
format_default,
|
||||||
|
format_subunit,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum output_format output_format;
|
||||||
|
|
||||||
|
const char *name_filter;
|
||||||
|
|
||||||
const char *group_nesting[XTEST_MAX_GROUP_NESTING];
|
const char *group_nesting[XTEST_MAX_GROUP_NESTING];
|
||||||
int group_nesting_pos = 0;
|
int group_nesting_pos = 0;
|
||||||
|
|
||||||
|
@ -37,20 +48,131 @@ const char *skip_reason;
|
||||||
|
|
||||||
int xtest_indent = 0;
|
int xtest_indent = 0;
|
||||||
|
|
||||||
|
|
||||||
int expecting_assertion = 0;
|
int expecting_assertion = 0;
|
||||||
int num_tests = 0;
|
int num_tests = 0;
|
||||||
int successful_tests = 0;
|
int successful_tests = 0;
|
||||||
int failed_tests = 0;
|
int failed_tests = 0;
|
||||||
int skipped_tests = 0;
|
int skipped_tests = 0;
|
||||||
|
|
||||||
|
_Bool list_tests = 0;
|
||||||
|
|
||||||
|
|
||||||
|
#define USAGE_BASE "[-?|--help] [-l|--list] [-m|--match filter] [-f|--output-format format]"
|
||||||
|
|
||||||
|
#define USAGE_HELP_LIST "-l|--list list the available tests"
|
||||||
|
#define USAGE_HELP_MATCH "-m|--match filter only run/list tests whose name matches the filter"
|
||||||
|
#define USAGE_HELP_FORMAT "-f|--output-format format specify the output format; supported formats are \"default\" and \"subunit\""
|
||||||
|
#define USAGE_HELP_HELP "-?|--help show this message"
|
||||||
|
|
||||||
|
#define USAGE "Usage: %s " USAGE_BASE "\n " USAGE_HELP_LIST "\n " USAGE_HELP_MATCH "\n " USAGE_HELP_FORMAT "\n " USAGE_HELP_HELP
|
||||||
|
|
||||||
|
|
||||||
|
static _Noreturn void usage(const char *progname) {
|
||||||
|
printf(USAGE, progname);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool matches(const char *name, const char *filter) {
|
||||||
|
// eat up the matching parts of name and filter until the first * or end of either string
|
||||||
|
while (*name == *filter && *name != '\0' && *filter != '\0' && *filter != '*') {
|
||||||
|
name++;
|
||||||
|
filter++;
|
||||||
|
}
|
||||||
|
// if the next character in filter is not a * then there is no match (otherwise the previous loop would have continued)
|
||||||
|
if (*filter != '\0' && *filter != '*') return 0;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
// if the filter ends before the name then they don't match
|
||||||
|
if (*filter == '\0') {
|
||||||
|
return *name == '\0';
|
||||||
|
}
|
||||||
|
// the filter points to a * at this point because everything else was eaten up either before the loop or in the previous iteration of the loop
|
||||||
|
assert (*filter == '*');
|
||||||
|
while (*filter == '*') filter++; // eat up consecutive *s
|
||||||
|
size_t part_len = strcspn(filter, "*");
|
||||||
|
if (part_len == 0) return 1; // filter ends with a * so every remaining input in name is accepted
|
||||||
|
char part[part_len + 1];
|
||||||
|
strncpy(part, filter, part_len);
|
||||||
|
part[part_len] = '\0'; // ensure null-termination since strncpy does not null-terminate on its own
|
||||||
|
name = strstr(name, part);
|
||||||
|
if (name == NULL) return 0;
|
||||||
|
name += part_len;
|
||||||
|
filter += part_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void xtest_init(int argc, char **argv) {
|
||||||
|
int c;
|
||||||
|
int list_flag = 0;
|
||||||
|
int help_flag = 0;
|
||||||
|
const char *format = NULL;
|
||||||
|
|
||||||
|
const char *progname = argv[0];
|
||||||
|
|
||||||
|
struct option long_options[] =
|
||||||
|
{
|
||||||
|
{"list", no_argument, &list_flag, 1}, // list the tests instead of running them
|
||||||
|
{"help", no_argument, &help_flag, 1}, // list the tests instead of running them
|
||||||
|
{"match", required_argument, 0, 'm'}, // only run tests that match the provided filter
|
||||||
|
{"output-format", required_argument, 0, 'f'}, // select output format
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
while (1) {
|
||||||
|
int option_index = 0;
|
||||||
|
c = getopt_long(argc, argv, "?lm:f:", long_options, &option_index);
|
||||||
|
if (c == -1) break;
|
||||||
|
switch (c) {
|
||||||
|
case 0:
|
||||||
|
assert(long_options[option_index].flag != 0);
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
list_flag = 1;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
name_filter = optarg;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
format = optarg;
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
help_flag = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (help_flag) {
|
||||||
|
usage(progname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list_flag)
|
||||||
|
list_tests = 1;
|
||||||
|
|
||||||
|
if (format == NULL) {
|
||||||
|
format = "default";
|
||||||
|
}
|
||||||
|
if (strcmp(format, "default") == 0) {
|
||||||
|
output_format = format_default;
|
||||||
|
} else if (strcmp(format, "subunit") == 0) {
|
||||||
|
output_format = format_subunit;
|
||||||
|
} else {
|
||||||
|
printf("invalid output format '%s', allowed formats are 'default' and 'subunit'\n", format);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int xtest_complete() {
|
int xtest_complete() {
|
||||||
#ifndef XTEST_ENABLE_SUBUNIT
|
if (!list_tests) {
|
||||||
printf("============\nTotal: %d, Failed: %d, Skipped: %d\n", num_tests, failed_tests, skipped_tests);
|
if (output_format == format_default) {
|
||||||
#endif //XTEST_ENABLE_SUBUNIT
|
printf("============\nTotal: %d, Failed: %d, Skipped: %d\n", num_tests, failed_tests, skipped_tests);
|
||||||
fflush(stdout);
|
}
|
||||||
return failed_tests != 0 ? 1 : 0;
|
fflush(stdout);
|
||||||
|
return failed_tests != 0 ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void subunit_message(const char *name, const char *message, const char *details) {
|
void subunit_message(const char *name, const char *message, const char *details) {
|
||||||
|
@ -64,7 +186,6 @@ void subunit_message(const char *name, const char *message, const char *details)
|
||||||
|
|
||||||
void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params, xtest_setup_fn setup,
|
void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params, xtest_setup_fn setup,
|
||||||
xtest_teardown_fn teardown) {
|
xtest_teardown_fn teardown) {
|
||||||
num_tests += 1;
|
|
||||||
expecting_assertion = 0;
|
expecting_assertion = 0;
|
||||||
|
|
||||||
char full_name[XTEST_TEST_NAME_MAX_LENGTH + 1] = "";
|
char full_name[XTEST_TEST_NAME_MAX_LENGTH + 1] = "";
|
||||||
|
@ -76,14 +197,25 @@ void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params,
|
||||||
name_len += snprintf(full_name + name_len, XTEST_TEST_NAME_MAX_LENGTH - name_len, "%s", name);
|
name_len += snprintf(full_name + name_len, XTEST_TEST_NAME_MAX_LENGTH - name_len, "%s", name);
|
||||||
assert(name_len <= XTEST_TEST_NAME_MAX_LENGTH);
|
assert(name_len <= XTEST_TEST_NAME_MAX_LENGTH);
|
||||||
|
|
||||||
|
if (name_filter != NULL) {
|
||||||
|
if (!matches(full_name, name_filter)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list_tests) {
|
||||||
|
printf("%s\n", full_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_tests += 1;
|
||||||
|
|
||||||
|
|
||||||
void *fixture = NULL;
|
void *fixture = NULL;
|
||||||
|
|
||||||
#ifndef XTEST_ENABLE_SUBUNIT
|
if (output_format == format_subunit) {
|
||||||
printf("%*s%s: ", xtest_indent, "", name);
|
subunit_message(full_name, "test", NULL);
|
||||||
#else
|
} else {
|
||||||
subunit_message(full_name, "test", NULL);
|
printf("%*s%s: ", xtest_indent, "", name);
|
||||||
#endif //XTEST_ENABLE_SUBUNIT
|
}
|
||||||
|
|
||||||
if (setup != NULL) {
|
if (setup != NULL) {
|
||||||
int jmpres = setjmp(xtest_jmp);
|
int jmpres = setjmp(xtest_jmp);
|
||||||
if (jmpres == 0) {
|
if (jmpres == 0) {
|
||||||
|
@ -154,32 +286,35 @@ void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params,
|
||||||
}
|
}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
#ifndef XTEST_ENABLE_SUBUNIT
|
|
||||||
printf("SUCCESS\n");
|
if (output_format == format_subunit) {
|
||||||
#else
|
subunit_message(full_name, "success", NULL);
|
||||||
subunit_message(full_name, "success", NULL);
|
} else {
|
||||||
#endif //XTEST_ENABLE_SUBUNIT
|
printf("SUCCESS\n");
|
||||||
|
}
|
||||||
successful_tests += 1;
|
successful_tests += 1;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
failed:
|
failed:
|
||||||
#ifndef XTEST_ENABLE_SUBUNIT
|
|
||||||
printf("FAILED:\n%*s %s\n", xtest_indent, "", assert_message);
|
if (output_format == format_subunit) {
|
||||||
#else
|
subunit_message(full_name, "failure", assert_message);
|
||||||
subunit_message(full_name, "failure", assert_message);
|
} else {
|
||||||
#endif //XTEST_ENABLE_SUBUNIT
|
printf("FAILED:\n%*s %s\n", xtest_indent, "", assert_message);
|
||||||
|
}
|
||||||
failed_tests += 1;
|
failed_tests += 1;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
skipped:
|
skipped:
|
||||||
skipped_tests += 1;
|
skipped_tests += 1;
|
||||||
#ifndef XTEST_ENABLE_SUBUNIT
|
|
||||||
if (skip_reason != NULL) {
|
if (output_format == format_subunit) {
|
||||||
printf("SKIPPED: %s\n", skip_reason);
|
subunit_message(full_name, "skipped", skip_reason);
|
||||||
} else {
|
} else {
|
||||||
printf("SKIPPED\n");
|
if (skip_reason != NULL) {
|
||||||
|
printf("SKIPPED: %s\n", skip_reason);
|
||||||
|
} else {
|
||||||
|
printf("SKIPPED\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
subunit_message(full_name, "skipped", skip_reason);
|
|
||||||
#endif //XTEST_ENABLE_SUBUNIT
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
@ -321,9 +456,11 @@ void xtest_skip(const char *reason) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void xtest_internal_start_group(const char *name) {
|
void xtest_internal_start_group(const char *name) {
|
||||||
#ifndef XTEST_ENABLE_SUBUNIT
|
if (!list_tests) {
|
||||||
printf("%*s%s:\n", xtest_indent, "", name);
|
if (output_format == format_default) {
|
||||||
#endif
|
printf("%*s%s:\n", xtest_indent, "", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
group_nesting[group_nesting_pos] = name;
|
group_nesting[group_nesting_pos] = name;
|
||||||
group_nesting_pos += 1;
|
group_nesting_pos += 1;
|
||||||
xtest_indent += XTEST_INDENT;
|
xtest_indent += XTEST_INDENT;
|
||||||
|
@ -337,4 +474,4 @@ void xtest_internal_end_group() {
|
||||||
|
|
||||||
void xtest_expect_assertion_failure() {
|
void xtest_expect_assertion_failure() {
|
||||||
expecting_assertion = 1;
|
expecting_assertion = 1;
|
||||||
}
|
}
|
2
xtest.h
2
xtest.h
|
@ -42,6 +42,7 @@ enum xtest_type {
|
||||||
xtest_type_other,
|
xtest_type_other,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void xtest_init(int argc, char**argv);
|
||||||
|
|
||||||
void xtest_fail_assert(const char *expression, const char *file, int line, void *expected,
|
void xtest_fail_assert(const char *expression, const char *file, int line, void *expected,
|
||||||
void *actual, _Bool invert, enum xtest_type type);
|
void *actual, _Bool invert, enum xtest_type type);
|
||||||
|
@ -129,6 +130,7 @@ int xtest_complete();
|
||||||
|
|
||||||
|
|
||||||
#define XTEST_RUN_MAIN(fn) int main(int argc, char ** argv) { \
|
#define XTEST_RUN_MAIN(fn) int main(int argc, char ** argv) { \
|
||||||
|
xtest_init(argc, argv); \
|
||||||
fn(); \
|
fn(); \
|
||||||
return xtest_complete(); \
|
return xtest_complete(); \
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue