summaryrefslogtreecommitdiffstats
path: root/tests/gnu
diff options
context:
space:
mode:
Diffstat (limited to 'tests/gnu')
-rw-r--r--tests/gnu/L_delete.out2
-rw-r--r--tests/gnu/L_delete.sh8
-rw-r--r--tests/gnu/L_loops_continue.out11
-rw-r--r--tests/gnu/L_loops_continue.sh1
-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/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_path_dot.sh1
-rw-r--r--tests/gnu/execdir_path_empty.sh1
-rw-r--r--tests/gnu/execdir_path_relative.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/execdir_ulimit.out16
-rw-r--r--tests/gnu/execdir_ulimit.sh2
-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_error.sh1
-rw-r--r--tests/gnu/files0_from_file.out33
-rw-r--r--tests/gnu/files0_from_file.sh4
-rw-r--r--tests/gnu/files0_from_none.out0
-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/fls_nonexistent.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/fprint0_nonexistent.sh1
-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/fprintf_nonexistent.sh1
-rw-r--r--tests/gnu/fstype.out19
-rw-r--r--tests/gnu/fstype.sh2
-rw-r--r--tests/gnu/fstype_stacked.out1
-rw-r--r--tests/gnu/fstype_stacked.sh12
-rw-r--r--tests/gnu/fstype_umount.out0
-rw-r--r--tests/gnu/fstype_umount.sh12
-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.sh7
-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.sh14
-rw-r--r--tests/gnu/iwholename.out7
-rw-r--r--tests/gnu/iwholename.sh2
-rw-r--r--tests/gnu/newer_link.out1
-rw-r--r--tests/gnu/newer_link.sh1
-rw-r--r--tests/gnu/noleaf.out19
-rw-r--r--tests/gnu/noleaf.sh1
-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_flush.out19
-rw-r--r--tests/gnu/ok_flush.sh4
-rw-r--r--tests/gnu/ok_nothing.sh2
-rw-r--r--tests/gnu/okdir_path_dot.sh1
-rw-r--r--tests/gnu/okdir_path_empty.sh1
-rw-r--r--tests/gnu/okdir_path_relative.sh1
-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.sh8
-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.sh2
-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/used.out4
-rw-r--r--tests/gnu/used.sh40
-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.sh10
-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
209 files changed, 1221 insertions, 0 deletions
diff --git a/tests/gnu/L_delete.out b/tests/gnu/L_delete.out
new file mode 100644
index 0000000..7ed5f0d
--- /dev/null
+++ b/tests/gnu/L_delete.out
@@ -0,0 +1,2 @@
+.
+./foo
diff --git a/tests/gnu/L_delete.sh b/tests/gnu/L_delete.sh
new file mode 100644
index 0000000..0559c49
--- /dev/null
+++ b/tests/gnu/L_delete.sh
@@ -0,0 +1,8 @@
+cd "$TEST"
+mkdir foo bar
+ln -s ../foo bar/baz
+
+# Don't try to rmdir() a symlink
+invoke_bfs -L bar -delete
+
+bfs_diff .
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..55aeb33
--- /dev/null
+++ b/tests/gnu/L_loops_continue.sh
@@ -0,0 +1 @@
+! bfs_diff -L loops
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..9c3be1a
--- /dev/null
+++ b/tests/gnu/daystart.sh
@@ -0,0 +1 @@
+TZ=WAT-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..edbf18d
--- /dev/null
+++ b/tests/gnu/daystart_twice.sh
@@ -0,0 +1 @@
+TZ=WAT-1 bfs_diff basic -daystart -daystart -mtime 0
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..ff6088e
--- /dev/null
+++ b/tests/gnu/exec_flush.sh
@@ -0,0 +1,4 @@
+# I/O 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..5505f7a
--- /dev/null
+++ b/tests/gnu/exec_flush_fail.sh
@@ -0,0 +1,3 @@
+# Failure to flush streams before exec should be caught
+test -e /dev/full || skip
+! 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..443aa0d
--- /dev/null
+++ b/tests/gnu/exec_nothing.sh
@@ -0,0 +1,2 @@
+# Regression test: don't segfault on missing command
+! 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..53a50e5
--- /dev/null
+++ b/tests/gnu/exec_plus_flush_fail.sh
@@ -0,0 +1,2 @@
+test -e /dev/full || skip
+! 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_path_dot.sh b/tests/gnu/execdir_path_dot.sh
new file mode 100644
index 0000000..632dbb4
--- /dev/null
+++ b/tests/gnu/execdir_path_dot.sh
@@ -0,0 +1 @@
+! PATH=".:$PATH" invoke_bfs basic -execdir echo {} +
diff --git a/tests/gnu/execdir_path_empty.sh b/tests/gnu/execdir_path_empty.sh
new file mode 100644
index 0000000..eda6b1c
--- /dev/null
+++ b/tests/gnu/execdir_path_empty.sh
@@ -0,0 +1 @@
+! PATH=":$PATH" invoke_bfs basic -execdir echo {} +
diff --git a/tests/gnu/execdir_path_relative.sh b/tests/gnu/execdir_path_relative.sh
new file mode 100644
index 0000000..69899ad
--- /dev/null
+++ b/tests/gnu/execdir_path_relative.sh
@@ -0,0 +1 @@
+! PATH="foo:$PATH" invoke_bfs 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/execdir_ulimit.out b/tests/gnu/execdir_ulimit.out
new file mode 100644
index 0000000..6749f7d
--- /dev/null
+++ b/tests/gnu/execdir_ulimit.out
@@ -0,0 +1,16 @@
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
diff --git a/tests/gnu/execdir_ulimit.sh b/tests/gnu/execdir_ulimit.sh
new file mode 100644
index 0000000..e14e716
--- /dev/null
+++ b/tests/gnu/execdir_ulimit.sh
@@ -0,0 +1,2 @@
+ulimit -Sn 64
+bfs_diff deep -type f -execdir bash -c 'printf "%d %s\n" $(ulimit -Sn) "$1"' bash {} \;
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..85eee8f
--- /dev/null
+++ b/tests/gnu/files0_from_empty.sh
@@ -0,0 +1 @@
+! printf "\0" | invoke_bfs -files0-from -
diff --git a/tests/gnu/files0_from_error.sh b/tests/gnu/files0_from_error.sh
new file mode 100644
index 0000000..1515d0b
--- /dev/null
+++ b/tests/gnu/files0_from_error.sh
@@ -0,0 +1 @@
+! invoke_bfs -files0-from basic
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..81435a0
--- /dev/null
+++ b/tests/gnu/files0_from_file.sh
@@ -0,0 +1,4 @@
+FILE="$TMP/$TEST.in"
+cd weirdnames
+invoke_bfs -mindepth 1 -fprintf "$FILE" "%P\0"
+bfs_diff -files0-from "$FILE"
diff --git a/tests/gnu/files0_from_none.out b/tests/gnu/files0_from_none.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gnu/files0_from_none.out
diff --git a/tests/gnu/files0_from_none.sh b/tests/gnu/files0_from_none.sh
new file mode 100644
index 0000000..1633163
--- /dev/null
+++ b/tests/gnu/files0_from_none.sh
@@ -0,0 +1 @@
+printf "" | bfs_diff -files0-from -
diff --git a/tests/gnu/files0_from_nothing.sh b/tests/gnu/files0_from_nothing.sh
new file mode 100644
index 0000000..fee50a8
--- /dev/null
+++ b/tests/gnu/files0_from_nothing.sh
@@ -0,0 +1 @@
+! 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..68eea4b
--- /dev/null
+++ b/tests/gnu/files0_from_nowhere.sh
@@ -0,0 +1 @@
+! 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..8e145ce
--- /dev/null
+++ b/tests/gnu/files0_from_ok.sh
@@ -0,0 +1 @@
+! printf "basic\0" | 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..d2ff794
--- /dev/null
+++ b/tests/gnu/fls.sh
@@ -0,0 +1 @@
+invoke_bfs rainbow -fls "$OUT"
diff --git a/tests/gnu/fls_nonexistent.sh b/tests/gnu/fls_nonexistent.sh
new file mode 100644
index 0000000..2854569
--- /dev/null
+++ b/tests/gnu/fls_nonexistent.sh
@@ -0,0 +1 @@
+! invoke_bfs rainbow -fls nonexistent/path
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/fprint0_nonexistent.sh b/tests/gnu/fprint0_nonexistent.sh
new file mode 100644
index 0000000..4906081
--- /dev/null
+++ b/tests/gnu/fprint0_nonexistent.sh
@@ -0,0 +1 @@
+! invoke_bfs basic -fprint0 nonexistent/path
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..8533b05
--- /dev/null
+++ b/tests/gnu/fprint_duplicate.sh
@@ -0,0 +1,7 @@
+"$XTOUCH" -p "$TEST/foo.out"
+ln "$TEST/foo.out" "$TEST/foo.hard"
+ln -s foo.out "$TEST/foo.soft"
+
+invoke_bfs basic -fprint "$TEST/foo.out" -fprint "$TEST/foo.hard" -fprint "$TEST/foo.soft"
+sort "$TEST/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..7617034
--- /dev/null
+++ b/tests/gnu/fprint_error.sh
@@ -0,0 +1,2 @@
+test -e /dev/full || skip
+! 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..8511649
--- /dev/null
+++ b/tests/gnu/fprint_noarg.sh
@@ -0,0 +1 @@
+! invoke_bfs basic -fprint
diff --git a/tests/gnu/fprint_noerror.sh b/tests/gnu/fprint_noerror.sh
new file mode 100644
index 0000000..f13a62b
--- /dev/null
+++ b/tests/gnu/fprint_noerror.sh
@@ -0,0 +1,3 @@
+# Regression test: /dev/full should not fail until actually written to
+test -e /dev/full || skip
+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..2a403a2
--- /dev/null
+++ b/tests/gnu/fprint_nonexistent.sh
@@ -0,0 +1 @@
+! invoke_bfs basic -fprint 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..4e79002
--- /dev/null
+++ b/tests/gnu/fprintf_nofile.sh
@@ -0,0 +1 @@
+! invoke_bfs basic -fprintf
diff --git a/tests/gnu/fprintf_noformat.sh b/tests/gnu/fprintf_noformat.sh
new file mode 100644
index 0000000..fd97f4c
--- /dev/null
+++ b/tests/gnu/fprintf_noformat.sh
@@ -0,0 +1 @@
+! invoke_bfs basic -fprintf /dev/null
diff --git a/tests/gnu/fprintf_nonexistent.sh b/tests/gnu/fprintf_nonexistent.sh
new file mode 100644
index 0000000..b1eea10
--- /dev/null
+++ b/tests/gnu/fprintf_nonexistent.sh
@@ -0,0 +1 @@
+! invoke_bfs basic -fprintf nonexistent/path '%p\n'
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..05645c3
--- /dev/null
+++ b/tests/gnu/fstype.sh
@@ -0,0 +1,2 @@
+fstype=$(invoke_bfs basic -maxdepth 0 -printf '%F\n') || skip
+bfs_diff basic -fstype "$fstype"
diff --git a/tests/gnu/fstype_stacked.out b/tests/gnu/fstype_stacked.out
new file mode 100644
index 0000000..c1e0e6c
--- /dev/null
+++ b/tests/gnu/fstype_stacked.out
@@ -0,0 +1 @@
+mnt
diff --git a/tests/gnu/fstype_stacked.sh b/tests/gnu/fstype_stacked.sh
new file mode 100644
index 0000000..a9739bb
--- /dev/null
+++ b/tests/gnu/fstype_stacked.sh
@@ -0,0 +1,12 @@
+test "$UNAME" = "Linux" || skip
+
+cd "$TEST"
+mkdir mnt
+
+bfs_sudo mount -t tmpfs tmpfs mnt || skip
+defer bfs_sudo umount mnt
+
+bfs_sudo mount -t ramfs ramfs mnt || skip
+defer bfs_sudo umount mnt
+
+bfs_diff mnt -fstype ramfs -print -o -printf '%p: %F\n'
diff --git a/tests/gnu/fstype_umount.out b/tests/gnu/fstype_umount.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gnu/fstype_umount.out
diff --git a/tests/gnu/fstype_umount.sh b/tests/gnu/fstype_umount.sh
new file mode 100644
index 0000000..81c195f
--- /dev/null
+++ b/tests/gnu/fstype_umount.sh
@@ -0,0 +1,12 @@
+test "$UNAME" = "Linux" || skip
+
+cd "$TEST"
+
+mkdir tmp
+bfs_sudo mount -t tmpfs tmpfs tmp || skip
+defer bfs_sudo umount -R tmp
+
+mkdir tmp/ram
+bfs_sudo mount -t ramfs ramfs tmp/ram || skip
+
+bfs_diff tmp -path tmp -exec "${SUDO[@]}" umount tmp/ram \; , -fstype ramfs -print
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..ccba0e6
--- /dev/null
+++ b/tests/gnu/gid_plus.sh
@@ -0,0 +1,2 @@
+test "$(id -g)" -eq 0 && skip
+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..ec7ae86
--- /dev/null
+++ b/tests/gnu/gid_plus_plus.sh
@@ -0,0 +1,2 @@
+test "$(id -g)" -eq 0 && skip
+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..75165f6
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race.sh
@@ -0,0 +1,5 @@
+cd "$TEST"
+"$XTOUCH" foo bar
+
+# -links 1 forces a stat() call, which will fail for the second file
+invoke_bfs . -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..12e9fe6
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race_notdir.sh
@@ -0,0 +1,7 @@
+# Check -ignore_readdir_race handling when a directory is replaced with a file
+cd "$TEST"
+mkdir foo
+
+invoke_bfs . -mindepth 1 -ignore_readdir_race \
+ -type d -execdir rmdir {} \; \
+ -execdir "$XTOUCH" {} \;
diff --git a/tests/gnu/ignore_readdir_race_root.sh b/tests/gnu/ignore_readdir_race_root.sh
new file mode 100644
index 0000000..dc41e7f
--- /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
+! 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..3378e2d
--- /dev/null
+++ b/tests/gnu/inum_automount.out
@@ -0,0 +1 @@
+./automnt
diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh
new file mode 100644
index 0000000..86b23e1
--- /dev/null
+++ b/tests/gnu/inum_automount.sh
@@ -0,0 +1,14 @@
+# bfs shouldn't trigger automounts unless it descends into them
+
+command -v systemd-mount &>/dev/null || skip
+
+cd "$TEST"
+mkdir foo automnt
+
+bfs_sudo systemd-mount -A -o bind "$TMP/basic" automnt || skip
+defer bfs_sudo systemd-umount automnt
+
+before=$(inum automnt)
+bfs_diff . -inum "$before" -prune
+after=$(inum automnt)
+((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..0b2d038
--- /dev/null
+++ b/tests/gnu/iwholename.sh
@@ -0,0 +1,2 @@
+invoke_bfs -quit -iwholename PATTERN || skip
+bfs_diff basic -iwholename 'basic/*F*'
diff --git a/tests/gnu/newer_link.out b/tests/gnu/newer_link.out
new file mode 100644
index 0000000..d2dcdd1
--- /dev/null
+++ b/tests/gnu/newer_link.out
@@ -0,0 +1 @@
+times
diff --git a/tests/gnu/newer_link.sh b/tests/gnu/newer_link.sh
new file mode 100644
index 0000000..685ac78
--- /dev/null
+++ b/tests/gnu/newer_link.sh
@@ -0,0 +1 @@
+bfs_diff times -newer times/l
diff --git a/tests/gnu/noleaf.out b/tests/gnu/noleaf.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/gnu/noleaf.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/noleaf.sh b/tests/gnu/noleaf.sh
new file mode 100644
index 0000000..b19438c
--- /dev/null
+++ b/tests/gnu/noleaf.sh
@@ -0,0 +1 @@
+bfs_diff basic -noleaf
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_flush.out b/tests/gnu/ok_flush.out
new file mode 100644
index 0000000..6731408
--- /dev/null
+++ b/tests/gnu/ok_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/ok_flush.sh b/tests/gnu/ok_flush.sh
new file mode 100644
index 0000000..87c7298
--- /dev/null
+++ b/tests/gnu/ok_flush.sh
@@ -0,0 +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"
+sort_output
+diff_output
diff --git a/tests/gnu/ok_nothing.sh b/tests/gnu/ok_nothing.sh
new file mode 100644
index 0000000..52c3547
--- /dev/null
+++ b/tests/gnu/ok_nothing.sh
@@ -0,0 +1,2 @@
+# Regression test: don't segfault on missing command
+! invoke_bfs basic -ok \;
diff --git a/tests/gnu/okdir_path_dot.sh b/tests/gnu/okdir_path_dot.sh
new file mode 100644
index 0000000..5b40e27
--- /dev/null
+++ b/tests/gnu/okdir_path_dot.sh
@@ -0,0 +1 @@
+! PATH=".:$PATH" invoke_bfs basic -okdir echo {} \;
diff --git a/tests/gnu/okdir_path_empty.sh b/tests/gnu/okdir_path_empty.sh
new file mode 100644
index 0000000..2669ee8
--- /dev/null
+++ b/tests/gnu/okdir_path_empty.sh
@@ -0,0 +1 @@
+! PATH=":$PATH" invoke_bfs basic -okdir echo {} \;
diff --git a/tests/gnu/okdir_path_relative.sh b/tests/gnu/okdir_path_relative.sh
new file mode 100644
index 0000000..05100a1
--- /dev/null
+++ b/tests/gnu/okdir_path_relative.sh
@@ -0,0 +1 @@
+! PATH="foo:$PATH" invoke_bfs basic -okdir echo {} \;
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..bc79637
--- /dev/null
+++ b/tests/gnu/print_error.sh
@@ -0,0 +1,2 @@
+test -e /dev/full || skip
+! 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..1dd554e
--- /dev/null
+++ b/tests/gnu/printf_Y_error.out
@@ -0,0 +1,3 @@
+(.) () d d
+(./bar) (foo/bar) l ?
+(./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..d3130ce
--- /dev/null
+++ b/tests/gnu/printf_Y_error.sh
@@ -0,0 +1,8 @@
+cd "$TEST"
+mkdir foo
+ln -s foo/bar bar
+
+chmod -x foo
+defer chmod +x foo
+
+! bfs_diff . -printf '(%p) (%l) %y %Y\n'
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..e4f5155
--- /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 %A@ | %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..c621b9b
--- /dev/null
+++ b/tests/gnu/printf_u_g_ulimit.sh
@@ -0,0 +1,2 @@
+ulimit -n $((NOPENFD + 13))
+[ "$(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..4af933f
--- /dev/null
+++ b/tests/gnu/regex_error.sh
@@ -0,0 +1 @@
+! 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..a133b1a
--- /dev/null
+++ b/tests/gnu/regex_invalid_utf8.out
@@ -0,0 +1 @@
+./â„
diff --git a/tests/gnu/regex_invalid_utf8.sh b/tests/gnu/regex_invalid_utf8.sh
new file mode 100644
index 0000000..7006dcd
--- /dev/null
+++ b/tests/gnu/regex_invalid_utf8.sh
@@ -0,0 +1,8 @@
+cd "$TEST"
+
+# Incomplete UTF-8 sequences
+touch $'\xC3' || skip
+touch $'\xE2\x84' || skip
+touch $'\xF0\x9F\x92' || skip
+
+bfs_diff . -regex '\./..'
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..3cc388c
--- /dev/null
+++ b/tests/gnu/regextype_emacs.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype emacs -quit || skip
+
+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..0830667
--- /dev/null
+++ b/tests/gnu/regextype_grep.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype grep -quit || skip
+
+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..22b2c8e
--- /dev/null
+++ b/tests/gnu/uid_plus.sh
@@ -0,0 +1,2 @@
+test "$(id -u)" -eq 0 && skip
+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..e021888
--- /dev/null
+++ b/tests/gnu/uid_plus_plus.sh
@@ -0,0 +1,2 @@
+test "$(id -u)" -eq 0 && skip
+bfs_diff basic -uid ++0
diff --git a/tests/gnu/used.out b/tests/gnu/used.out
new file mode 100644
index 0000000..647621b
--- /dev/null
+++ b/tests/gnu/used.out
@@ -0,0 +1,4 @@
+-used +7: ./nextyear
+-used 1: ./tomorrow
+-used 2: ./dayafter
+-used 7: ./nextweek
diff --git a/tests/gnu/used.sh b/tests/gnu/used.sh
new file mode 100644
index 0000000..5e5d4e9
--- /dev/null
+++ b/tests/gnu/used.sh
@@ -0,0 +1,40 @@
+iso8601() {
+ printf '%04d-%02d-%02dT%02d:%02d:%02d\n' "$@"
+}
+
+cd "$TEST"
+
+now=$(date '+%Y-%m-%dT%H:%M:%S')
+
+# Parse the current date
+[[ "$now" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})$ ]] || fail
+# Treat leading zeros as decimal, not octal
+YYYY=$((10#${BASH_REMATCH[1]}))
+MM=$((10#${BASH_REMATCH[2]}))
+DD=$((10#${BASH_REMATCH[3]}))
+hh=$((10#${BASH_REMATCH[4]}))
+mm=$((10#${BASH_REMATCH[5]}))
+ss=$((10#${BASH_REMATCH[6]}))
+
+# -used is always false if atime < ctime
+yesterday=$(iso8601 $YYYY $MM $((DD - 1)) $hh $mm $ss)
+"$XTOUCH" -at "$yesterday" yesterday
+
+# -used rounds up
+tomorrow=$(iso8601 $YYYY $MM $DD $((hh + 1)) $mm $ss)
+"$XTOUCH" -at "$tomorrow" tomorrow
+
+dayafter=$(iso8601 $YYYY $MM $((DD + 1)) $((hh + 1)) $mm $ss)
+"$XTOUCH" -at "$dayafter" dayafter
+
+nextweek=$(iso8601 $YYYY $MM $((DD + 6)) $((hh + 1)) $mm $ss)
+"$XTOUCH" -at "$nextweek" nextweek
+
+nextyear=$(iso8601 $((YYYY + 1)) $MM $DD $hh $mm $ss)
+"$XTOUCH" -at "$nextyear" nextyear
+
+bfs_diff -mindepth 1 \
+ -a -used 1 -printf '-used 1: %p\n' \
+ -o -used 2 -printf '-used 2: %p\n' \
+ -o -used 7 -printf '-used 7: %p\n' \
+ -o -used +7 -printf '-used +7: %p\n'
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..d18d706
--- /dev/null
+++ b/tests/gnu/xtype_bind_mount.out
@@ -0,0 +1,2 @@
+./link
+./null
diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh
new file mode 100644
index 0000000..35fb3f5
--- /dev/null
+++ b/tests/gnu/xtype_bind_mount.sh
@@ -0,0 +1,10 @@
+test "$UNAME" = "Linux" || skip
+
+cd "$TEST"
+"$XTOUCH" file null
+ln -s /dev/null link
+
+bfs_sudo mount --bind /dev/null null || skip
+defer bfs_sudo umount null
+
+bfs_diff . -xtype c
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