summaryrefslogtreecommitdiffstats
path: root/tests/posix
diff options
context:
space:
mode:
Diffstat (limited to 'tests/posix')
-rw-r--r--tests/posix/HL.out17
-rw-r--r--tests/posix/HL.sh1
-rw-r--r--tests/posix/LH.out1
-rw-r--r--tests/posix/LH.sh1
-rw-r--r--tests/posix/L_loops.sh4
-rw-r--r--tests/posix/L_mount.out2
-rw-r--r--tests/posix/L_mount.sh13
-rw-r--r--tests/posix/L_xdev.out10
-rw-r--r--tests/posix/L_xdev.sh16
-rw-r--r--tests/posix/atime.out6
-rw-r--r--tests/posix/atime.sh15
-rw-r--r--tests/posix/closed_stderr.sh1
-rw-r--r--tests/posix/closed_stdin.sh1
-rw-r--r--tests/posix/closed_stdout.sh1
-rw-r--r--tests/posix/deep.sh4
-rw-r--r--tests/posix/depth_error.out6
-rw-r--r--tests/posix/depth_error.sh8
-rw-r--r--tests/posix/exec_nonexistent.sh7
-rw-r--r--tests/posix/exec_plus_nonexistent.sh6
-rw-r--r--tests/posix/exec_return.out18
-rw-r--r--tests/posix/exec_return.sh1
-rw-r--r--tests/posix/exec_sigmask.out1
-rw-r--r--tests/posix/exec_sigmask.sh16
-rw-r--r--tests/posix/exec_substring_plus.out (renamed from tests/posix/closed_stdin.out)0
-rw-r--r--tests/posix/exec_substring_plus.sh14
-rw-r--r--tests/posix/exec_ulimit.out16
-rw-r--r--tests/posix/exec_ulimit.sh2
-rw-r--r--tests/posix/group_invalid_id.sh1
-rw-r--r--tests/posix/group_invalid_name.sh1
-rw-r--r--tests/posix/iname.out4
-rw-r--r--tests/posix/iname.sh1
-rw-r--r--tests/posix/mount.out3
-rw-r--r--tests/posix/mount.sh11
-rw-r--r--tests/posix/mtime.out6
-rw-r--r--tests/posix/mtime.sh15
-rw-r--r--tests/posix/name_bracket.sh8
-rw-r--r--tests/posix/name_slash.out1
-rw-r--r--tests/posix/name_slash.sh1
-rw-r--r--tests/posix/name_slashes.out1
-rw-r--r--tests/posix/name_slashes.sh1
-rw-r--r--tests/posix/newer_broken.out (renamed from tests/posix/newer_link.out)0
-rw-r--r--tests/posix/newer_broken.sh4
-rw-r--r--tests/posix/newer_link.sh1
-rw-r--r--tests/posix/nogroup_ulimit.sh10
-rw-r--r--tests/posix/nouser_ulimit.sh9
-rw-r--r--tests/posix/overlayfs.out5
-rw-r--r--tests/posix/overlayfs.sh11
-rw-r--r--tests/posix/perm_000.out2
-rw-r--r--tests/posix/perm_000_minus.out35
-rw-r--r--tests/posix/perm_222.out2
-rw-r--r--tests/posix/perm_222_minus.out6
-rw-r--r--tests/posix/perm_644.out2
-rw-r--r--tests/posix/perm_644_minus.out11
-rw-r--r--tests/posix/perm_leading_plus_symbolic_minus.out7
-rw-r--r--tests/posix/perm_leading_plus_umask.out10
-rw-r--r--tests/posix/perm_leading_plus_umask.sh3
-rw-r--r--tests/posix/perm_symbolic_minus.out11
-rw-r--r--tests/posix/permcopy.out2
-rw-r--r--tests/posix/print0.outbin0 -> 16 bytes
-rw-r--r--tests/posix/print0.sh2
-rw-r--r--tests/posix/prune_error.out1
-rw-r--r--tests/posix/prune_error.sh1
-rw-r--r--tests/posix/readdir_error.sh20
-rw-r--r--tests/posix/type_bind_mount.out2
-rw-r--r--tests/posix/type_bind_mount.sh10
-rw-r--r--tests/posix/unionfs.out10
-rw-r--r--tests/posix/unionfs.sh9
-rw-r--r--tests/posix/user_invalid_id.sh1
-rw-r--r--tests/posix/user_invalid_name.sh1
-rw-r--r--tests/posix/xdev.out8
-rw-r--r--tests/posix/xdev.sh12
71 files changed, 351 insertions, 99 deletions
diff --git a/tests/posix/HL.out b/tests/posix/HL.out
new file mode 100644
index 0000000..ec9e861
--- /dev/null
+++ b/tests/posix/HL.out
@@ -0,0 +1,17 @@
+links
+links/broken
+links/deeply
+links/deeply/nested
+links/deeply/nested/broken
+links/deeply/nested/dir
+links/deeply/nested/file
+links/deeply/nested/link
+links/file
+links/hardlink
+links/notdir
+links/skip
+links/skip/broken
+links/skip/dir
+links/skip/file
+links/skip/link
+links/symlink
diff --git a/tests/posix/HL.sh b/tests/posix/HL.sh
new file mode 100644
index 0000000..1858982
--- /dev/null
+++ b/tests/posix/HL.sh
@@ -0,0 +1 @@
+bfs_diff -HL links
diff --git a/tests/posix/LH.out b/tests/posix/LH.out
new file mode 100644
index 0000000..ff635ff
--- /dev/null
+++ b/tests/posix/LH.out
@@ -0,0 +1 @@
+links/deeply/nested/dir
diff --git a/tests/posix/LH.sh b/tests/posix/LH.sh
new file mode 100644
index 0000000..ef1d980
--- /dev/null
+++ b/tests/posix/LH.sh
@@ -0,0 +1 @@
+bfs_diff -LH links/deeply/nested/dir
diff --git a/tests/posix/L_loops.sh b/tests/posix/L_loops.sh
index 1314401..01b7efc 100644
--- a/tests/posix/L_loops.sh
+++ b/tests/posix/L_loops.sh
@@ -1,4 +1,4 @@
# POSIX says it's okay to either stop or keep going on seeing a filesystem
# loop, as long as a diagnostic is printed
-! errors=$(invoke_bfs -L loops 2>&1 >/dev/null)
-[ -n "$errors" ]
+invoke_bfs -L loops >/dev/null 2>"$OUT" && fail
+test -s "$OUT"
diff --git a/tests/posix/L_mount.out b/tests/posix/L_mount.out
new file mode 100644
index 0000000..7ed5f0d
--- /dev/null
+++ b/tests/posix/L_mount.out
@@ -0,0 +1,2 @@
+.
+./foo
diff --git a/tests/posix/L_mount.sh b/tests/posix/L_mount.sh
new file mode 100644
index 0000000..fd8042a
--- /dev/null
+++ b/tests/posix/L_mount.sh
@@ -0,0 +1,13 @@
+test "$UNAME" = "Darwin" && skip
+
+cd "$TEST"
+mkdir foo mnt
+
+bfs_sudo mount -t tmpfs tmpfs mnt || skip
+defer bfs_sudo umount mnt
+
+ln -s ../mnt foo/bar
+"$XTOUCH" mnt/baz
+ln -s ../mnt/baz foo/qux
+
+bfs_diff -L . -mount
diff --git a/tests/posix/L_xdev.out b/tests/posix/L_xdev.out
index 2e80082..788579d 100644
--- a/tests/posix/L_xdev.out
+++ b/tests/posix/L_xdev.out
@@ -1,5 +1,5 @@
-scratch
-scratch/foo
-scratch/foo/bar
-scratch/foo/qux
-scratch/mnt
+.
+./foo
+./foo/bar
+./foo/qux
+./mnt
diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh
index 172ea23..82d8605 100644
--- a/tests/posix/L_xdev.sh
+++ b/tests/posix/L_xdev.sh
@@ -1,13 +1,13 @@
test "$UNAME" = "Darwin" && skip
-clean_scratch
-mkdir scratch/{foo,mnt}
+cd "$TEST"
+mkdir foo mnt
-bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip
-trap "bfs_sudo umount scratch/mnt" EXIT
+bfs_sudo mount -t tmpfs tmpfs mnt || skip
+defer bfs_sudo umount mnt
-ln -s ../mnt scratch/foo/bar
-"$XTOUCH" scratch/mnt/baz
-ln -s ../mnt/baz scratch/foo/qux
+ln -s ../mnt foo/bar
+"$XTOUCH" mnt/baz
+ln -s ../mnt/baz foo/qux
-bfs_diff -L scratch -xdev
+bfs_diff -L . -xdev
diff --git a/tests/posix/atime.out b/tests/posix/atime.out
new file mode 100644
index 0000000..5ed206b
--- /dev/null
+++ b/tests/posix/atime.out
@@ -0,0 +1,6 @@
+-atime 1: ./yesterday
+-atime +1: ./last_week
+-atime +1: ./two_days_ago
+-atime -1: ./now
+-atime -1: ./one_hour_ago
+-atime -1: ./tomorrow
diff --git a/tests/posix/atime.sh b/tests/posix/atime.sh
new file mode 100644
index 0000000..25dfd7e
--- /dev/null
+++ b/tests/posix/atime.sh
@@ -0,0 +1,15 @@
+cd "$TEST"
+
+now=$(epoch_time)
+
+"$XTOUCH" -at "@$((now - 60 * 60 * 24 * 7))" last_week
+"$XTOUCH" -at "@$((now - 60 * 60 * 49))" two_days_ago
+"$XTOUCH" -at "@$((now - 60 * 60 * 25))" yesterday
+"$XTOUCH" -at "@$((now - 60 * 60))" one_hour_ago
+"$XTOUCH" -at "@$((now))" now
+"$XTOUCH" -at "@$((now + 60 * 60 * 24))" tomorrow
+
+bfs_diff . \! -name . \
+ \( -atime -1 -exec printf -- '-atime -1: %s\n' {} \; -o -prune \) \
+ \( -atime 1 -exec printf -- '-atime 1: %s\n' {} \; -o -prune \) \
+ \( -atime +1 -exec printf -- '-atime +1: %s\n' {} \; -o -prune \)
diff --git a/tests/posix/closed_stderr.sh b/tests/posix/closed_stderr.sh
deleted file mode 100644
index 570d5bb..0000000
--- a/tests/posix/closed_stderr.sh
+++ /dev/null
@@ -1 +0,0 @@
-! invoke_bfs basic >&- 2>&-
diff --git a/tests/posix/closed_stdin.sh b/tests/posix/closed_stdin.sh
deleted file mode 100644
index 6932be8..0000000
--- a/tests/posix/closed_stdin.sh
+++ /dev/null
@@ -1 +0,0 @@
-bfs_diff basic <&-
diff --git a/tests/posix/closed_stdout.sh b/tests/posix/closed_stdout.sh
deleted file mode 100644
index 25c060d..0000000
--- a/tests/posix/closed_stdout.sh
+++ /dev/null
@@ -1 +0,0 @@
-! invoke_bfs basic >&-
diff --git a/tests/posix/deep.sh b/tests/posix/deep.sh
index 3d1cd60..36a88c0 100644
--- a/tests/posix/deep.sh
+++ b/tests/posix/deep.sh
@@ -1,4 +1,2 @@
-closefrom 4
-
-ulimit -n 16
+ulimit -n $((NOPENFD + 13))
bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash {} \;
diff --git a/tests/posix/depth_error.out b/tests/posix/depth_error.out
index ed0e9a1..c4f8ce4 100644
--- a/tests/posix/depth_error.out
+++ b/tests/posix/depth_error.out
@@ -1,2 +1,4 @@
-scratch
-scratch/foo
+inaccessible
+inaccessible/dir
+inaccessible/file
+inaccessible/link
diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh
index e91fbf6..9b29385 100644
--- a/tests/posix/depth_error.sh
+++ b/tests/posix/depth_error.sh
@@ -1,7 +1 @@
-clean_scratch
-"$XTOUCH" -p scratch/foo/bar
-
-chmod a-r scratch/foo
-trap "chmod +r scratch/foo" EXIT
-
-! bfs_diff scratch -depth
+! bfs_diff inaccessible -depth
diff --git a/tests/posix/exec_nonexistent.sh b/tests/posix/exec_nonexistent.sh
index 901be86..a9ff052 100644
--- a/tests/posix/exec_nonexistent.sh
+++ b/tests/posix/exec_nonexistent.sh
@@ -1,7 +1,4 @@
# Failure to execute the command should lead to an error message and
# non-zero exit status. See https://unix.stackexchange.com/q/704522/56202
-
-! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} \; 2>&1 >/dev/null)
-[ -n "$stderr" ]
-
-! bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print
+bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print 2>"$TEST/err" && fail
+test -s "$TEST/err"
diff --git a/tests/posix/exec_plus_nonexistent.sh b/tests/posix/exec_plus_nonexistent.sh
index 6bddc67..24582a3 100644
--- a/tests/posix/exec_plus_nonexistent.sh
+++ b/tests/posix/exec_plus_nonexistent.sh
@@ -1,4 +1,2 @@
-! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} + 2>&1 >/dev/null)
-[ -n "$stderr" ]
-
-! bfs_diff basic -exec "$TESTS/nonexistent" {} + -print
+bfs_diff basic -exec "$TESTS/nonexistent" {} + -print 2>"$TEST/err" && fail
+test -s "$TEST/err"
diff --git a/tests/posix/exec_return.out b/tests/posix/exec_return.out
new file mode 100644
index 0000000..600c93a
--- /dev/null
+++ b/tests/posix/exec_return.out
@@ -0,0 +1,18 @@
+basic
+basic/a
+basic/b
+basic/c/d
+basic/e
+basic/e/f
+basic/g
+basic/g/h
+basic/i
+basic/j
+basic/j/foo
+basic/k
+basic/k/foo
+basic/k/foo/bar
+basic/l
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar/baz
diff --git a/tests/posix/exec_return.sh b/tests/posix/exec_return.sh
new file mode 100644
index 0000000..cfa0f5d
--- /dev/null
+++ b/tests/posix/exec_return.sh
@@ -0,0 +1 @@
+bfs_diff basic -exec test {} = basic/c \; -o -print
diff --git a/tests/posix/exec_sigmask.out b/tests/posix/exec_sigmask.out
new file mode 100644
index 0000000..bb646f3
--- /dev/null
+++ b/tests/posix/exec_sigmask.out
@@ -0,0 +1 @@
+SigBlk: 0000000000000000
diff --git a/tests/posix/exec_sigmask.sh b/tests/posix/exec_sigmask.sh
new file mode 100644
index 0000000..d1192a4
--- /dev/null
+++ b/tests/posix/exec_sigmask.sh
@@ -0,0 +1,16 @@
+# Regression test: restore the signal mask after fork()
+
+cd "$TEST"
+mkfifo p1 p2
+
+{
+ # Get the PID of `sh`
+ read -r pid <p1
+ # Send SIGTERM -- this will hang forever if signals are blocked
+ kill $pid
+} &
+
+# Write the `sh` PID to p1, then hang reading p2 until we're killed
+! invoke_bfs p1 -exec sh -c 'echo $$ >p1 && read -r _ <p2' {} + || fail
+
+wait
diff --git a/tests/posix/closed_stdin.out b/tests/posix/exec_substring_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/posix/closed_stdin.out
+++ b/tests/posix/exec_substring_plus.out
diff --git a/tests/posix/exec_substring_plus.sh b/tests/posix/exec_substring_plus.sh
new file mode 100644
index 0000000..90309b0
--- /dev/null
+++ b/tests/posix/exec_substring_plus.sh
@@ -0,0 +1,14 @@
+# https://pubs.opengroup.org/onlinepubs/9799919799/utilities/find.html
+#
+# Only a <plus-sign> that immediately follows an argument containing only
+# the two characters "{}" shall punctuate the end of the primary expression.
+# Other uses of the <plus-sign> shall not be treated as special.
+# ...
+# If a utility_name or argument string contains the two characters "{}", but
+# not just the two characters "{}", it is implementation-defined whether
+# find replaces those two characters or uses the string without change.
+
+invoke_bfs basic -exec printf '%s %s %s %s\n' {} {}+ +{} + \; | sed 's/ .*//' >"$OUT"
+sort_output
+diff_output
+
diff --git a/tests/posix/exec_ulimit.out b/tests/posix/exec_ulimit.out
new file mode 100644
index 0000000..144169e
--- /dev/null
+++ b/tests/posix/exec_ulimit.out
@@ -0,0 +1,16 @@
+64 deep/0/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/1/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/2/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/3/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/4/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/5/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/6/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/7/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/8/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/9/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/A/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/B/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/C/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/D/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/E/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
+64 deep/F/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE
diff --git a/tests/posix/exec_ulimit.sh b/tests/posix/exec_ulimit.sh
new file mode 100644
index 0000000..655fbec
--- /dev/null
+++ b/tests/posix/exec_ulimit.sh
@@ -0,0 +1,2 @@
+ulimit -Sn 64
+bfs_diff deep -type f -exec bash -c 'printf "%d %s\n" $(ulimit -Sn) "${1:0:6}/.../${1##*/}"' bash {} \;
diff --git a/tests/posix/group_invalid_id.sh b/tests/posix/group_invalid_id.sh
new file mode 100644
index 0000000..1a89747
--- /dev/null
+++ b/tests/posix/group_invalid_id.sh
@@ -0,0 +1 @@
+! invoke_bfs -group 1eW6f5RM9Qi
diff --git a/tests/posix/group_invalid_name.sh b/tests/posix/group_invalid_name.sh
new file mode 100644
index 0000000..a08dc72
--- /dev/null
+++ b/tests/posix/group_invalid_name.sh
@@ -0,0 +1 @@
+! invoke_bfs -group eW6f5RM9Qi
diff --git a/tests/posix/iname.out b/tests/posix/iname.out
new file mode 100644
index 0000000..a9e5d42
--- /dev/null
+++ b/tests/posix/iname.out
@@ -0,0 +1,4 @@
+basic/e/f
+basic/j/foo
+basic/k/foo
+basic/l/foo
diff --git a/tests/posix/iname.sh b/tests/posix/iname.sh
new file mode 100644
index 0000000..a9297ac
--- /dev/null
+++ b/tests/posix/iname.sh
@@ -0,0 +1 @@
+bfs_diff basic -iname '*F*'
diff --git a/tests/posix/mount.out b/tests/posix/mount.out
new file mode 100644
index 0000000..b0ad937
--- /dev/null
+++ b/tests/posix/mount.out
@@ -0,0 +1,3 @@
+.
+./foo
+./foo/bar
diff --git a/tests/posix/mount.sh b/tests/posix/mount.sh
new file mode 100644
index 0000000..c9abde5
--- /dev/null
+++ b/tests/posix/mount.sh
@@ -0,0 +1,11 @@
+test "$UNAME" = "Darwin" && skip
+
+cd "$TEST"
+mkdir foo mnt
+
+bfs_sudo mount -t tmpfs tmpfs mnt || skip
+defer bfs_sudo umount mnt
+
+"$XTOUCH" foo/bar mnt/baz
+
+bfs_diff . -mount
diff --git a/tests/posix/mtime.out b/tests/posix/mtime.out
new file mode 100644
index 0000000..91f0114
--- /dev/null
+++ b/tests/posix/mtime.out
@@ -0,0 +1,6 @@
+-mtime 1: ./yesterday
+-mtime +1: ./last_week
+-mtime +1: ./two_days_ago
+-mtime -1: ./now
+-mtime -1: ./one_hour_ago
+-mtime -1: ./tomorrow
diff --git a/tests/posix/mtime.sh b/tests/posix/mtime.sh
new file mode 100644
index 0000000..8367631
--- /dev/null
+++ b/tests/posix/mtime.sh
@@ -0,0 +1,15 @@
+cd "$TEST"
+
+now=$(epoch_time)
+
+"$XTOUCH" -mt "@$((now - 60 * 60 * 24 * 7))" last_week
+"$XTOUCH" -mt "@$((now - 60 * 60 * 49))" two_days_ago
+"$XTOUCH" -mt "@$((now - 60 * 60 * 25))" yesterday
+"$XTOUCH" -mt "@$((now - 60 * 60))" one_hour_ago
+"$XTOUCH" -mt "@$((now))" now
+"$XTOUCH" -mt "@$((now + 60 * 60 * 24))" tomorrow
+
+bfs_diff . \! -name . \
+ \( -mtime -1 -exec printf -- '-mtime -1: %s\n' {} \; -o -prune \) \
+ \( -mtime 1 -exec printf -- '-mtime 1: %s\n' {} \; -o -prune \) \
+ \( -mtime +1 -exec printf -- '-mtime +1: %s\n' {} \; -o -prune \)
diff --git a/tests/posix/name_bracket.sh b/tests/posix/name_bracket.sh
index 80ca186..e2f943d 100644
--- a/tests/posix/name_bracket.sh
+++ b/tests/posix/name_bracket.sh
@@ -1,5 +1,9 @@
-# fnmatch() is broken on macOS
-test "$UNAME" = "Darwin" && skip
+# fnmatch() is broken on some platforms
+case "$UNAME" in
+ Darwin|NetBSD)
+ skip
+ ;;
+esac
# An unclosed [ should be matched literally
bfs_diff weirdnames -name '['
diff --git a/tests/posix/name_slash.out b/tests/posix/name_slash.out
new file mode 100644
index 0000000..b498fd4
--- /dev/null
+++ b/tests/posix/name_slash.out
@@ -0,0 +1 @@
+/
diff --git a/tests/posix/name_slash.sh b/tests/posix/name_slash.sh
new file mode 100644
index 0000000..b42b145
--- /dev/null
+++ b/tests/posix/name_slash.sh
@@ -0,0 +1 @@
+bfs_diff / -prune -name /
diff --git a/tests/posix/name_slashes.out b/tests/posix/name_slashes.out
new file mode 100644
index 0000000..187b81f
--- /dev/null
+++ b/tests/posix/name_slashes.out
@@ -0,0 +1 @@
+///
diff --git a/tests/posix/name_slashes.sh b/tests/posix/name_slashes.sh
new file mode 100644
index 0000000..45a39d3
--- /dev/null
+++ b/tests/posix/name_slashes.sh
@@ -0,0 +1 @@
+bfs_diff /// -prune -name /
diff --git a/tests/posix/newer_link.out b/tests/posix/newer_broken.out
index d2dcdd1..d2dcdd1 100644
--- a/tests/posix/newer_link.out
+++ b/tests/posix/newer_broken.out
diff --git a/tests/posix/newer_broken.sh b/tests/posix/newer_broken.sh
new file mode 100644
index 0000000..dccaa73
--- /dev/null
+++ b/tests/posix/newer_broken.sh
@@ -0,0 +1,4 @@
+ln -s nowhere "$TEST/broken"
+"$XTOUCH" -h -t "1991-12-14 00:03" "$TEST/broken"
+
+bfs_diff times -newer "$TEST/broken"
diff --git a/tests/posix/newer_link.sh b/tests/posix/newer_link.sh
deleted file mode 100644
index 685ac78..0000000
--- a/tests/posix/newer_link.sh
+++ /dev/null
@@ -1 +0,0 @@
-bfs_diff times -newer times/l
diff --git a/tests/posix/nogroup_ulimit.sh b/tests/posix/nogroup_ulimit.sh
index 8f758c4..a39dd1f 100644
--- a/tests/posix/nogroup_ulimit.sh
+++ b/tests/posix/nogroup_ulimit.sh
@@ -1,8 +1,2 @@
-closefrom 4
-ulimit -n 16
-
-# -mindepth 18, but POSIX
-path="*/*/*/*/*/*"
-path="$path/$path/$path"
-bfs_diff deep -path "deep/$path" -nogroup
-
+ulimit -n $((NOPENFD + 13))
+bfs_diff deep -type f -nogroup
diff --git a/tests/posix/nouser_ulimit.sh b/tests/posix/nouser_ulimit.sh
index 2777589..a94b8c5 100644
--- a/tests/posix/nouser_ulimit.sh
+++ b/tests/posix/nouser_ulimit.sh
@@ -1,7 +1,2 @@
-closefrom 4
-ulimit -n 16
-
-# -mindepth 18, but POSIX
-path="*/*/*/*/*/*"
-path="$path/$path/$path"
-bfs_diff deep -path "deep/$path" -nouser
+ulimit -n $((NOPENFD + 13))
+bfs_diff deep -type f -nouser
diff --git a/tests/posix/overlayfs.out b/tests/posix/overlayfs.out
new file mode 100644
index 0000000..b472b56
--- /dev/null
+++ b/tests/posix/overlayfs.out
@@ -0,0 +1,5 @@
+merged
+merged/bar
+merged/baz
+merged/baz/qux
+merged/foo
diff --git a/tests/posix/overlayfs.sh b/tests/posix/overlayfs.sh
new file mode 100644
index 0000000..21ef22f
--- /dev/null
+++ b/tests/posix/overlayfs.sh
@@ -0,0 +1,11 @@
+test "$UNAME" = "Linux" || skip
+
+cd "$TEST"
+"$XTOUCH" -p lower/{foo,bar,baz} upper/{bar,baz/qux}
+
+mkdir -p work merged
+bfs_sudo mount -t overlay overlay -olowerdir=lower,upperdir=upper,workdir=work merged || skip
+defer bfs_sudo rm -rf work
+defer bfs_sudo umount merged
+
+bfs_diff merged
diff --git a/tests/posix/perm_000.out b/tests/posix/perm_000.out
index 5fd30bc..9df7f46 100644
--- a/tests/posix/perm_000.out
+++ b/tests/posix/perm_000.out
@@ -1 +1 @@
-perms/0
+perms/f---------
diff --git a/tests/posix/perm_000_minus.out b/tests/posix/perm_000_minus.out
index d7494b8..e279684 100644
--- a/tests/posix/perm_000_minus.out
+++ b/tests/posix/perm_000_minus.out
@@ -1,8 +1,29 @@
perms
-perms/0
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f---------
+perms/f--x------
+perms/f--x--x--x
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_222.out b/tests/posix/perm_222.out
index 1690e43..bdc5590 100644
--- a/tests/posix/perm_222.out
+++ b/tests/posix/perm_222.out
@@ -1 +1 @@
-perms/w
+perms/f-w--w--w-
diff --git a/tests/posix/perm_222_minus.out b/tests/posix/perm_222_minus.out
index 1690e43..342b285 100644
--- a/tests/posix/perm_222_minus.out
+++ b/tests/posix/perm_222_minus.out
@@ -1 +1,5 @@
-perms/w
+perms/drwxrwxrwx
+perms/f-w--w--w-
+perms/f-wx-wx-wx
+perms/frw-rw-rw-
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_644.out b/tests/posix/perm_644.out
index 4e64e49..9f77ce6 100644
--- a/tests/posix/perm_644.out
+++ b/tests/posix/perm_644.out
@@ -1 +1 @@
-perms/rw
+perms/frw-r--r--
diff --git a/tests/posix/perm_644_minus.out b/tests/posix/perm_644_minus.out
index 2e2576b..84f69f5 100644
--- a/tests/posix/perm_644_minus.out
+++ b/tests/posix/perm_644_minus.out
@@ -1,3 +1,10 @@
perms
-perms/rw
-perms/rwx
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_leading_plus_symbolic_minus.out b/tests/posix/perm_leading_plus_symbolic_minus.out
index e69de29..38d0e1c 100644
--- a/tests/posix/perm_leading_plus_symbolic_minus.out
+++ b/tests/posix/perm_leading_plus_symbolic_minus.out
@@ -0,0 +1,7 @@
+perms
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_leading_plus_umask.out b/tests/posix/perm_leading_plus_umask.out
new file mode 100644
index 0000000..6ed4b7f
--- /dev/null
+++ b/tests/posix/perm_leading_plus_umask.out
@@ -0,0 +1,10 @@
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_leading_plus_umask.sh b/tests/posix/perm_leading_plus_umask.sh
new file mode 100644
index 0000000..948b4ad
--- /dev/null
+++ b/tests/posix/perm_leading_plus_umask.sh
@@ -0,0 +1,3 @@
+# Test for https://www.austingroupbugs.net/view.php?id=1392
+umask 002
+bfs_diff perms -perm -+w
diff --git a/tests/posix/perm_symbolic_minus.out b/tests/posix/perm_symbolic_minus.out
index 2e2576b..84f69f5 100644
--- a/tests/posix/perm_symbolic_minus.out
+++ b/tests/posix/perm_symbolic_minus.out
@@ -1,3 +1,10 @@
perms
-perms/rw
-perms/rwx
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/permcopy.out b/tests/posix/permcopy.out
index 4e64e49..9f77ce6 100644
--- a/tests/posix/permcopy.out
+++ b/tests/posix/permcopy.out
@@ -1 +1 @@
-perms/rw
+perms/frw-r--r--
diff --git a/tests/posix/print0.out b/tests/posix/print0.out
new file mode 100644
index 0000000..1347444
--- /dev/null
+++ b/tests/posix/print0.out
Binary files differ
diff --git a/tests/posix/print0.sh b/tests/posix/print0.sh
new file mode 100644
index 0000000..b916172
--- /dev/null
+++ b/tests/posix/print0.sh
@@ -0,0 +1,2 @@
+invoke_bfs basic/a basic/b -print0 >"$OUT"
+diff_output
diff --git a/tests/posix/prune_error.out b/tests/posix/prune_error.out
new file mode 100644
index 0000000..436c48e
--- /dev/null
+++ b/tests/posix/prune_error.out
@@ -0,0 +1 @@
+inaccessible
diff --git a/tests/posix/prune_error.sh b/tests/posix/prune_error.sh
new file mode 100644
index 0000000..07a2523
--- /dev/null
+++ b/tests/posix/prune_error.sh
@@ -0,0 +1 @@
+! bfs_diff -L inaccessible -path '*/*' -prune -o -print
diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh
index 9a002a1..82fcd17 100644
--- a/tests/posix/readdir_error.sh
+++ b/tests/posix/readdir_error.sh
@@ -1,27 +1,27 @@
test "$UNAME" = "Linux" || skip
-clean_scratch
-mkfifo scratch/{fever,pid,wait,running}
+cd "$TEST"
+mkfifo hang pid wait running
(
# Create a zombie process
- cat scratch/fever >/dev/null &
- # Write the PID to scratch/pid
- echo $! >scratch/pid
+ cat hang >/dev/null &
+ # Write the PID to pid
+ echo $! >pid
# Don't wait on the zombie process
- exec cat scratch/wait scratch/fever >scratch/running
+ exec cat wait hang >running
) &
# Kill the parent cat on exit
-trap "kill -9 %1" EXIT
+defer kill -9 %1
# Read the child PID
-read -r pid <scratch/pid
+read -r pid <pid
# Make sure the parent cat is running before we kill the child, because bash
# will wait() on its children
-echo >scratch/wait &
-read -r _ <scratch/running
+echo >wait &
+read -r _ <running
# Turn the child into a zombie
kill -9 "$pid"
diff --git a/tests/posix/type_bind_mount.out b/tests/posix/type_bind_mount.out
index 6435159..2f06c47 100644
--- a/tests/posix/type_bind_mount.out
+++ b/tests/posix/type_bind_mount.out
@@ -1 +1 @@
-scratch/null
+./null
diff --git a/tests/posix/type_bind_mount.sh b/tests/posix/type_bind_mount.sh
index c9a161d..97b7305 100644
--- a/tests/posix/type_bind_mount.sh
+++ b/tests/posix/type_bind_mount.sh
@@ -1,9 +1,9 @@
test "$UNAME" = "Linux" || skip
-clean_scratch
-"$XTOUCH" scratch/{file,null}
+cd "$TEST"
+"$XTOUCH" file null
-bfs_sudo mount --bind /dev/null scratch/null || skip
-trap "bfs_sudo umount scratch/null" EXIT
+bfs_sudo mount --bind /dev/null null || skip
+defer bfs_sudo umount null
-bfs_diff scratch -type c
+bfs_diff . -type c
diff --git a/tests/posix/unionfs.out b/tests/posix/unionfs.out
new file mode 100644
index 0000000..28c4ec1
--- /dev/null
+++ b/tests/posix/unionfs.out
@@ -0,0 +1,10 @@
+.
+./lower
+./lower/bar
+./lower/baz
+./lower/foo
+./upper
+./upper/bar
+./upper/baz
+./upper/baz/qux
+./upper/foo
diff --git a/tests/posix/unionfs.sh b/tests/posix/unionfs.sh
new file mode 100644
index 0000000..94d3929
--- /dev/null
+++ b/tests/posix/unionfs.sh
@@ -0,0 +1,9 @@
+[[ "$UNAME" == *BSD* ]] || skip
+
+cd "$TEST"
+"$XTOUCH" -p lower/{foo,bar,baz} upper/{bar,baz/qux}
+
+bfs_sudo mount -t unionfs -o below lower upper || skip
+defer bfs_sudo umount upper
+
+bfs_diff .
diff --git a/tests/posix/user_invalid_id.sh b/tests/posix/user_invalid_id.sh
new file mode 100644
index 0000000..c378f7e
--- /dev/null
+++ b/tests/posix/user_invalid_id.sh
@@ -0,0 +1 @@
+! invoke_bfs -user 1eW6f5RM9Qi
diff --git a/tests/posix/user_invalid_name.sh b/tests/posix/user_invalid_name.sh
new file mode 100644
index 0000000..bbf3031
--- /dev/null
+++ b/tests/posix/user_invalid_name.sh
@@ -0,0 +1 @@
+! invoke_bfs -user eW6f5RM9Qi
diff --git a/tests/posix/xdev.out b/tests/posix/xdev.out
index f7839fb..6253434 100644
--- a/tests/posix/xdev.out
+++ b/tests/posix/xdev.out
@@ -1,4 +1,4 @@
-scratch
-scratch/foo
-scratch/foo/bar
-scratch/mnt
+.
+./foo
+./foo/bar
+./mnt
diff --git a/tests/posix/xdev.sh b/tests/posix/xdev.sh
index 33412bf..c59c5c8 100644
--- a/tests/posix/xdev.sh
+++ b/tests/posix/xdev.sh
@@ -1,11 +1,11 @@
test "$UNAME" = "Darwin" && skip
-clean_scratch
-mkdir scratch/{foo,mnt}
+cd "$TEST"
+mkdir foo mnt
-bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip
-trap "bfs_sudo umount scratch/mnt" EXIT
+bfs_sudo mount -t tmpfs tmpfs mnt || skip
+defer bfs_sudo umount mnt
-"$XTOUCH" scratch/foo/bar scratch/mnt/baz
+"$XTOUCH" foo/bar mnt/baz
-bfs_diff scratch -xdev
+bfs_diff . -xdev