diff options
Diffstat (limited to 'tests')
63 files changed, 1368 insertions, 191 deletions
diff --git a/tests/alloc.c b/tests/alloc.c index f91b428..4aae515 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -10,27 +10,45 @@ #include <stdlib.h> #include <stdint.h> +struct flexible { + alignas(64) int foo[8]; + int bar[]; +}; + +/** Check varena_realloc() poisoning for a size combination. */ +static struct flexible *check_varena_realloc(struct varena *varena, struct flexible *flexy, size_t old_count, size_t new_count) { + flexy = varena_realloc(varena, flexy, old_count, new_count); + bfs_everify(flexy); + + for (size_t i = 0; i < new_count; ++i) { + if (i < old_count) { + bfs_check(flexy->bar[i] == (int)i); + } else { + flexy->bar[i] = i; + } + } + + return flexy; +} + void check_alloc(void) { + // Check aligned allocation + void *ptr; + bfs_everify((ptr = zalloc(64, 129))); + bfs_check((uintptr_t)ptr % 64 == 0); + bfs_echeck((ptr = xrealloc(ptr, 64, 129, 65))); + bfs_check((uintptr_t)ptr % 64 == 0); + free(ptr); + // Check sizeof_flex() - struct flexible { - alignas(64) int foo[8]; - int bar[]; - }; bfs_check(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); bfs_check(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0); - size_t too_many = SIZE_MAX / sizeof(int) + 1; + // volatile to suppress -Walloc-size-larger-than + volatile size_t too_many = SIZE_MAX / sizeof(int) + 1; bfs_check(sizeof_flex(struct flexible, bar, too_many) == align_floor(alignof(struct flexible), SIZE_MAX)); - // Corner case: sizeof(type) > align_ceil(alignof(type), offsetof(type, member)) - // Doesn't happen in typical ABIs - bfs_check(flex_size(8, 16, 4, 4, 1) == 16); - // Make sure we detect allocation size overflows -#if __GNUC__ && !__clang__ -# pragma GCC diagnostic ignored "-Walloc-size-larger-than=" -#endif - bfs_check(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); bfs_check(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); bfs_check(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); @@ -41,10 +59,20 @@ void check_alloc(void) { VARENA_INIT(&varena, struct flexible, bar); for (size_t i = 0; i < 256; ++i) { - bfs_verify(varena_alloc(&varena, i)); + bfs_everify(varena_alloc(&varena, i)); struct arena *arena = &varena.arenas[varena.narenas - 1]; bfs_check(arena->size >= sizeof_flex(struct flexible, bar, i)); } + // Check varena_realloc() (un)poisoning + struct flexible *flexy = varena_alloc(&varena, 160); + bfs_everify(flexy); + + flexy = check_varena_realloc(&varena, flexy, 0, 160); + flexy = check_varena_realloc(&varena, flexy, 160, 192); + flexy = check_varena_realloc(&varena, flexy, 192, 160); + flexy = check_varena_realloc(&varena, flexy, 160, 320); + flexy = check_varena_realloc(&varena, flexy, 320, 96); + varena_destroy(&varena); } diff --git a/tests/bfs/color_ca.out b/tests/bfs/color_ca.out new file mode 100644 index 0000000..bf74202 --- /dev/null +++ b/tests/bfs/color_ca.out @@ -0,0 +1,4 @@ +[01;34m.[0m +[01;34m./[0m[01;36mlink[0m +[01;34m./[0m[30;41mcapable[0m +[01;34m./[0mnormal diff --git a/tests/bfs/color_ca.sh b/tests/bfs/color_ca.sh new file mode 100644 index 0000000..3aaaaf1 --- /dev/null +++ b/tests/bfs/color_ca.sh @@ -0,0 +1,10 @@ +test "$UNAME" = "Linux" || skip +invoke_bfs . -quit -capable || skip + +cd "$TEST" + +"$XTOUCH" normal capable +bfs_sudo setcap all+ep capable || skip +ln -s capable link + +LS_COLORS="ca=30;41:" bfs_diff . -color diff --git a/tests/bfs/color_ca_incapable.out b/tests/bfs/color_ca_incapable.out new file mode 100644 index 0000000..a439814 --- /dev/null +++ b/tests/bfs/color_ca_incapable.out @@ -0,0 +1,27 @@ +[01;34m$'rainbow/\e[1m'[0m +[01;34m$'rainbow/\e[1m/'[0m$'\e[0m' +[01;34mrainbow[0m +[01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m +[01;34mrainbow/[0m[01;36mbroken[0m +[01;34mrainbow/[0m[01;36mchardev_link[0m +[01;34mrainbow/[0m[01;36mlink.txt[0m +[01;34mrainbow/[0m[30;42msticky_ow[0m +[01;34mrainbow/[0m[30;43msgid[0m +[01;34mrainbow/[0m[33mpipe[0m +[01;34mrainbow/[0m[34;42mow[0m +[01;34mrainbow/[0m[37;41msugid[0m +[01;34mrainbow/[0m[37;41msuid[0m +[01;34mrainbow/[0m[37;44msticky[0m +[01;34mrainbow/[0mfile.dat +[01;34mrainbow/[0mfile.txt +[01;34mrainbow/[0mlower.gz +[01;34mrainbow/[0mlower.tar +[01;34mrainbow/[0mlower.tar.gz +[01;34mrainbow/[0mlu.tar.GZ +[01;34mrainbow/[0mmh1 +[01;34mrainbow/[0mmh2 +[01;34mrainbow/[0mul.TAR.gz +[01;34mrainbow/[0mupper.GZ +[01;34mrainbow/[0mupper.TAR +[01;34mrainbow/[0mupper.TAR.GZ diff --git a/tests/bfs/color_ca_incapable.sh b/tests/bfs/color_ca_incapable.sh new file mode 100644 index 0000000..f46a127 --- /dev/null +++ b/tests/bfs/color_ca_incapable.sh @@ -0,0 +1 @@ +LS_COLORS="ca=30;41:" bfs_diff rainbow -color diff --git a/tests/bfs/color_ext_case_nul.out b/tests/bfs/color_ext_case_nul.out new file mode 100644 index 0000000..8ccd9a7 --- /dev/null +++ b/tests/bfs/color_ext_case_nul.out @@ -0,0 +1,27 @@ +[01;34m$'rainbow/\e[1m'[0m +[01;34m$'rainbow/\e[1m/'[0m$'\e[0m' +[01;34mrainbow[0m +[01;34mrainbow/[0m[01;31mlower.gz[0m +[01;34mrainbow/[0m[01;31mlower.tar.gz[0m +[01;34mrainbow/[0m[01;31mlu.tar.GZ[0m +[01;34mrainbow/[0m[01;31mul.TAR.gz[0m +[01;34mrainbow/[0m[01;31mupper.GZ[0m +[01;34mrainbow/[0m[01;31mupper.TAR.GZ[0m +[01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m +[01;34mrainbow/[0m[01;36mbroken[0m +[01;34mrainbow/[0m[01;36mchardev_link[0m +[01;34mrainbow/[0m[01;36mlink.txt[0m +[01;34mrainbow/[0m[30;42msticky_ow[0m +[01;34mrainbow/[0m[30;43msgid[0m +[01;34mrainbow/[0m[33mpipe[0m +[01;34mrainbow/[0m[34;42mow[0m +[01;34mrainbow/[0m[37;41msugid[0m +[01;34mrainbow/[0m[37;41msuid[0m +[01;34mrainbow/[0m[37;44msticky[0m +[01;34mrainbow/[0mfile.dat +[01;34mrainbow/[0mfile.txt +[01;34mrainbow/[0mlower.tar +[01;34mrainbow/[0mmh1 +[01;34mrainbow/[0mmh2 +[01;34mrainbow/[0mupper.TAR diff --git a/tests/bfs/color_ext_case_nul.sh b/tests/bfs/color_ext_case_nul.sh new file mode 100644 index 0000000..68fea1c --- /dev/null +++ b/tests/bfs/color_ext_case_nul.sh @@ -0,0 +1,5 @@ +# Regression test: embedded NUL bytes in an extension caused an assertion +# failure in the trie implementation + +export LS_COLORS='*.gz=01;31:*\0.GZ=01;32:' +bfs_diff rainbow -color diff --git a/tests/bfs/color_ls.out b/tests/bfs/color_ls.out index f69eb9c..cc64318 100644 --- a/tests/bfs/color_ls.out +++ b/tests/bfs/color_ls.out @@ -8,5 +8,5 @@ [01;34mfoo/bar/[0m[01;31mnowhere/nothing[0m [01;34mfoo/bar/[0mbaz [01;34mfoo/bar/[0mbaz -[01;34mfoo/bar/baz[0m[01;31m//qux[0m -[01;34mfoo/bar/baz[0m[01;31m//qux[0m +[01;34mfoo/bar/[0mbaz[01;31m//qux[0m +[01;34mfoo/bar/[0mbaz[01;31m//qux[0m diff --git a/tests/bfs/color_notdir_slash_error.out b/tests/bfs/color_notdir_slash_error.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/bfs/color_notdir_slash_error.out diff --git a/tests/bfs/color_notdir_slash_error.sh b/tests/bfs/color_notdir_slash_error.sh new file mode 100644 index 0000000..ca26d50 --- /dev/null +++ b/tests/bfs/color_notdir_slash_error.sh @@ -0,0 +1,2 @@ +# Regression test: infinite loop printing the error message for .../notdir/nowhere +! bfs_diff -color links/notdir/nowhere diff --git a/tests/bfs/execdir_path_relative_slash.out b/tests/bfs/execdir_path_relative_slash.out new file mode 100644 index 0000000..62b31f6 --- /dev/null +++ b/tests/bfs/execdir_path_relative_slash.out @@ -0,0 +1,19 @@ +./a +./b +./bar +./bar +./basic +./baz +./c +./d +./e +./f +./foo +./foo +./foo +./g +./h +./i +./j +./k +./l diff --git a/tests/bfs/execdir_path_relative_slash.sh b/tests/bfs/execdir_path_relative_slash.sh new file mode 100644 index 0000000..fb5a924 --- /dev/null +++ b/tests/bfs/execdir_path_relative_slash.sh @@ -0,0 +1 @@ +PATH="foo:$PATH" bfs_diff basic -execdir /bin/sh -c 'printf "%s\\n" "$@"' sh {} + diff --git a/tests/bfs/files0_from_root.sh b/tests/bfs/files0_from_root.sh new file mode 100644 index 0000000..6ba5f00 --- /dev/null +++ b/tests/bfs/files0_from_root.sh @@ -0,0 +1,2 @@ +printf 'basic\0' >"$TEST/input" +! invoke_bfs basic -files0-from "$TEST/input" diff --git a/tests/bfstd.c b/tests/bfstd.c index a43783a..6e15e2b 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -6,35 +6,15 @@ #include "bfstd.h" #include "diag.h" +#include <errno.h> #include <langinfo.h> +#include <limits.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> -/** Check the result of xdirname()/xbasename(). */ -static void check_base_dir(const char *path, const char *dir, const char *base) { - char *xdir = xdirname(path); - bfs_everify(xdir, "xdirname()"); - bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir); - free(xdir); - - char *xbase = xbasename(path); - bfs_everify(xbase, "xbasename()"); - bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base); - free(xbase); -} - -/** Check the result of wordesc(). */ -static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) { - char buf[256]; - char *end = buf + sizeof(buf); - char *esc = wordesc(buf, end, str, flags); - - if (bfs_check(esc != end)) { - bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp); - } -} - -void check_bfstd(void) { +/** asciilen() test cases. */ +static void check_asciilen(void) { bfs_check(asciilen("") == 0); bfs_check(asciilen("@") == 1); bfs_check(asciilen("@@") == 2); @@ -49,7 +29,23 @@ void check_bfstd(void) { bfs_check(asciilen("@@@@@@@\xFF@@@@@@a\xFF@@@@@@@") == 7); bfs_check(asciilen("@@@@@@@@\xFF@@@@@a\xFF@@@@@@@") == 8); bfs_check(asciilen("@@@@@@@@@\xFF@@@@a\xFF@@@@@@@") == 9); +} + +/** Check the result of xdirname()/xbasename(). */ +static void check_base_dir(const char *path, const char *dir, const char *base) { + char *xdir = xdirname(path); + bfs_everify(xdir, "xdirname()"); + bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir); + free(xdir); + char *xbase = xbasename(path); + bfs_everify(xbase, "xbasename()"); + bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base); + free(xbase); +} + +/** xdirname()/xbasename() test cases. */ +static void check_basedirs(void) { // From man 3p basename check_base_dir("usr", ".", "usr"); check_base_dir("usr/", ".", "usr"); @@ -61,7 +57,21 @@ void check_bfstd(void) { check_base_dir("/usr/lib", "/usr", "lib"); check_base_dir("//usr//lib//", "//usr", "lib"); check_base_dir("/home//dwc//test", "/home//dwc", "test"); +} + +/** Check the result of wordesc(). */ +static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) { + char buf[256]; + char *end = buf + sizeof(buf); + char *esc = wordesc(buf, end, str, flags); + if (bfs_check(esc != end)) { + bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp); + } +} + +/** wordesc() test cases. */ +static void check_wordescs(void) { check_wordesc("", "\"\"", WESC_SHELL); check_wordesc("word", "word", WESC_SHELL); check_wordesc("two words", "\"two words\"", WESC_SHELL); @@ -80,7 +90,123 @@ void check_bfstd(void) { check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); check_wordesc("\xCB\x9Cuser", "\xCB\x9Cuser", WESC_SHELL); } +} + +/** xstrto*() test cases. */ +static void check_strtox(void) { + short s; + unsigned short us; + int i; + unsigned int ui; + long l; + unsigned long ul; + long long ll; + unsigned long long ull; + char *end; + +#define check_strtouerr(err, str, end, base) \ + do { \ + bfs_echeck(xstrtous(str, end, base, &us) != 0 && errno == err); \ + bfs_echeck(xstrtoui(str, end, base, &ui) != 0 && errno == err); \ + bfs_echeck(xstrtoul(str, end, base, &ul) != 0 && errno == err); \ + bfs_echeck(xstrtoull(str, end, base, &ull) != 0 && errno == err); \ + } while (0) + + check_strtouerr(ERANGE, "-1", NULL, 0); + check_strtouerr(ERANGE, "-0x1", NULL, 0); + check_strtouerr(EINVAL, "-", NULL, 0); + check_strtouerr(EINVAL, "-q", NULL, 0); + check_strtouerr(EINVAL, "-1q", NULL, 0); + check_strtouerr(EINVAL, "-0x", NULL, 0); + +#define check_strtoerr(err, str, end, base) \ + do { \ + bfs_echeck(xstrtos(str, end, base, &s) != 0 && errno == err); \ + bfs_echeck(xstrtoi(str, end, base, &i) != 0 && errno == err); \ + bfs_echeck(xstrtol(str, end, base, &l) != 0 && errno == err); \ + bfs_echeck(xstrtoll(str, end, base, &ll) != 0 && errno == err); \ + check_strtouerr(err, str, end, base); \ + } while (0) + + check_strtoerr(EINVAL, "", NULL, 0); + check_strtoerr(EINVAL, "", &end, 0); + check_strtoerr(EINVAL, " 1 ", &end, 0); + check_strtoerr(EINVAL, " -1", NULL, 0); + check_strtoerr(EINVAL, " 123", NULL, 0); + check_strtoerr(EINVAL, "123 ", NULL, 0); + check_strtoerr(EINVAL, "0789", NULL, 0); + check_strtoerr(EINVAL, "789A", NULL, 0); + check_strtoerr(EINVAL, "0x", NULL, 0); + check_strtoerr(EINVAL, "0x789A", NULL, 10); + check_strtoerr(EINVAL, "0x-1", NULL, 0); + +#define check_strtotype(type, min, max, fmt, fn, str, base, v, n) \ + do { \ + if ((n) >= min && (n) <= max) { \ + bfs_echeck(fn(str, NULL, base, &v) == 0); \ + bfs_check(v == (type)(n), "%s('%s') == " fmt " (!= " fmt ")", #fn, str, v, (type)(n)); \ + } else { \ + bfs_echeck(fn(str, NULL, base, &v) != 0 && errno == ERANGE); \ + } \ + } while (0) + +#define check_strtoint(str, base, n) \ + do { \ + check_strtotype( signed short, SHRT_MIN, SHRT_MAX, "%d", xstrtos, str, base, s, n); \ + check_strtotype( signed int, INT_MIN, INT_MAX, "%d", xstrtoi, str, base, i, n); \ + check_strtotype( signed long, LONG_MIN, LONG_MAX, "%ld", xstrtol, str, base, l, n); \ + check_strtotype( signed long long, LLONG_MIN, LLONG_MAX, "%lld", xstrtoll, str, base, ll, n); \ + check_strtotype(unsigned short, 0, USHRT_MAX, "%u", xstrtous, str, base, us, n); \ + check_strtotype(unsigned int, 0, UINT_MAX, "%u", xstrtoui, str, base, ui, n); \ + check_strtotype(unsigned long, 0, ULONG_MAX, "%lu", xstrtoul, str, base, ul, n); \ + check_strtotype(unsigned long long, 0, ULLONG_MAX, "%llu", xstrtoull, str, base, ull, n); \ + } while (0) + + check_strtoint("123", 0, 123); + check_strtoint("+123", 0, 123); + check_strtoint("-123", 0, -123); + + check_strtoint("0123", 0, 0123); + check_strtoint("0x789A", 0, 0x789A); + + check_strtoint("0123", 10, 123); + check_strtoint("0789", 10, 789); + + check_strtoint("123", 16, 0x123); + + check_strtoint("0x7FFF", 0, 0x7FFF); + check_strtoint("-0x8000", 0, -0x8000); + + check_strtoint("0x7FFFFFFF", 0, 0x7FFFFFFFL); + check_strtoint("-0x80000000", 0, -0x7FFFFFFFL - 1); + + check_strtoint("0x7FFFFFFFFFFFFFFF", 0, 0x7FFFFFFFFFFFFFFFLL); + check_strtoint("-0x8000000000000000", 0, -0x7FFFFFFFFFFFFFFFLL - 1); + +#define check_strtoend(str, estr, base, n) \ + do { \ + bfs_echeck(xstrtoll(str, &end, base, &ll) == 0); \ + bfs_check(ll == (n), "xstrtoll('%s') == %lld (!= %lld)", str, ll, (long long)(n)); \ + bfs_check(strcmp(end, estr) == 0, "xstrtoll('%s'): end == '%s' (!= '%s')", str, end, estr); \ + } while (0) + + check_strtoend("123 ", " ", 0, 123); + check_strtoend("0789", "89", 0, 07); + check_strtoend("789A", "A", 0, 789); + check_strtoend("0xDEFG", "G", 0, 0xDEF); +} + +/** xstrwidth() test cases. */ +static void check_strwidth(void) { bfs_check(xstrwidth("Hello world") == 11); bfs_check(xstrwidth("Hello\1world") == 10); } + +void check_bfstd(void) { + check_asciilen(); + check_basedirs(); + check_wordescs(); + check_strtox(); + check_strwidth(); +} diff --git a/tests/bit.c b/tests/bit.c index cc95785..09d470b 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -64,7 +64,7 @@ static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); bfs_check((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b)) void check_bit(void) { - const char *str = "\x1\x2\x3\x4"; + const char *str = "\x1\x2\x3\x4\x5\x6\x7\x8"; uint32_t word; memcpy(&word, str, sizeof(word)); @@ -81,6 +81,22 @@ void check_bit(void) { check_eq(bswap((uint32_t)0x12345678), 0x78563412); check_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412); + // Make sure we can bswap() every unsigned type + (void)bswap((unsigned char)0); + (void)bswap((unsigned short)0); + (void)bswap(0U); + (void)bswap(0UL); + (void)bswap(0ULL); + + check_eq(load8_beu8(str), 0x01); + check_eq(load8_leu8(str), 0x01); + check_eq(load8_beu16(str), 0x0102); + check_eq(load8_leu16(str), 0x0201); + check_eq(load8_beu32(str), 0x01020304); + check_eq(load8_leu32(str), 0x04030201); + check_eq(load8_beu64(str), 0x0102030405060708ULL); + check_eq(load8_leu64(str), 0x0807060504030201ULL); + check_eq(count_ones(0x0U), 0); check_eq(count_ones(0x1U), 1); check_eq(count_ones(0x2U), 1); diff --git a/tests/color.sh b/tests/color.sh index 4f4312e..9e2e0f6 100644 --- a/tests/color.sh +++ b/tests/color.sh @@ -46,9 +46,7 @@ show_bar() { # Name the pipe deterministically based on the ttyname, so that concurrent # tests.sh runs on the same terminal (e.g. make -jN check) cooperate - local pipe - pipe=$(printf '%s' "$TTY" | tr '/' '-') - pipe="${TMPDIR:-/tmp}/bfs$pipe.bar" + local pipe="${TMPDIR:-/tmp}/bfs${TTY//\//-}.bar" if mkfifo "$pipe" 2>/dev/null; then # We won the race, create the background process to manage the bar @@ -78,7 +76,7 @@ hide_bar() { # The background process that muxes multiple status bars for one TTY bar_proc() { # Read from the pipe, write to the TTY - exec <"$1" >"$TTY" + exec <"$1" >/dev/tty # Delete the pipe when done defer rm "$1" @@ -133,7 +131,7 @@ bar_proc() { # Resize the status bar resize_bar() { # Bash gets $LINES from stderr, so if it's redirected use tput instead - TTY_HEIGHT="${LINES:-$(tput lines 2>"$TTY")}" + TTY_HEIGHT="${LINES:-$(tput lines 2>/dev/tty)}" if ((BAR_HEIGHT == 0)); then return diff --git a/tests/getopts.sh b/tests/getopts.sh index 5214e9f..a16511f 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -5,11 +5,7 @@ ## Argument parsing -if command -v nproc &>/dev/null; then - JOBS=$(nproc) -else - JOBS=1 -fi +JOBS=$(_nproc) MAKE= PATTERNS=() SUDO=() @@ -23,7 +19,6 @@ VERBOSE_TESTS=0 # Print usage information usage() { - local pad=$(printf "%*s" ${#0} "") color cat <<EOF Usage: ${GRN}$0${RST} [${BLU}-j${RST}${BLD}N${RST}] [${BLU}--make${RST}=${BLD}MAKE${RST}] [${BLU}--bfs${RST}=${BLD}path/to/bfs${RST}] [${BLU}--sudo${RST}[=${BLD}COMMAND${RST}]] diff --git a/tests/gnu/L_printf_types.out b/tests/gnu/L_printf_types.out new file mode 100644 index 0000000..734b15f --- /dev/null +++ b/tests/gnu/L_printf_types.out @@ -0,0 +1,17 @@ +(links) () d d +(links/broken) (nowhere) l N +(links/deeply) () d d +(links/deeply/nested) () d d +(links/deeply/nested/broken) (nowhere) l N +(links/deeply/nested/dir) () d d +(links/deeply/nested/file) () f f +(links/deeply/nested/link) () f f +(links/file) () f f +(links/hardlink) () f f +(links/notdir) (symlink/file) l N +(links/skip) () d d +(links/skip/broken) (nowhere) l N +(links/skip/dir) () d d +(links/skip/file) () f f +(links/skip/link) () f f +(links/symlink) () f f diff --git a/tests/gnu/L_printf_types.sh b/tests/gnu/L_printf_types.sh new file mode 100644 index 0000000..caa9083 --- /dev/null +++ b/tests/gnu/L_printf_types.sh @@ -0,0 +1 @@ +bfs_diff -L links -printf '(%p) (%l) %y %Y\n' diff --git a/tests/gnu/execdir_self.out b/tests/gnu/execdir_self.out new file mode 100644 index 0000000..3ad0640 --- /dev/null +++ b/tests/gnu/execdir_self.out @@ -0,0 +1 @@ +./bar.sh diff --git a/tests/gnu/execdir_self.sh b/tests/gnu/execdir_self.sh new file mode 100644 index 0000000..1fc5d04 --- /dev/null +++ b/tests/gnu/execdir_self.sh @@ -0,0 +1,9 @@ +cd "$TEST" +mkdir foo +cat >foo/bar.sh <<EOF +#!/bin/sh +printf '%s\n' "\$@" +EOF +chmod +x foo/bar.sh + +bfs_diff . -name bar.sh -execdir {} {} \; diff --git a/tests/gnu/files0_from_empty.sh b/tests/gnu/files0_from_empty.sh index 85eee8f..7b42772 100644 --- a/tests/gnu/files0_from_empty.sh +++ b/tests/gnu/files0_from_empty.sh @@ -1 +1 @@ -! printf "\0" | invoke_bfs -files0-from - +! printf '\0' | invoke_bfs -files0-from - diff --git a/tests/gnu/files0_from_file_file.out b/tests/gnu/files0_from_file_file.out new file mode 100644 index 0000000..fb683c7 --- /dev/null +++ b/tests/gnu/files0_from_file_file.out @@ -0,0 +1,2 @@ +basic/g +basic/g/h diff --git a/tests/gnu/files0_from_file_file.sh b/tests/gnu/files0_from_file_file.sh new file mode 100644 index 0000000..1119952 --- /dev/null +++ b/tests/gnu/files0_from_file_file.sh @@ -0,0 +1,3 @@ +printf 'basic/c\0' >"$TEST/in1" +printf 'basic/g\0' >"$TEST/in2" +bfs_diff -files0-from "$TEST/in1" -files0-from "$TEST/in2" diff --git a/tests/gnu/files0_from_ok.sh b/tests/gnu/files0_from_ok.sh deleted file mode 100644 index 8e145ce..0000000 --- a/tests/gnu/files0_from_ok.sh +++ /dev/null @@ -1 +0,0 @@ -! printf "basic\0" | invoke_bfs -files0-from - -ok echo {} \; diff --git a/tests/gnu/files0_from_stdin_ok.sh b/tests/gnu/files0_from_stdin_ok.sh new file mode 100644 index 0000000..0283c8d --- /dev/null +++ b/tests/gnu/files0_from_stdin_ok.sh @@ -0,0 +1 @@ +! printf 'basic\0' | invoke_bfs -files0-from - -ok echo {} \; diff --git a/tests/gnu/files0_from_stdin_ok_file.out b/tests/gnu/files0_from_stdin_ok_file.out new file mode 100644 index 0000000..0f6b00d --- /dev/null +++ b/tests/gnu/files0_from_stdin_ok_file.out @@ -0,0 +1,45 @@ + + + + + + /j + /j +! +!- +!-/e +!-/e +!/d +!/d +( +(- +(-/c +(-/c +(/b +(/b +) +)/g +)/g +* +*/m +*/m +, +,/f +,/f +- +-/a +-/a +... +.../h +.../h +/n +/n +[ +[/k +[/k +\ +\/i +\/i +{ +{/l +{/l diff --git a/tests/gnu/files0_from_stdin_ok_file.sh b/tests/gnu/files0_from_stdin_ok_file.sh new file mode 100644 index 0000000..028df0c --- /dev/null +++ b/tests/gnu/files0_from_stdin_ok_file.sh @@ -0,0 +1,4 @@ +FILE="$TMP/$TEST.in" +cd weirdnames +invoke_bfs -mindepth 1 -fprintf "$FILE" "%P\0" +yes | bfs_diff -files0-from - -ok printf '%s\n' {} \; -files0-from "$FILE" diff --git a/tests/gnu/files0_from_stdin_stdin.out b/tests/gnu/files0_from_stdin_stdin.out new file mode 100644 index 0000000..0f6b00d --- /dev/null +++ b/tests/gnu/files0_from_stdin_stdin.out @@ -0,0 +1,45 @@ + + + + + + /j + /j +! +!- +!-/e +!-/e +!/d +!/d +( +(- +(-/c +(-/c +(/b +(/b +) +)/g +)/g +* +*/m +*/m +, +,/f +,/f +- +-/a +-/a +... +.../h +.../h +/n +/n +[ +[/k +[/k +\ +\/i +\/i +{ +{/l +{/l diff --git a/tests/gnu/files0_from_stdin_stdin.sh b/tests/gnu/files0_from_stdin_stdin.sh new file mode 100644 index 0000000..8f6368f --- /dev/null +++ b/tests/gnu/files0_from_stdin_stdin.sh @@ -0,0 +1,2 @@ +cd weirdnames +invoke_bfs -mindepth 1 -printf "%P\0" | bfs_diff -files0-from - -files0-from - diff --git a/tests/gnu/fls_overflow.sh b/tests/gnu/fls_overflow.sh new file mode 100644 index 0000000..067bc86 --- /dev/null +++ b/tests/gnu/fls_overflow.sh @@ -0,0 +1,4 @@ +# Regression test: times that overflow localtime() should still print +cd "$TEST" +"$XTOUCH" -t "@1111111111111111111" overflow || skip +invoke_bfs . -fls "$OUT" diff --git a/tests/gnu/follow_files0_from.out b/tests/gnu/follow_files0_from.out new file mode 100644 index 0000000..c77d546 --- /dev/null +++ b/tests/gnu/follow_files0_from.out @@ -0,0 +1,42 @@ +links +links/broken +links/broken +links/deeply +links/deeply +links/deeply/nested +links/deeply/nested +links/deeply/nested +links/deeply/nested/broken +links/deeply/nested/broken +links/deeply/nested/broken +links/deeply/nested/broken +links/deeply/nested/dir +links/deeply/nested/dir +links/deeply/nested/dir +links/deeply/nested/dir +links/deeply/nested/file +links/deeply/nested/file +links/deeply/nested/file +links/deeply/nested/file +links/deeply/nested/link +links/deeply/nested/link +links/deeply/nested/link +links/deeply/nested/link +links/file +links/file +links/hardlink +links/hardlink +links/notdir +links/notdir +links/skip +links/skip +links/skip/broken +links/skip/broken +links/skip/dir +links/skip/dir +links/skip/file +links/skip/file +links/skip/link +links/skip/link +links/symlink +links/symlink diff --git a/tests/gnu/follow_files0_from.sh b/tests/gnu/follow_files0_from.sh new file mode 100644 index 0000000..8c20f6d --- /dev/null +++ b/tests/gnu/follow_files0_from.sh @@ -0,0 +1 @@ +invoke_bfs links -print0 | bfs_diff -follow -files0-from - diff --git a/tests/gnu/fstype_btrfs_subvol.out b/tests/gnu/fstype_btrfs_subvol.out new file mode 100644 index 0000000..8871fb9 --- /dev/null +++ b/tests/gnu/fstype_btrfs_subvol.out @@ -0,0 +1,4 @@ +mnt +mnt/file +mnt/subvol +mnt/subvol/file diff --git a/tests/gnu/fstype_btrfs_subvol.sh b/tests/gnu/fstype_btrfs_subvol.sh new file mode 100644 index 0000000..71df45c --- /dev/null +++ b/tests/gnu/fstype_btrfs_subvol.sh @@ -0,0 +1,25 @@ +# Test that -fstype works in btrfs subvolumes + +command -v btrfs &>/dev/null || skip + +cd "$TEST" + +# Make a btrfs filesystem image +truncate -s128M img +mkfs.btrfs img >&2 + +# Mount it +mkdir mnt +bfs_sudo mount img mnt || skip +defer bfs_sudo umount mnt + +# Make it owned by us +bfs_sudo chown "$(id -u):$(id -g)" mnt + +# Create a subvolume inside it +btrfs subvolume create mnt/subvol >&2 + +# Make a file in and outside the subvolume +"$XTOUCH" mnt/file mnt/subvol/file + +bfs_diff mnt -fstype btrfs -print -o -printf '%p %F\n' diff --git a/tests/gnu/ignore_readdir_race_rmdir.out b/tests/gnu/ignore_readdir_race_rmdir.out new file mode 100644 index 0000000..ede8749 --- /dev/null +++ b/tests/gnu/ignore_readdir_race_rmdir.out @@ -0,0 +1,2 @@ +./bar +./foo diff --git a/tests/gnu/ignore_readdir_race_rmdir.sh b/tests/gnu/ignore_readdir_race_rmdir.sh new file mode 100644 index 0000000..87f36a9 --- /dev/null +++ b/tests/gnu/ignore_readdir_race_rmdir.sh @@ -0,0 +1,5 @@ +cd "$TEST" +"$XTOUCH" -p foo/ bar/ + +# Check that -ignore_readdir_race suppresses errors from opendir() +bfs_diff . -ignore_readdir_race -mindepth 1 -print -name foo -exec rmdir {} \; diff --git a/tests/gnu/ok_files0_from_stdin.sh b/tests/gnu/ok_files0_from_stdin.sh new file mode 100644 index 0000000..2c4de7b --- /dev/null +++ b/tests/gnu/ok_files0_from_stdin.sh @@ -0,0 +1 @@ +! printf 'basic\0' | invoke_bfs -ok echo {} \; -files0-from - diff --git a/tests/gnu/ok_flush.sh b/tests/gnu/ok_flush.sh index 87c7298..a5dc0d0 100644 --- a/tests/gnu/ok_flush.sh +++ b/tests/gnu/ok_flush.sh @@ -1,4 +1,4 @@ # I/O streams should be flushed before -ok prompts -yes | invoke_bfs basic -printf '%p ? ' -ok echo found \; 2>&1 | tr '\0' ' ' | sed 's/?.*?/?/' >"$OUT" +yes | invoke_bfs basic -printf '%p ? ' -ok echo found \; 2>&1 | sed 's/?.*?/?/' >"$OUT" sort_output diff_output diff --git a/tests/ioq.c b/tests/ioq.c index f067436..1a0da97 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -49,6 +49,7 @@ static void check_ioq_push_block(void) { int ret = ioq_opendir(ioq, dir, AT_FDCWD, ".", 0, NULL); bfs_everify(ret == 0, "ioq_opendir()"); } + ioq_submit(ioq); bfs_verify(ioq_capacity(ioq) == 0); // Now cancel the queue, pushing an additional IOQ_STOP message diff --git a/tests/main.c b/tests/main.c index 81c2311..9240e1c 100644 --- a/tests/main.c +++ b/tests/main.c @@ -7,24 +7,40 @@ #include "tests.h" +#include "alloc.h" +#include "bfstd.h" #include "color.h" +#include "list.h" #include <locale.h> #include <stdio.h> #include <stdlib.h> +#include <stdint.h> #include <string.h> +#include <sys/wait.h> #include <time.h> +#include <unistd.h> /** Result of the current test. */ -static thread_local bool pass; +static bool pass; bool bfs_check_impl(bool result) { pass &= result; return result; } -/** Unit test function type. */ -typedef void test_fn(void); +/** + * A running test. + */ +struct test_proc { + /** Linked list links. */ + struct test_proc *prev, *next; + + /** The PID of this test. */ + pid_t pid; + /** The name of this test. */ + const char *name; +}; /** * Global test context. @@ -35,6 +51,17 @@ struct test_ctx { /** The arguments themselves. */ char **argv; + /** Maximum jobs (-j). */ + int jobs; + /** Current jobs. */ + int running; + /** Completed jobs. */ + int done; + /** List of running tests. */ + struct { + struct test_proc *head, *tail; + } procs; + /** Parsed colors. */ struct colors *colors; /** Colorized output stream. */ @@ -45,10 +72,15 @@ struct test_ctx { }; /** Initialize the test context. */ -static int test_init(struct test_ctx *ctx, int argc, char **argv) { +static int test_init(struct test_ctx *ctx, int jobs, int argc, char **argv) { ctx->argc = argc; ctx->argv = argv; + ctx->jobs = jobs; + ctx->running = 0; + ctx->done = 0; + LIST_INIT(&ctx->procs); + ctx->colors = parse_colors(); ctx->cout = cfwrap(stdout, ctx->colors, false); if (!ctx->cout) { @@ -60,26 +92,15 @@ static int test_init(struct test_ctx *ctx, int argc, char **argv) { return 0; } -/** Finalize the test context. */ -static int test_fini(struct test_ctx *ctx) { - if (ctx->cout) { - cfclose(ctx->cout); - } - - free_colors(ctx->colors); - - return ctx->ret; -} - /** Check if a test case is enabled for this run. */ static bool should_run(const struct test_ctx *ctx, const char *test) { // Run all tests by default - if (ctx->argc < 2) { + if (ctx->argc == 0) { return true; } // With args, run only specified tests - for (int i = 1; i < ctx->argc; ++i) { + for (int i = 0; i < ctx->argc; ++i) { if (strcmp(test, ctx->argv[i]) == 0) { return true; } @@ -88,19 +109,104 @@ static bool should_run(const struct test_ctx *ctx, const char *test) { return false; } -/** Run a test if it's enabled. */ -static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { - if (should_run(ctx, test)) { - pass = true; - fn(); +/** Wait for a test to finish. */ +static void wait_test(struct test_ctx *ctx) { + int wstatus; + pid_t pid = xwaitpid(0, &wstatus, 0); + bfs_everify(pid > 0, "xwaitpid()"); - if (pass) { - cfprintf(ctx->cout, "${grn}[PASS]${rs} ${bld}%s${rs}\n", test); + struct test_proc *proc = NULL; + for_list (struct test_proc, i, &ctx->procs) { + if (i->pid == pid) { + proc = i; + break; + } + } + + bfs_verify(proc, "No test_proc for PID %ju", (intmax_t)pid); + + bool passed = false; + + if (WIFEXITED(wstatus)) { + int status = WEXITSTATUS(wstatus); + if (status == EXIT_SUCCESS) { + cfprintf(ctx->cout, "${grn}[PASS]${rs} ${bld}%s${rs}\n", proc->name); + passed = true; + } else if (status == EXIT_FAILURE) { + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs}\n", proc->name); } else { - cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs}\n", test); - ctx->ret = EXIT_FAILURE; + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs} (Exit %d)\n", proc->name, status); + } + } else { + const char *str = NULL; + if (WIFSIGNALED(wstatus)) { + str = strsignal(WTERMSIG(wstatus)); } + if (!str) { + str = "Unknown"; + } + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs} (%s)\n", proc->name, str); + } + + if (!passed) { + ctx->ret = EXIT_FAILURE; } + + --ctx->running; + ++ctx->done; + LIST_REMOVE(&ctx->procs, proc); + free(proc); +} + +/** Unit test function type. */ +typedef void test_fn(void); + +/** Run a test if it's enabled. */ +static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { + if (!should_run(ctx, test)) { + return; + } + + while (ctx->running >= ctx->jobs) { + wait_test(ctx); + } + + struct test_proc *proc = ALLOC(struct test_proc); + bfs_everify(proc, "alloc()"); + + LIST_ITEM_INIT(proc); + proc->name = test; + + fflush(NULL); + proc->pid = fork(); + bfs_everify(proc->pid >= 0, "fork()"); + + if (proc->pid > 0) { + // Parent + ++ctx->running; + LIST_APPEND(&ctx->procs, proc); + return; + } + + // Child + pass = true; + fn(); + exit(pass ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/** Finalize the test context. */ +static int test_fini(struct test_ctx *ctx) { + while (ctx->running > 0) { + wait_test(ctx); + } + + if (ctx->cout) { + cfclose(ctx->cout); + } + + free_colors(ctx->colors); + + return ctx->ret; } int main(int argc, char *argv[]) { @@ -116,8 +222,37 @@ int main(int argc, char *argv[]) { } tzset(); + unsigned int jobs = 0; + + const char *cmd = argc > 0 ? argv[0] : "units"; + int c; + while (c = getopt(argc, argv, ":j:"), c != -1) { + switch (c) { + case 'j': + if (xstrtoui(optarg, NULL, 10, &jobs) != 0) { + fprintf(stderr, "%s: Bad job count '%s': %s\n", cmd, optarg, errstr()); + return EXIT_FAILURE; + } + break; + case ':': + fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt); + return EXIT_FAILURE; + case '?': + fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt); + return EXIT_FAILURE; + } + } + + if (!jobs) { + jobs = nproc(); + } + + if (optind > argc) { + optind = argc; + } + struct test_ctx ctx; - if (test_init(&ctx, argc, argv) != 0) { + if (test_init(&ctx, jobs, argc - optind, argv + optind) != 0) { goto done; } diff --git a/tests/posix/exec_return.out b/tests/posix/exec_return.out new file mode 100644 index 0000000..600c93a --- /dev/null +++ b/tests/posix/exec_return.out @@ -0,0 +1,18 @@ +basic +basic/a +basic/b +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/posix/exec_return.sh b/tests/posix/exec_return.sh new file mode 100644 index 0000000..cfa0f5d --- /dev/null +++ b/tests/posix/exec_return.sh @@ -0,0 +1 @@ +bfs_diff basic -exec test {} = basic/c \; -o -print diff --git a/tests/posix/exec_sigmask.out b/tests/posix/exec_sigmask.out new file mode 100644 index 0000000..bb646f3 --- /dev/null +++ b/tests/posix/exec_sigmask.out @@ -0,0 +1 @@ +SigBlk: 0000000000000000 diff --git a/tests/posix/exec_sigmask.sh b/tests/posix/exec_sigmask.sh new file mode 100644 index 0000000..2907458 --- /dev/null +++ b/tests/posix/exec_sigmask.sh @@ -0,0 +1,16 @@ +# Regression test: restore the signal mask after fork() + +cd "$TEST" +mkfifo p1 p2 + +{ + # Get the PID of `sh` + read -r pid <p1 + # Send SIGTERM -- this will hang forever if signals are blocked + kill $pid +} & + +# Write the `sh` PID to p1, then hang reading p2 until we're killed +! invoke_bfs p1 -exec bash -c 'echo $$ >p1 && read -r _ <p2' bash {} + || fail + +_wait diff --git a/tests/posix/exec_substring_plus.out b/tests/posix/exec_substring_plus.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/posix/exec_substring_plus.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/posix/exec_substring_plus.sh b/tests/posix/exec_substring_plus.sh new file mode 100644 index 0000000..90309b0 --- /dev/null +++ b/tests/posix/exec_substring_plus.sh @@ -0,0 +1,14 @@ +# https://pubs.opengroup.org/onlinepubs/9799919799/utilities/find.html +# +# Only a <plus-sign> that immediately follows an argument containing only +# the two characters "{}" shall punctuate the end of the primary expression. +# Other uses of the <plus-sign> shall not be treated as special. +# ... +# If a utility_name or argument string contains the two characters "{}", but +# not just the two characters "{}", it is implementation-defined whether +# find replaces those two characters or uses the string without change. + +invoke_bfs basic -exec printf '%s %s %s %s\n' {} {}+ +{} + \; | sed 's/ .*//' >"$OUT" +sort_output +diff_output + diff --git a/tests/posix/group_o_group.out b/tests/posix/group_o_group.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/posix/group_o_group.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/posix/group_o_group.sh b/tests/posix/group_o_group.sh new file mode 100644 index 0000000..60aefc0 --- /dev/null +++ b/tests/posix/group_o_group.sh @@ -0,0 +1,3 @@ +# Regression test for +# https://github.com/tavianator/bfs/issues/155 +bfs_diff basic -group 0 -o -group "$(id -g)" diff --git a/tests/posix/root_order.out b/tests/posix/root_order.out new file mode 100644 index 0000000..ea94276 --- /dev/null +++ b/tests/posix/root_order.out @@ -0,0 +1,4 @@ +basic/a +basic/b +basic/c/d +basic/e/f diff --git a/tests/posix/root_order.sh b/tests/posix/root_order.sh new file mode 100644 index 0000000..86adf20 --- /dev/null +++ b/tests/posix/root_order.sh @@ -0,0 +1,6 @@ +# Root paths must be processed in order +# https://www.austingroupbugs.net/view.php?id=1859 + +# -size forces a stat(), which we don't want to be async +invoke_bfs basic/{a,b,c/d,e/f} -size -1000 >"$OUT" +diff_output diff --git a/tests/posix/user_o_user.out b/tests/posix/user_o_user.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/posix/user_o_user.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/posix/user_o_user.sh b/tests/posix/user_o_user.sh new file mode 100644 index 0000000..7c143ae --- /dev/null +++ b/tests/posix/user_o_user.sh @@ -0,0 +1,3 @@ +# Regression test for +# https://github.com/tavianator/bfs/issues/155 +bfs_diff basic -user 0 -o -user "$(id -u)" diff --git a/tests/ptyx.c b/tests/ptyx.c new file mode 100644 index 0000000..59292df --- /dev/null +++ b/tests/ptyx.c @@ -0,0 +1,252 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +/** + * Execute a command in a pseudo-terminal. + * + * $ ptyx [-w WIDTH] [-h HEIGHT] [--] COMMAND [ARGS...] + */ + +#include "bfs.h" +#include "bfstd.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> + +#if __has_include(<stropts.h>) +# include <stropts.h> +#endif + +#if __sun +/** + * Push a STREAMS module, if it's not already there. + * + * See https://www.illumos.org/issues/9042. + */ +static int i_push(int fd, const char *name) { + int ret = ioctl(fd, I_FIND, name); + if (ret < 0) { + return ret; + } else if (ret == 0) { + return ioctl(fd, I_PUSH, name); + } else { + return 0; + } +} +#endif + +int main(int argc, char *argv[]) { + const char *cmd = argc > 0 ? argv[0] : "ptyx"; + +/** Report an error message and exit. */ +#define die(...) die_(__VA_ARGS__, ) + +#define die_(format, ...) \ + do { \ + fprintf(stderr, "%s: " format "%s", cmd, __VA_ARGS__ "\n"); \ + exit(EXIT_FAILURE); \ + } while (0) + +/** Report an error code and exit. */ +#define edie(...) edie_(__VA_ARGS__, ) + +#define edie_(format, ...) \ + do { \ + fprintf(stderr, "%s: " format ": %s\n", cmd, __VA_ARGS__ errstr()); \ + exit(EXIT_FAILURE); \ + } while (0) + + unsigned short width = 0; + unsigned short height = 0; + + // Parse the command line + int c; + while (c = getopt(argc, argv, "+:w:h:"), c != -1) { + switch (c) { + case 'w': + if (xstrtous(optarg, NULL, 10, &width) != 0) { + edie("Bad width '%s'", optarg); + } + break; + case 'h': + if (xstrtous(optarg, NULL, 10, &height) != 0) { + edie("Bad height '%s'", optarg); + } + break; + case ':': + die("Missing argument to -%c", optopt); + case '?': + die("Unrecognized option -%c", optopt); + } + } + + if (optind >= argc) { + die("Missing command"); + } + char **args = argv + optind; + + // Create a new pty, and set it up + int ptm = posix_openpt(O_RDWR | O_NOCTTY); + if (ptm < 0) { + edie("posix_openpt()"); + } + if (grantpt(ptm) != 0) { + edie("grantpt()"); + } + if (unlockpt(ptm) != 0) { + edie("unlockpt()"); + } + + // Get the subsidiary device path + char *name = ptsname(ptm); + if (!name) { + edie("ptsname()"); + } + + // Open the subsidiary device + int pts = open(name, O_RDWR | O_NOCTTY); + if (pts < 0) { + edie("%s", name); + } + +#if __sun + // On Solaris/illumos, a pty doesn't behave like a terminal until we + // push some STREAMS modules (see ptm(4D), ptem(4M), ldterm(4M)). + if (i_push(pts, "ptem") != 0) { + die("ioctl(I_PUSH, ptem)"); + } + if (i_push(pts, "ldterm") != 0) { + die("ioctl(I_PUSH, ldterm)"); + } +#endif + + // A new pty starts at 0x0, which is not very useful. Instead, grab the + // default size from the current controlling terminal, if possible. + if (!width || !height) { + int tty = open_cterm(O_RDONLY | O_CLOEXEC); + if (tty >= 0) { + struct winsize ws; + if (xtcgetwinsize(tty, &ws) != 0) { + edie("tcgetwinsize()"); + } + if (!width) { + width = ws.ws_col; + } + if (!height) { + height = ws.ws_row; + } + xclose(tty); + } + } + if (!width) { + width = 80; + } + if (!height) { + height = 24; + } + + // Update the pty size + struct winsize ws; + if (xtcgetwinsize(pts, &ws) != 0) { + edie("tcgetwinsize()"); + } + ws.ws_col = width; + ws.ws_row = height; + if (xtcsetwinsize(pts, &ws) != 0) { + edie("tcsetwinsize()"); + } + + // Set custom terminal attributes + struct termios attrs; + if (tcgetattr(pts, &attrs) != 0) { + edie("tcgetattr()"); + } + attrs.c_oflag &= ~OPOST; // Don't convert \n to \r\n + if (tcsetattr(pts, TCSANOW, &attrs) != 0) { + edie("tcsetattr()"); + } + + pid_t pid = fork(); + if (pid < 0) { + edie("fork()"); + } else if (pid == 0) { + // Child + close(ptm); + + // Make ourselves a session leader so we can have our own + // controlling terminal + if (setsid() < 0) { + edie("setsid()"); + } + +#ifdef TIOCSCTTY + // Set the pty as the controlling terminal + if (ioctl(pts, TIOCSCTTY, 0) != 0) { + edie("ioctl(TIOCSCTTY)"); + } +#endif + + // Redirect std{in,out,err} to the pty + if (dup2(pts, STDIN_FILENO) < 0 + || dup2(pts, STDOUT_FILENO) < 0 + || dup2(pts, STDERR_FILENO) < 0) { + edie("dup2()"); + } + if (pts > STDERR_FILENO) { + xclose(pts); + } + + // Run the requested command + execvp(args[0], args); + edie("execvp(): %s", args[0]); + } + + // Parent + xclose(pts); + + // Read output from the pty and copy it to stdout + char buf[1024]; + while (true) { + ssize_t len = read(ptm, buf, sizeof(buf)); + if (len > 0) { + if (xwrite(STDOUT_FILENO, buf, len) < 0) { + edie("write()"); + } + } else if (len == 0) { + break; + } else if (errno == EINTR) { + continue; + } else if (errno == EIO) { + // Linux reports EIO rather than EOF when pts is closed + break; + } else { + die("read()"); + } + } + + xclose(ptm); + + int wstatus; + if (xwaitpid(pid, &wstatus, 0) < 0) { + edie("waitpid()"); + } + + if (WIFEXITED(wstatus)) { + return WEXITSTATUS(wstatus); + } else if (WIFSIGNALED(wstatus)) { + int sig = WTERMSIG(wstatus); + fprintf(stderr, "%s: %s: %s\n", cmd, args[0], strsignal(sig)); + return 128 + sig; + } else { + return 128; + } +} diff --git a/tests/run.sh b/tests/run.sh index 612e11a..3ed2a9c 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -96,16 +96,13 @@ reap_test() { wait_test() { local pid line ret - while true; do + while :; do line=$((LINENO + 1)) - wait -n -ppid + _wait -n -ppid ret=$? if [ "${pid:-}" ]; then break - elif ((ret > 128)); then - # Interrupted by signal - continue else debug "${BASH_SOURCE[0]}" $line "${RED}error $ret${RST}" >&$DUPERR exit 1 @@ -159,7 +156,7 @@ comake() { exec {READY_PIPE}<&${COPROC[0]} {DONE_PIPE}>&${COPROC[1]} } -# Print the current test progess +# Print the current test progress progress() { if [ "${BAR:-}" ]; then print_bar "$(printf "$@")" @@ -362,20 +359,12 @@ invoke_bfs() { fi } -if command -v unbuffer &>/dev/null; then - UNBUFFER=unbuffer -elif command -v expect_unbuffer &>/dev/null; then - UNBUFFER=expect_unbuffer -fi - # Run bfs with a pseudo-terminal attached bfs_pty() { - test -n "${UNBUFFER:-}" || skip - bfs_verbose "$@" local ret=0 - "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@" </dev/null' bash "${BFS[@]}" "$@" || ret=$? + "$PTYX" -w80 -h24 -- "${BFS[@]}" "$@" || ret=$? if ((ret > 125)); then exit $ret @@ -415,8 +404,13 @@ make_xattrs() { # Get the Unix epoch time in seconds epoch_time() { - # https://stackoverflow.com/a/12746260/502399 - awk 'BEGIN { srand(); print srand(); }' + if [ "${EPOCHSECONDS:-}" ]; then + # Added in bash 5 + printf '%d' "$EPOCHSECONDS" + else + # https://stackoverflow.com/a/12746260/502399 + awk 'BEGIN { srand(); print srand(); }' + fi } ## Snapshot testing diff --git a/tests/sighook.c b/tests/sighook.c index aa01c36..82e0ae5 100644 --- a/tests/sighook.c +++ b/tests/sighook.c @@ -4,28 +4,40 @@ #include "tests.h" #include "atomic.h" -#include "thread.h" +#include "bfstd.h" #include "sighook.h" +#include "thread.h" +#include "xtime.h" -#include <stddef.h> #include <errno.h> #include <pthread.h> #include <signal.h> -#include <sys/time.h> +#include <stddef.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <unistd.h> /** Counts SIGALRM deliveries. */ static atomic size_t count = 0; -/** Keeps the background thread alive. */ -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; -static bool done = false; - /** SIGALRM handler. */ static void alrm_hook(int sig, siginfo_t *info, void *arg) { fetch_add(&count, 1, relaxed); } +/** SH_ONESHOT counter. */ +static atomic size_t shots = 0; + +/** SH_ONESHOT hook. */ +static void alrm_oneshot(int sig, siginfo_t *info, void *arg) { + fetch_add(&shots, 1, relaxed); +} + +/** Keeps the background thread alive. */ +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static bool done = false; + /** Background thread that receives signals. */ static void *hook_thread(void *ptr) { mutex_lock(&mutex); @@ -54,35 +66,62 @@ static int block_signal(int sig, sigset_t *old) { return 0; } -void check_sighook(void) { - struct sighook *hook = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE); +/** Tests for sighook(). */ +static void check_hooks(void) { + struct sighook *hook = NULL; + struct sighook *oneshot = NULL; + + hook = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE); if (!bfs_echeck(hook, "sighook(SIGALRM)")) { return; } - // Create a timer that sends SIGALRM every 100 microseconds - struct itimerval ival = {0}; - ival.it_value.tv_usec = 100; - ival.it_interval.tv_usec = 100; - if (!bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0)) { - goto unhook; - } - - // Create a background thread to receive signals + // Create a background thread to receive SIGALRM pthread_t thread; if (!bfs_echeck(thread_create(&thread, NULL, hook_thread, NULL) == 0)) { - goto untime; + goto unhook; } // Block SIGALRM in this thread so the handler runs concurrently with // sighook()/sigunhook() sigset_t mask; if (!bfs_echeck(block_signal(SIGALRM, &mask) == 0)) { - goto untime; + goto unthread; + } + + // Check that we can unregister and re-register a hook + sigunhook(hook); + hook = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE); + if (!bfs_echeck(hook, "sighook(SIGALRM)")) { + goto unblock; + } + + // Test SH_ONESHOT + oneshot = sighook(SIGALRM, alrm_oneshot, NULL, SH_ONESHOT); + if (!bfs_echeck(oneshot, "sighook(SH_ONESHOT)")) { + goto unblock; + } + + // Create a timer that sends SIGALRM every 100 microseconds + const struct timespec ival = { .tv_nsec = 100 * 1000 }; + struct timer *timer = xtimer_start(&ival); + if (!bfs_echeck(timer, "xtimer_start()")) { + goto unblock; } // Rapidly register/unregister SIGALRM hooks - while (load(&count, relaxed) < 1000) { + size_t alarms; + while (alarms = load(&count, relaxed), alarms < 1000) { + size_t nshots = load(&shots, relaxed); + bfs_check(nshots <= 1); + if (alarms > 1) { + bfs_check(nshots == 1); + } + if (alarms >= 500) { + sigunhook(oneshot); + oneshot = NULL; + } + struct sighook *next = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE); if (!bfs_echeck(next, "sighook(SIGALRM)")) { break; @@ -92,21 +131,98 @@ void check_sighook(void) { hook = next; } + // Stop the timer + xtimer_stop(timer); +unblock: + // Restore the old signal mask + errno = pthread_sigmask(SIG_SETMASK, &mask, NULL); + bfs_echeck(errno == 0, "pthread_sigmask()"); +unthread: // Quit the background thread mutex_lock(&mutex); done = true; mutex_unlock(&mutex); cond_signal(&cond); thread_join(thread, NULL); - - // Restore the old signal mask - errno = pthread_sigmask(SIG_SETMASK, &mask, NULL); - bfs_echeck(errno == 0, "pthread_sigmask()"); -untime: - // Stop the timer - ival.it_value.tv_usec = 0; - bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0); unhook: - // Unregister the SIGALRM hook + // Unregister the SIGALRM hooks + sigunhook(oneshot); sigunhook(hook); } + +/** atsigexit() hook. */ +static void exit_hook(int sig, siginfo_t *info, void *arg) { + // Write the signal that's killing us to the pipe + int *pipes = arg; + if (xwrite(pipes[1], &sig, sizeof(sig)) != sizeof(sig)) { + abort(); + } +} + +/** Tests for atsigexit(). */ +static void check_sigexit(int sig) { + // To wait for the child to call atsigexit() + int ready[2]; + bfs_everify(pipe(ready) == 0); + + // Written in the atsigexit() handler + int killed[2]; + bfs_everify(pipe(killed) == 0); + + pid_t pid; + bfs_everify((pid = fork()) >= 0); + + if (pid > 0) { + // Parent + xclose(ready[1]); + xclose(killed[1]); + + // Wait for the child to call atsigexit() + char c; + bfs_everify(xread(ready[0], &c, 1) == 1); + + // Kill the child with the signal + bfs_everify(kill(pid, sig) == 0); + + // Check that the child died to the right signal + int wstatus; + if (bfs_echeck(xwaitpid(pid, &wstatus, 0) == pid)) { + bfs_check(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == sig); + } + + // Check that the signal hook wrote the signal number to the pipe + int hsig; + if (bfs_echeck(xread(killed[0], &hsig, sizeof(hsig)) == sizeof(hsig))) { + bfs_check(hsig == sig); + } + } else { + // Child + xclose(ready[0]); + xclose(killed[0]); + + // exit_hook() will write to killed[1] + bfs_everify(atsigexit(exit_hook, killed) != NULL); + + // Tell the parent we're ready + bfs_everify(xwrite(ready[1], "A", 1) == 1); + + // Wait until we're killed + const struct timespec dur = { .tv_nsec = 1 }; + while (true) { + nanosleep(&dur, NULL); + } + } +} + +void check_sighook(void) { + check_hooks(); + + check_sigexit(SIGINT); + check_sigexit(SIGQUIT); + check_sigexit(SIGPIPE); + + // macOS cannot distinguish between sync and async SIG{BUS,ILL,SEGV} +#if !__APPLE__ + check_sigexit(SIGSEGV); +#endif +} diff --git a/tests/stddirs.sh b/tests/stddirs.sh index 1183970..1569fee 100644 --- a/tests/stddirs.sh +++ b/tests/stddirs.sh @@ -151,7 +151,7 @@ make_stddirs() { if ((CLEAN)); then defer clean_stddirs else - printf "Test files saved to ${BLD}%s${RST}\n" "$TMP" + color printf "Test files saved to ${BLD}%s${RST}\n" "$TMP" fi chown "$(id -u):$(id -g)" "$TMP" diff --git a/tests/tests.h b/tests/tests.h index 4c6b3d2..d395c7c 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -45,26 +45,30 @@ bool bfs_check_impl(bool result); * Check a condition, logging a message on failure but continuing. */ #define bfs_check(...) \ - bfs_check_impl(bfs_check_(#__VA_ARGS__, __VA_ARGS__, "", "")) + bfs_check_(#__VA_ARGS__, __VA_ARGS__, "", ) #define bfs_check_(str, cond, format, ...) \ - ((cond) ? true : (bfs_diag( \ - sizeof(format) > 1 \ - ? "%.0s" format "%s%s" \ - : "Check failed: `%s`%s", \ - str, __VA_ARGS__), false)) + bfs_check_impl((cond) || (bfs_check__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__), false)) + +#define bfs_check__(format, ...) \ + bfs_diagf(sizeof(format) > 1 \ + ? BFS_DIAG_FORMAT_("%s" format "%s") \ + : BFS_DIAG_FORMAT_("Check failed: `%s`"), \ + BFS_DIAG_ARGS_(__VA_ARGS__)) /** * Check a condition, logging the current error string on failure. */ #define bfs_echeck(...) \ - bfs_check_impl(bfs_echeck_(#__VA_ARGS__, __VA_ARGS__, "", errstr())) + bfs_echeck_(#__VA_ARGS__, __VA_ARGS__, "", ) #define bfs_echeck_(str, cond, format, ...) \ - ((cond) ? true : (bfs_diag( \ - sizeof(format) > 1 \ - ? "%.0s" format "%s: %s" \ - : "Check failed: `%s`: %s", \ - str, __VA_ARGS__), false)) + bfs_check_impl((cond) || (bfs_echeck__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__), false)) + +#define bfs_echeck__(format, ...) \ + bfs_diagf(sizeof(format) > 1 \ + ? BFS_DIAG_FORMAT_("%s" format "%s: %s") \ + : BFS_DIAG_FORMAT_("Check failed: `%s`: %s"), \ + BFS_DIAG_ARGS_(__VA_ARGS__ errstr(), )) #endif // BFS_TESTS_H diff --git a/tests/trie.c b/tests/trie.c index 9e9a294..59bde40 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -20,9 +20,11 @@ static const char *keys[] = { "quuuux", "pre", - "pref", "prefi", + "pref", "prefix", + "p", + "pRefix", "AAAA", "AADD", @@ -37,6 +39,10 @@ static const char *keys[] = { ">>>>>>", ">>><<<", ">>>", + + "AAAAAAA", + "AAAAAAAB", + "AAAAAAAa", }; static const size_t nkeys = countof(keys); @@ -75,6 +81,7 @@ void check_trie(void) { size_t i = 0; for_trie (leaf, &trie) { bfs_check(leaf == trie_find_str(&trie, keys[i])); + bfs_check(leaf == trie_insert_str(&trie, keys[i])); bfs_check(!leaf->prev || leaf->prev->next == leaf); bfs_check(!leaf->next || leaf->next->prev == leaf); ++i; diff --git a/tests/util.sh b/tests/util.sh index 76b72b9..1718a1a 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -16,6 +16,7 @@ ROOT=$(_realpath "$(dirname -- "$TESTS")") TESTS="$ROOT/tests" BIN="$ROOT/bin" MKSOCK="$BIN/tests/mksock" +PTYX="$BIN/tests/ptyx" XTOUCH="$BIN/tests/xtouch" UNAME=$(uname) @@ -70,7 +71,7 @@ stdenv() { fi } -# Drop root priviliges or bail +# Drop root privileges or bail drop_root() { if command -v capsh &>/dev/null; then if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then @@ -189,3 +190,28 @@ pop_defers() { return $ret } + +## Parallelism + +# Get the number of processors +_nproc() { + { + nproc \ + || sysctl -n hw.ncpu \ + || getconf _NPROCESSORS_ONLN \ + || echo 1 + } 2>/dev/null +} + +# Run wait, looping if interrupted +_wait() { + local ret=130 + + # "If wait is interrupted by a signal, the return status will be greater than 128" + while ((ret > 128)); do + ret=0 + wait "$@" || ret=$? + done + + return $ret +} diff --git a/tests/xspawn.c b/tests/xspawn.c index 3194adc..6864192 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -50,6 +50,71 @@ fail: return NULL; } +/** Add an entry to $PATH. */ +static int add_path(const char *entry, char **old_path) { + int ret = -1; + const char *new_path = NULL; + + *old_path = getenv("PATH"); + if (*old_path) { + *old_path = strdup(*old_path); + if (!*old_path) { + goto done; + } + + new_path = dstrprintf("%s:%s", entry, *old_path); + if (!new_path) { + goto done; + } + } else { + new_path = entry; + } + + ret = setenv("PATH", new_path, true); + +done: + if (new_path && new_path != entry) { + dstrfree((dchar *)new_path); + } + + if (ret != 0) { + free(*old_path); + *old_path = NULL; + } + + return ret; +} + +/** Undo add_path(). */ +static int reset_path(char *old_path) { + int ret; + + if (old_path) { + ret = setenv("PATH", old_path, true); + free(old_path); + } else { + ret = unsetenv("PATH"); + } + + return ret; +} + +/** Spawn the test binary and check for success. */ +static void check_spawnee(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { + pid_t pid = bfs_spawn(exe, ctx, argv, envp); + if (!bfs_echeck(pid >= 0, "bfs_spawn('%s')", exe)) { + return; + } + + int wstatus; + bool exited = bfs_echeck(xwaitpid(pid, &wstatus, 0) == pid) + && bfs_check(WIFEXITED(wstatus)); + if (exited) { + int wexit = WEXITSTATUS(wstatus); + bfs_check(wexit == EXIT_SUCCESS, "xspawnee: exit(%d)", wexit); + } +} + /** Check that we resolve executables in $PATH correctly. */ static void check_use_path(bool use_posix) { struct bfs_spawn spawn; @@ -78,48 +143,16 @@ static void check_use_path(bool use_posix) { } // Check that $PATH is resolved after the file actions - char *old_path = getenv("PATH"); - dchar *new_path = NULL; - if (old_path) { - old_path = strdup(old_path); - if (!bfs_echeck(old_path, "strdup()")) { - goto env; - } - new_path = dstrprintf("tests:%s", old_path); - } else { - new_path = dstrdup("tests"); - } - if (!bfs_check(new_path)) { - goto path; - } - - if (!bfs_echeck(setenv("PATH", new_path, true) == 0)) { - goto path; + char *old_path; + if (!bfs_echeck(add_path("tests", &old_path) == 0)) { + goto env; } char *argv[] = {"xspawnee", old_path, NULL}; - pid_t pid = bfs_spawn("xspawnee", &spawn, argv, envp); - if (!bfs_echeck(pid >= 0, "bfs_spawn()")) { - goto unset; - } + check_spawnee("xspawnee", &spawn, argv, envp); + check_spawnee("tests/xspawnee", &spawn, argv, envp); - int wstatus; - bool exited = bfs_echeck(xwaitpid(pid, &wstatus, 0) == pid) - && bfs_check(WIFEXITED(wstatus)); - if (exited) { - int wexit = WEXITSTATUS(wstatus); - bfs_check(wexit == EXIT_SUCCESS, "xspawnee: exit(%d)", wexit); - } - -unset: - if (old_path) { - bfs_echeck(setenv("PATH", old_path, true) == 0); - } else { - bfs_echeck(unsetenv("PATH") == 0); - } -path: - dstrfree(new_path); - free(old_path); + bfs_echeck(reset_path(old_path) == 0); env: for (char **var = envp; *var; ++var) { free(*var); @@ -166,6 +199,14 @@ static void check_resolve(void) { bfs_echeck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT); bfs_echeck(!bfs_spawn_resolve("bin/eW6f5RM9Qi") && errno == ENOENT); + + char *old_path; + if (bfs_echeck(add_path("bin/tests", &old_path) == 0)) { + exe = bfs_spawn_resolve("xspawnee"); + bfs_echeck(exe && strcmp(exe, "bin/tests/xspawnee") == 0); + free(exe); + bfs_echeck(reset_path(old_path) == 0); + } } void check_xspawn(void) { diff --git a/tests/xtime.c b/tests/xtime.c index 3472bea..c890a1e 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -154,6 +154,14 @@ static void check_xtimegm(void) { .tm_isdst = -1, }; +#if BFS_HAS_TIMEGM + // Check that xtimegm(-1) isn't an error + for (time_t time = -10; time <= 10; ++time) { + if (bfs_check(gmtime_r(&time, &tm), "gmtime_r(%jd)", (intmax_t)time)) { + check_one_xtimegm(&tm); + } + } +#else // Check equivalence with mktime() for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) @@ -164,13 +172,12 @@ static void check_xtimegm(void) { check_one_xtimegm(&tm); } -#if !BFS_HAS_TIMEGM // Check integer overflow cases check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX }); check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX }); check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX }); check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX }); -#endif +#endif // !BFS_HAS_TIMEGM } void check_xtime(void) { diff --git a/tests/xtouch.c b/tests/xtouch.c index e7c2e00..f33c573 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -217,11 +217,8 @@ int main(int argc, char *argv[]) { } if (marg) { - char *end; - long mode = strtol(marg, &end, 8); - // https://github.com/llvm/llvm-project/issues/64946 - sanitize_init(&end); - if (*marg && !*end && mode >= 0 && mode < 01000) { + unsigned int mode; + if (xstrtoui(marg, NULL, 8, &mode) == 0 && mode < 01000) { args.fmode = args.dmode = mode; } else { fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, marg); |