summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/alloc.c56
-rw-r--r--tests/bfs/color_ca.out4
-rw-r--r--tests/bfs/color_ca.sh10
-rw-r--r--tests/bfs/color_ca_incapable.out27
-rw-r--r--tests/bfs/color_ca_incapable.sh1
-rw-r--r--tests/bfs/color_ext_case_nul.out27
-rw-r--r--tests/bfs/color_ext_case_nul.sh5
-rw-r--r--tests/bfs/color_ls.out4
-rw-r--r--tests/bfs/color_notdir_slash_error.out0
-rw-r--r--tests/bfs/color_notdir_slash_error.sh2
-rw-r--r--tests/bfs/execdir_path_relative_slash.out19
-rw-r--r--tests/bfs/execdir_path_relative_slash.sh1
-rw-r--r--tests/bfs/files0_from_root.sh2
-rw-r--r--tests/bfstd.c176
-rw-r--r--tests/bit.c18
-rw-r--r--tests/color.sh8
-rw-r--r--tests/getopts.sh7
-rw-r--r--tests/gnu/L_printf_types.out17
-rw-r--r--tests/gnu/L_printf_types.sh1
-rw-r--r--tests/gnu/execdir_self.out1
-rw-r--r--tests/gnu/execdir_self.sh9
-rw-r--r--tests/gnu/files0_from_empty.sh2
-rw-r--r--tests/gnu/files0_from_file_file.out2
-rw-r--r--tests/gnu/files0_from_file_file.sh3
-rw-r--r--tests/gnu/files0_from_ok.sh1
-rw-r--r--tests/gnu/files0_from_stdin_ok.sh1
-rw-r--r--tests/gnu/files0_from_stdin_ok_file.out45
-rw-r--r--tests/gnu/files0_from_stdin_ok_file.sh4
-rw-r--r--tests/gnu/files0_from_stdin_stdin.out45
-rw-r--r--tests/gnu/files0_from_stdin_stdin.sh2
-rw-r--r--tests/gnu/fls_overflow.sh4
-rw-r--r--tests/gnu/follow_files0_from.out42
-rw-r--r--tests/gnu/follow_files0_from.sh1
-rw-r--r--tests/gnu/fstype_btrfs_subvol.out4
-rw-r--r--tests/gnu/fstype_btrfs_subvol.sh25
-rw-r--r--tests/gnu/ignore_readdir_race_rmdir.out2
-rw-r--r--tests/gnu/ignore_readdir_race_rmdir.sh5
-rw-r--r--tests/gnu/ok_files0_from_stdin.sh1
-rw-r--r--tests/gnu/ok_flush.sh2
-rw-r--r--tests/ioq.c1
-rw-r--r--tests/main.c189
-rw-r--r--tests/posix/exec_return.out18
-rw-r--r--tests/posix/exec_return.sh1
-rw-r--r--tests/posix/exec_sigmask.out1
-rw-r--r--tests/posix/exec_sigmask.sh16
-rw-r--r--tests/posix/exec_substring_plus.out19
-rw-r--r--tests/posix/exec_substring_plus.sh14
-rw-r--r--tests/posix/group_o_group.out19
-rw-r--r--tests/posix/group_o_group.sh3
-rw-r--r--tests/posix/root_order.out4
-rw-r--r--tests/posix/root_order.sh6
-rw-r--r--tests/posix/user_o_user.out19
-rw-r--r--tests/posix/user_o_user.sh3
-rw-r--r--tests/ptyx.c252
-rw-r--r--tests/run.sh28
-rw-r--r--tests/sighook.c178
-rw-r--r--tests/stddirs.sh2
-rw-r--r--tests/tests.h28
-rw-r--r--tests/trie.c9
-rw-r--r--tests/util.sh28
-rw-r--r--tests/xspawn.c117
-rw-r--r--tests/xtime.c11
-rw-r--r--tests/xtouch.c7
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 @@
+.
+./link
+./capable
+./normal
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 @@
+$'rainbow/\e[1m'
+$'rainbow/\e[1m/'$'\e[0m'
+rainbow
+rainbow/exec.sh
+rainbow/socket
+rainbow/broken
+rainbow/chardev_link
+rainbow/link.txt
+rainbow/sticky_ow
+rainbow/sgid
+rainbow/pipe
+rainbow/ow
+rainbow/sugid
+rainbow/suid
+rainbow/sticky
+rainbow/file.dat
+rainbow/file.txt
+rainbow/lower.gz
+rainbow/lower.tar
+rainbow/lower.tar.gz
+rainbow/lu.tar.GZ
+rainbow/mh1
+rainbow/mh2
+rainbow/ul.TAR.gz
+rainbow/upper.GZ
+rainbow/upper.TAR
+rainbow/upper.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 @@
+$'rainbow/\e[1m'
+$'rainbow/\e[1m/'$'\e[0m'
+rainbow
+rainbow/lower.gz
+rainbow/lower.tar.gz
+rainbow/lu.tar.GZ
+rainbow/ul.TAR.gz
+rainbow/upper.GZ
+rainbow/upper.TAR.GZ
+rainbow/exec.sh
+rainbow/socket
+rainbow/broken
+rainbow/chardev_link
+rainbow/link.txt
+rainbow/sticky_ow
+rainbow/sgid
+rainbow/pipe
+rainbow/ow
+rainbow/sugid
+rainbow/suid
+rainbow/sticky
+rainbow/file.dat
+rainbow/file.txt
+rainbow/lower.tar
+rainbow/mh1
+rainbow/mh2
+rainbow/upper.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 @@
foo/bar/nowhere/nothing
foo/bar/baz
foo/bar/baz
-foo/bar/baz//qux
-foo/bar/baz//qux
+foo/bar/baz//qux
+foo/bar/baz//qux
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);