summaryrefslogtreecommitdiffstats
path: root/tests/gnu
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2022-10-19 10:29:05 -0400
committerTavian Barnes <tavianator@tavianator.com>2022-10-19 11:50:03 -0400
commit3b387d81e63893ed3fe3b45e3721fbcfb1c5dde0 (patch)
tree513c32eda43d92a8ed977f394492ba198bba1f3b /tests/gnu
parente5972621ffa8864b18d3e303ac714fdbe231be74 (diff)
downloadbfs-3b387d81e63893ed3fe3b45e3721fbcfb1c5dde0.tar.xz
tests: Split test cases into separate files
Diffstat (limited to 'tests/gnu')
-rw-r--r--tests/gnu/L_delete.out2
-rw-r--r--tests/gnu/L_delete.sh9
-rw-r--r--tests/gnu/L_loops_continue.out11
-rw-r--r--tests/gnu/L_loops_continue.sh2
-rw-r--r--tests/gnu/L_xtype_f.out4
-rw-r--r--tests/gnu/L_xtype_f.sh1
-rw-r--r--tests/gnu/L_xtype_l.out8
-rw-r--r--tests/gnu/L_xtype_l.sh1
-rw-r--r--tests/gnu/and.out2
-rw-r--r--tests/gnu/and.sh1
-rw-r--r--tests/gnu/and_false_or_true.out1
-rw-r--r--tests/gnu/and_false_or_true.sh3
-rw-r--r--tests/gnu/and_purity.out0
-rw-r--r--tests/gnu/and_purity.sh2
-rw-r--r--tests/gnu/comma.out23
-rw-r--r--tests/gnu/comma.sh1
-rw-r--r--tests/gnu/comma_reachability.out1
-rw-r--r--tests/gnu/comma_reachability.sh1
-rw-r--r--tests/gnu/comma_redundant_false.out1
-rw-r--r--tests/gnu/comma_redundant_false.sh2
-rw-r--r--tests/gnu/comma_redundant_true.out1
-rw-r--r--tests/gnu/comma_redundant_true.sh2
-rw-r--r--tests/gnu/daystart.out19
-rw-r--r--tests/gnu/daystart.sh1
-rw-r--r--tests/gnu/daystart_twice.out19
-rw-r--r--tests/gnu/daystart_twice.sh1
-rw-r--r--tests/gnu/empty.out8
-rw-r--r--tests/gnu/empty.sh1
-rw-r--r--tests/gnu/empty_special.out14
-rw-r--r--tests/gnu/empty_special.sh1
-rw-r--r--tests/gnu/exec_flush.out19
-rw-r--r--tests/gnu/exec_flush.sh4
-rw-r--r--tests/gnu/exec_flush_fail.sh3
-rw-r--r--tests/gnu/exec_nothing.sh2
-rw-r--r--tests/gnu/exec_plus_flush.outbin0 -> 22 bytes
-rw-r--r--tests/gnu/exec_plus_flush.sh2
-rw-r--r--tests/gnu/exec_plus_flush_fail.sh2
-rw-r--r--tests/gnu/execdir.out19
-rw-r--r--tests/gnu/execdir.sh1
-rw-r--r--tests/gnu/execdir_plus_semicolon.out19
-rw-r--r--tests/gnu/execdir_plus_semicolon.sh1
-rw-r--r--tests/gnu/execdir_substring.out19
-rw-r--r--tests/gnu/execdir_substring.sh1
-rw-r--r--tests/gnu/executable.out4
-rw-r--r--tests/gnu/executable.sh1
-rw-r--r--tests/gnu/false.out0
-rw-r--r--tests/gnu/false.sh1
-rw-r--r--tests/gnu/files0_from_empty.sh1
-rw-r--r--tests/gnu/files0_from_file.out33
-rw-r--r--tests/gnu/files0_from_file.sh3
-rw-r--r--tests/gnu/files0_from_none.sh1
-rw-r--r--tests/gnu/files0_from_nothing.sh1
-rw-r--r--tests/gnu/files0_from_nowhere.sh1
-rw-r--r--tests/gnu/files0_from_ok.sh1
-rw-r--r--tests/gnu/files0_from_stdin.out33
-rw-r--r--tests/gnu/files0_from_stdin.sh2
-rw-r--r--tests/gnu/fls.sh1
-rw-r--r--tests/gnu/follow_comma.out23
-rw-r--r--tests/gnu/follow_comma.sh3
-rw-r--r--tests/gnu/fprint.out19
-rw-r--r--tests/gnu/fprint.sh3
-rw-r--r--tests/gnu/fprint0.outbin0 -> 16 bytes
-rw-r--r--tests/gnu/fprint0.sh2
-rw-r--r--tests/gnu/fprint_duplicate.out57
-rw-r--r--tests/gnu/fprint_duplicate.sh7
-rw-r--r--tests/gnu/fprint_error.sh2
-rw-r--r--tests/gnu/fprint_noarg.sh1
-rw-r--r--tests/gnu/fprint_noerror.sh3
-rw-r--r--tests/gnu/fprint_nonexistent.sh1
-rw-r--r--tests/gnu/fprint_truncate.out1
-rw-r--r--tests/gnu/fprint_truncate.sh5
-rw-r--r--tests/gnu/fprintf.out19
-rw-r--r--tests/gnu/fprintf.sh3
-rw-r--r--tests/gnu/fprintf_nofile.sh1
-rw-r--r--tests/gnu/fprintf_noformat.sh1
-rw-r--r--tests/gnu/fstype.out19
-rw-r--r--tests/gnu/fstype.sh2
-rw-r--r--tests/gnu/gid.out19
-rw-r--r--tests/gnu/gid.sh1
-rw-r--r--tests/gnu/gid_minus.out19
-rw-r--r--tests/gnu/gid_minus.sh1
-rw-r--r--tests/gnu/gid_minus_plus.out19
-rw-r--r--tests/gnu/gid_minus_plus.sh1
-rw-r--r--tests/gnu/gid_plus.out19
-rw-r--r--tests/gnu/gid_plus.sh2
-rw-r--r--tests/gnu/gid_plus_plus.out19
-rw-r--r--tests/gnu/gid_plus_plus.sh2
-rw-r--r--tests/gnu/ignore_readdir_race.sh5
-rw-r--r--tests/gnu/ignore_readdir_race_notdir.sh5
-rw-r--r--tests/gnu/ignore_readdir_race_root.sh2
-rw-r--r--tests/gnu/inum_automount.out1
-rw-r--r--tests/gnu/inum_automount.sh17
-rw-r--r--tests/gnu/iwholename.out7
-rw-r--r--tests/gnu/iwholename.sh2
-rw-r--r--tests/gnu/not.out16
-rw-r--r--tests/gnu/not.sh1
-rw-r--r--tests/gnu/not_reachability.out1
-rw-r--r--tests/gnu/not_reachability.sh1
-rw-r--r--tests/gnu/ok_nothing.sh2
-rw-r--r--tests/gnu/or.out13
-rw-r--r--tests/gnu/or.sh1
-rw-r--r--tests/gnu/path_d.out19
-rw-r--r--tests/gnu/path_d.sh1
-rw-r--r--tests/gnu/perm_000_slash.out8
-rw-r--r--tests/gnu/perm_000_slash.sh1
-rw-r--r--tests/gnu/perm_222_slash.out5
-rw-r--r--tests/gnu/perm_222_slash.sh1
-rw-r--r--tests/gnu/perm_644_slash.out7
-rw-r--r--tests/gnu/perm_644_slash.sh1
-rw-r--r--tests/gnu/perm_leading_plus_symbolic_slash.out7
-rw-r--r--tests/gnu/perm_leading_plus_symbolic_slash.sh1
-rw-r--r--tests/gnu/perm_symbolic_slash.out7
-rw-r--r--tests/gnu/perm_symbolic_slash.sh1
-rw-r--r--tests/gnu/precedence.out4
-rw-r--r--tests/gnu/precedence.sh1
-rw-r--r--tests/gnu/print0.outbin0 -> 16 bytes
-rw-r--r--tests/gnu/print0.sh2
-rw-r--r--tests/gnu/print_error.sh2
-rw-r--r--tests/gnu/printf.out19
-rw-r--r--tests/gnu/printf.sh1
-rw-r--r--tests/gnu/printf_H.out32
-rw-r--r--tests/gnu/printf_H.sh1
-rw-r--r--tests/gnu/printf_Y_error.out3
-rw-r--r--tests/gnu/printf_Y_error.sh12
-rw-r--r--tests/gnu/printf_empty.out0
-rw-r--r--tests/gnu/printf_empty.sh1
-rw-r--r--tests/gnu/printf_escapes.out1
-rw-r--r--tests/gnu/printf_escapes.sh1
-rw-r--r--tests/gnu/printf_flags.out19
-rw-r--r--tests/gnu/printf_flags.sh1
-rw-r--r--tests/gnu/printf_l_nonlink.out13
-rw-r--r--tests/gnu/printf_l_nonlink.sh1
-rw-r--r--tests/gnu/printf_leak.out1
-rw-r--r--tests/gnu/printf_leak.sh2
-rw-r--r--tests/gnu/printf_nul.outbin0 -> 16 bytes
-rw-r--r--tests/gnu/printf_nul.sh3
-rw-r--r--tests/gnu/printf_slash.out1
-rw-r--r--tests/gnu/printf_slash.sh1
-rw-r--r--tests/gnu/printf_slashes.out1
-rw-r--r--tests/gnu/printf_slashes.sh1
-rw-r--r--tests/gnu/printf_times.out3
-rw-r--r--tests/gnu/printf_times.sh1
-rw-r--r--tests/gnu/printf_trailing_slash.out19
-rw-r--r--tests/gnu/printf_trailing_slash.sh1
-rw-r--r--tests/gnu/printf_trailing_slashes.out19
-rw-r--r--tests/gnu/printf_trailing_slashes.sh1
-rw-r--r--tests/gnu/printf_types.out11
-rw-r--r--tests/gnu/printf_types.sh1
-rw-r--r--tests/gnu/printf_u_g_ulimit.sh3
-rw-r--r--tests/gnu/readable.out5
-rw-r--r--tests/gnu/readable.sh1
-rw-r--r--tests/gnu/regex_error.sh1
-rw-r--r--tests/gnu/regex_invalid_utf8.out1
-rw-r--r--tests/gnu/regex_invalid_utf8.sh8
-rw-r--r--tests/gnu/regextype_ed.out1
-rw-r--r--tests/gnu/regextype_ed.sh2
-rw-r--r--tests/gnu/regextype_emacs.out6
-rw-r--r--tests/gnu/regextype_emacs.sh3
-rw-r--r--tests/gnu/regextype_grep.out4
-rw-r--r--tests/gnu/regextype_grep.sh3
-rw-r--r--tests/gnu/regextype_posix_basic.out1
-rw-r--r--tests/gnu/regextype_posix_basic.sh2
-rw-r--r--tests/gnu/regextype_posix_extended.out1
-rw-r--r--tests/gnu/regextype_posix_extended.sh2
-rw-r--r--tests/gnu/regextype_sed.out1
-rw-r--r--tests/gnu/regextype_sed.sh2
-rw-r--r--tests/gnu/true.out19
-rw-r--r--tests/gnu/true.sh1
-rw-r--r--tests/gnu/uid.out19
-rw-r--r--tests/gnu/uid.sh1
-rw-r--r--tests/gnu/uid_minus.out19
-rw-r--r--tests/gnu/uid_minus.sh1
-rw-r--r--tests/gnu/uid_minus_plus.out19
-rw-r--r--tests/gnu/uid_minus_plus.sh1
-rw-r--r--tests/gnu/uid_plus.out19
-rw-r--r--tests/gnu/uid_plus.sh2
-rw-r--r--tests/gnu/uid_plus_plus.out19
-rw-r--r--tests/gnu/uid_plus_plus.sh2
-rw-r--r--tests/gnu/wholename.out7
-rw-r--r--tests/gnu/wholename.sh1
-rw-r--r--tests/gnu/writable.out5
-rw-r--r--tests/gnu/writable.sh1
-rw-r--r--tests/gnu/xtype_bind_mount.out2
-rw-r--r--tests/gnu/xtype_bind_mount.sh13
-rw-r--r--tests/gnu/xtype_f.out5
-rw-r--r--tests/gnu/xtype_f.sh1
-rw-r--r--tests/gnu/xtype_l.out3
-rw-r--r--tests/gnu/xtype_l.sh1
188 files changed, 1113 insertions, 0 deletions
diff --git a/tests/gnu/L_delete.out b/tests/gnu/L_delete.out
new file mode 100644
index 0000000..ed0e9a1
--- /dev/null
+++ b/tests/gnu/L_delete.out
@@ -0,0 +1,2 @@
+scratch
+scratch/foo
diff --git a/tests/gnu/L_delete.sh b/tests/gnu/L_delete.sh
new file mode 100644
index 0000000..08f39af
--- /dev/null
+++ b/tests/gnu/L_delete.sh
@@ -0,0 +1,9 @@
+rm -rf scratch/*
+mkdir scratch/foo
+mkdir scratch/bar
+ln -s ../foo scratch/bar/baz
+
+# Don't try to rmdir() a symlink
+invoke_bfs -L scratch/bar -delete || return 1
+
+bfs_diff scratch
diff --git a/tests/gnu/L_loops_continue.out b/tests/gnu/L_loops_continue.out
new file mode 100644
index 0000000..a514555
--- /dev/null
+++ b/tests/gnu/L_loops_continue.out
@@ -0,0 +1,11 @@
+loops
+loops/broken
+loops/deeply
+loops/deeply/nested
+loops/deeply/nested/dir
+loops/file
+loops/notdir
+loops/skip
+loops/skip/dir
+loops/skip/loop
+loops/symlink
diff --git a/tests/gnu/L_loops_continue.sh b/tests/gnu/L_loops_continue.sh
new file mode 100644
index 0000000..0244137
--- /dev/null
+++ b/tests/gnu/L_loops_continue.sh
@@ -0,0 +1,2 @@
+bfs_diff -L loops
+[ $? -eq $EX_BFS ]
diff --git a/tests/gnu/L_xtype_f.out b/tests/gnu/L_xtype_f.out
new file mode 100644
index 0000000..8b95397
--- /dev/null
+++ b/tests/gnu/L_xtype_f.out
@@ -0,0 +1,4 @@
+links/deeply/nested/file
+links/file
+links/hardlink
+links/skip/file
diff --git a/tests/gnu/L_xtype_f.sh b/tests/gnu/L_xtype_f.sh
new file mode 100644
index 0000000..47f7be7
--- /dev/null
+++ b/tests/gnu/L_xtype_f.sh
@@ -0,0 +1 @@
+bfs_diff -L links -xtype f
diff --git a/tests/gnu/L_xtype_l.out b/tests/gnu/L_xtype_l.out
new file mode 100644
index 0000000..973864f
--- /dev/null
+++ b/tests/gnu/L_xtype_l.out
@@ -0,0 +1,8 @@
+links/broken
+links/deeply/nested/broken
+links/deeply/nested/link
+links/notdir
+links/skip
+links/skip/broken
+links/skip/link
+links/symlink
diff --git a/tests/gnu/L_xtype_l.sh b/tests/gnu/L_xtype_l.sh
new file mode 100644
index 0000000..afe52ef
--- /dev/null
+++ b/tests/gnu/L_xtype_l.sh
@@ -0,0 +1 @@
+bfs_diff -L links -xtype l
diff --git a/tests/gnu/and.out b/tests/gnu/and.out
new file mode 100644
index 0000000..722962c
--- /dev/null
+++ b/tests/gnu/and.out
@@ -0,0 +1,2 @@
+basic/k/foo
+basic/l/foo
diff --git a/tests/gnu/and.sh b/tests/gnu/and.sh
new file mode 100644
index 0000000..1606455
--- /dev/null
+++ b/tests/gnu/and.sh
@@ -0,0 +1 @@
+bfs_diff basic -name foo -and -type d
diff --git a/tests/gnu/and_false_or_true.out b/tests/gnu/and_false_or_true.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/and_false_or_true.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/and_false_or_true.sh b/tests/gnu/and_false_or_true.sh
new file mode 100644
index 0000000..e500722
--- /dev/null
+++ b/tests/gnu/and_false_or_true.sh
@@ -0,0 +1,3 @@
+# Test (-a lhs(always_true) -false) <==> (! lhs),
+# (-o lhs(always_false) -true) <==> (! lhs)
+bfs_diff basic -prune -false -o -true
diff --git a/tests/gnu/and_purity.out b/tests/gnu/and_purity.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gnu/and_purity.out
diff --git a/tests/gnu/and_purity.sh b/tests/gnu/and_purity.sh
new file mode 100644
index 0000000..55e2cfc
--- /dev/null
+++ b/tests/gnu/and_purity.sh
@@ -0,0 +1,2 @@
+# Regression test: (-a lhs(pure) rhs(always_false)) <==> rhs is only valid if rhs is pure
+bfs_diff basic -name nonexistent \( -print , -false \)
diff --git a/tests/gnu/comma.out b/tests/gnu/comma.out
new file mode 100644
index 0000000..740eefc
--- /dev/null
+++ b/tests/gnu/comma.out
@@ -0,0 +1,23 @@
+basic
+basic/a
+basic/b
+basic/c
+basic/c/d
+basic/e
+basic/e/f
+basic/e/f
+basic/g
+basic/g/h
+basic/i
+basic/j
+basic/j/foo
+basic/j/foo
+basic/k
+basic/k/foo
+basic/k/foo
+basic/k/foo/bar
+basic/l
+basic/l/foo
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar/baz
diff --git a/tests/gnu/comma.sh b/tests/gnu/comma.sh
new file mode 100644
index 0000000..cdcebf8
--- /dev/null
+++ b/tests/gnu/comma.sh
@@ -0,0 +1 @@
+bfs_diff basic -name '*f*' -print , -print
diff --git a/tests/gnu/comma_reachability.out b/tests/gnu/comma_reachability.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/comma_reachability.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/comma_reachability.sh b/tests/gnu/comma_reachability.sh
new file mode 100644
index 0000000..60c26bc
--- /dev/null
+++ b/tests/gnu/comma_reachability.sh
@@ -0,0 +1 @@
+bfs_diff basic -print -quit , -print
diff --git a/tests/gnu/comma_redundant_false.out b/tests/gnu/comma_redundant_false.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/comma_redundant_false.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/comma_redundant_false.sh b/tests/gnu/comma_redundant_false.sh
new file mode 100644
index 0000000..f35d9b8
--- /dev/null
+++ b/tests/gnu/comma_redundant_false.sh
@@ -0,0 +1,2 @@
+# Test (, lhs(always_false) -false) <==> lhs
+bfs_diff basic -print -not -prune , -false
diff --git a/tests/gnu/comma_redundant_true.out b/tests/gnu/comma_redundant_true.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/comma_redundant_true.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/comma_redundant_true.sh b/tests/gnu/comma_redundant_true.sh
new file mode 100644
index 0000000..f9eef57
--- /dev/null
+++ b/tests/gnu/comma_redundant_true.sh
@@ -0,0 +1,2 @@
+# Test (, lhs(always_true) -true) <==> lhs
+bfs_diff basic -prune , -true
diff --git a/tests/gnu/daystart.out b/tests/gnu/daystart.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/daystart.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/gnu/daystart.sh b/tests/gnu/daystart.sh
new file mode 100644
index 0000000..9799bca
--- /dev/null
+++ b/tests/gnu/daystart.sh
@@ -0,0 +1 @@
+bfs_diff basic -daystart -mtime 0
diff --git a/tests/gnu/daystart_twice.out b/tests/gnu/daystart_twice.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/daystart_twice.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/gnu/daystart_twice.sh b/tests/gnu/daystart_twice.sh
new file mode 100644
index 0000000..21b2c0f
--- /dev/null
+++ b/tests/gnu/daystart_twice.sh
@@ -0,0 +1 @@
+bfs_diff basic -daystart -daystart -mtime 0
diff --git a/tests/gnu/empty.out b/tests/gnu/empty.out
new file mode 100644
index 0000000..a0f4b76
--- /dev/null
+++ b/tests/gnu/empty.out
@@ -0,0 +1,8 @@
+basic/a
+basic/b
+basic/c/d
+basic/e/f
+basic/g/h
+basic/i
+basic/j/foo
+basic/k/foo/bar
diff --git a/tests/gnu/empty.sh b/tests/gnu/empty.sh
new file mode 100644
index 0000000..95ee988
--- /dev/null
+++ b/tests/gnu/empty.sh
@@ -0,0 +1 @@
+bfs_diff basic -empty
diff --git a/tests/gnu/empty_special.out b/tests/gnu/empty_special.out
new file mode 100644
index 0000000..3927f2b
--- /dev/null
+++ b/tests/gnu/empty_special.out
@@ -0,0 +1,14 @@
+rainbow/exec.sh
+rainbow/file.dat
+rainbow/file.txt
+rainbow/mh1
+rainbow/mh2
+rainbow/ow
+rainbow/sgid
+rainbow/star.gz
+rainbow/star.tar
+rainbow/star.tar.gz
+rainbow/sticky
+rainbow/sticky_ow
+rainbow/sugid
+rainbow/suid
diff --git a/tests/gnu/empty_special.sh b/tests/gnu/empty_special.sh
new file mode 100644
index 0000000..31e9d2e
--- /dev/null
+++ b/tests/gnu/empty_special.sh
@@ -0,0 +1 @@
+bfs_diff rainbow -empty
diff --git a/tests/gnu/exec_flush.out b/tests/gnu/exec_flush.out
new file mode 100644
index 0000000..fdd7b16
--- /dev/null
+++ b/tests/gnu/exec_flush.out
@@ -0,0 +1,19 @@
+basic found
+basic/a found
+basic/b found
+basic/c found
+basic/c/d found
+basic/e found
+basic/e/f found
+basic/g found
+basic/g/h found
+basic/i found
+basic/j found
+basic/j/foo found
+basic/k found
+basic/k/foo found
+basic/k/foo/bar found
+basic/l found
+basic/l/foo found
+basic/l/foo/bar found
+basic/l/foo/bar/baz found
diff --git a/tests/gnu/exec_flush.sh b/tests/gnu/exec_flush.sh
new file mode 100644
index 0000000..aaeef6a
--- /dev/null
+++ b/tests/gnu/exec_flush.sh
@@ -0,0 +1,4 @@
+# IO streams should be flushed before executing programs
+invoke_bfs basic -print0 -exec echo found \; | tr '\0' ' ' >"$OUT"
+sort_output
+diff_output
diff --git a/tests/gnu/exec_flush_fail.sh b/tests/gnu/exec_flush_fail.sh
new file mode 100644
index 0000000..4772a14
--- /dev/null
+++ b/tests/gnu/exec_flush_fail.sh
@@ -0,0 +1,3 @@
+# Failure to flush streams before exec should be caught
+skip_unless test -e /dev/full
+fail invoke_bfs basic -print0 -exec true \; >/dev/full
diff --git a/tests/gnu/exec_nothing.sh b/tests/gnu/exec_nothing.sh
new file mode 100644
index 0000000..9d613e8
--- /dev/null
+++ b/tests/gnu/exec_nothing.sh
@@ -0,0 +1,2 @@
+# Regression test: don't segfault on missing command
+fail invoke_bfs basic -exec \;
diff --git a/tests/gnu/exec_plus_flush.out b/tests/gnu/exec_plus_flush.out
new file mode 100644
index 0000000..3e276be
--- /dev/null
+++ b/tests/gnu/exec_plus_flush.out
Binary files differ
diff --git a/tests/gnu/exec_plus_flush.sh b/tests/gnu/exec_plus_flush.sh
new file mode 100644
index 0000000..0c03837
--- /dev/null
+++ b/tests/gnu/exec_plus_flush.sh
@@ -0,0 +1,2 @@
+invoke_bfs basic/a -print0 -exec echo found {} + >"$OUT"
+diff_output
diff --git a/tests/gnu/exec_plus_flush_fail.sh b/tests/gnu/exec_plus_flush_fail.sh
new file mode 100644
index 0000000..5c74fd8
--- /dev/null
+++ b/tests/gnu/exec_plus_flush_fail.sh
@@ -0,0 +1,2 @@
+skip_unless test -e /dev/full
+fail invoke_bfs basic/a -print0 -exec echo found {} + >/dev/full
diff --git a/tests/gnu/execdir.out b/tests/gnu/execdir.out
new file mode 100644
index 0000000..62b31f6
--- /dev/null
+++ b/tests/gnu/execdir.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/gnu/execdir.sh b/tests/gnu/execdir.sh
new file mode 100644
index 0000000..5a3a95a
--- /dev/null
+++ b/tests/gnu/execdir.sh
@@ -0,0 +1 @@
+bfs_diff basic -execdir echo {} \;
diff --git a/tests/gnu/execdir_plus_semicolon.out b/tests/gnu/execdir_plus_semicolon.out
new file mode 100644
index 0000000..e39f452
--- /dev/null
+++ b/tests/gnu/execdir_plus_semicolon.out
@@ -0,0 +1,19 @@
+foo ./a bar + baz
+foo ./b bar + baz
+foo ./bar bar + baz
+foo ./bar bar + baz
+foo ./basic bar + baz
+foo ./baz bar + baz
+foo ./c bar + baz
+foo ./d bar + baz
+foo ./e bar + baz
+foo ./f bar + baz
+foo ./foo bar + baz
+foo ./foo bar + baz
+foo ./foo bar + baz
+foo ./g bar + baz
+foo ./h bar + baz
+foo ./i bar + baz
+foo ./j bar + baz
+foo ./k bar + baz
+foo ./l bar + baz
diff --git a/tests/gnu/execdir_plus_semicolon.sh b/tests/gnu/execdir_plus_semicolon.sh
new file mode 100644
index 0000000..c5cdafe
--- /dev/null
+++ b/tests/gnu/execdir_plus_semicolon.sh
@@ -0,0 +1 @@
+bfs_diff basic -execdir echo foo {} bar + baz \;
diff --git a/tests/gnu/execdir_substring.out b/tests/gnu/execdir_substring.out
new file mode 100644
index 0000000..f7a9ac0
--- /dev/null
+++ b/tests/gnu/execdir_substring.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/gnu/execdir_substring.sh b/tests/gnu/execdir_substring.sh
new file mode 100644
index 0000000..feeabc4
--- /dev/null
+++ b/tests/gnu/execdir_substring.sh
@@ -0,0 +1 @@
+bfs_diff basic -execdir echo '-{}-' \;
diff --git a/tests/gnu/executable.out b/tests/gnu/executable.out
new file mode 100644
index 0000000..49c1b21
--- /dev/null
+++ b/tests/gnu/executable.out
@@ -0,0 +1,4 @@
+perms
+perms/rwx
+perms/rx
+perms/wx
diff --git a/tests/gnu/executable.sh b/tests/gnu/executable.sh
new file mode 100644
index 0000000..f7f6633
--- /dev/null
+++ b/tests/gnu/executable.sh
@@ -0,0 +1 @@
+bfs_diff perms -executable
diff --git a/tests/gnu/false.out b/tests/gnu/false.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gnu/false.out
diff --git a/tests/gnu/false.sh b/tests/gnu/false.sh
new file mode 100644
index 0000000..89d86c2
--- /dev/null
+++ b/tests/gnu/false.sh
@@ -0,0 +1 @@
+bfs_diff basic -false
diff --git a/tests/gnu/files0_from_empty.sh b/tests/gnu/files0_from_empty.sh
new file mode 100644
index 0000000..bd4fbf4
--- /dev/null
+++ b/tests/gnu/files0_from_empty.sh
@@ -0,0 +1 @@
+printf "\0" | fail invoke_bfs -files0-from -
diff --git a/tests/gnu/files0_from_file.out b/tests/gnu/files0_from_file.out
new file mode 100644
index 0000000..1d87e6b
--- /dev/null
+++ b/tests/gnu/files0_from_file.out
@@ -0,0 +1,33 @@
+
+ /j
+ /j
+!
+!-
+!-/e
+!-/e
+!/d
+!/d
+(
+(-
+(-/c
+(-/c
+(/b
+(/b
+)
+)/g
+)/g
+,
+,/f
+,/f
+-
+-/a
+-/a
+...
+.../h
+.../h
+[
+[/k
+[/k
+\
+\/i
+\/i
diff --git a/tests/gnu/files0_from_file.sh b/tests/gnu/files0_from_file.sh
new file mode 100644
index 0000000..7563149
--- /dev/null
+++ b/tests/gnu/files0_from_file.sh
@@ -0,0 +1,3 @@
+cd weirdnames
+invoke_bfs -mindepth 1 -fprintf ../scratch/files0.in "%P\0"
+bfs_diff -files0-from ../scratch/files0.in
diff --git a/tests/gnu/files0_from_none.sh b/tests/gnu/files0_from_none.sh
new file mode 100644
index 0000000..c6e5b97
--- /dev/null
+++ b/tests/gnu/files0_from_none.sh
@@ -0,0 +1 @@
+printf "" | fail invoke_bfs -files0-from -
diff --git a/tests/gnu/files0_from_nothing.sh b/tests/gnu/files0_from_nothing.sh
new file mode 100644
index 0000000..5fdae60
--- /dev/null
+++ b/tests/gnu/files0_from_nothing.sh
@@ -0,0 +1 @@
+fail invoke_bfs -files0-from basic/nonexistent
diff --git a/tests/gnu/files0_from_nowhere.sh b/tests/gnu/files0_from_nowhere.sh
new file mode 100644
index 0000000..2337613
--- /dev/null
+++ b/tests/gnu/files0_from_nowhere.sh
@@ -0,0 +1 @@
+fail invoke_bfs -files0-from
diff --git a/tests/gnu/files0_from_ok.sh b/tests/gnu/files0_from_ok.sh
new file mode 100644
index 0000000..5387e5c
--- /dev/null
+++ b/tests/gnu/files0_from_ok.sh
@@ -0,0 +1 @@
+printf "basic\0" | fail invoke_bfs -files0-from - -ok echo {} \;
diff --git a/tests/gnu/files0_from_stdin.out b/tests/gnu/files0_from_stdin.out
new file mode 100644
index 0000000..1d87e6b
--- /dev/null
+++ b/tests/gnu/files0_from_stdin.out
@@ -0,0 +1,33 @@
+
+ /j
+ /j
+!
+!-
+!-/e
+!-/e
+!/d
+!/d
+(
+(-
+(-/c
+(-/c
+(/b
+(/b
+)
+)/g
+)/g
+,
+,/f
+,/f
+-
+-/a
+-/a
+...
+.../h
+.../h
+[
+[/k
+[/k
+\
+\/i
+\/i
diff --git a/tests/gnu/files0_from_stdin.sh b/tests/gnu/files0_from_stdin.sh
new file mode 100644
index 0000000..9df7736
--- /dev/null
+++ b/tests/gnu/files0_from_stdin.sh
@@ -0,0 +1,2 @@
+cd weirdnames
+invoke_bfs -mindepth 1 -printf "%P\0" | bfs_diff -files0-from -
diff --git a/tests/gnu/fls.sh b/tests/gnu/fls.sh
new file mode 100644
index 0000000..94e2c16
--- /dev/null
+++ b/tests/gnu/fls.sh
@@ -0,0 +1 @@
+invoke_bfs rainbow -fls scratch/fls.out
diff --git a/tests/gnu/follow_comma.out b/tests/gnu/follow_comma.out
new file mode 100644
index 0000000..920b3d3
--- /dev/null
+++ b/tests/gnu/follow_comma.out
@@ -0,0 +1,23 @@
+.
+./
+./ /j
+./!
+./!-
+./!-/e
+./!/d
+./(
+./(-
+./(-/c
+./(/b
+./)
+./)/g
+./,
+./,/f
+./-
+./-/a
+./...
+./.../h
+./[
+./[/k
+./\
+./\/i
diff --git a/tests/gnu/follow_comma.sh b/tests/gnu/follow_comma.sh
new file mode 100644
index 0000000..f57b932
--- /dev/null
+++ b/tests/gnu/follow_comma.sh
@@ -0,0 +1,3 @@
+# , is an operator after a non-flag is seen
+cd weirdnames
+bfs_diff -follow ',' -print
diff --git a/tests/gnu/fprint.out b/tests/gnu/fprint.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/fprint.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/gnu/fprint.sh b/tests/gnu/fprint.sh
new file mode 100644
index 0000000..04b50fa
--- /dev/null
+++ b/tests/gnu/fprint.sh
@@ -0,0 +1,3 @@
+invoke_bfs basic -fprint "$OUT"
+sort_output
+diff_output
diff --git a/tests/gnu/fprint0.out b/tests/gnu/fprint0.out
new file mode 100644
index 0000000..1347444
--- /dev/null
+++ b/tests/gnu/fprint0.out
Binary files differ
diff --git a/tests/gnu/fprint0.sh b/tests/gnu/fprint0.sh
new file mode 100644
index 0000000..dd10b5f
--- /dev/null
+++ b/tests/gnu/fprint0.sh
@@ -0,0 +1,2 @@
+invoke_bfs basic/a basic/b -fprint0 "$OUT"
+diff_output
diff --git a/tests/gnu/fprint_duplicate.out b/tests/gnu/fprint_duplicate.out
new file mode 100644
index 0000000..2575f35
--- /dev/null
+++ b/tests/gnu/fprint_duplicate.out
@@ -0,0 +1,57 @@
+basic
+basic
+basic
+basic/a
+basic/a
+basic/a
+basic/b
+basic/b
+basic/b
+basic/c
+basic/c
+basic/c
+basic/c/d
+basic/c/d
+basic/c/d
+basic/e
+basic/e
+basic/e
+basic/e/f
+basic/e/f
+basic/e/f
+basic/g
+basic/g
+basic/g
+basic/g/h
+basic/g/h
+basic/g/h
+basic/i
+basic/i
+basic/i
+basic/j
+basic/j
+basic/j
+basic/j/foo
+basic/j/foo
+basic/j/foo
+basic/k
+basic/k
+basic/k
+basic/k/foo
+basic/k/foo
+basic/k/foo
+basic/k/foo/bar
+basic/k/foo/bar
+basic/k/foo/bar
+basic/l
+basic/l
+basic/l
+basic/l/foo
+basic/l/foo
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar
+basic/l/foo/bar
+basic/l/foo/bar/baz
+basic/l/foo/bar/baz
+basic/l/foo/bar/baz
diff --git a/tests/gnu/fprint_duplicate.sh b/tests/gnu/fprint_duplicate.sh
new file mode 100644
index 0000000..b4eb047
--- /dev/null
+++ b/tests/gnu/fprint_duplicate.sh
@@ -0,0 +1,7 @@
+touchp scratch/foo.out
+ln scratch/foo.out scratch/foo.hard
+ln -s foo.out scratch/foo.soft
+
+invoke_bfs basic -fprint scratch/foo.out -fprint scratch/foo.hard -fprint scratch/foo.soft
+sort scratch/foo.out >"$OUT"
+diff_output
diff --git a/tests/gnu/fprint_error.sh b/tests/gnu/fprint_error.sh
new file mode 100644
index 0000000..e7f2394
--- /dev/null
+++ b/tests/gnu/fprint_error.sh
@@ -0,0 +1,2 @@
+skip_unless test -e /dev/full
+fail invoke_bfs basic -maxdepth 0 -fprint /dev/full
diff --git a/tests/gnu/fprint_noarg.sh b/tests/gnu/fprint_noarg.sh
new file mode 100644
index 0000000..bf772f3
--- /dev/null
+++ b/tests/gnu/fprint_noarg.sh
@@ -0,0 +1 @@
+fail invoke_bfs basic -fprint
diff --git a/tests/gnu/fprint_noerror.sh b/tests/gnu/fprint_noerror.sh
new file mode 100644
index 0000000..142e935
--- /dev/null
+++ b/tests/gnu/fprint_noerror.sh
@@ -0,0 +1,3 @@
+# Regression test: /dev/full should not fail until actually written to
+skip_unless test -e /dev/full
+invoke_bfs basic -false -fprint /dev/full
diff --git a/tests/gnu/fprint_nonexistent.sh b/tests/gnu/fprint_nonexistent.sh
new file mode 100644
index 0000000..b6dac8a
--- /dev/null
+++ b/tests/gnu/fprint_nonexistent.sh
@@ -0,0 +1 @@
+fail invoke_bfs basic -fprint scratch/nonexistent/path
diff --git a/tests/gnu/fprint_truncate.out b/tests/gnu/fprint_truncate.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/fprint_truncate.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/fprint_truncate.sh b/tests/gnu/fprint_truncate.sh
new file mode 100644
index 0000000..db58a7a
--- /dev/null
+++ b/tests/gnu/fprint_truncate.sh
@@ -0,0 +1,5 @@
+printf "basic\nbasic\n" >"$OUT"
+
+invoke_bfs basic -maxdepth 0 -fprint "$OUT"
+sort_output
+diff_output
diff --git a/tests/gnu/fprintf.out b/tests/gnu/fprintf.out
new file mode 100644
index 0000000..77ce17a
--- /dev/null
+++ b/tests/gnu/fprintf.out
@@ -0,0 +1,19 @@
+%p(basic) %d(0) %f(basic) %h(.) %H(basic) %P() %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/a) %d(1) %f(a) %h(basic) %H(basic) %P(a) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/b) %d(1) %f(b) %h(basic) %H(basic) %P(b) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/c) %d(1) %f(c) %h(basic) %H(basic) %P(c) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/c/d) %d(2) %f(d) %h(basic/c) %H(basic) %P(c/d) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/e) %d(1) %f(e) %h(basic) %H(basic) %P(e) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/e/f) %d(2) %f(f) %h(basic/e) %H(basic) %P(e/f) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/g) %d(1) %f(g) %h(basic) %H(basic) %P(g) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/g/h) %d(2) %f(h) %h(basic/g) %H(basic) %P(g/h) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/i) %d(1) %f(i) %h(basic) %H(basic) %P(i) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/j) %d(1) %f(j) %h(basic) %H(basic) %P(j) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/j/foo) %d(2) %f(foo) %h(basic/j) %H(basic) %P(j/foo) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/k) %d(1) %f(k) %h(basic) %H(basic) %P(k) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/k/foo) %d(2) %f(foo) %h(basic/k) %H(basic) %P(k/foo) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/k/foo/bar) %d(3) %f(bar) %h(basic/k/foo) %H(basic) %P(k/foo/bar) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/l) %d(1) %f(l) %h(basic) %H(basic) %P(l) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/l/foo) %d(2) %f(foo) %h(basic/l) %H(basic) %P(l/foo) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/l/foo/bar) %d(3) %f(bar) %h(basic/l/foo) %H(basic) %P(l/foo/bar) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/l/foo/bar/baz) %d(4) %f(baz) %h(basic/l/foo/bar) %H(basic) %P(l/foo/bar/baz) %m(644) %M(-rw-r--r--) %y(f)
diff --git a/tests/gnu/fprintf.sh b/tests/gnu/fprintf.sh
new file mode 100644
index 0000000..9c6355a
--- /dev/null
+++ b/tests/gnu/fprintf.sh
@@ -0,0 +1,3 @@
+invoke_bfs basic -fprintf "$OUT" '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%m(%m) %%M(%M) %%y(%y)\n'
+sort_output
+diff_output
diff --git a/tests/gnu/fprintf_nofile.sh b/tests/gnu/fprintf_nofile.sh
new file mode 100644
index 0000000..c2c48a5
--- /dev/null
+++ b/tests/gnu/fprintf_nofile.sh
@@ -0,0 +1 @@
+fail invoke_bfs basic -fprintf
diff --git a/tests/gnu/fprintf_noformat.sh b/tests/gnu/fprintf_noformat.sh
new file mode 100644
index 0000000..0d285c3
--- /dev/null
+++ b/tests/gnu/fprintf_noformat.sh
@@ -0,0 +1 @@
+fail invoke_bfs basic -fprintf /dev/null
diff --git a/tests/gnu/fstype.out b/tests/gnu/fstype.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/fstype.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/gnu/fstype.sh b/tests/gnu/fstype.sh
new file mode 100644
index 0000000..176688d
--- /dev/null
+++ b/tests/gnu/fstype.sh
@@ -0,0 +1,2 @@
+fstype=$(invoke_bfs basic -maxdepth 0 -printf '%F\n')
+bfs_diff basic -fstype "$fstype"
diff --git a/tests/gnu/gid.out b/tests/gnu/gid.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/gid.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/gnu/gid.sh b/tests/gnu/gid.sh
new file mode 100644
index 0000000..2707b4a
--- /dev/null
+++ b/tests/gnu/gid.sh
@@ -0,0 +1 @@
+bfs_diff basic -gid "$(id -g)"
diff --git a/tests/gnu/gid_minus.out b/tests/gnu/gid_minus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/gid_minus.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/gnu/gid_minus.sh b/tests/gnu/gid_minus.sh
new file mode 100644
index 0000000..e3822f0
--- /dev/null
+++ b/tests/gnu/gid_minus.sh
@@ -0,0 +1 @@
+bfs_diff basic -gid "-$(($(id -g) + 1))"
diff --git a/tests/gnu/gid_minus_plus.out b/tests/gnu/gid_minus_plus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/gid_minus_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/gnu/gid_minus_plus.sh b/tests/gnu/gid_minus_plus.sh
new file mode 100644
index 0000000..4ff0877
--- /dev/null
+++ b/tests/gnu/gid_minus_plus.sh
@@ -0,0 +1 @@
+bfs_diff basic -gid "-+$(($(id -g) + 1))"
diff --git a/tests/gnu/gid_plus.out b/tests/gnu/gid_plus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/gid_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/gnu/gid_plus.sh b/tests/gnu/gid_plus.sh
new file mode 100644
index 0000000..8ad493b
--- /dev/null
+++ b/tests/gnu/gid_plus.sh
@@ -0,0 +1,2 @@
+skip_if test "$(id -g)" -eq 0
+bfs_diff basic -gid +0
diff --git a/tests/gnu/gid_plus_plus.out b/tests/gnu/gid_plus_plus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/gid_plus_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/gnu/gid_plus_plus.sh b/tests/gnu/gid_plus_plus.sh
new file mode 100644
index 0000000..7982633
--- /dev/null
+++ b/tests/gnu/gid_plus_plus.sh
@@ -0,0 +1,2 @@
+skip_if test "$(id -g)" -eq 0
+bfs_diff basic -gid ++0
diff --git a/tests/gnu/ignore_readdir_race.sh b/tests/gnu/ignore_readdir_race.sh
new file mode 100644
index 0000000..429933c
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race.sh
@@ -0,0 +1,5 @@
+rm -rf scratch/*
+$TOUCH scratch/{foo,bar}
+
+# -links 1 forces a stat() call, which will fail for the second file
+invoke_bfs scratch -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" {} \;
diff --git a/tests/gnu/ignore_readdir_race_notdir.sh b/tests/gnu/ignore_readdir_race_notdir.sh
new file mode 100644
index 0000000..0c3ff5d
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race_notdir.sh
@@ -0,0 +1,5 @@
+# Check -ignore_readdir_race handling when a directory is replaced with a file
+rm -rf scratch/*
+touchp scratch/foo/bar
+
+invoke_bfs scratch -mindepth 1 -ignore_readdir_race -execdir rm -r {} \; -execdir $TOUCH {} \;
diff --git a/tests/gnu/ignore_readdir_race_root.sh b/tests/gnu/ignore_readdir_race_root.sh
new file mode 100644
index 0000000..bad7391
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race_root.sh
@@ -0,0 +1,2 @@
+# Make sure -ignore_readdir_race doesn't suppress ENOENT at the root
+fail invoke_bfs basic/nonexistent -ignore_readdir_race
diff --git a/tests/gnu/inum_automount.out b/tests/gnu/inum_automount.out
new file mode 100644
index 0000000..99c7511
--- /dev/null
+++ b/tests/gnu/inum_automount.out
@@ -0,0 +1 @@
+scratch/mnt
diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh
new file mode 100644
index 0000000..0149043
--- /dev/null
+++ b/tests/gnu/inum_automount.sh
@@ -0,0 +1,17 @@
+# bfs shouldn't trigger automounts unless it descends into them
+
+skip_unless test "$SUDO"
+skip_unless command -v systemd-mount &>/dev/null
+
+rm -rf scratch/*
+mkdir scratch/{foo,mnt}
+skip_unless sudo systemd-mount -A -o bind basic scratch/mnt
+
+before=$(inum scratch/mnt)
+bfs_diff scratch -inum "$before" -prune
+ret=$?
+after=$(inum scratch/mnt)
+
+sudo systemd-umount scratch/mnt
+
+((ret == 0 && before == after))
diff --git a/tests/gnu/iwholename.out b/tests/gnu/iwholename.out
new file mode 100644
index 0000000..ae1ae21
--- /dev/null
+++ b/tests/gnu/iwholename.out
@@ -0,0 +1,7 @@
+basic/e/f
+basic/j/foo
+basic/k/foo
+basic/k/foo/bar
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar/baz
diff --git a/tests/gnu/iwholename.sh b/tests/gnu/iwholename.sh
new file mode 100644
index 0000000..67e9630
--- /dev/null
+++ b/tests/gnu/iwholename.sh
@@ -0,0 +1,2 @@
+skip_unless invoke_bfs -quit -iwholename PATTERN
+bfs_diff basic -iwholename 'basic/*F*'
diff --git a/tests/gnu/not.out b/tests/gnu/not.out
new file mode 100644
index 0000000..b286454
--- /dev/null
+++ b/tests/gnu/not.out
@@ -0,0 +1,16 @@
+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/k
+basic/k/foo/bar
+basic/l
+basic/l/foo/bar
+basic/l/foo/bar/baz
diff --git a/tests/gnu/not.sh b/tests/gnu/not.sh
new file mode 100644
index 0000000..9fa9edc
--- /dev/null
+++ b/tests/gnu/not.sh
@@ -0,0 +1 @@
+bfs_diff basic -not -name foo
diff --git a/tests/gnu/not_reachability.out b/tests/gnu/not_reachability.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/not_reachability.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/not_reachability.sh b/tests/gnu/not_reachability.sh
new file mode 100644
index 0000000..7fd3c74
--- /dev/null
+++ b/tests/gnu/not_reachability.sh
@@ -0,0 +1 @@
+bfs_diff basic -print \! -quit -print
diff --git a/tests/gnu/ok_nothing.sh b/tests/gnu/ok_nothing.sh
new file mode 100644
index 0000000..439687b
--- /dev/null
+++ b/tests/gnu/ok_nothing.sh
@@ -0,0 +1,2 @@
+# Regression test: don't segfault on missing command
+fail invoke_bfs basic -ok \;
diff --git a/tests/gnu/or.out b/tests/gnu/or.out
new file mode 100644
index 0000000..1650c4d
--- /dev/null
+++ b/tests/gnu/or.out
@@ -0,0 +1,13 @@
+basic
+basic/c
+basic/e
+basic/g
+basic/g/h
+basic/i
+basic/j
+basic/j/foo
+basic/k
+basic/k/foo
+basic/l
+basic/l/foo
+basic/l/foo/bar
diff --git a/tests/gnu/or.sh b/tests/gnu/or.sh
new file mode 100644
index 0000000..eb28030
--- /dev/null
+++ b/tests/gnu/or.sh
@@ -0,0 +1 @@
+bfs_diff basic -name foo -or -type d
diff --git a/tests/gnu/path_d.out b/tests/gnu/path_d.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/path_d.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/gnu/path_d.sh b/tests/gnu/path_d.sh
new file mode 100644
index 0000000..e546ff3
--- /dev/null
+++ b/tests/gnu/path_d.sh
@@ -0,0 +1 @@
+bfs_diff basic -d
diff --git a/tests/gnu/perm_000_slash.out b/tests/gnu/perm_000_slash.out
new file mode 100644
index 0000000..d7494b8
--- /dev/null
+++ b/tests/gnu/perm_000_slash.out
@@ -0,0 +1,8 @@
+perms
+perms/0
+perms/r
+perms/rw
+perms/rwx
+perms/rx
+perms/w
+perms/wx
diff --git a/tests/gnu/perm_000_slash.sh b/tests/gnu/perm_000_slash.sh
new file mode 100644
index 0000000..f4b2665
--- /dev/null
+++ b/tests/gnu/perm_000_slash.sh
@@ -0,0 +1 @@
+bfs_diff perms -perm /000
diff --git a/tests/gnu/perm_222_slash.out b/tests/gnu/perm_222_slash.out
new file mode 100644
index 0000000..9a5b95a
--- /dev/null
+++ b/tests/gnu/perm_222_slash.out
@@ -0,0 +1,5 @@
+perms
+perms/rw
+perms/rwx
+perms/w
+perms/wx
diff --git a/tests/gnu/perm_222_slash.sh b/tests/gnu/perm_222_slash.sh
new file mode 100644
index 0000000..f4be665
--- /dev/null
+++ b/tests/gnu/perm_222_slash.sh
@@ -0,0 +1 @@
+bfs_diff perms -perm /222
diff --git a/tests/gnu/perm_644_slash.out b/tests/gnu/perm_644_slash.out
new file mode 100644
index 0000000..7e5ae98
--- /dev/null
+++ b/tests/gnu/perm_644_slash.out
@@ -0,0 +1,7 @@
+perms
+perms/r
+perms/rw
+perms/rwx
+perms/rx
+perms/w
+perms/wx
diff --git a/tests/gnu/perm_644_slash.sh b/tests/gnu/perm_644_slash.sh
new file mode 100644
index 0000000..e883f17
--- /dev/null
+++ b/tests/gnu/perm_644_slash.sh
@@ -0,0 +1 @@
+bfs_diff perms -perm /644
diff --git a/tests/gnu/perm_leading_plus_symbolic_slash.out b/tests/gnu/perm_leading_plus_symbolic_slash.out
new file mode 100644
index 0000000..7e5ae98
--- /dev/null
+++ b/tests/gnu/perm_leading_plus_symbolic_slash.out
@@ -0,0 +1,7 @@
+perms
+perms/r
+perms/rw
+perms/rwx
+perms/rx
+perms/w
+perms/wx
diff --git a/tests/gnu/perm_leading_plus_symbolic_slash.sh b/tests/gnu/perm_leading_plus_symbolic_slash.sh
new file mode 100644
index 0000000..3db27bd
--- /dev/null
+++ b/tests/gnu/perm_leading_plus_symbolic_slash.sh
@@ -0,0 +1 @@
+bfs_diff perms -perm /+rwx
diff --git a/tests/gnu/perm_symbolic_slash.out b/tests/gnu/perm_symbolic_slash.out
new file mode 100644
index 0000000..7e5ae98
--- /dev/null
+++ b/tests/gnu/perm_symbolic_slash.out
@@ -0,0 +1,7 @@
+perms
+perms/r
+perms/rw
+perms/rwx
+perms/rx
+perms/w
+perms/wx
diff --git a/tests/gnu/perm_symbolic_slash.sh b/tests/gnu/perm_symbolic_slash.sh
new file mode 100644
index 0000000..253b14e
--- /dev/null
+++ b/tests/gnu/perm_symbolic_slash.sh
@@ -0,0 +1 @@
+bfs_diff perms -perm /a+r,u=wX,g+wX-w
diff --git a/tests/gnu/precedence.out b/tests/gnu/precedence.out
new file mode 100644
index 0000000..7f589f2
--- /dev/null
+++ b/tests/gnu/precedence.out
@@ -0,0 +1,4 @@
+basic/k/foo
+basic/k/foo/bar
+basic/l/foo
+basic/l/foo/bar/baz
diff --git a/tests/gnu/precedence.sh b/tests/gnu/precedence.sh
new file mode 100644
index 0000000..b35d160
--- /dev/null
+++ b/tests/gnu/precedence.sh
@@ -0,0 +1 @@
+bfs_diff basic \( -name foo -type d -o -name bar -a -type f \) -print , \! -empty -type f -print
diff --git a/tests/gnu/print0.out b/tests/gnu/print0.out
new file mode 100644
index 0000000..1347444
--- /dev/null
+++ b/tests/gnu/print0.out
Binary files differ
diff --git a/tests/gnu/print0.sh b/tests/gnu/print0.sh
new file mode 100644
index 0000000..b916172
--- /dev/null
+++ b/tests/gnu/print0.sh
@@ -0,0 +1,2 @@
+invoke_bfs basic/a basic/b -print0 >"$OUT"
+diff_output
diff --git a/tests/gnu/print_error.sh b/tests/gnu/print_error.sh
new file mode 100644
index 0000000..9fd5af5
--- /dev/null
+++ b/tests/gnu/print_error.sh
@@ -0,0 +1,2 @@
+skip_unless test -e /dev/full
+fail invoke_bfs basic -maxdepth 0 >/dev/full
diff --git a/tests/gnu/printf.out b/tests/gnu/printf.out
new file mode 100644
index 0000000..77ce17a
--- /dev/null
+++ b/tests/gnu/printf.out
@@ -0,0 +1,19 @@
+%p(basic) %d(0) %f(basic) %h(.) %H(basic) %P() %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/a) %d(1) %f(a) %h(basic) %H(basic) %P(a) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/b) %d(1) %f(b) %h(basic) %H(basic) %P(b) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/c) %d(1) %f(c) %h(basic) %H(basic) %P(c) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/c/d) %d(2) %f(d) %h(basic/c) %H(basic) %P(c/d) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/e) %d(1) %f(e) %h(basic) %H(basic) %P(e) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/e/f) %d(2) %f(f) %h(basic/e) %H(basic) %P(e/f) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/g) %d(1) %f(g) %h(basic) %H(basic) %P(g) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/g/h) %d(2) %f(h) %h(basic/g) %H(basic) %P(g/h) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/i) %d(1) %f(i) %h(basic) %H(basic) %P(i) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/j) %d(1) %f(j) %h(basic) %H(basic) %P(j) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/j/foo) %d(2) %f(foo) %h(basic/j) %H(basic) %P(j/foo) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/k) %d(1) %f(k) %h(basic) %H(basic) %P(k) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/k/foo) %d(2) %f(foo) %h(basic/k) %H(basic) %P(k/foo) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/k/foo/bar) %d(3) %f(bar) %h(basic/k/foo) %H(basic) %P(k/foo/bar) %m(644) %M(-rw-r--r--) %y(f)
+%p(basic/l) %d(1) %f(l) %h(basic) %H(basic) %P(l) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/l/foo) %d(2) %f(foo) %h(basic/l) %H(basic) %P(l/foo) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/l/foo/bar) %d(3) %f(bar) %h(basic/l/foo) %H(basic) %P(l/foo/bar) %m(755) %M(drwxr-xr-x) %y(d)
+%p(basic/l/foo/bar/baz) %d(4) %f(baz) %h(basic/l/foo/bar) %H(basic) %P(l/foo/bar/baz) %m(644) %M(-rw-r--r--) %y(f)
diff --git a/tests/gnu/printf.sh b/tests/gnu/printf.sh
new file mode 100644
index 0000000..4dd48e8
--- /dev/null
+++ b/tests/gnu/printf.sh
@@ -0,0 +1 @@
+bfs_diff basic -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%m(%m) %%M(%M) %%y(%y)\n'
diff --git a/tests/gnu/printf_H.out b/tests/gnu/printf_H.out
new file mode 100644
index 0000000..6b605ff
--- /dev/null
+++ b/tests/gnu/printf_H.out
@@ -0,0 +1,32 @@
+%p(basic) %d(0) %f(basic) %h(.) %H(basic) %P() %y(d)
+%p(basic/a) %d(1) %f(a) %h(basic) %H(basic) %P(a) %y(f)
+%p(basic/b) %d(1) %f(b) %h(basic) %H(basic) %P(b) %y(f)
+%p(basic/c) %d(1) %f(c) %h(basic) %H(basic) %P(c) %y(d)
+%p(basic/c/d) %d(2) %f(d) %h(basic/c) %H(basic) %P(c/d) %y(f)
+%p(basic/e) %d(1) %f(e) %h(basic) %H(basic) %P(e) %y(d)
+%p(basic/e/f) %d(2) %f(f) %h(basic/e) %H(basic) %P(e/f) %y(f)
+%p(basic/g) %d(1) %f(g) %h(basic) %H(basic) %P(g) %y(d)
+%p(basic/g/h) %d(2) %f(h) %h(basic/g) %H(basic) %P(g/h) %y(d)
+%p(basic/i) %d(1) %f(i) %h(basic) %H(basic) %P(i) %y(d)
+%p(basic/j) %d(1) %f(j) %h(basic) %H(basic) %P(j) %y(d)
+%p(basic/j/foo) %d(2) %f(foo) %h(basic/j) %H(basic) %P(j/foo) %y(f)
+%p(basic/k) %d(1) %f(k) %h(basic) %H(basic) %P(k) %y(d)
+%p(basic/k/foo) %d(2) %f(foo) %h(basic/k) %H(basic) %P(k/foo) %y(d)
+%p(basic/k/foo/bar) %d(3) %f(bar) %h(basic/k/foo) %H(basic) %P(k/foo/bar) %y(f)
+%p(basic/l) %d(1) %f(l) %h(basic) %H(basic) %P(l) %y(d)
+%p(basic/l/foo) %d(2) %f(foo) %h(basic/l) %H(basic) %P(l/foo) %y(d)
+%p(basic/l/foo/bar) %d(3) %f(bar) %h(basic/l/foo) %H(basic) %P(l/foo/bar) %y(d)
+%p(basic/l/foo/bar/baz) %d(4) %f(baz) %h(basic/l/foo/bar) %H(basic) %P(l/foo/bar/baz) %y(f)
+%p(links) %d(0) %f(links) %h(.) %H(links) %P() %y(d)
+%p(links/broken) %d(1) %f(broken) %h(links) %H(links) %P(broken) %y(l)
+%p(links/deeply) %d(1) %f(deeply) %h(links) %H(links) %P(deeply) %y(d)
+%p(links/deeply/nested) %d(2) %f(nested) %h(links/deeply) %H(links) %P(deeply/nested) %y(d)
+%p(links/deeply/nested/broken) %d(3) %f(broken) %h(links/deeply/nested) %H(links) %P(deeply/nested/broken) %y(l)
+%p(links/deeply/nested/dir) %d(3) %f(dir) %h(links/deeply/nested) %H(links) %P(deeply/nested/dir) %y(d)
+%p(links/deeply/nested/file) %d(3) %f(file) %h(links/deeply/nested) %H(links) %P(deeply/nested/file) %y(f)
+%p(links/deeply/nested/link) %d(3) %f(link) %h(links/deeply/nested) %H(links) %P(deeply/nested/link) %y(l)
+%p(links/file) %d(1) %f(file) %h(links) %H(links) %P(file) %y(f)
+%p(links/hardlink) %d(1) %f(hardlink) %h(links) %H(links) %P(hardlink) %y(f)
+%p(links/notdir) %d(1) %f(notdir) %h(links) %H(links) %P(notdir) %y(l)
+%p(links/skip) %d(1) %f(skip) %h(links) %H(links) %P(skip) %y(l)
+%p(links/symlink) %d(1) %f(symlink) %h(links) %H(links) %P(symlink) %y(l)
diff --git a/tests/gnu/printf_H.sh b/tests/gnu/printf_H.sh
new file mode 100644
index 0000000..ddef7e2
--- /dev/null
+++ b/tests/gnu/printf_H.sh
@@ -0,0 +1 @@
+bfs_diff basic links -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%y(%y)\n'
diff --git a/tests/gnu/printf_Y_error.out b/tests/gnu/printf_Y_error.out
new file mode 100644
index 0000000..410a9b5
--- /dev/null
+++ b/tests/gnu/printf_Y_error.out
@@ -0,0 +1,3 @@
+(scratch) () d d
+(scratch/bar) (foo/bar) l ?
+(scratch/foo) () d d
diff --git a/tests/gnu/printf_Y_error.sh b/tests/gnu/printf_Y_error.sh
new file mode 100644
index 0000000..933c734
--- /dev/null
+++ b/tests/gnu/printf_Y_error.sh
@@ -0,0 +1,12 @@
+rm -rf scratch/*
+mkdir scratch/foo
+chmod -x scratch/foo
+ln -s foo/bar scratch/bar
+
+bfs_diff scratch -printf '(%p) (%l) %y %Y\n'
+ret=$?
+
+chmod +x scratch/foo
+rm -rf scratch/*
+
+[ $ret -eq $EX_BFS ]
diff --git a/tests/gnu/printf_empty.out b/tests/gnu/printf_empty.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gnu/printf_empty.out
diff --git a/tests/gnu/printf_empty.sh b/tests/gnu/printf_empty.sh
new file mode 100644
index 0000000..ed5eb04
--- /dev/null
+++ b/tests/gnu/printf_empty.sh
@@ -0,0 +1 @@
+bfs_diff basic -printf ''
diff --git a/tests/gnu/printf_escapes.out b/tests/gnu/printf_escapes.out
new file mode 100644
index 0000000..20ea120
--- /dev/null
+++ b/tests/gnu/printf_escapes.out
@@ -0,0 +1 @@
+8 8I8I18
diff --git a/tests/gnu/printf_escapes.sh b/tests/gnu/printf_escapes.sh
new file mode 100644
index 0000000..ece7c0e
--- /dev/null
+++ b/tests/gnu/printf_escapes.sh
@@ -0,0 +1 @@
+bfs_diff basic -maxdepth 0 -printf '\18\118\1118\11118\n\cfoo'
diff --git a/tests/gnu/printf_flags.out b/tests/gnu/printf_flags.out
new file mode 100644
index 0000000..c2c1f0a
--- /dev/null
+++ b/tests/gnu/printf_flags.out
@@ -0,0 +1,19 @@
+|basic | +00 0755
+|basic/a | +01 0644
+|basic/b | +01 0644
+|basic/c | +01 0755
+|basic/c/d | +02 0644
+|basic/e | +01 0755
+|basic/e/f | +02 0644
+|basic/g | +01 0755
+|basic/g/h | +02 0755
+|basic/i | +01 0755
+|basic/j | +01 0755
+|basic/j/fo| +02 0644
+|basic/k | +01 0755
+|basic/k/fo| +02 0755
+|basic/k/fo| +03 0644
+|basic/l | +01 0755
+|basic/l/fo| +02 0755
+|basic/l/fo| +03 0755
+|basic/l/fo| +04 0644
diff --git a/tests/gnu/printf_flags.sh b/tests/gnu/printf_flags.sh
new file mode 100644
index 0000000..2ef37ad
--- /dev/null
+++ b/tests/gnu/printf_flags.sh
@@ -0,0 +1 @@
+bfs_diff basic -printf '|%- 10.10p| %+03d %#4m\n'
diff --git a/tests/gnu/printf_l_nonlink.out b/tests/gnu/printf_l_nonlink.out
new file mode 100644
index 0000000..30df155
--- /dev/null
+++ b/tests/gnu/printf_l_nonlink.out
@@ -0,0 +1,13 @@
+| links -> |
+| links/file -> |
+| links/skip -> deeply/nested |
+| links/broken -> nowhere |
+| links/deeply -> |
+| links/notdir -> symlink/file |
+| links/symlink -> file |
+| links/hardlink -> |
+| links/deeply/nested -> |
+| links/deeply/nested/dir -> |
+| links/deeply/nested/file -> |
+| links/deeply/nested/link -> file |
+| links/deeply/nested/broken -> nowhere |
diff --git a/tests/gnu/printf_l_nonlink.sh b/tests/gnu/printf_l_nonlink.sh
new file mode 100644
index 0000000..1c66442
--- /dev/null
+++ b/tests/gnu/printf_l_nonlink.sh
@@ -0,0 +1 @@
+bfs_diff links -printf '| %26p -> %-26l |\n'
diff --git a/tests/gnu/printf_leak.out b/tests/gnu/printf_leak.out
new file mode 100644
index 0000000..15a13db
--- /dev/null
+++ b/tests/gnu/printf_leak.out
@@ -0,0 +1 @@
+basic
diff --git a/tests/gnu/printf_leak.sh b/tests/gnu/printf_leak.sh
new file mode 100644
index 0000000..c4092c7
--- /dev/null
+++ b/tests/gnu/printf_leak.sh
@@ -0,0 +1,2 @@
+# Memory leak regression test
+bfs_diff basic -maxdepth 0 -printf '%p'
diff --git a/tests/gnu/printf_nul.out b/tests/gnu/printf_nul.out
new file mode 100644
index 0000000..fdb6c6b
--- /dev/null
+++ b/tests/gnu/printf_nul.out
Binary files differ
diff --git a/tests/gnu/printf_nul.sh b/tests/gnu/printf_nul.sh
new file mode 100644
index 0000000..0b8b928
--- /dev/null
+++ b/tests/gnu/printf_nul.sh
@@ -0,0 +1,3 @@
+# NUL byte regression test
+invoke_bfs basic/a basic/b -maxdepth 0 -printf '%h\0%f\n' >"$OUT"
+diff_output
diff --git a/tests/gnu/printf_slash.out b/tests/gnu/printf_slash.out
new file mode 100644
index 0000000..5571971
--- /dev/null
+++ b/tests/gnu/printf_slash.out
@@ -0,0 +1 @@
+(/)/(/)
diff --git a/tests/gnu/printf_slash.sh b/tests/gnu/printf_slash.sh
new file mode 100644
index 0000000..b64ff10
--- /dev/null
+++ b/tests/gnu/printf_slash.sh
@@ -0,0 +1 @@
+bfs_diff / -maxdepth 0 -printf '(%h)/(%f)\n'
diff --git a/tests/gnu/printf_slashes.out b/tests/gnu/printf_slashes.out
new file mode 100644
index 0000000..5571971
--- /dev/null
+++ b/tests/gnu/printf_slashes.out
@@ -0,0 +1 @@
+(/)/(/)
diff --git a/tests/gnu/printf_slashes.sh b/tests/gnu/printf_slashes.sh
new file mode 100644
index 0000000..d56a287
--- /dev/null
+++ b/tests/gnu/printf_slashes.sh
@@ -0,0 +1 @@
+bfs_diff /// -maxdepth 0 -printf '(%h)/(%f)\n'
diff --git a/tests/gnu/printf_times.out b/tests/gnu/printf_times.out
new file mode 100644
index 0000000..7e7d256
--- /dev/null
+++ b/tests/gnu/printf_times.out
@@ -0,0 +1,3 @@
+times/a | Sat Dec 14 00:00:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668800.0000000000 | Sat Dec 14 00:00:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668800.0000000000
+times/b | Sat Dec 14 00:01:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668860.0000000000 | Sat Dec 14 00:01:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668860.0000000000
+times/c | Sat Dec 14 00:02:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668920.0000000000 | Sat Dec 14 00:02:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668920.0000000000
diff --git a/tests/gnu/printf_times.sh b/tests/gnu/printf_times.sh
new file mode 100644
index 0000000..d952620
--- /dev/null
+++ b/tests/gnu/printf_times.sh
@@ -0,0 +1 @@
+bfs_diff times -type f -printf '%p | %a %AY-%Am-%Ad %AH:%AI:%AS %T@ | %t %TY-%Tm-%Td %TH:%TI:%TS %T@\n'
diff --git a/tests/gnu/printf_trailing_slash.out b/tests/gnu/printf_trailing_slash.out
new file mode 100644
index 0000000..017ac0d
--- /dev/null
+++ b/tests/gnu/printf_trailing_slash.out
@@ -0,0 +1,19 @@
+(.)/(basic/)
+(basic)/(a)
+(basic)/(b)
+(basic)/(c)
+(basic)/(e)
+(basic)/(g)
+(basic)/(i)
+(basic)/(j)
+(basic)/(k)
+(basic)/(l)
+(basic/c)/(d)
+(basic/e)/(f)
+(basic/g)/(h)
+(basic/j)/(foo)
+(basic/k)/(foo)
+(basic/k/foo)/(bar)
+(basic/l)/(foo)
+(basic/l/foo)/(bar)
+(basic/l/foo/bar)/(baz)
diff --git a/tests/gnu/printf_trailing_slash.sh b/tests/gnu/printf_trailing_slash.sh
new file mode 100644
index 0000000..2df818d
--- /dev/null
+++ b/tests/gnu/printf_trailing_slash.sh
@@ -0,0 +1 @@
+bfs_diff basic/ -printf '(%h)/(%f)\n'
diff --git a/tests/gnu/printf_trailing_slashes.out b/tests/gnu/printf_trailing_slashes.out
new file mode 100644
index 0000000..fd27101
--- /dev/null
+++ b/tests/gnu/printf_trailing_slashes.out
@@ -0,0 +1,19 @@
+(.)/(basic///)
+(basic//)/(a)
+(basic//)/(b)
+(basic//)/(c)
+(basic//)/(e)
+(basic//)/(g)
+(basic//)/(i)
+(basic//)/(j)
+(basic//)/(k)
+(basic//)/(l)
+(basic///c)/(d)
+(basic///e)/(f)
+(basic///g)/(h)
+(basic///j)/(foo)
+(basic///k)/(foo)
+(basic///k/foo)/(bar)
+(basic///l)/(foo)
+(basic///l/foo)/(bar)
+(basic///l/foo/bar)/(baz)
diff --git a/tests/gnu/printf_trailing_slashes.sh b/tests/gnu/printf_trailing_slashes.sh
new file mode 100644
index 0000000..6dc532c
--- /dev/null
+++ b/tests/gnu/printf_trailing_slashes.sh
@@ -0,0 +1 @@
+bfs_diff basic/// -printf '(%h)/(%f)\n'
diff --git a/tests/gnu/printf_types.out b/tests/gnu/printf_types.out
new file mode 100644
index 0000000..8144c7c
--- /dev/null
+++ b/tests/gnu/printf_types.out
@@ -0,0 +1,11 @@
+(loops) () d d
+(loops/broken) (nowhere) l N
+(loops/deeply) () d d
+(loops/deeply/nested) () d d
+(loops/deeply/nested/dir) () d d
+(loops/deeply/nested/loop) (../../deeply) l d
+(loops/file) () f f
+(loops/loop) (loop) l L
+(loops/notdir) (symlink/file) l N
+(loops/skip) (deeply/nested/loop/nested) l d
+(loops/symlink) (file) l f
diff --git a/tests/gnu/printf_types.sh b/tests/gnu/printf_types.sh
new file mode 100644
index 0000000..6ed1d75
--- /dev/null
+++ b/tests/gnu/printf_types.sh
@@ -0,0 +1 @@
+bfs_diff loops -printf '(%p) (%l) %y %Y\n'
diff --git a/tests/gnu/printf_u_g_ulimit.sh b/tests/gnu/printf_u_g_ulimit.sh
new file mode 100644
index 0000000..a84ee29
--- /dev/null
+++ b/tests/gnu/printf_u_g_ulimit.sh
@@ -0,0 +1,3 @@
+closefrom 4
+ulimit -n 16
+[ "$(invoke_bfs deep -printf '%u %g\n' | uniq)" = "$(id -un) $(id -gn)" ]
diff --git a/tests/gnu/readable.out b/tests/gnu/readable.out
new file mode 100644
index 0000000..386feba
--- /dev/null
+++ b/tests/gnu/readable.out
@@ -0,0 +1,5 @@
+perms
+perms/r
+perms/rw
+perms/rwx
+perms/rx
diff --git a/tests/gnu/readable.sh b/tests/gnu/readable.sh
new file mode 100644
index 0000000..a496667
--- /dev/null
+++ b/tests/gnu/readable.sh
@@ -0,0 +1 @@
+bfs_diff perms -readable
diff --git a/tests/gnu/regex_error.sh b/tests/gnu/regex_error.sh
new file mode 100644
index 0000000..9bd4c8d
--- /dev/null
+++ b/tests/gnu/regex_error.sh
@@ -0,0 +1 @@
+fail invoke_bfs basic -regex '['
diff --git a/tests/gnu/regex_invalid_utf8.out b/tests/gnu/regex_invalid_utf8.out
new file mode 100644
index 0000000..03f3f58
--- /dev/null
+++ b/tests/gnu/regex_invalid_utf8.out
@@ -0,0 +1 @@
+scratch/â„
diff --git a/tests/gnu/regex_invalid_utf8.sh b/tests/gnu/regex_invalid_utf8.sh
new file mode 100644
index 0000000..85f1897
--- /dev/null
+++ b/tests/gnu/regex_invalid_utf8.sh
@@ -0,0 +1,8 @@
+rm -rf scratch/*
+
+# Incomplete UTF-8 sequences
+skip_unless touch scratch/$'\xC3'
+skip_unless touch scratch/$'\xE2\x84'
+skip_unless touch scratch/$'\xF0\x9F\x92'
+
+bfs_diff scratch -regex 'scratch/..'
diff --git a/tests/gnu/regextype_ed.out b/tests/gnu/regextype_ed.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/gnu/regextype_ed.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/gnu/regextype_ed.sh b/tests/gnu/regextype_ed.sh
new file mode 100644
index 0000000..0e92db3
--- /dev/null
+++ b/tests/gnu/regextype_ed.sh
@@ -0,0 +1,2 @@
+cd weirdnames
+bfs_diff -regextype ed -regex '\./\((\)'
diff --git a/tests/gnu/regextype_emacs.out b/tests/gnu/regextype_emacs.out
new file mode 100644
index 0000000..95942b4
--- /dev/null
+++ b/tests/gnu/regextype_emacs.out
@@ -0,0 +1,6 @@
+basic/e/f
+basic/j/foo
+basic/k/foo
+basic/k/foo/bar
+basic/l/foo
+basic/l/foo/bar
diff --git a/tests/gnu/regextype_emacs.sh b/tests/gnu/regextype_emacs.sh
new file mode 100644
index 0000000..d0f68cc
--- /dev/null
+++ b/tests/gnu/regextype_emacs.sh
@@ -0,0 +1,3 @@
+skip_unless invoke_bfs -regextype emacs -quit
+
+bfs_diff basic -regextype emacs -regex '.*/\(f+o?o?\|bar\)'
diff --git a/tests/gnu/regextype_grep.out b/tests/gnu/regextype_grep.out
new file mode 100644
index 0000000..a9e5d42
--- /dev/null
+++ b/tests/gnu/regextype_grep.out
@@ -0,0 +1,4 @@
+basic/e/f
+basic/j/foo
+basic/k/foo
+basic/l/foo
diff --git a/tests/gnu/regextype_grep.sh b/tests/gnu/regextype_grep.sh
new file mode 100644
index 0000000..0136700
--- /dev/null
+++ b/tests/gnu/regextype_grep.sh
@@ -0,0 +1,3 @@
+skip_unless invoke_bfs -regextype grep -quit
+
+bfs_diff basic -regextype grep -regex '.*/f\+o\?o\?'
diff --git a/tests/gnu/regextype_posix_basic.out b/tests/gnu/regextype_posix_basic.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/gnu/regextype_posix_basic.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/gnu/regextype_posix_basic.sh b/tests/gnu/regextype_posix_basic.sh
new file mode 100644
index 0000000..fa2254c
--- /dev/null
+++ b/tests/gnu/regextype_posix_basic.sh
@@ -0,0 +1,2 @@
+cd weirdnames
+bfs_diff -regextype posix-basic -regex '\./\((\)'
diff --git a/tests/gnu/regextype_posix_extended.out b/tests/gnu/regextype_posix_extended.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/gnu/regextype_posix_extended.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/gnu/regextype_posix_extended.sh b/tests/gnu/regextype_posix_extended.sh
new file mode 100644
index 0000000..f82ed65
--- /dev/null
+++ b/tests/gnu/regextype_posix_extended.sh
@@ -0,0 +1,2 @@
+cd weirdnames
+bfs_diff -regextype posix-extended -regex '\./(\()'
diff --git a/tests/gnu/regextype_sed.out b/tests/gnu/regextype_sed.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/gnu/regextype_sed.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/gnu/regextype_sed.sh b/tests/gnu/regextype_sed.sh
new file mode 100644
index 0000000..9ce6f4e
--- /dev/null
+++ b/tests/gnu/regextype_sed.sh
@@ -0,0 +1,2 @@
+cd weirdnames
+bfs_diff -regextype sed -regex '\./\((\)'
diff --git a/tests/gnu/true.out b/tests/gnu/true.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/true.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/gnu/true.sh b/tests/gnu/true.sh
new file mode 100644
index 0000000..65f3254
--- /dev/null
+++ b/tests/gnu/true.sh
@@ -0,0 +1 @@
+bfs_diff basic -true
diff --git a/tests/gnu/uid.out b/tests/gnu/uid.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/uid.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/gnu/uid.sh b/tests/gnu/uid.sh
new file mode 100644
index 0000000..fb3cd93
--- /dev/null
+++ b/tests/gnu/uid.sh
@@ -0,0 +1 @@
+bfs_diff basic -uid "$(id -u)"
diff --git a/tests/gnu/uid_minus.out b/tests/gnu/uid_minus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/uid_minus.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/gnu/uid_minus.sh b/tests/gnu/uid_minus.sh
new file mode 100644
index 0000000..6d371f2
--- /dev/null
+++ b/tests/gnu/uid_minus.sh
@@ -0,0 +1 @@
+bfs_diff basic -uid "-$(($(id -u) + 1))"
diff --git a/tests/gnu/uid_minus_plus.out b/tests/gnu/uid_minus_plus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/uid_minus_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/gnu/uid_minus_plus.sh b/tests/gnu/uid_minus_plus.sh
new file mode 100644
index 0000000..e7a0496
--- /dev/null
+++ b/tests/gnu/uid_minus_plus.sh
@@ -0,0 +1 @@
+bfs_diff basic -uid "-+$(($(id -u) + 1))"
diff --git a/tests/gnu/uid_plus.out b/tests/gnu/uid_plus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/uid_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/gnu/uid_plus.sh b/tests/gnu/uid_plus.sh
new file mode 100644
index 0000000..fc4bce3
--- /dev/null
+++ b/tests/gnu/uid_plus.sh
@@ -0,0 +1,2 @@
+skip_if test "$(id -u)" -eq 0
+bfs_diff basic -uid +0
diff --git a/tests/gnu/uid_plus_plus.out b/tests/gnu/uid_plus_plus.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/uid_plus_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/gnu/uid_plus_plus.sh b/tests/gnu/uid_plus_plus.sh
new file mode 100644
index 0000000..5d5e086
--- /dev/null
+++ b/tests/gnu/uid_plus_plus.sh
@@ -0,0 +1,2 @@
+skip_if test "$(id -u)" -eq 0
+bfs_diff basic -uid ++0
diff --git a/tests/gnu/wholename.out b/tests/gnu/wholename.out
new file mode 100644
index 0000000..ae1ae21
--- /dev/null
+++ b/tests/gnu/wholename.out
@@ -0,0 +1,7 @@
+basic/e/f
+basic/j/foo
+basic/k/foo
+basic/k/foo/bar
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar/baz
diff --git a/tests/gnu/wholename.sh b/tests/gnu/wholename.sh
new file mode 100644
index 0000000..4c641b8
--- /dev/null
+++ b/tests/gnu/wholename.sh
@@ -0,0 +1 @@
+bfs_diff basic -wholename 'basic/*f*'
diff --git a/tests/gnu/writable.out b/tests/gnu/writable.out
new file mode 100644
index 0000000..9a5b95a
--- /dev/null
+++ b/tests/gnu/writable.out
@@ -0,0 +1,5 @@
+perms
+perms/rw
+perms/rwx
+perms/w
+perms/wx
diff --git a/tests/gnu/writable.sh b/tests/gnu/writable.sh
new file mode 100644
index 0000000..93c666f
--- /dev/null
+++ b/tests/gnu/writable.sh
@@ -0,0 +1 @@
+bfs_diff perms -writable
diff --git a/tests/gnu/xtype_bind_mount.out b/tests/gnu/xtype_bind_mount.out
new file mode 100644
index 0000000..16804ea
--- /dev/null
+++ b/tests/gnu/xtype_bind_mount.out
@@ -0,0 +1,2 @@
+scratch/link
+scratch/null
diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh
new file mode 100644
index 0000000..56f9c5c
--- /dev/null
+++ b/tests/gnu/xtype_bind_mount.sh
@@ -0,0 +1,13 @@
+skip_unless test "$SUDO"
+skip_unless test "$UNAME" = "Linux"
+
+rm -rf scratch/*
+$TOUCH scratch/{file,null}
+sudo mount --bind /dev/null scratch/null
+ln -s /dev/null scratch/link
+
+bfs_diff -L scratch -type c
+ret=$?
+
+sudo umount scratch/null
+return $ret
diff --git a/tests/gnu/xtype_f.out b/tests/gnu/xtype_f.out
new file mode 100644
index 0000000..e6ba322
--- /dev/null
+++ b/tests/gnu/xtype_f.out
@@ -0,0 +1,5 @@
+links/deeply/nested/file
+links/deeply/nested/link
+links/file
+links/hardlink
+links/symlink
diff --git a/tests/gnu/xtype_f.sh b/tests/gnu/xtype_f.sh
new file mode 100644
index 0000000..0ea27bb
--- /dev/null
+++ b/tests/gnu/xtype_f.sh
@@ -0,0 +1 @@
+bfs_diff links -xtype f
diff --git a/tests/gnu/xtype_l.out b/tests/gnu/xtype_l.out
new file mode 100644
index 0000000..f29c978
--- /dev/null
+++ b/tests/gnu/xtype_l.out
@@ -0,0 +1,3 @@
+links/broken
+links/deeply/nested/broken
+links/notdir
diff --git a/tests/gnu/xtype_l.sh b/tests/gnu/xtype_l.sh
new file mode 100644
index 0000000..39c8ea4
--- /dev/null
+++ b/tests/gnu/xtype_l.sh
@@ -0,0 +1 @@
+bfs_diff links -xtype l