rewrite portions and add tests
This commit is contained in:
parent
327b8ef28c
commit
853616934a
8 changed files with 1838 additions and 115 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
test
|
||||
*.exe
|
1225
greatest.h
Normal file
1225
greatest.h
Normal file
File diff suppressed because it is too large
Load diff
119
kyaa.h
119
kyaa.h
|
@ -3,111 +3,116 @@
|
|||
Refer to kyaa.md for documentation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef KYAA_ONCE
|
||||
#define KYAA_ONCE
|
||||
|
||||
/* set some sane defaults. */
|
||||
#ifndef KYAA_OKAY
|
||||
#define KYAA_OKAY 0
|
||||
#endif
|
||||
#ifndef KYAA_ERROR
|
||||
#define KYAA_ERROR 1
|
||||
#ifndef KYAA_FAIL
|
||||
#define KYAA_FAIL 1
|
||||
#endif
|
||||
#ifndef KYAA_ITER
|
||||
#define KYAA_ITER i
|
||||
#ifndef KYAA_OUT
|
||||
#define KYAA_OUT(...) printf(__VA_ARGS__)
|
||||
#endif
|
||||
#ifndef KYAA_ERR
|
||||
#define KYAA_ERR(...) fprintf(stderr, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define KYAA_IS_LONG(arg, name) \
|
||||
(strncmp(arg, "--" name, strlen("--" name)) == 0 && \
|
||||
(arg[strlen("--" name)] == '\0' || arg[strlen("--" name)] == '='))
|
||||
|
||||
#define KYAA_SETUP \
|
||||
/* sanity checks */ \
|
||||
if (argc <= 0 || argv == NULL || argv[0] == NULL) { \
|
||||
fprintf(stderr, "You've met with a terrible fate.\n"); \
|
||||
return KYAA_ERROR; \
|
||||
KYAA_ERR("malformed argc/argv\n"); \
|
||||
return KYAA_FAIL; \
|
||||
} \
|
||||
char *kyaa_name = argv[0]; \
|
||||
const char *kyaa_name = argv[0]; \
|
||||
bool kyaa_read_stdin = false; \
|
||||
char kyaa_flag = '\0'; \
|
||||
bool kyaa_keep_parsing = true; \
|
||||
bool kyaa_parse_next = false; \
|
||||
char kyaa_char = '\0'; \
|
||||
bool kyaa_parsing = true; \
|
||||
bool kyaa_skip = false; \
|
||||
|
||||
#define KYAA_LOOP \
|
||||
KYAA_SETUP \
|
||||
for (int KYAA_ITER = 1; KYAA_ITER < argc; KYAA_ITER++) \
|
||||
for (int kyaa_iter = 1; kyaa_iter < argc; kyaa_iter++) \
|
||||
|
||||
#define KYAA_BEGIN \
|
||||
char *kyaa_arg = argv[KYAA_ITER]; \
|
||||
if (kyaa_keep_parsing && (kyaa_parse_next || kyaa_arg[0] == '-')) { \
|
||||
if (!kyaa_parse_next && kyaa_arg[1] == '-' && kyaa_arg[2] == '\0') { \
|
||||
kyaa_keep_parsing = false; \
|
||||
const char *kyaa_arg = argv[kyaa_iter]; \
|
||||
if (kyaa_parsing && (kyaa_skip || kyaa_arg[0] == '-')) { \
|
||||
if (!kyaa_skip && kyaa_arg[1] == '-' && kyaa_arg[2] == '\0') { \
|
||||
kyaa_parsing = false; \
|
||||
continue; \
|
||||
} \
|
||||
if (!kyaa_parse_next && kyaa_arg[1] == '\0') { \
|
||||
kyaa_read_stdin = true; \
|
||||
} else { \
|
||||
/* case: kyaa_parse_next: arg is at least 1 char initialized. */ \
|
||||
/* case: !kyaa_parse_next: arg is at least 3 chars initialized. */ \
|
||||
char *kyaa_etc = kyaa_parse_next ? kyaa_arg : kyaa_arg + 2; \
|
||||
bool kyaa_no_more = false; \
|
||||
if (!kyaa_skip && kyaa_arg[1] == '\0') kyaa_read_stdin = true; \
|
||||
else { \
|
||||
/* case: kyaa_skip: kyaa_arg is at least 1 char long. */ \
|
||||
/* case: !kyaa_skip: kyaa_arg is at least 3 chars long. */ \
|
||||
const char *kyaa_etc = kyaa_skip ? kyaa_arg : kyaa_arg + 2; \
|
||||
const char *kyaa_equals = kyaa_skip ? NULL : strchr(kyaa_etc, '='); \
|
||||
bool kyaa_helping = false; \
|
||||
bool kyaa_any = false; \
|
||||
if (!kyaa_parse_next && kyaa_arg[1] != '-') { \
|
||||
kyaa_flag = kyaa_arg[1]; \
|
||||
kyaa_no_more = kyaa_arg[2] == '\0'; \
|
||||
} \
|
||||
if (kyaa_flag == 'h' || !strcmp(kyaa_arg, "--help")) { \
|
||||
printf("usage:\n"); \
|
||||
kyaa_helping = true; \
|
||||
if (!kyaa_skip) { \
|
||||
if (kyaa_arg[1] != '-') { \
|
||||
kyaa_char = kyaa_arg[1]; \
|
||||
if (kyaa_arg[2] == '\0') kyaa_etc = NULL; \
|
||||
} else if (kyaa_equals == NULL) kyaa_etc = NULL; \
|
||||
else kyaa_etc = kyaa_equals + 1; \
|
||||
if (strcmp(kyaa_arg, "--help") == 0) kyaa_char = 'h'; \
|
||||
if (kyaa_char == 'h') { \
|
||||
KYAA_OUT("usage:\n"); \
|
||||
kyaa_helping = true; \
|
||||
} \
|
||||
} \
|
||||
if (0) { \
|
||||
|
||||
#define KYAA_END \
|
||||
} \
|
||||
if (!kyaa_any && !kyaa_helping) { \
|
||||
if (kyaa_flag) { \
|
||||
fprintf(stderr, "unknown flag: -%c\n", kyaa_flag); \
|
||||
} else { \
|
||||
fprintf(stderr, "unknown flag: %s\n", kyaa_arg); \
|
||||
} \
|
||||
return KYAA_ERROR; \
|
||||
if (kyaa_char) KYAA_ERR("unknown flag: -%c\n", kyaa_char); \
|
||||
else KYAA_ERR("unknown flag: %s\n", kyaa_arg); \
|
||||
return KYAA_FAIL; \
|
||||
} \
|
||||
if (kyaa_helping) { \
|
||||
return KYAA_OKAY; \
|
||||
} \
|
||||
kyaa_parse_next = false; \
|
||||
kyaa_flag = '\0'; \
|
||||
kyaa_skip = false; \
|
||||
kyaa_char = '\0'; \
|
||||
continue; \
|
||||
} \
|
||||
} \
|
||||
|
||||
#define KYAA_DESCRIBE(c, name, description) \
|
||||
printf(" -%c --%s\n%s\n", c, name, description) \
|
||||
KYAA_OUT(" -%c --%s\n%s\n", c, name, description) \
|
||||
|
||||
#define KYAA_FLAG(c, name, description) \
|
||||
} \
|
||||
if (kyaa_helping) { \
|
||||
KYAA_DESCRIBE(c, name, description); \
|
||||
} else if (kyaa_flag == c || !strcmp(kyaa_arg, "--"name)) { \
|
||||
kyaa_flag = c; \
|
||||
if (kyaa_helping) KYAA_DESCRIBE(c, name, description); \
|
||||
else if (kyaa_char == c || KYAA_IS_LONG(kyaa_arg, name)) { \
|
||||
kyaa_char = c; \
|
||||
kyaa_any = true; \
|
||||
|
||||
#define KYAA_FLAG_ARG(c, name, description) \
|
||||
} \
|
||||
if (kyaa_helping) { \
|
||||
KYAA_DESCRIBE(c, name, description); \
|
||||
} else if (kyaa_flag == c || !strcmp(kyaa_arg, "--"name)) { \
|
||||
if (kyaa_no_more || kyaa_flag != c) { \
|
||||
kyaa_parse_next = true; \
|
||||
if (KYAA_ITER + 1 == argc || argv[KYAA_ITER + 1][0] == '\0') { \
|
||||
fprintf(stderr, "expected an argument for --%s (-%c)\n", name, c); \
|
||||
return KYAA_ERROR; \
|
||||
if (kyaa_helping) KYAA_DESCRIBE(c, name, description); \
|
||||
else if (kyaa_char == c || KYAA_IS_LONG(kyaa_arg, name)) { \
|
||||
if (kyaa_etc == NULL) { \
|
||||
kyaa_skip = true; \
|
||||
if (kyaa_iter + 1 == argc || argv[kyaa_iter + 1] == NULL) { \
|
||||
KYAA_ERR("expected an argument for --%s (-%c)\n", name, c); \
|
||||
return KYAA_FAIL; \
|
||||
} \
|
||||
kyaa_flag = c; \
|
||||
kyaa_char = c; \
|
||||
continue; \
|
||||
} \
|
||||
kyaa_flag = c; \
|
||||
kyaa_char = c; \
|
||||
kyaa_any = true; \
|
||||
|
||||
#define KYAA_HELP(description) \
|
||||
} \
|
||||
if (kyaa_helping) { \
|
||||
if (description != NULL) { \
|
||||
printf("%s\n", description); \
|
||||
} \
|
||||
if (description != NULL) KYAA_OUT("%s\n", description); \
|
||||
|
||||
#endif /* KYAA_ONCE */
|
||||
|
|
87
kyaa.md
87
kyaa.md
|
@ -22,9 +22,9 @@ kyaa doesn't actually care if it's in `main` or not.
|
|||
iterate over the arguments with `KYAA_LOOP`:
|
||||
|
||||
```c
|
||||
int main(int argc, char *argv[]) {
|
||||
int main(int argc, char **argv) {
|
||||
KYAA_LOOP {
|
||||
// KYAA_ITER, kyaa_name, kyaa_read_stdin, and kyaa_flag are exposed here.
|
||||
// kyaa_iter, kyaa_name, kyaa_read_stdin, and kyaa_flag are exposed here.
|
||||
// [other code goes here]
|
||||
}
|
||||
return 0;
|
||||
|
@ -50,31 +50,32 @@ and `KYAA_FLAG_ARG` or `KYAA_FLAG_LONG` for flags that do:
|
|||
|
||||
```c
|
||||
bool use_feature = false;
|
||||
char *log_fn = "logs.txt";
|
||||
const char *log_fn = "log.txt";
|
||||
long my_var = 0;
|
||||
KYAA_LOOP {
|
||||
// [other code goes here]
|
||||
KYAA_BEGIN
|
||||
|
||||
// arguments: short flag, long flag, help description
|
||||
KYAA_FLAG("-x", "--enable-feature",
|
||||
KYAA_FLAG('x', "enable-feature",
|
||||
" enable some feature")
|
||||
use_feature = true;
|
||||
|
||||
// same arguments, but kyaa_etc contains the relevant string.
|
||||
KYAA_FLAG_ARG("-l", "--log-file",
|
||||
KYAA_FLAG_ARG('l', "log-file",
|
||||
" use a given filename for the log file")
|
||||
log_fn = kyaa_etc;
|
||||
|
||||
// same arguments, kyaa_etc is set, but kyaa_flag_arg is also set.
|
||||
KYAA_FLAG_LONG("-v", "--var",
|
||||
// same arguments, kyaa_etc is set, but kyaa_long_value is also set.
|
||||
KYAA_FLAG_LONG('v', "var",
|
||||
" set an integer variable\n"
|
||||
" default: 0")
|
||||
my_var = kyaa_flag_arg;
|
||||
my_var = kyaa_long_value;
|
||||
|
||||
KYAA_END
|
||||
// [other code goes here]
|
||||
}
|
||||
return 0;
|
||||
```
|
||||
|
||||
kyaa secretly wraps flag handling in if/else statements with {} blocks.
|
||||
|
@ -98,55 +99,53 @@ additional help text may be defined using `KYAA_HELP`:
|
|||
kyaa interprets an argument of `-` as a request to enable reading from stdin:
|
||||
`kyaa_read_stdin` is set to true.
|
||||
|
||||
arguments may be passed in three ways, consider:
|
||||
flag values may be specified in four ways; the following are equivalent:
|
||||
* `-v42`
|
||||
* `-v 42`
|
||||
* `--var 42`
|
||||
* `--var=42`
|
||||
|
||||
kyaa returns `KYAA_OKAY` when `-h` or `--help` is given,
|
||||
and `KYAA_ERROR` in the event of invalid flags, missing arguments,
|
||||
or `KYAA_FAIL` in the event of invalid flags, missing arguments,
|
||||
or invalid numbers (`KYAA_FLAG_LONG`).
|
||||
kyaa uses `continue` for handling arguments.
|
||||
|
||||
kyaa prints error messages to `stderr`, and help text to `stdout`.
|
||||
|
||||
`KYAA_ITER` may be redefined to avoid name collisions.
|
||||
|
||||
## TODO
|
||||
|
||||
* support `--var=42` argument style
|
||||
* fix overlapping names, e.g. `KYAA_FLAG` vs `kyaa_flag`, `KYAA_FLAG_ARG` vs `kyaa_flag_arg`, etc.
|
||||
* maybe pass `argc`/`argv` manually?
|
||||
kyaa prints help text to `stdout` and error messages to `stderr`.
|
||||
this can be overridden by specifying `KYAA_OUT` and `KYAA_ERR` respectively.
|
||||
|
||||
## API
|
||||
|
||||
### kyaa.h
|
||||
|
||||
return type or type | name | arguments or default value
|
||||
--- | --- | ---
|
||||
define | KYAA\_OKAY | 0
|
||||
define | KYAA\_ERROR | 1
|
||||
define | KYAA\_ITER | i
|
||||
||
|
||||
macro | KYAA\_SETUP | *none*
|
||||
macro | KYAA\_LOOP | *none*
|
||||
macro | KYAA\_BEGIN | *none*
|
||||
macro | KYAA\_END | *none*
|
||||
macro | KYAA\_DESCRIBE | c, name, description
|
||||
macro | KYAA\_FLAG | c, name, description
|
||||
macro | KYAA\_FLAG\_ARG | c, name, description
|
||||
macro | KYAA\_HELP | description
|
||||
||
|
||||
`char *` | kyaa\_name | *n/a*
|
||||
`bool` | kyaa\_read\_stdin | *n/a*
|
||||
`char *` | kyaa\_arg | *n/a*
|
||||
|| **internal use** ||
|
||||
`char` | kyaa\_flag | *n/a*
|
||||
`bool` | kyaa\_keep\_parsing | *n/a*
|
||||
`bool` | kyaa\_parse\_next | *n/a*
|
||||
`bool` | kyaa\_no\_more | *n/a*
|
||||
`bool` | kyaa\_helping | *n/a*
|
||||
`bool` | kyaa\_any | *n/a*
|
||||
return type or type | name | arguments or default value | description
|
||||
--- | --- | --- | ---
|
||||
define | KYAA\_OKAY | 0 | value to return upon a successful early exit
|
||||
define | KYAA\_FAIL | 1 | value to return upon encountering an error
|
||||
define | KYAA\_OUT | printf(__VA_ARGS__) | printf-like function for help text
|
||||
define | KYAA\_ERR | fprintf(stderr, __VA_ARGS__) | printf-like function for error text
|
||||
|||
|
||||
macro | KYAA\_SETUP | *none* | performs a sanity check and initializes variables
|
||||
macro | KYAA\_LOOP | *none* | begins iterating over arguments
|
||||
macro | KYAA\_BEGIN | *none* | begins parsing arguments
|
||||
macro | KYAA\_END | *none* | finishes parsing arguments
|
||||
macro | KYAA\_DESCRIBE | c, name, description | describes a flag for help text
|
||||
macro | KYAA\_FLAG | c, name, description | handles a flag that takes no arguments
|
||||
macro | KYAA\_FLAG\_ARG | c, name, description | handles a flag that takes a string argument
|
||||
macro | KYAA\_HELP | description | prints additional text for help text
|
||||
|||
|
||||
`const char *` | kyaa\_name | *n/a* | the name of the program (usually `argv[0]`)
|
||||
`int` | kyaa\_iter | *n/a* | the index of the argument being parsed
|
||||
`const char *` | kyaa\_arg | *n/a* | the whole argument being parsed
|
||||
`const char *` | kyaa\_etc | *n/a* | an argument to a flag, or `NULL`
|
||||
`bool` | kyaa\_read\_stdin | *n/a* | set when an argument is `-` (remains set unless reset by user)
|
||||
||| **the following entries are for internal use and may be changed without notice**
|
||||
macro | KYAA\_IS\_LONG | arg, name | tests whether `arg` describes a long flag called `name`
|
||||
`char` | kyaa\_char | *n/a* | the character assigned to the current flag being parsed
|
||||
`bool` | kyaa\_parsing | *n/a* | whether parsing occurs (`--` cancels parsing)
|
||||
`bool` | kyaa\_skip | *n/a* | set when a flag needs another argument
|
||||
`bool` | kyaa\_helping | *n/a* | set when `-h` or `--help` is encountered
|
||||
`bool` | kyaa\_any | *n/a* | set when any flag has been matched
|
||||
`const char *` | kyaa\_equals | *n/a* | a pointer to the first `=` character in a long flag, or `NULL`
|
||||
|
||||
### kyaa\_extra.h
|
||||
|
||||
|
|
32
kyaa_extra.h
32
kyaa_extra.h
|
@ -3,7 +3,10 @@
|
|||
Refer to kyaa.md for documentation.
|
||||
*/
|
||||
|
||||
static char *kyaa_skip_spaces(char *str) {
|
||||
#ifndef KYAA_EXTRA
|
||||
#define KYAA_EXTRA
|
||||
|
||||
static const char *kyaa_skip_spaces(const char *str) {
|
||||
/* iterates str to first non-space character according to the C locale */
|
||||
while (*str != '\0') {
|
||||
switch (*str) {
|
||||
|
@ -19,8 +22,8 @@ static char *kyaa_skip_spaces(char *str) {
|
|||
return str;
|
||||
}
|
||||
|
||||
static const char *kyaa__base_2(char **p_str, long *p_out) {
|
||||
char *str = *p_str;
|
||||
static const char *kyaa__base_2(const char **p_str, long *p_out) {
|
||||
const char *str = *p_str;
|
||||
long out = *p_out;
|
||||
for (char c; (c = *str) != '\0'; str++) {
|
||||
switch (c) {
|
||||
|
@ -47,8 +50,8 @@ exit:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static const char *kyaa__base_8(char **p_str, long *p_out) {
|
||||
char *str = *p_str;
|
||||
static const char *kyaa__base_8(const char **p_str, long *p_out) {
|
||||
const char *str = *p_str;
|
||||
long out = *p_out;
|
||||
for (char c; (c = *str) != '\0'; str++) {
|
||||
switch (c) {
|
||||
|
@ -75,8 +78,8 @@ exit:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static const char *kyaa__base_10(char **p_str, long *p_out) {
|
||||
char *str = *p_str;
|
||||
static const char *kyaa__base_10(const char **p_str, long *p_out) {
|
||||
const char *str = *p_str;
|
||||
long out = *p_out;
|
||||
for (char c; (c = *str) != '\0'; str++) {
|
||||
switch (c) {
|
||||
|
@ -102,8 +105,8 @@ exit:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static const char *kyaa__base_16(char **p_str, long *p_out) {
|
||||
char *str = *p_str;
|
||||
static const char *kyaa__base_16(const char **p_str, long *p_out) {
|
||||
const char *str = *p_str;
|
||||
long out = *p_out;
|
||||
for (char c; (c = *str) != '\0'; str++) {
|
||||
switch (c) {
|
||||
|
@ -143,7 +146,7 @@ exit:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static const char *kyaa_str_to_long(char *str, long *p_out) {
|
||||
static const char *kyaa_str_to_long(const char *str, long *p_out) {
|
||||
/* returns error message or NULL */
|
||||
long out = 0;
|
||||
int base = 10;
|
||||
|
@ -202,10 +205,11 @@ static const char *kyaa_str_to_long(char *str, long *p_out) {
|
|||
|
||||
#define KYAA_FLAG_LONG(c, name, description) \
|
||||
KYAA_FLAG_ARG(c, name, description) \
|
||||
long kyaa_flag_arg; \
|
||||
const char *err = kyaa_str_to_long(kyaa_etc, &kyaa_flag_arg); \
|
||||
long kyaa_long_value; \
|
||||
const char *err = kyaa_str_to_long(kyaa_etc, &kyaa_long_value); \
|
||||
if (err) { \
|
||||
fprintf(stderr, "%s\n", err); \
|
||||
return KYAA_ERROR; \
|
||||
KYAA_ERR("%s\n", err); \
|
||||
return KYAA_FAIL; \
|
||||
} \
|
||||
|
||||
#endif /* KYAA_EXTRA */
|
||||
|
|
410
test.c
Normal file
410
test.c
Normal file
|
@ -0,0 +1,410 @@
|
|||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "greatest.h"
|
||||
|
||||
GREATEST_MAIN_DEFS();
|
||||
|
||||
// use non-default return codes to test that they actually change.
|
||||
// this also allows distinguishing KYAA_OKAY from the user's main() returning 0.
|
||||
#define KYAA_OKAY 123
|
||||
#define KYAA_FAIL -666
|
||||
|
||||
// capture stdout and stderr for later testing.
|
||||
#define CAPTURE_SIZE 2048
|
||||
static char global_buf[CAPTURE_SIZE] = {0};
|
||||
static char global_out[CAPTURE_SIZE] = {0};
|
||||
static char global_err[CAPTURE_SIZE] = {0};
|
||||
static int out_offset = 0;
|
||||
static int err_offset = 0;
|
||||
static bool out_overflown = false;
|
||||
static bool err_overflown = false;
|
||||
|
||||
static int capture_stdout(int printf_result) {
|
||||
if (printf_result < 0 || printf_result >= CAPTURE_SIZE) goto fail;
|
||||
for (int i = 0; i < printf_result; i++) {
|
||||
// copy as much as possible before capping out.
|
||||
if (out_offset >= CAPTURE_SIZE) goto fail;
|
||||
global_out[out_offset] = global_buf[i];
|
||||
out_offset++;
|
||||
}
|
||||
return printf_result;
|
||||
|
||||
fail:
|
||||
out_overflown = true;
|
||||
return printf_result;
|
||||
}
|
||||
|
||||
static int capture_stderr(int printf_result) {
|
||||
if (printf_result < 0 || printf_result >= CAPTURE_SIZE) goto fail;
|
||||
for (int i = 0; i < printf_result; i++) {
|
||||
// copy as much as possible before capping out.
|
||||
if (err_offset >= CAPTURE_SIZE) goto fail;
|
||||
global_err[err_offset] = global_buf[i];
|
||||
err_offset++;
|
||||
}
|
||||
return printf_result;
|
||||
|
||||
fail:
|
||||
err_overflown = true;
|
||||
return printf_result;
|
||||
}
|
||||
|
||||
#define KYAA_OUT(...) capture_stdout(snprintf(global_buf, CAPTURE_SIZE, __VA_ARGS__))
|
||||
#define KYAA_ERR(...) capture_stderr(snprintf(global_buf, CAPTURE_SIZE, __VA_ARGS__))
|
||||
|
||||
#include "kyaa.h"
|
||||
#include "kyaa_extra.h"
|
||||
|
||||
#define main(a, b) subject(a, const b)
|
||||
#include "test_subject.c"
|
||||
#undef main
|
||||
|
||||
// testing begins here!
|
||||
|
||||
#define ASSERT_ZERO(ret) ASSERT_EQ(ret, 0)
|
||||
#define ASSERT_OKAY(ret) ASSERT_EQ(ret, KYAA_OKAY)
|
||||
#define ASSERT_FAIL(ret) ASSERT_EQ(ret, KYAA_FAIL)
|
||||
#define ASSERT_OUT(str) do { \
|
||||
ASSERT_FALSE(out_overflown); \
|
||||
ASSERT_STR_EQ(str, global_out); \
|
||||
} while (0)
|
||||
#define ASSERT_ERR(str) do { \
|
||||
ASSERT_FALSE(err_overflown); \
|
||||
ASSERT_STR_EQ(str, global_err); \
|
||||
} while (0)
|
||||
|
||||
static void reset() {
|
||||
use_feature = false;
|
||||
log_fn = "log.txt";
|
||||
my_var = 0;
|
||||
read_stdin = false;
|
||||
|
||||
out_offset = 0;
|
||||
err_offset = 0;
|
||||
memset(global_buf, 0, CAPTURE_SIZE);
|
||||
memset(global_out, 0, CAPTURE_SIZE);
|
||||
memset(global_err, 0, CAPTURE_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
int kyaa(int argc, const char **argv) {
|
||||
reset();
|
||||
return subject(argc, argv);
|
||||
}
|
||||
|
||||
int kyaa_va(int argc, ...) {
|
||||
static const char *argv[256] = {0};
|
||||
va_list args;
|
||||
assert(argc <= 256);
|
||||
va_start(args, argc);
|
||||
for (int i = 0; i < argc; i++) {
|
||||
argv[i] = va_arg(args, const char *);
|
||||
}
|
||||
va_end(args);
|
||||
return kyaa(argc, argv);
|
||||
}
|
||||
|
||||
static const char *blank = "";
|
||||
static const char *basic_argv[] = {"kyaa"};
|
||||
static const char *help_text = "usage:\n\
|
||||
-x --enable-feature\n\
|
||||
enable some feature\n\
|
||||
-l --log-file\n\
|
||||
use a given filename for the log file\n\
|
||||
-v --var\n\
|
||||
set an integer variable\n\
|
||||
default: 0\n\
|
||||
";
|
||||
|
||||
#define ASSERT_VALUES_UNCHANGED() do { \
|
||||
ASSERT(!use_feature); \
|
||||
ASSERT_STR_EQ("log.txt", log_fn); \
|
||||
ASSERT_EQ(0, my_var); \
|
||||
} while (0)
|
||||
|
||||
#define ASSERT_VALUES_CHANGED() do { \
|
||||
ASSERT(use_feature); \
|
||||
ASSERT_STR_EQ("/var/log/kyaa", log_fn); \
|
||||
ASSERT_EQ(1337, my_var); \
|
||||
} while (0)
|
||||
|
||||
TEST no_arguments() { // (as if invoked by a shell)
|
||||
ASSERT_ZERO(kyaa(1, basic_argv));
|
||||
ASSERT_VALUES_UNCHANGED();
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST one_flag() {
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "-x"));
|
||||
ASSERT(use_feature);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST arg_flag() {
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-l", "/var/log/kyaa"));
|
||||
ASSERT_EQ("/var/log/kyaa", log_fn);
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "1337"));
|
||||
ASSERT_EQ(1337, my_var);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST undefined_flag() {
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "-p"));
|
||||
ASSERT_ERR("unknown flag: -p\n");
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-o", "outfile"));
|
||||
ASSERT_ERR("unknown flag: -o\n");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST many_flags() {
|
||||
ASSERT_ZERO(kyaa_va(6, "kyaa", "-v", "1337", "-l", "/var/log/kyaa", "-x")); ASSERT_VALUES_CHANGED();
|
||||
ASSERT_ZERO(kyaa_va(6, "kyaa", "-v", "1337", "-x", "-l", "/var/log/kyaa")); ASSERT_VALUES_CHANGED();
|
||||
ASSERT_ZERO(kyaa_va(6, "kyaa", "-l", "/var/log/kyaa", "-v", "1337", "-x")); ASSERT_VALUES_CHANGED();
|
||||
ASSERT_ZERO(kyaa_va(6, "kyaa", "-l", "/var/log/kyaa", "-x", "-v", "1337")); ASSERT_VALUES_CHANGED();
|
||||
ASSERT_ZERO(kyaa_va(6, "kyaa", "-x", "-v", "1337", "-l", "/var/log/kyaa")); ASSERT_VALUES_CHANGED();
|
||||
ASSERT_ZERO(kyaa_va(6, "kyaa", "-x", "-l", "/var/log/kyaa", "-v", "1337")); ASSERT_VALUES_CHANGED();
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST empty_flag() {
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-l", ""));
|
||||
ASSERT_STR_EQ("", log_fn);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST empty_long_flag() {
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "--log-file", ""));
|
||||
ASSERT_STR_EQ("", log_fn);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST empty_long_flag_int() {
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "--var", ""));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST long_flag() {
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "--enable-feature"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST long_flag_arg() {
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "--log-file", "/var/log/kyaa"));
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "--var", "1337"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST long_flag_equals() {
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "--log-file=/var/log/kyaa"));
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "--var=1337"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST long_flag_equals_empty() {
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "--log-file="));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST long_flag_help_arg() {
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-l", "-h"));
|
||||
ASSERT_STR_EQ("-h", log_fn);
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "--log-file", "--help"));
|
||||
ASSERT_STR_EQ("--help", log_fn);
|
||||
ASSERT_OUT(blank);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST long_flag_equals_empty_int() {
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "--var="));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST undefined_long_flag() {
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "--print"));
|
||||
ASSERT_ERR("unknown flag: --print\n");
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "--out", "outfile"));
|
||||
ASSERT_ERR("unknown flag: --out\n");
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "--log", "/var/log/kyaa"));
|
||||
ASSERT_ERR("unknown flag: --log\n");
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "--log=/var/log/kyaa"));
|
||||
ASSERT_ERR("unknown flag: --log=/var/log/kyaa\n");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST dangling_arg() {
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "-l"));
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ERR("expected an argument for --log-file (-l)\n");
|
||||
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "-v"));
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ERR("expected an argument for --var (-v)\n");
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST valid_int() {
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "123456789"));
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "-1"));
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "0"));
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "1"));
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "-2147483648"));
|
||||
ASSERT_EQ(-2147483648, my_var);
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "2147483647"));
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "0xBADF00D"));
|
||||
ASSERT_EQ(0xBADF00D, my_var);
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "-v", "0755"));
|
||||
ASSERT_EQ(0755, my_var);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST invalid_int() {
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "hey"));
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", " "));
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "0xG00DF00D"));
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "123abc"));
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "0123456789"));
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "--"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST huge_int() {
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "2147483648"));
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", "-2147483649"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST help() {
|
||||
static const char *argv1[] = {"kyaa", "-h"};
|
||||
static const char *argv2[] = {"kyaa", "--help"};
|
||||
/* static const char *argv3[] = {"kyaa", "-?"}; */
|
||||
/* static const char *argv4[] = {"kyaa", "/?"}; */
|
||||
|
||||
ASSERT_OKAY(kyaa(2, argv1));
|
||||
ASSERT_OUT(help_text);
|
||||
ASSERT_ERR(blank);
|
||||
|
||||
ASSERT_OKAY(kyaa(2, argv2));
|
||||
ASSERT_OUT(help_text);
|
||||
ASSERT_ERR(blank);
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST use_stdin() {
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "-"));
|
||||
ASSERT(read_stdin);
|
||||
ASSERT_OUT("-\n");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST stop_parsing() {
|
||||
ASSERT_ZERO(kyaa_va(2, "kyaa", "--"));
|
||||
ASSERT_FALSE(read_stdin);
|
||||
ASSERT_ZERO(kyaa_va(5, "kyaa", "--", "blah", "--blah", "-b"));
|
||||
ASSERT_OUT("blah\n--blah\n-b\n");
|
||||
ASSERT_ZERO(kyaa_va(3, "kyaa", "--", "-x"));
|
||||
ASSERT_VALUES_UNCHANGED();
|
||||
ASSERT_ZERO(kyaa_va(4, "kyaa", "--", "-h", "--help"));
|
||||
ASSERT_OUT("-h\n--help\n");
|
||||
ASSERT_ERR(blank);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST keep_parsing() {
|
||||
ASSERT_ZERO(kyaa_va(4, "kyaa", "-l", "--", "-x"));
|
||||
ASSERT_STR_EQ("--", log_fn);
|
||||
ASSERT(use_feature);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST dangling_arg_oob() {
|
||||
// intentionally passing an inaccurate argc here:
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "-l", "don't read this"));
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ERR("expected an argument for --log-file (-l)\n");
|
||||
|
||||
ASSERT_FAIL(kyaa_va(2, "kyaa", "-v", "don't read this"));
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ERR("expected an argument for --var (-v)\n");
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST arg_oob() {
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-l", NULL));
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ERR("expected an argument for --log-file (-l)\n");
|
||||
|
||||
ASSERT_FAIL(kyaa_va(3, "kyaa", "-v", NULL));
|
||||
ASSERT_OUT(blank);
|
||||
ASSERT_ERR("expected an argument for --var (-v)\n");
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST negative_argc() {
|
||||
ASSERT_FAIL(kyaa(-1, basic_argv));
|
||||
ASSERT_FAIL(kyaa(LONG_MIN, basic_argv));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST null_argv() {
|
||||
ASSERT_FAIL(kyaa(0, NULL));
|
||||
ASSERT_FAIL(kyaa(1, NULL));
|
||||
ASSERT_FAIL(kyaa(2, NULL));
|
||||
ASSERT_FAIL(kyaa(-1, NULL));
|
||||
ASSERT_FAIL(kyaa(LONG_MIN, NULL));
|
||||
PASS();
|
||||
}
|
||||
|
||||
SUITE(goods) {
|
||||
RUN_TEST(no_arguments);
|
||||
RUN_TEST(one_flag);
|
||||
RUN_TEST(arg_flag);
|
||||
RUN_TEST(many_flags);
|
||||
RUN_TEST(empty_flag);
|
||||
RUN_TEST(empty_long_flag);
|
||||
RUN_TEST(long_flag);
|
||||
RUN_TEST(long_flag_arg);
|
||||
RUN_TEST(long_flag_equals);
|
||||
RUN_TEST(long_flag_equals_empty);
|
||||
RUN_TEST(long_flag_help_arg);
|
||||
RUN_TEST(valid_int);
|
||||
RUN_TEST(help);
|
||||
RUN_TEST(use_stdin);
|
||||
RUN_TEST(stop_parsing);
|
||||
RUN_TEST(keep_parsing);
|
||||
}
|
||||
|
||||
SUITE(bads) {
|
||||
RUN_TEST(undefined_flag);
|
||||
RUN_TEST(empty_long_flag_int);
|
||||
RUN_TEST(long_flag_equals_empty_int);
|
||||
RUN_TEST(undefined_long_flag);
|
||||
RUN_TEST(dangling_arg);
|
||||
RUN_TEST(invalid_int);
|
||||
RUN_TEST(huge_int);
|
||||
}
|
||||
|
||||
SUITE(uglies) {
|
||||
RUN_TEST(dangling_arg_oob);
|
||||
RUN_TEST(arg_oob);
|
||||
RUN_TEST(negative_argc);
|
||||
RUN_TEST(null_argv);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
GREATEST_MAIN_BEGIN();
|
||||
RUN_SUITE(goods);
|
||||
RUN_SUITE(bads);
|
||||
RUN_SUITE(uglies);
|
||||
GREATEST_MAIN_END();
|
||||
}
|
42
test_subject.c
Normal file
42
test_subject.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "kyaa.h"
|
||||
#include "kyaa_extra.h"
|
||||
|
||||
bool use_feature = false;
|
||||
const char *log_fn = "log.txt";
|
||||
long my_var = 0;
|
||||
bool read_stdin = false;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
KYAA_LOOP {
|
||||
// [other code goes here]
|
||||
KYAA_BEGIN
|
||||
|
||||
// arguments: short flag, long flag, help description
|
||||
KYAA_FLAG('x', "enable-feature",
|
||||
" enable some feature")
|
||||
use_feature = true;
|
||||
|
||||
// same arguments, but kyaa_etc contains the relevant string.
|
||||
KYAA_FLAG_ARG('l', "log-file",
|
||||
" use a given filename for the log file")
|
||||
log_fn = kyaa_etc;
|
||||
|
||||
// same arguments, kyaa_etc is set, but kyaa_long_value is also set.
|
||||
KYAA_FLAG_LONG('v', "var",
|
||||
" set an integer variable\n"
|
||||
" default: 0")
|
||||
my_var = kyaa_long_value;
|
||||
|
||||
KYAA_END
|
||||
|
||||
// [other code goes here]
|
||||
read_stdin = kyaa_read_stdin;
|
||||
KYAA_OUT("%s\n", kyaa_arg);
|
||||
}
|
||||
return 0;
|
||||
}
|
36
testall
Normal file
36
testall
Normal file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
CC=${CC:-gcc}
|
||||
|
||||
# ensure all tests are queued to run.
|
||||
awk '
|
||||
/^TEST/ {
|
||||
match($0, /[a-z0-9_]+/)
|
||||
key = substr($0, RSTART, RLENGTH)
|
||||
seen[key] = or(seen[key], 1)
|
||||
}
|
||||
/RUN_TEST/ {
|
||||
match($0, /[a-z0-9][a-z0-9_]*/)
|
||||
key = substr($0, RSTART, RLENGTH)
|
||||
seen[key] = or(seen[key], 2)
|
||||
}
|
||||
END {
|
||||
result = 0
|
||||
for (key in seen) {
|
||||
if (seen[key] == 1) {
|
||||
print "defined but never tested: " key
|
||||
result = or(result, 1)
|
||||
} else if (seen[key] == 2) {
|
||||
print "tested but never defined: " key
|
||||
result = or(result, 1)
|
||||
}
|
||||
}
|
||||
exit(result)
|
||||
}
|
||||
' test.c
|
||||
|
||||
$CC -std=c99 -Wall -Wextra -Wno-unused -Werror $CFLAGS test_subject.c -o test_subject
|
||||
$CC -std=c99 -Wall -Wextra -Wno-unused -Werror $CFLAGS test.c -o test
|
||||
./test
|
Loading…
Add table
Reference in a new issue