diff --git a/.gitignore b/.gitignore index 76758b0..91dcf40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.o test +unit diff --git a/Makefile b/Makefile index 70df6fd..a0fbc37 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ - test: test.c src/commander.c $(CC) $^ -std=c99 -o $@ +unit: unit.c src/commander.c + $(CC) $^ -std=c99 -o $@ + clean: - rm -f test + rm -f test unit .PHONY: clean \ No newline at end of file diff --git a/src/commander.c b/src/commander.c index 5de10e5..98c3f03 100644 --- a/src/commander.c +++ b/src/commander.c @@ -16,9 +16,9 @@ */ static void -error(char *msg) { +error(command_t *self, char *msg) { fprintf(stderr, "%s\n", msg); - exit(1); + self->exit(1); } /* @@ -28,7 +28,12 @@ error(char *msg) { static void command_version(command_t *self) { printf("%s\n", self->version); - exit(0); + self->exit(0); +} + +static void +doExit(int status) { + exit(status); } /* @@ -50,7 +55,7 @@ command_help(command_t *self) { , option->description); } printf("\n"); - exit(0); + self->exit(0); } /* @@ -66,6 +71,7 @@ command_init(command_t *self, const char *name, const char *version) { self->usage = "[options]"; command_option(self, "-V", "--version", "output program version", command_version); command_option(self, "-h", "--help", "output help information", command_help); + self->exit = doExit; } /* @@ -102,7 +108,7 @@ parse_argname(const char *str, char *flag, char *arg) { void command_option(command_t *self, const char *small, const char *large, const char *desc, command_callback_t cb) { int n = self->option_count++; - if (n == COMMANDER_MAX_OPTIONS) error("Maximum option definitions exceeded"); + if (n == COMMANDER_MAX_OPTIONS) error(self, "Maximum option definitions exceeded"); command_option_t *option = &self->options[n]; option->cb = cb; option->small = small; @@ -142,7 +148,7 @@ command_parse(command_t *self, int argc, char **argv) { arg = argv[++i]; if (!arg || '-' == arg[0]) { fprintf(stderr, "%s %s argument required\n", option->large, option->argname); - exit(1); + self->exit(1); } self->arg = arg; } @@ -169,12 +175,12 @@ command_parse(command_t *self, int argc, char **argv) { // unrecognized if ('-' == arg[0] && !literal) { fprintf(stderr, "unrecognized flag %s\n", arg); - exit(1); + self->exit(1); } int n = self->argc++; - if (n == COMMANDER_MAX_ARGS) error("Maximum number of arguments exceeded"); + if (n == COMMANDER_MAX_ARGS) error(self, "Maximum number of arguments exceeded"); self->argv[n] = (char *) arg; match:; } -} \ No newline at end of file +} diff --git a/src/commander.h b/src/commander.h index a7589ff..0158a49 100644 --- a/src/commander.h +++ b/src/commander.h @@ -65,6 +65,7 @@ typedef struct command { command_option_t options[COMMANDER_MAX_OPTIONS]; int argc; char *argv[COMMANDER_MAX_ARGS]; + void (*exit)(int); } command_t; // prototypes @@ -81,4 +82,4 @@ command_option(command_t *self, const char *small, const char *large, const char void command_parse(command_t *self, int argc, char **argv); -#endif /* COMMANDER_H */ \ No newline at end of file +#endif /* COMMANDER_H */ diff --git a/unit.c b/unit.c new file mode 100644 index 0000000..b9f456a --- /dev/null +++ b/unit.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include + +#include "src/commander.h" + +#define assertEquals(a,e) _assertEqualsI(a,e,__func__,__LINE__) +#define assertEqualStr(a,e) _assertEqualsI(strcmp(a,e),0,__func__,__LINE__) + +static void _assertEqualsI(int actual, int expected, const char *file, int line) { + if (actual == expected) { + printf("%s/%d:\033[0;32m OK\033[0m\n", file, line); + } else { + printf("%s/%d:\033[0;31m expected %d but got %d\033[0m\n", + file, line, expected, actual); + } +} + +#define assertFalse(a) _assertTrue(!(a),__func__,__LINE__) +#define assertTrue(a) _assertTrue((a),__func__,__LINE__) + +static void _assertTrue(int actual, const char *file, int line) { + if (actual) { + printf("%s/%d:\033[0;32m OK\033[0m\n", file, line); + } else { + printf("%s/%d:\033[0;31m got %d\033[0m\n", file, line, actual); + } +} + +static double getElapsedSecondsSince(struct timeval* start) { + struct timeval now; + gettimeofday(&now, 0); + int usec = now.tv_usec - start->tv_usec; + int sec = now.tv_sec - start->tv_sec; + + return usec * 1e-6 + sec; +} + +static int isVerboseSet = 0; +static int isRequiredSet = 0; +static int isOptionalSet = 0; +static int exitStatus = 0; + +static void +verbose(command_t *self) { + isVerboseSet = 1; +} + +static void +required(command_t *self) { + isRequiredSet = 1; +} + +static void +optional(command_t *self) { + isOptionalSet = 1; +} + +static jmp_buf exitCalled; + +static void setStatus(int status) { + exitStatus = status; + longjmp(exitCalled, 1); +} + +static void setUp() { + isVerboseSet = 0; + isRequiredSet = 0; + isOptionalSet = 0; + exitStatus = 0; +} + +static void init(command_t* target) { + command_init(target, "name", "0.0.1"); + target->exit = setStatus; +} + +static void shouldEnableVerbose(void) +{ + setUp(); + command_t target; + init(&target); + command_option(&target, "-v", "--verbose", "enable verbose stuff", verbose); + char* argv[] = { "name", "-v", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 2, argv); + assertTrue(isVerboseSet); +} + +static void shouldSetExitStatus(void) +{ + setUp(); + command_t target; + init(&target); + command_option(&target, "-v", "--verbose", "enable verbose stuff", verbose); + char* argv[] = { "name", "-w", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 2, argv); + assertFalse(isVerboseSet); + assertEquals(exitStatus, 1); +} + +static void shouldSetRequired(void) +{ + setUp(); + command_t target; + init(&target); + command_option(&target, "-r", "--required ", "required arg", required); + char* argv[] = { "name", "-r", "val", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 3, argv); + assertEquals(exitStatus, 0); + assertTrue(isRequiredSet); +} + +static void shouldFailIfRequiredIsMissingValue(void) +{ + setUp(); + command_t target; + init(&target); + command_option(&target, "-r", "--required ", "required arg", required); + char* argv[] = { "name", "-r", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 2, argv); + assertEquals(exitStatus, 1); + assertFalse(isRequiredSet); +} + +static void shouldSetOptional(void) +{ + setUp(); + command_t target; + init(&target); + command_option(&target, "-o", "--optional [arg]", "optional arg", optional); + char* argv[] = { "name", "-o", "val", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 3, argv); + assertEquals(exitStatus, 0); + assertTrue(isOptionalSet); +} + +static void shouldNotFailIfOptionalIsMissingValue(void) +{ + setUp(); + command_t target; + init(&target); + command_option(&target, "-o", "--optional [arg]", "optional arg", optional); + char* argv[] = { "name", "-o", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 2, argv); + assertEquals(exitStatus, 0); + assertTrue(isOptionalSet); +} + +static void shouldSetAdditional(void) +{ + setUp(); + command_t target; + init(&target); + char* argv[] = { "name", "o", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 2, argv); + assertEquals(exitStatus, 0); + assertEquals(target.argc, 1); + assertEqualStr(target.argv[0], "o"); +} + +static void shouldSetLiteral(void) +{ + setUp(); + command_t target; + init(&target); + char* argv[] = { "name", "--", "-o", 0 }; + if (!setjmp(exitCalled)) command_parse(&target, 3, argv); + assertEquals(exitStatus, 0); + assertEquals(target.argc, 1); + assertEqualStr(target.argv[0], "-o"); +} + +int main(void) { + struct timeval start; + gettimeofday(&start, 0); + + shouldEnableVerbose(); + shouldSetExitStatus(); + shouldSetRequired(); + shouldFailIfRequiredIsMissingValue(); + shouldSetOptional(); + shouldNotFailIfOptionalIsMissingValue(); + shouldSetAdditional(); + shouldSetLiteral(); + printf("%f seconds\n", getElapsedSecondsSince(&start)); + + return 0; +}