1#!/usr/local/bin/bash
2
3############################################################################
4# bfs                                                                      #
5# Copyright (C) 2015-2021 Tavian Barnes <tavianator@tavianator.com>        #
6#                                                                          #
7# Permission to use, copy, modify, and/or distribute this software for any #
8# purpose with or without fee is hereby granted.                           #
9#                                                                          #
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES #
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF         #
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR  #
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES   #
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN    #
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF  #
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.           #
17############################################################################
18
19set -e
20set -o physical
21umask 022
22
23export LC_ALL=C
24export TZ=UTC0
25
26export ASAN_OPTIONS="abort_on_error=1"
27export LSAN_OPTIONS="abort_on_error=1"
28export MSAN_OPTIONS="abort_on_error=1"
29export TSAN_OPTIONS="abort_on_error=1"
30export UBSAN_OPTIONS="abort_on_error=1"
31
32if [ -t 1 ]; then
33    BLD="$(printf '\033[01m')"
34    RED="$(printf '\033[01;31m')"
35    GRN="$(printf '\033[01;32m')"
36    YLW="$(printf '\033[01;33m')"
37    BLU="$(printf '\033[01;34m')"
38    MAG="$(printf '\033[01;35m')"
39    CYN="$(printf '\033[01;36m')"
40    RST="$(printf '\033[0m')"
41fi
42
43if command -v capsh &>/dev/null; then
44    if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then
45	if [ -n "$BFS_TRIED_DROP" ]; then
46            cat >&2 <<EOF
47${RED}error:${RST} Failed to drop capabilities.
48EOF
49
50	    exit 1
51	fi
52
53        cat >&2 <<EOF
54${YLW}warning:${RST} Running as ${BLD}$(id -un)${RST} is not recommended.  Dropping ${BLD}cap_dac_override${RST} and
55${BLD}cap_dac_read_search${RST}.
56
57EOF
58
59        BFS_TRIED_DROP=y exec capsh \
60            --drop=cap_dac_override,cap_dac_read_search \
61            --caps=cap_dac_override,cap_dac_read_search-eip \
62            -- "$0" "$@"
63    fi
64elif [ "$EUID" -eq 0 ]; then
65    UNLESS=
66    if [ "$(uname)" = "Linux" ]; then
67	UNLESS=" unless ${GRN}capsh${RST} is installed"
68    fi
69
70    cat >&2 <<EOF
71${RED}error:${RST} These tests expect filesystem permissions to be enforced, and therefore
72will not work when run as ${BLD}$(id -un)${RST}${UNLESS}.
73EOF
74    exit 1
75fi
76
77function usage() {
78    local pad=$(printf "%*s" ${#0} "")
79    cat <<EOF
80Usage: ${GRN}$0${RST} [${BLU}--bfs${RST}=${MAG}path/to/bfs${RST}] [${BLU}--posix${RST}] [${BLU}--bsd${RST}] [${BLU}--gnu${RST}] [${BLU}--all${RST}] [${BLU}--sudo${RST}]
81       $pad [${BLU}--noclean${RST}] [${BLU}--update${RST}] [${BLU}--verbose${RST}] [${BLU}--help${RST}]
82       $pad [${BLD}test_*${RST} [${BLD}test_*${RST} ...]]
83
84  ${BLU}--bfs${RST}=${MAG}path/to/bfs${RST}
85      Set the path to the bfs executable to test (default: ${MAG}./bfs${RST})
86
87  ${BLU}--posix${RST}, ${BLU}--bsd${RST}, ${BLU}--gnu${RST}, ${BLU}--all${RST}
88      Choose which test cases to run (default: ${BLU}--all${RST})
89
90  ${BLU}--sudo${RST}
91      Run tests that require root (not included in ${BLU}--all${RST})
92
93  ${BLU}--noclean${RST}
94      Keep the test directories around after the run
95
96  ${BLU}--update${RST}
97      Update the expected outputs for the test cases
98
99  ${BLU}--verbose${RST}
100      Log the commands that get executed
101
102  ${BLU}--help${RST}
103      This message
104
105  ${BLD}test_*${RST}
106      Select individual test cases to run
107EOF
108}
109
110function _realpath() {
111    (
112        cd "${1%/*}"
113        echo "$PWD/${1##*/}"
114    )
115}
116
117BFS="$(_realpath ./bfs)"
118TESTS="$(_realpath ./tests)"
119UNAME="$(uname)"
120
121DEFAULT=yes
122POSIX=
123BSD=
124GNU=
125ALL=
126SUDO=
127CLEAN=yes
128UPDATE=
129VERBOSE=
130EXPLICIT=
131
132enabled_tests=()
133
134for arg; do
135    case "$arg" in
136        --bfs=*)
137            BFS="${arg#*=}"
138            ;;
139        --posix)
140            DEFAULT=
141            POSIX=yes
142            ;;
143        --bsd)
144            DEFAULT=
145            POSIX=yes
146            BSD=yes
147            ;;
148        --gnu)
149            DEFAULT=
150            POSIX=yes
151            GNU=yes
152            ;;
153        --all)
154            DEFAULT=
155            POSIX=yes
156            BSD=yes
157            GNU=yes
158            ALL=yes
159            ;;
160        --sudo)
161            DEFAULT=
162            SUDO=yes
163            ;;
164        --noclean)
165            CLEAN=
166            ;;
167        --update)
168            UPDATE=yes
169            ;;
170        --verbose)
171            VERBOSE=yes
172            ;;
173        --help)
174            usage
175            exit 0
176            ;;
177        test_*)
178            EXPLICIT=yes
179            enabled_tests+=("$arg")
180            ;;
181        *)
182            printf "${RED}error:${RST} Unrecognized option '%s'.\n\n" "$arg" >&2
183            usage >&2
184            exit 1
185            ;;
186    esac
187done
188
189posix_tests=(
190    # General parsing
191    test_basic
192
193    test_parens
194    test_bang
195    test_implicit_and
196    test_a
197    test_o
198
199    test_weird_names
200
201    test_incomplete
202    test_missing_paren
203    test_extra_paren
204
205    # Flags
206
207    test_H
208    test_H_slash
209    test_H_broken
210    test_H_notdir
211    test_H_loops
212
213    test_L
214    test_L_broken
215    test_L_notdir
216    test_L_loops
217
218    test_flag_weird_names
219    test_flag_comma
220
221    # Primaries
222
223    test_depth
224    test_depth_slash
225    test_depth_error
226    test_L_depth
227
228    test_exec
229    test_exec_plus
230    test_exec_plus_status
231    test_exec_plus_semicolon
232
233    test_group_name
234    test_group_id
235    test_group_nogroup
236
237    test_links
238    test_links_plus
239    test_links_minus
240
241    test_name
242    test_name_root
243    test_name_root_depth
244    test_name_trailing_slash
245
246    test_newer
247    test_newer_link
248
249    test_nogroup
250    test_nogroup_ulimit
251
252    test_nouser
253    test_nouser_ulimit
254
255    test_ok_stdin
256    test_ok_plus_semicolon
257
258    test_path
259
260    test_perm_000
261    test_perm_000_minus
262    test_perm_222
263    test_perm_222_minus
264    test_perm_644
265    test_perm_644_minus
266    test_perm_symbolic
267    test_perm_symbolic_minus
268    test_perm_leading_plus_symbolic_minus
269    test_permcopy
270    test_perm_setid
271    test_perm_sticky
272
273    test_prune
274    test_prune_or_print
275    test_not_prune
276
277    test_size
278    test_size_plus
279    test_size_bytes
280
281    test_type_d
282    test_type_f
283    test_type_l
284    test_H_type_l
285    test_L_type_l
286
287    test_user_name
288    test_user_id
289    test_user_nouser
290
291    # Closed file descriptors
292    test_closed_stdin
293    test_closed_stdout
294    test_closed_stderr
295
296    # PATH_MAX handling
297    test_deep
298
299    # Optimizer tests
300    test_or_purity
301    test_double_negation
302    test_de_morgan_not
303    test_de_morgan_and
304    test_de_morgan_or
305    test_data_flow_group
306    test_data_flow_user
307    test_data_flow_type
308    test_data_flow_and_swap
309    test_data_flow_or_swap
310)
311
312bsd_tests=(
313    # Flags
314
315    test_E
316
317    test_P
318    test_P_slash
319
320    test_X
321
322    test_d_path
323
324    test_f
325
326    test_s
327
328    test_double_dash
329    test_flag_double_dash
330
331    # Primaries
332
333    test_acl
334    test_L_acl
335
336    test_anewer
337    test_asince
338
339    test_delete
340    test_delete_many
341
342    test_depth_maxdepth_1
343    test_depth_maxdepth_2
344    test_depth_mindepth_1
345    test_depth_mindepth_2
346
347    test_depth_n
348    test_depth_n_plus
349    test_depth_n_minus
350    test_depth_depth_n
351    test_depth_depth_n_plus
352    test_depth_depth_n_minus
353    test_depth_overflow
354    test_data_flow_depth
355
356    test_exec_substring
357
358    test_execdir_pwd
359    test_execdir_slash
360    test_execdir_slash_pwd
361    test_execdir_slashes
362    test_execdir_ulimit
363
364    test_exit
365
366    test_flags
367
368    test_follow
369
370    test_gid_name
371
372    test_ilname
373    test_L_ilname
374
375    test_iname
376
377    test_inum
378
379    test_ipath
380
381    test_iregex
382
383    test_lname
384    test_L_lname
385
386    test_ls
387    test_L_ls
388
389    test_maxdepth
390
391    test_mindepth
392
393    test_mnewer
394    test_H_mnewer
395
396    test_msince
397
398    test_mtime_units
399
400    test_name_slash
401    test_name_slashes
402
403    test_H_newer
404
405    test_newerma
406    test_newermt
407    test_newermt_epoch_minus_one
408
409    test_ok_stdin
410    test_ok_closed_stdin
411
412    test_okdir_stdin
413    test_okdir_closed_stdin
414
415    test_perm_000_plus
416    test_perm_222_plus
417    test_perm_644_plus
418
419    test_printx
420
421    test_quit
422    test_quit_child
423    test_quit_depth
424    test_quit_depth_child
425    test_quit_after_print
426    test_quit_before_print
427    test_quit_implicit_print
428
429    test_rm
430
431    test_regex
432    test_regex_parens
433
434    test_samefile
435    test_samefile_symlink
436    test_H_samefile_symlink
437    test_L_samefile_symlink
438    test_samefile_broken
439    test_H_samefile_broken
440    test_L_samefile_broken
441    test_samefile_notdir
442    test_H_samefile_notdir
443    test_L_samefile_notdir
444
445    test_size_T
446    test_size_big
447
448    test_uid_name
449
450    # Optimizer tests
451    test_data_flow_sparse
452)
453
454gnu_tests=(
455    # General parsing
456
457    test_not
458    test_and
459    test_or
460    test_comma
461    test_precedence
462
463    test_follow_comma
464
465    # Flags
466
467    test_P
468    test_P_slash
469
470    test_L_loops_continue
471
472    test_double_dash
473    test_flag_double_dash
474
475    # Primaries
476
477    test_anewer
478
479    test_path_d
480
481    test_daystart
482    test_daystart_twice
483
484    test_delete
485    test_delete_many
486    test_L_delete
487
488    test_depth_mindepth_1
489    test_depth_mindepth_2
490    test_depth_maxdepth_1
491    test_depth_maxdepth_2
492
493    test_empty
494    test_empty_special
495
496    test_exec_nothing
497    test_exec_substring
498
499    test_execdir
500    test_execdir_substring
501    test_execdir_plus_semicolon
502    test_execdir_pwd
503    test_execdir_slash
504    test_execdir_slash_pwd
505    test_execdir_slashes
506    test_execdir_ulimit
507
508    test_executable
509
510    test_false
511
512    test_files0_from_file
513    test_files0_from_stdin
514    test_files0_from_none
515    test_files0_from_empty
516    test_files0_from_nowhere
517    test_files0_from_nothing
518    test_files0_from_ok
519
520    test_fls
521
522    test_follow
523
524    test_fprint
525    test_fprint_duplicate
526    test_fprint_error
527    test_fprint_noerror
528    test_fprint_noarg
529    test_fprint_nonexistent
530    test_fprint_truncate
531
532    test_fprint0
533
534    test_fprintf
535    test_fprintf_nofile
536    test_fprintf_noformat
537
538    test_fstype
539
540    test_gid
541    test_gid_plus
542    test_gid_plus_plus
543    test_gid_minus
544    test_gid_minus_plus
545
546    test_ignore_readdir_race
547    test_ignore_readdir_race_root
548    test_ignore_readdir_race_notdir
549
550    test_ilname
551    test_L_ilname
552
553    test_iname
554
555    test_inum
556
557    test_ipath
558
559    test_iregex
560
561    test_lname
562    test_L_lname
563
564    test_ls
565    test_L_ls
566
567    test_maxdepth
568
569    test_mindepth
570
571    test_name_slash
572    test_name_slashes
573
574    test_H_newer
575
576    test_newerma
577    test_newermt
578    test_newermt_epoch_minus_one
579
580    test_ok_closed_stdin
581    test_ok_nothing
582
583    test_okdir_closed_stdin
584    test_okdir_plus_semicolon
585
586    test_perm_000_slash
587    test_perm_222_slash
588    test_perm_644_slash
589    test_perm_symbolic_slash
590    test_perm_leading_plus_symbolic_slash
591
592    test_print_error
593
594    test_print0
595
596    test_printf
597    test_printf_empty
598    test_printf_slash
599    test_printf_slashes
600    test_printf_trailing_slash
601    test_printf_trailing_slashes
602    test_printf_flags
603    test_printf_types
604    test_printf_escapes
605    test_printf_times
606    test_printf_leak
607    test_printf_nul
608    test_printf_Y_error
609    test_printf_H
610    test_printf_u_g_ulimit
611    test_printf_l_nonlink
612
613    test_quit
614    test_quit_child
615    test_quit_depth
616    test_quit_depth_child
617    test_quit_after_print
618    test_quit_before_print
619
620    test_readable
621
622    test_regex
623    test_regex_parens
624    test_regex_error
625
626    test_regextype_posix_basic
627    test_regextype_posix_extended
628
629    test_samefile
630    test_samefile_symlink
631    test_H_samefile_symlink
632    test_L_samefile_symlink
633    test_samefile_broken
634    test_H_samefile_broken
635    test_L_samefile_broken
636    test_samefile_notdir
637    test_H_samefile_notdir
638    test_L_samefile_notdir
639
640    test_size_big
641
642    test_true
643
644    test_uid
645    test_uid_plus
646    test_uid_plus_plus
647    test_uid_minus
648    test_uid_minus_plus
649
650    test_writable
651
652    test_xtype_l
653    test_xtype_f
654    test_L_xtype_l
655    test_L_xtype_f
656
657    # Optimizer tests
658    test_and_purity
659    test_not_reachability
660    test_comma_reachability
661    test_and_false_or_true
662    test_comma_redundant_true
663    test_comma_redundant_false
664)
665
666bfs_tests=(
667    # General parsing
668    test_path_flag_expr
669    test_path_expr_flag
670    test_flag_expr_path
671    test_expr_flag_path
672    test_expr_path_flag
673
674    test_unexpected_operator
675
676    test_typo
677
678    # Flags
679
680    test_D_multi
681    test_D_all
682
683    test_O0
684    test_O1
685    test_O2
686    test_O3
687    test_Ofast
688
689    test_S_bfs
690    test_S_dfs
691    test_S_ids
692
693    # Special forms
694
695    test_exclude_name
696    test_exclude_depth
697    test_exclude_mindepth
698    test_exclude_print
699    test_exclude_exclude
700
701    # Primaries
702
703    test_color
704    test_color_L
705    test_color_rs_lc_rc_ec
706    test_color_escapes
707    test_color_nul
708    test_color_ln_target
709    test_color_L_ln_target
710    test_color_mh
711    test_color_mh0
712    test_color_or
713    test_color_mi
714    test_color_or_mi
715    test_color_or_mi0
716    test_color_or0_mi
717    test_color_or0_mi0
718    test_color_su_sg0
719    test_color_su0_sg
720    test_color_su0_sg0
721    test_color_st_tw_ow0
722    test_color_st_tw0_ow
723    test_color_st_tw0_ow0
724    test_color_st0_tw_ow
725    test_color_st0_tw_ow0
726    test_color_st0_tw0_ow
727    test_color_st0_tw0_ow0
728    test_color_ext
729    test_color_ext0
730    test_color_ext_override
731    test_color_ext_underride
732    test_color_missing_colon
733    test_color_no_stat
734    test_color_L_no_stat
735    test_color_star
736    test_color_ls
737
738    test_execdir_plus
739
740    test_fprint_duplicate_stdout
741    test_fprint_error_stdout
742    test_fprint_error_stderr
743
744    test_help
745
746    test_hidden
747    test_hidden_root
748
749    test_links_noarg
750    test_links_empty
751    test_links_negative
752    test_links_invalid
753
754    test_newerma_nonexistent
755    test_newermt_invalid
756    test_newermq
757    test_newerqm
758
759    test_nohidden
760    test_nohidden_depth
761
762    test_perm_symbolic_trailing_comma
763    test_perm_symbolic_double_comma
764    test_perm_symbolic_missing_action
765    test_perm_leading_plus_symbolic
766
767    test_printf_w
768    test_printf_incomplete_escape
769    test_printf_invalid_escape
770    test_printf_incomplete_format
771    test_printf_invalid_format
772    test_printf_duplicate_flag
773    test_printf_must_be_numeric
774    test_printf_color
775
776    test_type_multi
777
778    test_unique
779    test_unique_depth
780    test_L_unique
781    test_L_unique_loops
782    test_L_unique_depth
783
784    test_version
785
786    test_xtype_multi
787
788    # Optimizer tests
789    test_data_flow_hidden
790    test_xtype_reorder
791    test_xtype_depth
792
793    # PATH_MAX handling
794    test_deep_strict
795
796    # Error handling
797    test_stderr_fails_silently
798    test_stderr_fails_loudly
799)
800
801sudo_tests=(
802    test_capable
803    test_L_capable
804
805    test_mount
806    test_L_mount
807    test_xdev
808    test_L_xdev
809
810    test_inum_mount
811    test_inum_bind_mount
812    test_type_bind_mount
813    test_xtype_bind_mount
814)
815
816case "$UNAME" in
817    Darwin|FreeBSD)
818        bsd_tests+=(
819            test_xattr
820            test_L_xattr
821
822            test_xattrname
823            test_L_xattrname
824        )
825        ;;
826    *)
827        sudo_tests+=(
828            test_xattr
829            test_L_xattr
830
831            test_xattrname
832            test_L_xattrname
833        )
834        ;;
835esac
836
837if [ "$DEFAULT" ]; then
838    POSIX=yes
839    BSD=yes
840    GNU=yes
841    ALL=yes
842fi
843
844if [ ! "$EXPLICIT" ]; then
845    [ "$POSIX" ] && enabled_tests+=("${posix_tests[@]}")
846    [ "$BSD" ] && enabled_tests+=("${bsd_tests[@]}")
847    [ "$GNU" ] && enabled_tests+=("${gnu_tests[@]}")
848    [ "$ALL" ] && enabled_tests+=("${bfs_tests[@]}")
849    [ "$SUDO" ] && enabled_tests+=("${sudo_tests[@]}")
850fi
851
852eval enabled_tests=($(printf '%q\n' "${enabled_tests[@]}" | sort -u))
853
854# The temporary directory that will hold our test data
855TMP="$(mktemp -d "${TMPDIR:-/tmp}"/bfs.XXXXXXXXXX)"
856chown "$(id -u):$(id -g)" "$TMP"
857
858# Clean up temporary directories on exit
859function cleanup() {
860    # Don't force rm to deal with long paths
861    for dir in "$TMP"/deep/*/*; do
862        if [ -d "$dir" ]; then
863            (cd "$dir" && rm -rf *)
864        fi
865    done
866
867    # In case a test left anything weird in scratch/
868    if [ -e "$TMP"/scratch ]; then
869        chmod -R +rX "$TMP"/scratch
870    fi
871
872    rm -rf "$TMP"
873}
874
875if [ "$CLEAN" ]; then
876    trap cleanup EXIT
877else
878    echo "Test files saved to $TMP"
879fi
880
881# Install a file, creating any parent directories
882function installp() {
883    local target="${@: -1}"
884    mkdir -p "${target%/*}"
885    install "$@"
886}
887
888# Prefer GNU touch to work around https://apple.stackexchange.com/a/425730/397839
889if command -v gtouch &>/dev/null; then
890    TOUCH=gtouch
891else
892    TOUCH=touch
893fi
894
895# Like a mythical touch -p
896function touchp() {
897    for arg; do
898        installp -m644 /dev/null "$arg"
899    done
900}
901
902# Creates a simple file+directory structure for tests
903function make_basic() {
904    touchp "$1/a"
905    touchp "$1/b"
906    touchp "$1/c/d"
907    touchp "$1/e/f"
908    mkdir -p "$1/g/h"
909    mkdir -p "$1/i"
910    touchp "$1/j/foo"
911    touchp "$1/k/foo/bar"
912    touchp "$1/l/foo/bar/baz"
913    echo baz >"$1/l/foo/bar/baz"
914}
915make_basic "$TMP/basic"
916
917# Creates a file+directory structure with various permissions for tests
918function make_perms() {
919    installp -m000 /dev/null "$1/0"
920    installp -m444 /dev/null "$1/r"
921    installp -m222 /dev/null "$1/w"
922    installp -m644 /dev/null "$1/rw"
923    installp -m555 /dev/null "$1/rx"
924    installp -m311 /dev/null "$1/wx"
925    installp -m755 /dev/null "$1/rwx"
926}
927make_perms "$TMP/perms"
928
929# Creates a file+directory structure with various symbolic and hard links
930function make_links() {
931    touchp "$1/file"
932    ln -s file "$1/symlink"
933    ln "$1/file" "$1/hardlink"
934    ln -s nowhere "$1/broken"
935    ln -s symlink/file "$1/notdir"
936    mkdir -p "$1/deeply/nested/dir"
937    touchp "$1/deeply/nested/file"
938    ln -s file "$1/deeply/nested/link"
939    ln -s nowhere "$1/deeply/nested/broken"
940    ln -s deeply/nested "$1/skip"
941}
942make_links "$TMP/links"
943
944# Creates a file+directory structure with symbolic link loops
945function make_loops() {
946    touchp "$1/file"
947    ln -s file "$1/symlink"
948    ln -s nowhere "$1/broken"
949    ln -s symlink/file "$1/notdir"
950    ln -s loop "$1/loop"
951    mkdir -p "$1/deeply/nested/dir"
952    ln -s ../../deeply "$1/deeply/nested/loop"
953    ln -s deeply/nested/loop/nested "$1/skip"
954}
955make_loops "$TMP/loops"
956
957# Creates a file+directory structure with varying timestamps
958function make_times() {
959    mkdir -p "$1"
960    $TOUCH -t 199112140000 "$1/a"
961    $TOUCH -t 199112140001 "$1/b"
962    $TOUCH -t 199112140002 "$1/c"
963    ln -s a "$1/l"
964    $TOUCH -h -t 199112140003 "$1/l"
965    $TOUCH -t 199112140004 "$1"
966}
967make_times "$TMP/times"
968
969# Creates a file+directory structure with various weird file/directory names
970function make_weirdnames() {
971    touchp "$1/-/a"
972    touchp "$1/(/b"
973    touchp "$1/(-/c"
974    touchp "$1/!/d"
975    touchp "$1/!-/e"
976    touchp "$1/,/f"
977    touchp "$1/)/g"
978    touchp "$1/.../h"
979    touchp "$1/\\/i"
980    touchp "$1/ /j"
981}
982make_weirdnames "$TMP/weirdnames"
983
984# Creates a very deep directory structure for testing PATH_MAX handling
985function make_deep() {
986    mkdir -p "$1"
987
988    # $name will be 255 characters, aka _XOPEN_NAME_MAX
989    local name="0123456789ABCDEF"
990    name="${name}${name}${name}${name}"
991    name="${name}${name}${name}${name}"
992    name="${name:0:255}"
993
994    for i in {0..9} A B C D E F; do
995        (
996            mkdir "$1/$i"
997            cd "$1/$i"
998
999            # 16 * 256 == 4096 == PATH_MAX
1000            for j in {1..16}; do
1001                mkdir "$name"
1002                cd "$name" 2>/dev/null
1003            done
1004
1005            $TOUCH "$name"
1006        )
1007    done
1008}
1009make_deep "$TMP/deep"
1010
1011# Creates a directory structure with many different types, and therefore colors
1012function make_rainbow() {
1013    touchp "$1/file.txt"
1014    touchp "$1/file.dat"
1015    touchp "$1/star".{gz,tar,tar.gz}
1016    ln -s file.txt "$1/link.txt"
1017    touchp "$1/mh1"
1018    ln "$1/mh1" "$1/mh2"
1019    mkfifo "$1/pipe"
1020    # TODO: block
1021    ln -s /dev/null "$1/chardev_link"
1022    ln -s nowhere "$1/broken"
1023    "$TESTS/mksock" "$1/socket"
1024    touchp "$1"/s{u,g,ug}id
1025    chmod u+s "$1"/su{,g}id
1026    chmod g+s "$1"/s{u,}gid
1027    mkdir "$1/ow" "$1"/sticky{,_ow}
1028    chmod o+w "$1"/*ow
1029    chmod +t "$1"/sticky*
1030    touchp "$1"/exec.sh
1031    chmod +x "$1"/exec.sh
1032}
1033make_rainbow "$TMP/rainbow"
1034
1035# Creates a scratch directory that tests can modify
1036function make_scratch() {
1037    mkdir -p "$1"
1038}
1039make_scratch "$TMP/scratch"
1040
1041function bfs_sort() {
1042    awk -F/ '{ print NF - 1 " " $0 }' | sort -n | cut -d' ' -f2-
1043}
1044
1045# Close stdin so bfs doesn't think we're interactive
1046exec </dev/null
1047
1048if [ "$VERBOSE" ]; then
1049    # dup stdout for verbose logging even when redirected
1050    exec 3>&1
1051fi
1052
1053function bfs_verbose() {
1054    if [ "$VERBOSE" ]; then
1055        if [ -t 3 ]; then
1056            printf "${GRN}%q${RST} " $BFS >&3
1057
1058            local expr_started=
1059            for arg; do
1060                if [[ $arg == -[A-Z]* ]]; then
1061                    printf "${CYN}%q${RST} " "$arg" >&3
1062                elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then
1063                    expr_started=yes
1064                    printf "${RED}%q${RST} " "$arg" >&3
1065                elif [[ $expr_started && $arg == [\),] ]]; then
1066                    printf "${RED}%q${RST} " "$arg" >&3
1067                elif [[ $arg == -?* ]]; then
1068                    expr_started=yes
1069                    printf "${BLU}%q${RST} " "$arg" >&3
1070                elif [ "$expr_started" ]; then
1071                    printf "${BLD}%q${RST} " "$arg" >&3
1072                else
1073                    printf "${MAG}%q${RST} " "$arg" >&3
1074                fi
1075            done
1076        else
1077            printf '%q ' $BFS "$@" >&3
1078        fi
1079        printf '\n' >&3
1080    fi
1081}
1082
1083function invoke_bfs() {
1084    bfs_verbose "$@"
1085    $BFS "$@"
1086}
1087
1088# Silence stderr unless --verbose is set
1089function quiet() {
1090    if [ "$VERBOSE" ]; then
1091        "$@"
1092    else
1093        "$@" 2>/dev/null
1094    fi
1095}
1096
1097# Expect a command to fail, but not crash
1098function fail() {
1099    "$@"
1100    local STATUS="$?"
1101
1102    if ((STATUS > 125)); then
1103        return "$STATUS"
1104    elif ((STATUS)); then
1105        return 0
1106    else
1107        return 1
1108    fi
1109}
1110
1111# Return value when bfs fails
1112EX_BFS=10
1113# Return value when a difference is detected
1114EX_DIFF=20
1115
1116function bfs_diff() (
1117    bfs_verbose "$@"
1118
1119    # Close the dup()'d stdout to make sure we have enough fd's for the process
1120    # substitution, even with low ulimit -n
1121    exec 3>&-
1122
1123    local CALLER
1124    for CALLER in "${FUNCNAME[@]}"; do
1125        if [[ $CALLER == test_* ]]; then
1126            break
1127        fi
1128    done
1129
1130    local EXPECTED="$TESTS/$CALLER.out"
1131    if [ "$UPDATE" ]; then
1132        local ACTUAL="$EXPECTED"
1133    else
1134        local ACTUAL="$TMP/$CALLER.out"
1135    fi
1136
1137    $BFS "$@" | bfs_sort >"$ACTUAL"
1138    local STATUS="${PIPESTATUS[0]}"
1139
1140    if [ ! "$UPDATE" ]; then
1141        diff -u "$EXPECTED" "$ACTUAL" || return $EX_DIFF
1142    fi
1143
1144    if [ "$STATUS" -eq 0 ]; then
1145        return 0
1146    else
1147        return $EX_BFS
1148    fi
1149)
1150
1151function closefrom() {
1152    if [ -d /proc/self/fd ]; then
1153        local fds=/proc/self/fd
1154    else
1155        local fds=/dev/fd
1156    fi
1157
1158    for fd in "$fds"/*; do
1159        if [ ! -e "$fd" ]; then
1160            continue
1161        fi
1162
1163        local fd="${fd##*/}"
1164        if [ "$fd" -ge "$1" ]; then
1165            eval "exec ${fd}<&-"
1166        fi
1167    done
1168}
1169
1170function inum() {
1171    ls -id "$@" | awk '{ print $1 }'
1172}
1173
1174
1175cd "$TMP"
1176set +e
1177
1178# Test cases
1179
1180function test_basic() {
1181    bfs_diff basic
1182}
1183
1184function test_type_d() {
1185    bfs_diff basic -type d
1186}
1187
1188function test_type_f() {
1189    bfs_diff basic -type f
1190}
1191
1192function test_type_l() {
1193    bfs_diff links/skip -type l
1194}
1195
1196function test_H_type_l() {
1197    bfs_diff -H links/skip -type l
1198}
1199
1200function test_L_type_l() {
1201    bfs_diff -L links/skip -type l
1202}
1203
1204function test_type_multi() {
1205    bfs_diff links -type f,d,c
1206}
1207
1208function test_mindepth() {
1209    bfs_diff basic -mindepth 1
1210}
1211
1212function test_maxdepth() {
1213    bfs_diff basic -maxdepth 1
1214}
1215
1216function test_depth() {
1217    bfs_diff basic -depth
1218}
1219
1220function test_depth_slash() {
1221    bfs_diff basic/ -depth
1222}
1223
1224function test_depth_mindepth_1() {
1225    bfs_diff basic -mindepth 1 -depth
1226}
1227
1228function test_depth_mindepth_2() {
1229    bfs_diff basic -mindepth 2 -depth
1230}
1231
1232function test_depth_maxdepth_1() {
1233    bfs_diff basic -maxdepth 1 -depth
1234}
1235
1236function test_depth_maxdepth_2() {
1237    bfs_diff basic -maxdepth 2 -depth
1238}
1239
1240function test_depth_error() {
1241    rm -rf scratch/*
1242    touchp scratch/foo/bar
1243    chmod -r scratch/foo
1244
1245    quiet bfs_diff scratch -depth
1246    local ret=$?
1247
1248    chmod +r scratch/foo
1249    rm -rf scratch/*
1250
1251    [ $ret -eq $EX_BFS ]
1252}
1253
1254function test_name() {
1255    bfs_diff basic -name '*f*'
1256}
1257
1258function test_name_root() {
1259    bfs_diff basic/a -name a
1260}
1261
1262function test_name_root_depth() {
1263    bfs_diff basic/g -depth -name g
1264}
1265
1266function test_name_trailing_slash() {
1267    bfs_diff basic/g/ -name g
1268}
1269
1270function test_name_slash() {
1271    bfs_diff / -maxdepth 0 -name /
1272}
1273
1274function test_name_slashes() {
1275    bfs_diff /// -maxdepth 0 -name /
1276}
1277
1278function test_path() {
1279    bfs_diff basic -path 'basic/*f*'
1280}
1281
1282function test_true() {
1283    bfs_diff basic -true
1284}
1285
1286function test_false() {
1287    bfs_diff basic -false
1288}
1289
1290function test_executable() {
1291    bfs_diff perms -executable
1292}
1293
1294function test_readable() {
1295    bfs_diff perms -readable
1296}
1297
1298function test_writable() {
1299    bfs_diff perms -writable
1300}
1301
1302function test_empty() {
1303    bfs_diff basic -empty
1304}
1305
1306function test_empty_special() {
1307    bfs_diff rainbow -empty
1308}
1309
1310function test_gid() {
1311    bfs_diff basic -gid "$(id -g)"
1312}
1313
1314function test_gid_plus() {
1315    if [ "$(id -g)" -ne 0 ]; then
1316	bfs_diff basic -gid +0
1317    fi
1318}
1319
1320function test_gid_plus_plus() {
1321    if [ "$(id -g)" -ne 0 ]; then
1322	bfs_diff basic -gid ++0
1323    fi
1324}
1325
1326function test_gid_minus() {
1327    bfs_diff basic -gid "-$(($(id -g) + 1))"
1328}
1329
1330function test_gid_minus_plus() {
1331    bfs_diff basic -gid "-+$(($(id -g) + 1))"
1332}
1333
1334function test_uid() {
1335    bfs_diff basic -uid "$(id -u)"
1336}
1337
1338function test_uid_plus() {
1339    if [ "$(id -u)" -ne 0 ]; then
1340	bfs_diff basic -uid +0
1341    fi
1342}
1343
1344function test_uid_plus_plus() {
1345    if [ "$(id -u)" -ne 0 ]; then
1346	bfs_diff basic -uid ++0
1347    fi
1348}
1349
1350function test_uid_minus() {
1351    bfs_diff basic -uid "-$(($(id -u) + 1))"
1352}
1353
1354function test_uid_minus_plus() {
1355    bfs_diff basic -uid "-+$(($(id -u) + 1))"
1356}
1357
1358function test_newer() {
1359    bfs_diff times -newer times/a
1360}
1361
1362function test_newer_link() {
1363    bfs_diff times -newer times/l
1364}
1365
1366function test_anewer() {
1367    bfs_diff times -anewer times/a
1368}
1369
1370function test_asince() {
1371    bfs_diff times -asince 1991-12-14T00:01
1372}
1373
1374function test_links() {
1375    bfs_diff links -type f -links 2
1376}
1377
1378function test_links_plus() {
1379    bfs_diff links -type f -links +1
1380}
1381
1382function test_links_minus() {
1383    bfs_diff links -type f -links -2
1384}
1385
1386function test_links_noarg() {
1387    fail quiet invoke_bfs links -links
1388}
1389
1390function test_links_empty() {
1391    fail quiet invoke_bfs links -links ''
1392}
1393
1394function test_links_negative() {
1395    fail quiet invoke_bfs links -links +-1
1396}
1397
1398function test_links_invalid() {
1399    fail quiet invoke_bfs links -links ASDF
1400}
1401
1402function test_P() {
1403    bfs_diff -P links/deeply/nested/dir
1404}
1405
1406function test_P_slash() {
1407    bfs_diff -P links/deeply/nested/dir/
1408}
1409
1410function test_H() {
1411    bfs_diff -H links/deeply/nested/dir
1412}
1413
1414function test_H_slash() {
1415    bfs_diff -H links/deeply/nested/dir/
1416}
1417
1418function test_H_broken() {
1419    bfs_diff -H links/broken
1420}
1421
1422function test_H_notdir() {
1423    bfs_diff -H links/notdir
1424}
1425
1426function test_H_newer() {
1427    bfs_diff -H times -newer times/l
1428}
1429
1430function test_H_loops() {
1431    bfs_diff -H loops/deeply/nested/loop
1432}
1433
1434function test_L() {
1435    bfs_diff -L links
1436}
1437
1438function test_L_broken() {
1439    bfs_diff -H links/broken
1440}
1441
1442function test_L_notdir() {
1443    bfs_diff -H links/notdir
1444}
1445
1446function test_L_loops() {
1447    # POSIX says it's okay to either stop or keep going on seeing a filesystem
1448    # loop, as long as a diagnostic is printed
1449    local errors="$(invoke_bfs -L loops 2>&1 >/dev/null)"
1450    [ -n "$errors" ]
1451}
1452
1453function test_L_loops_continue() {
1454    quiet bfs_diff -L loops
1455    [ $? -eq $EX_BFS ]
1456}
1457
1458function test_X() {
1459    quiet bfs_diff -X weirdnames
1460    [ $? -eq $EX_BFS ]
1461}
1462
1463function test_follow() {
1464    bfs_diff links -follow
1465}
1466
1467function test_L_depth() {
1468    bfs_diff -L links -depth
1469}
1470
1471function test_samefile() {
1472    bfs_diff links -samefile links/file
1473}
1474
1475function test_samefile_symlink() {
1476    bfs_diff links -samefile links/symlink
1477}
1478
1479function test_H_samefile_symlink() {
1480    bfs_diff -H links -samefile links/symlink
1481}
1482
1483function test_L_samefile_symlink() {
1484    bfs_diff -L links -samefile links/symlink
1485}
1486
1487function test_samefile_broken() {
1488    bfs_diff links -samefile links/broken
1489}
1490
1491function test_H_samefile_broken() {
1492    bfs_diff -H links -samefile links/broken
1493}
1494
1495function test_L_samefile_broken() {
1496    bfs_diff -L links -samefile links/broken
1497}
1498
1499function test_samefile_notdir() {
1500    bfs_diff links -samefile links/notdir
1501}
1502
1503function test_H_samefile_notdir() {
1504    bfs_diff -H links -samefile links/notdir
1505}
1506
1507function test_L_samefile_notdir() {
1508    bfs_diff -L links -samefile links/notdir
1509}
1510
1511function test_xtype_l() {
1512    bfs_diff links -xtype l
1513}
1514
1515function test_xtype_f() {
1516    bfs_diff links -xtype f
1517}
1518
1519function test_L_xtype_l() {
1520    bfs_diff -L links -xtype l
1521}
1522
1523function test_L_xtype_f() {
1524    bfs_diff -L links -xtype f
1525}
1526
1527function test_xtype_multi() {
1528    bfs_diff links -xtype f,d,c
1529}
1530
1531function test_xtype_reorder() {
1532    # Make sure -xtype is not reordered in front of anything -- if -xtype runs
1533    # before -links 100, it will report an ELOOP error
1534    bfs_diff loops -links 100 -xtype l
1535    invoke_bfs loops -links 100 -xtype l
1536}
1537
1538function test_xtype_depth() {
1539    # Make sure -xtype is considered side-effecting for facts_when_impure
1540    fail quiet invoke_bfs loops -xtype l -depth 100
1541}
1542
1543function test_iname() {
1544    bfs_diff basic -iname '*F*'
1545}
1546
1547function test_ipath() {
1548    bfs_diff basic -ipath 'basic/*F*'
1549}
1550
1551function test_lname() {
1552    bfs_diff links -lname '[aq]'
1553}
1554
1555function test_ilname() {
1556    bfs_diff links -ilname '[AQ]'
1557}
1558
1559function test_L_lname() {
1560    bfs_diff -L links -lname '[aq]'
1561}
1562
1563function test_L_ilname() {
1564    bfs_diff -L links -ilname '[AQ]'
1565}
1566
1567function test_user_name() {
1568    bfs_diff basic -user "$(id -un)"
1569}
1570
1571function test_user_id() {
1572    bfs_diff basic -user "$(id -u)"
1573}
1574
1575function test_user_nouser() {
1576    # Regression test: this was wrongly optimized to -false
1577    bfs_diff basic -user "$(id -u)" \! -nouser
1578}
1579
1580function test_group_name() {
1581    bfs_diff basic -group "$(id -gn)"
1582}
1583
1584function test_group_id() {
1585    bfs_diff basic -group "$(id -g)"
1586}
1587
1588function test_group_nogroup() {
1589    # Regression test: this was wrongly optimized to -false
1590    bfs_diff basic -group "$(id -g)" \! -nogroup
1591}
1592
1593function test_daystart() {
1594    bfs_diff basic -daystart -mtime 0
1595}
1596
1597function test_daystart_twice() {
1598    bfs_diff basic -daystart -daystart -mtime 0
1599}
1600
1601function test_newerma() {
1602    bfs_diff times -newerma times/a
1603}
1604
1605function test_newermt() {
1606    bfs_diff times -newermt 1991-12-14T00:01
1607}
1608
1609function test_newermt_epoch_minus_one() {
1610    bfs_diff times -newermt 1969-12-31T23:59:59Z
1611}
1612
1613function test_newermt_invalid() {
1614    fail quiet invoke_bfs times -newermt not_a_date_time
1615}
1616
1617function test_newerma_nonexistent() {
1618    fail quiet invoke_bfs times -newerma basic/nonexistent
1619}
1620
1621function test_newermq() {
1622    fail quiet invoke_bfs times -newermq times/a
1623}
1624
1625function test_newerqm() {
1626    fail quiet invoke_bfs times -newerqm times/a
1627}
1628
1629function test_size() {
1630    bfs_diff basic -type f -size 0
1631}
1632
1633function test_size_plus() {
1634    bfs_diff basic -type f -size +0
1635}
1636
1637function test_size_bytes() {
1638    bfs_diff basic -type f -size +0c
1639}
1640
1641function test_size_big() {
1642    bfs_diff basic -size 9223372036854775807
1643}
1644
1645function test_exec() {
1646    bfs_diff basic -exec echo '{}' \;
1647}
1648
1649function test_exec_nothing() {
1650    # Regression test: don't segfault on missing command
1651    fail quiet invoke_bfs basic -exec \;
1652}
1653
1654function test_exec_plus() {
1655    bfs_diff basic -exec "$TESTS/sort-args.sh" '{}' +
1656}
1657
1658function test_exec_plus_status() {
1659    # -exec ... {} + should always return true, but if the command fails, bfs
1660    # should exit with a non-zero status
1661    bfs_diff basic -exec false '{}' + -print
1662    (($? == EX_BFS))
1663}
1664
1665function test_exec_plus_semicolon() {
1666    # POSIX says:
1667    #     Only a <plus-sign> that immediately follows an argument containing only the two characters "{}"
1668    #     shall punctuate the end of the primary expression. Other uses of the <plus-sign> shall not be
1669    #     treated as special.
1670    bfs_diff basic -exec echo foo '{}' bar + baz \;
1671}
1672
1673function test_exec_substring() {
1674    bfs_diff basic -exec echo '-{}-' ';'
1675}
1676
1677function test_execdir() {
1678    bfs_diff basic -execdir echo '{}' ';'
1679}
1680
1681function test_execdir_plus() {
1682    if [[ "$BFS" != *"-S dfs"* ]]; then
1683        bfs_diff basic -execdir "$TESTS/sort-args.sh" '{}' +
1684    fi
1685}
1686
1687function test_execdir_substring() {
1688    bfs_diff basic -execdir echo '-{}-' ';'
1689}
1690
1691function test_execdir_plus_semicolon() {
1692    bfs_diff basic -execdir echo foo '{}' bar + baz \;
1693}
1694
1695function test_execdir_pwd() {
1696    local TMP_REAL="$(cd "$TMP" && pwd)"
1697    local OFFSET="$((${#TMP_REAL} + 2))"
1698    bfs_diff basic -execdir bash -c "pwd | cut -b$OFFSET-" ';'
1699}
1700
1701function test_execdir_slash() {
1702    # Don't prepend ./ for absolute paths in -execdir
1703    bfs_diff / -maxdepth 0 -execdir echo '{}' ';'
1704}
1705
1706function test_execdir_slash_pwd() {
1707    bfs_diff / -maxdepth 0 -execdir pwd ';'
1708}
1709
1710function test_execdir_slashes() {
1711    bfs_diff /// -maxdepth 0 -execdir echo '{}' ';'
1712}
1713
1714function test_execdir_ulimit() {
1715    rm -rf scratch/*
1716    mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z
1717    mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/0/1/2/3/4/5/6/7/8/9/A/B/C
1718
1719    closefrom 4
1720    ulimit -n 13
1721    bfs_diff scratch -execdir echo '{}' ';'
1722}
1723
1724function test_weird_names() {
1725    cd weirdnames
1726    bfs_diff '-' '(-' '!-' ',' ')' './(' './!' \( \! -print -o -print \)
1727}
1728
1729function test_flag_weird_names() {
1730    cd weirdnames
1731    bfs_diff -L '-' '(-' '!-' ',' ')' './(' './!' \( \! -print -o -print \)
1732}
1733
1734function test_flag_comma() {
1735    # , is a filename until a non-flag is seen
1736    cd weirdnames
1737    bfs_diff -L ',' -print
1738}
1739
1740function test_follow_comma() {
1741    # , is an operator after a non-flag is seen
1742    cd weirdnames
1743    bfs_diff -follow ',' -print
1744}
1745
1746function test_fprint() {
1747    invoke_bfs basic -fprint scratch/test_fprint.out
1748    sort -o scratch/test_fprint.out scratch/test_fprint.out
1749
1750    if [ "$UPDATE" ]; then
1751        cp {scratch,"$TESTS"}/test_fprint.out
1752    else
1753        diff -u {"$TESTS",scratch}/test_fprint.out
1754    fi
1755}
1756
1757function test_fprint_duplicate() {
1758    touchp scratch/test_fprint_duplicate.out
1759    ln scratch/test_fprint_duplicate.out scratch/test_fprint_duplicate.hard
1760    ln -s test_fprint_duplicate.out scratch/test_fprint_duplicate.soft
1761
1762    invoke_bfs basic -fprint scratch/test_fprint_duplicate.out -fprint scratch/test_fprint_duplicate.hard -fprint scratch/test_fprint_duplicate.soft
1763    sort -o scratch/test_fprint_duplicate.out scratch/test_fprint_duplicate.out
1764
1765    if [ "$UPDATE" ]; then
1766        cp {scratch,"$TESTS"}/test_fprint_duplicate.out
1767    else
1768        diff -u {"$TESTS",scratch}/test_fprint_duplicate.out
1769    fi
1770}
1771
1772function test_fprint_duplicate_stdout() {
1773    touchp scratch/test_fprint_duplicate_stdout.out
1774
1775    invoke_bfs basic -fprint scratch/test_fprint_duplicate_stdout.out -print >scratch/test_fprint_duplicate_stdout.out
1776    sort -o scratch/test_fprint_duplicate_stdout.out{,}
1777
1778    if [ "$UPDATE" ]; then
1779        cp {scratch,"$TESTS"}/test_fprint_duplicate_stdout.out
1780    else
1781        diff -u {"$TESTS",scratch}/test_fprint_duplicate_stdout.out
1782    fi
1783}
1784
1785function test_fprint_noarg() {
1786    fail quiet invoke_bfs basic -fprint
1787}
1788
1789function test_fprint_nonexistent() {
1790    fail quiet invoke_bfs basic -fprint scratch/nonexistent/path
1791}
1792
1793function test_fprint_truncate() {
1794    printf "basic\nbasic\n" >scratch/test_fprint_truncate.out
1795
1796    invoke_bfs basic -maxdepth 0 -fprint scratch/test_fprint_truncate.out
1797    sort -o scratch/test_fprint_truncate.out scratch/test_fprint_truncate.out
1798
1799    if [ "$UPDATE" ]; then
1800        cp {scratch,"$TESTS"}/test_fprint_truncate.out
1801    else
1802        diff -u {"$TESTS",scratch}/test_fprint_truncate.out
1803    fi
1804}
1805
1806function test_double_dash() {
1807    cd basic
1808    bfs_diff -- . -type f
1809}
1810
1811function test_flag_double_dash() {
1812    cd basic
1813    bfs_diff -L -- . -type f
1814}
1815
1816function test_ignore_readdir_race() {
1817    rm -rf scratch/*
1818    $TOUCH scratch/{foo,bar}
1819
1820    # -links 1 forces a stat() call, which will fail for the second file
1821    invoke_bfs scratch -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" '{}' ';'
1822}
1823
1824function test_ignore_readdir_race_root() {
1825    # Make sure -ignore_readdir_race doesn't suppress ENOENT at the root
1826    fail quiet invoke_bfs basic/nonexistent -ignore_readdir_race
1827}
1828
1829function test_ignore_readdir_race_notdir() {
1830    # Check -ignore_readdir_race handling when a directory is replaced with a file
1831    rm -rf scratch/*
1832    touchp scratch/foo/bar
1833
1834    invoke_bfs scratch -mindepth 1 -ignore_readdir_race -execdir rm -r '{}' \; -execdir $TOUCH '{}' \;
1835}
1836
1837function test_perm_000() {
1838    bfs_diff perms -perm 000
1839}
1840
1841function test_perm_000_minus() {
1842    bfs_diff perms -perm -000
1843}
1844
1845function test_perm_000_slash() {
1846    bfs_diff perms -perm /000
1847}
1848
1849function test_perm_000_plus() {
1850    bfs_diff perms -perm +000
1851}
1852
1853function test_perm_222() {
1854    bfs_diff perms -perm 222
1855}
1856
1857function test_perm_222_minus() {
1858    bfs_diff perms -perm -222
1859}
1860
1861function test_perm_222_slash() {
1862    bfs_diff perms -perm /222
1863}
1864
1865function test_perm_222_plus() {
1866    bfs_diff perms -perm +222
1867}
1868
1869function test_perm_644() {
1870    bfs_diff perms -perm 644
1871}
1872
1873function test_perm_644_minus() {
1874    bfs_diff perms -perm -644
1875}
1876
1877function test_perm_644_slash() {
1878    bfs_diff perms -perm /644
1879}
1880
1881function test_perm_644_plus() {
1882    bfs_diff perms -perm +644
1883}
1884
1885function test_perm_symbolic() {
1886    bfs_diff perms -perm a+r,u=wX,g+wX-w
1887}
1888
1889function test_perm_symbolic_minus() {
1890    bfs_diff perms -perm -a+r,u=wX,g+wX-w
1891}
1892
1893function test_perm_symbolic_slash() {
1894    bfs_diff perms -perm /a+r,u=wX,g+wX-w
1895}
1896
1897function test_perm_symbolic_trailing_comma() {
1898    fail quiet invoke_bfs perms -perm a+r,
1899}
1900
1901function test_perm_symbolic_double_comma() {
1902    fail quiet invoke_bfs perms -perm a+r,,u+w
1903}
1904
1905function test_perm_symbolic_missing_action() {
1906    fail quiet invoke_bfs perms -perm a
1907}
1908
1909function test_perm_leading_plus_symbolic() {
1910    bfs_diff perms -perm +rwx
1911}
1912
1913function test_perm_leading_plus_symbolic_minus() {
1914    bfs_diff perms -perm -+rwx
1915}
1916
1917function test_perm_leading_plus_symbolic_slash() {
1918    bfs_diff perms -perm /+rwx
1919}
1920
1921function test_permcopy() {
1922    bfs_diff perms -perm u+rw,g+u-w,o=g
1923}
1924
1925function test_perm_setid() {
1926    bfs_diff rainbow -perm -u+s -o -perm -g+s
1927}
1928
1929function test_perm_sticky() {
1930    bfs_diff rainbow -perm -a+t
1931}
1932
1933function test_prune() {
1934    bfs_diff basic -name foo -prune
1935}
1936
1937function test_prune_or_print() {
1938    bfs_diff basic -name foo -prune -o -print
1939}
1940
1941function test_not_prune() {
1942    bfs_diff basic \! \( -name foo -prune \)
1943}
1944
1945function test_ok_nothing() {
1946    # Regression test: don't segfault on missing command
1947    fail quiet invoke_bfs basic -ok \;
1948}
1949
1950function test_ok_stdin() {
1951    # -ok should *not* close stdin
1952    # See https://savannah.gnu.org/bugs/?24561
1953    yes | quiet bfs_diff basic -ok bash -c 'printf "%s? " "$1" && head -n1' bash '{}' \;
1954}
1955
1956function test_okdir_stdin() {
1957    # -okdir should *not* close stdin
1958    yes | quiet bfs_diff basic -okdir bash -c 'printf "%s? " "$1" && head -n1' bash '{}' \;
1959}
1960
1961function test_ok_plus_semicolon() {
1962    yes | quiet bfs_diff basic -ok echo '{}' + \;
1963}
1964
1965function test_okdir_plus_semicolon() {
1966    yes | quiet bfs_diff basic -okdir echo '{}' + \;
1967}
1968
1969function test_delete() {
1970    rm -rf scratch/*
1971    touchp scratch/foo/bar/baz
1972
1973    # Don't try to delete '.'
1974    (cd scratch && invoke_bfs . -delete)
1975
1976    bfs_diff scratch
1977}
1978
1979function test_delete_many() {
1980    # Test for https://github.com/tavianator/bfs/issues/67
1981
1982    rm -rf scratch/*
1983    mkdir scratch/foo
1984    $TOUCH scratch/foo/{1..256}
1985
1986    invoke_bfs scratch/foo -delete
1987    bfs_diff scratch
1988}
1989
1990function test_L_delete() {
1991    rm -rf scratch/*
1992    mkdir scratch/foo
1993    mkdir scratch/bar
1994    ln -s ../foo scratch/bar/baz
1995
1996    # Don't try to rmdir() a symlink
1997    invoke_bfs -L scratch/bar -delete || return 1
1998
1999    bfs_diff scratch
2000}
2001
2002function test_rm() {
2003    rm -rf scratch/*
2004    touchp scratch/foo/bar/baz
2005
2006    (cd scratch && invoke_bfs . -rm)
2007
2008    bfs_diff scratch
2009}
2010
2011function test_regex() {
2012    bfs_diff basic -regex 'basic/./.'
2013}
2014
2015function test_iregex() {
2016    bfs_diff basic -iregex 'basic/[A-Z]/[a-z]'
2017}
2018
2019function test_regex_parens() {
2020    cd weirdnames
2021    bfs_diff . -regex '\./\((\)'
2022}
2023
2024function test_regex_error() {
2025    fail quiet invoke_bfs basic -regex '['
2026}
2027
2028function test_E() {
2029    cd weirdnames
2030    bfs_diff -E . -regex '\./(\()'
2031}
2032
2033function test_regextype_posix_basic() {
2034    cd weirdnames
2035    bfs_diff -regextype posix-basic -regex '\./\((\)'
2036}
2037
2038function test_regextype_posix_extended() {
2039    cd weirdnames
2040    bfs_diff -regextype posix-extended -regex '\./(\()'
2041}
2042
2043function test_d_path() {
2044    bfs_diff -d basic
2045}
2046
2047function test_path_d() {
2048    bfs_diff basic -d
2049}
2050
2051function test_f() {
2052    cd weirdnames
2053    bfs_diff -f '-' -f '('
2054}
2055
2056function test_s() {
2057    invoke_bfs -s weirdnames -maxdepth 1 >"$TMP/test_s.out"
2058
2059    if [ "$UPDATE" ]; then
2060        cp {"$TMP","$TESTS"}/test_s.out
2061    else
2062        diff -u {"$TESTS","$TMP"}/test_s.out
2063    fi
2064}
2065
2066function test_hidden() {
2067    bfs_diff weirdnames -hidden
2068}
2069
2070function test_hidden_root() {
2071    cd weirdnames
2072    bfs_diff . ./. ... ./... .../.. -hidden
2073}
2074
2075function test_nohidden() {
2076    bfs_diff weirdnames -nohidden
2077}
2078
2079function test_nohidden_depth() {
2080    bfs_diff weirdnames -depth -nohidden
2081}
2082
2083function test_depth_n() {
2084    bfs_diff basic -depth 2
2085}
2086
2087function test_depth_n_plus() {
2088    bfs_diff basic -depth +2
2089}
2090
2091function test_depth_n_minus() {
2092    bfs_diff basic -depth -2
2093}
2094
2095function test_depth_depth_n() {
2096    bfs_diff basic -depth -depth 2
2097}
2098
2099function test_depth_depth_n_plus() {
2100    bfs_diff basic -depth -depth +2
2101}
2102
2103function test_depth_depth_n_minus() {
2104    bfs_diff basic -depth -depth -2
2105}
2106
2107function test_depth_overflow() {
2108    bfs_diff basic -depth -4294967296
2109}
2110
2111function test_gid_name() {
2112    bfs_diff basic -gid "$(id -gn)"
2113}
2114
2115function test_uid_name() {
2116    bfs_diff basic -uid "$(id -un)"
2117}
2118
2119function test_mnewer() {
2120    bfs_diff times -mnewer times/a
2121}
2122
2123function test_H_mnewer() {
2124    bfs_diff -H times -mnewer times/l
2125}
2126
2127function test_msince() {
2128    bfs_diff times -msince 1991-12-14T00:01
2129}
2130
2131function test_mtime_units() {
2132    bfs_diff times -mtime +500w400d300h200m100s
2133}
2134
2135function test_size_T() {
2136    bfs_diff basic -type f -size 1T
2137}
2138
2139function test_quit() {
2140    bfs_diff basic/g -print -name g -quit
2141}
2142
2143function test_quit_child() {
2144    bfs_diff basic/g -print -name h -quit
2145}
2146
2147function test_quit_depth() {
2148    bfs_diff basic/g -depth -print -name g -quit
2149}
2150
2151function test_quit_depth_child() {
2152    bfs_diff basic/g -depth -print -name h -quit
2153}
2154
2155function test_quit_after_print() {
2156    bfs_diff basic basic -print -quit
2157}
2158
2159function test_quit_before_print() {
2160    bfs_diff basic basic -quit -print
2161}
2162
2163function test_quit_implicit_print() {
2164    bfs_diff basic -name basic -o -quit
2165}
2166
2167function test_inum() {
2168    bfs_diff basic -inum "$(inum basic/k/foo/bar)"
2169}
2170
2171function test_nogroup() {
2172    bfs_diff basic -nogroup
2173}
2174
2175function test_nogroup_ulimit() {
2176    closefrom 4
2177    ulimit -n 16
2178    bfs_diff deep -nogroup
2179}
2180
2181function test_nouser() {
2182    bfs_diff basic -nouser
2183}
2184
2185function test_nouser_ulimit() {
2186    closefrom 4
2187    ulimit -n 16
2188    bfs_diff deep -nouser
2189}
2190
2191function test_ls() {
2192    invoke_bfs rainbow -ls >scratch/test_ls.out
2193}
2194
2195function test_L_ls() {
2196    invoke_bfs -L rainbow -ls >scratch/test_L_ls.out
2197}
2198
2199function test_fls() {
2200    invoke_bfs rainbow -fls scratch/test_fls.out
2201}
2202
2203function test_printf() {
2204    bfs_diff basic -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%m(%m) %%M(%M) %%y(%y)\n'
2205}
2206
2207function test_printf_empty() {
2208    bfs_diff basic -printf ''
2209}
2210
2211function test_printf_slash() {
2212    bfs_diff / -maxdepth 0 -printf '(%h)/(%f)\n'
2213}
2214
2215function test_printf_slashes() {
2216    bfs_diff /// -maxdepth 0 -printf '(%h)/(%f)\n'
2217}
2218
2219function test_printf_trailing_slash() {
2220    bfs_diff basic/ -printf '(%h)/(%f)\n'
2221}
2222
2223function test_printf_trailing_slashes() {
2224    bfs_diff basic/// -printf '(%h)/(%f)\n'
2225}
2226
2227function test_printf_flags() {
2228    bfs_diff basic -printf '|%- 10.10p| %+03d %#4m\n'
2229}
2230
2231function test_printf_types() {
2232    bfs_diff loops -printf '(%p) (%l) %y %Y\n'
2233}
2234
2235function test_printf_escapes() {
2236    bfs_diff basic -maxdepth 0 -printf '\18\118\1118\11118\n\cfoo'
2237}
2238
2239function test_printf_times() {
2240    bfs_diff times -type f -printf '%p | %a %AY-%Am-%Ad %AH:%AI:%AS %T@ | %t %TY-%Tm-%Td %TH:%TI:%TS %T@\n'
2241}
2242
2243function test_printf_leak() {
2244    # Memory leak regression test
2245    bfs_diff basic -maxdepth 0 -printf '%p'
2246}
2247
2248function test_printf_nul() {
2249    # NUL byte regression test
2250    local EXPECTED="$TESTS/${FUNCNAME[0]}.out"
2251    if [ "$UPDATE" ]; then
2252        local ACTUAL="$EXPECTED"
2253    else
2254        local ACTUAL="$TMP/${FUNCNAME[0]}.out"
2255    fi
2256
2257    invoke_bfs basic -maxdepth 0 -printf '%h\0%f\n' >"$ACTUAL"
2258
2259    if [ ! "$UPDATE" ]; then
2260        diff -u "$EXPECTED" "$ACTUAL"
2261    fi
2262}
2263
2264function test_printf_w() {
2265    # Birth times may not be supported, so just check that %w/%W/%B can be parsed
2266    bfs_diff times -false -printf '%w %WY %BY\n'
2267}
2268
2269function test_printf_Y_error() {
2270    rm -rf scratch/*
2271    mkdir scratch/foo
2272    chmod -x scratch/foo
2273    ln -s foo/bar scratch/bar
2274
2275    quiet bfs_diff scratch -printf '(%p) (%l) %y %Y\n'
2276    local ret=$?
2277
2278    chmod +x scratch/foo
2279    rm -rf scratch/*
2280
2281    [ $ret -eq $EX_BFS ]
2282}
2283
2284function test_printf_H() {
2285    bfs_diff basic links -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%y(%y)\n'
2286}
2287
2288function test_printf_u_g_ulimit() {
2289    closefrom 4
2290    ulimit -n 16
2291    [ "$(invoke_bfs deep -printf '%u %g\n' | uniq)" = "$(id -un) $(id -gn)" ]
2292}
2293
2294function test_printf_l_nonlink() {
2295    bfs_diff links -printf '| %26p -> %-26l |\n'
2296}
2297
2298function test_printf_incomplete_escape() {
2299    fail quiet invoke_bfs basic -printf '\'
2300}
2301
2302function test_printf_invalid_escape() {
2303    fail quiet invoke_bfs basic -printf '\!'
2304}
2305
2306function test_printf_incomplete_format() {
2307    fail quiet invoke_bfs basic -printf '%'
2308}
2309
2310function test_printf_invalid_format() {
2311    fail quiet invoke_bfs basic -printf '%!'
2312}
2313
2314function test_printf_duplicate_flag() {
2315    fail quiet invoke_bfs basic -printf '%--p'
2316}
2317
2318function test_printf_must_be_numeric() {
2319    fail quiet invoke_bfs basic -printf '%+p'
2320}
2321
2322function test_printf_color() {
2323    LS_COLORS= bfs_diff -color -path './rainbow*' -printf '%H %h %f %p %P %l\n'
2324}
2325
2326function test_fprintf() {
2327    invoke_bfs basic -fprintf scratch/test_fprintf.out '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%m(%m) %%M(%M) %%y(%y)\n'
2328    sort -o scratch/test_fprintf.out scratch/test_fprintf.out
2329
2330    if [ "$UPDATE" ]; then
2331        cp scratch/test_fprintf.out "$TESTS/test_fprintf.out"
2332    else
2333        diff -u "$TESTS/test_fprintf.out" scratch/test_fprintf.out
2334    fi
2335}
2336
2337function test_fprintf_nofile() {
2338    fail quiet invoke_bfs basic -fprintf
2339}
2340
2341function test_fprintf_noformat() {
2342    fail quiet invoke_bfs basic -fprintf /dev/null
2343}
2344
2345function test_fstype() {
2346    fstype="$(invoke_bfs basic -maxdepth 0 -printf '%F\n')"
2347    bfs_diff basic -fstype "$fstype"
2348}
2349
2350function test_path_flag_expr() {
2351    bfs_diff links/skip -H -type l
2352}
2353
2354function test_path_expr_flag() {
2355    bfs_diff links/skip -type l -H
2356}
2357
2358function test_flag_expr_path() {
2359    bfs_diff -H -type l links/skip
2360}
2361
2362function test_expr_flag_path() {
2363    bfs_diff -type l -H links/skip
2364}
2365
2366function test_expr_path_flag() {
2367    bfs_diff -type l links/skip -H
2368}
2369
2370function test_parens() {
2371    bfs_diff basic \( -name '*f*' \)
2372}
2373
2374function test_bang() {
2375    bfs_diff basic \! -name foo
2376}
2377
2378function test_not() {
2379    bfs_diff basic -not -name foo
2380}
2381
2382function test_implicit_and() {
2383    bfs_diff basic -name foo -type d
2384}
2385
2386function test_a() {
2387    bfs_diff basic -name foo -a -type d
2388}
2389
2390function test_and() {
2391    bfs_diff basic -name foo -and -type d
2392}
2393
2394function test_o() {
2395    bfs_diff basic -name foo -o -type d
2396}
2397
2398function test_or() {
2399    bfs_diff basic -name foo -or -type d
2400}
2401
2402function test_comma() {
2403    bfs_diff basic -name '*f*' -print , -print
2404}
2405
2406function test_precedence() {
2407    bfs_diff basic \( -name foo -type d -o -name bar -a -type f \) -print , \! -empty -type f -print
2408}
2409
2410function test_incomplete() {
2411    fail quiet invoke_bfs basic \(
2412}
2413
2414function test_missing_paren() {
2415    fail quiet invoke_bfs basic \( -print
2416}
2417
2418function test_extra_paren() {
2419    fail quiet invoke_bfs basic -print \)
2420}
2421
2422function test_color() {
2423    LS_COLORS= bfs_diff rainbow -color
2424}
2425
2426function test_color_L() {
2427    LS_COLORS= bfs_diff -L rainbow -color
2428}
2429
2430function test_color_rs_lc_rc_ec() {
2431    LS_COLORS="rs=RS:lc=LC:rc=RC:ec=EC:" bfs_diff rainbow -color
2432}
2433
2434function test_color_escapes() {
2435    LS_COLORS="lc=\e[:rc=\155\::ec=^[\x5B\x6d:" bfs_diff rainbow -color
2436}
2437
2438function test_color_nul() {
2439    local EXPECTED="$TESTS/${FUNCNAME[0]}.out"
2440    if [ "$UPDATE" ]; then
2441        local ACTUAL="$EXPECTED"
2442    else
2443        local ACTUAL="$TMP/${FUNCNAME[0]}.out"
2444    fi
2445
2446    LS_COLORS="ec=\33[m\0:" invoke_bfs rainbow -color -maxdepth 0 >"$ACTUAL"
2447
2448    if [ ! "$UPDATE" ]; then
2449        diff -u "$EXPECTED" "$ACTUAL"
2450    fi
2451}
2452
2453function test_color_ln_target() {
2454    LS_COLORS="ln=target:or=01;31:mi=01;33:" bfs_diff rainbow -color
2455}
2456
2457function test_color_L_ln_target() {
2458    LS_COLORS="ln=target:or=01;31:mi=01;33:" bfs_diff -L rainbow -color
2459}
2460
2461function test_color_mh() {
2462    LS_COLORS="mh=01:" bfs_diff rainbow -color
2463}
2464
2465function test_color_mh0() {
2466    LS_COLORS="mh=00:" bfs_diff rainbow -color
2467}
2468
2469function test_color_or() {
2470    LS_COLORS="or=01:" bfs_diff rainbow -color
2471}
2472
2473function test_color_mi() {
2474    LS_COLORS="mi=01:" bfs_diff rainbow -color
2475}
2476
2477function test_color_or_mi() {
2478    LS_COLORS="or=01;31:mi=01;33:" bfs_diff rainbow -color
2479}
2480
2481function test_color_or_mi0() {
2482    LS_COLORS="or=01;31:mi=00:" bfs_diff rainbow -color
2483}
2484
2485function test_color_or0_mi() {
2486    LS_COLORS="or=00:mi=01;33:" bfs_diff rainbow -color
2487}
2488
2489function test_color_or0_mi0() {
2490    LS_COLORS="or=00:mi=00:" bfs_diff rainbow -color
2491}
2492
2493function test_color_su_sg0() {
2494    LS_COLORS="su=37;41:sg=00:" bfs_diff rainbow -color
2495}
2496
2497function test_color_su0_sg() {
2498    LS_COLORS="su=00:sg=30;43:" bfs_diff rainbow -color
2499}
2500
2501function test_color_su0_sg0() {
2502    LS_COLORS="su=00:sg=00:" bfs_diff rainbow -color
2503}
2504
2505function test_color_st_tw_ow0() {
2506    LS_COLORS="st=37;44:tw=40;32:ow=00:" bfs_diff rainbow -color
2507}
2508
2509function test_color_st_tw0_ow() {
2510    LS_COLORS="st=37;44:tw=00:ow=34;42:" bfs_diff rainbow -color
2511}
2512
2513function test_color_st_tw0_ow0() {
2514    LS_COLORS="st=37;44:tw=00:ow=00:" bfs_diff rainbow -color
2515}
2516
2517function test_color_st0_tw_ow() {
2518    LS_COLORS="st=00:tw=40;32:ow=34;42:" bfs_diff rainbow -color
2519}
2520
2521function test_color_st0_tw_ow0() {
2522    LS_COLORS="st=00:tw=40;32:ow=00:" bfs_diff rainbow -color
2523}
2524
2525function test_color_st0_tw0_ow() {
2526    LS_COLORS="st=00:tw=00:ow=34;42:" bfs_diff rainbow -color
2527}
2528
2529function test_color_st0_tw0_ow0() {
2530    LS_COLORS="st=00:tw=00:ow=00:" bfs_diff rainbow -color
2531}
2532
2533function test_color_ext() {
2534    LS_COLORS="*.txt=01:" bfs_diff rainbow -color
2535}
2536
2537function test_color_ext0() {
2538    LS_COLORS="*.txt=00:" bfs_diff rainbow -color
2539}
2540
2541function test_color_ext_override() {
2542    LS_COLORS="*.tar.gz=01;31:*.TAR=01;32:*.gz=01;33:" bfs_diff rainbow -color
2543}
2544
2545function test_color_ext_underride() {
2546    LS_COLORS="*.gz=01;33:*.TAR=01;32:*.tar.gz=01;31:" bfs_diff rainbow -color
2547}
2548
2549function test_color_missing_colon() {
2550    LS_COLORS="*.txt=01" bfs_diff rainbow -color
2551}
2552
2553function test_color_no_stat() {
2554    LS_COLORS="mh=0:ex=0:sg=0:su=0:st=0:ow=0:tw=0:*.txt=01:" bfs_diff rainbow -color
2555}
2556
2557function test_color_L_no_stat() {
2558    LS_COLORS="mh=0:ex=0:sg=0:su=0:st=0:ow=0:tw=0:*.txt=01:" bfs_diff -L rainbow -color
2559}
2560
2561function test_color_star() {
2562    # Regression test: don't segfault on LS_COLORS="*"
2563    LS_COLORS="*" bfs_diff rainbow -color
2564}
2565
2566function test_color_ls() {
2567    rm -rf scratch/*
2568    touchp scratch/foo/bar/baz
2569    ln -s foo/bar/baz scratch/link
2570    ln -s foo/bar/nowhere scratch/broken
2571    ln -s foo/bar/nowhere/nothing scratch/nested
2572    ln -s foo/bar/baz/qux scratch/notdir
2573    ln -s scratch/foo/bar scratch/relative
2574    mkdir scratch/__bfs__
2575    ln -s /__bfs__/nowhere scratch/absolute
2576
2577    local EXPECTED="$TESTS/${FUNCNAME[0]}.out"
2578    if [ "$UPDATE" ]; then
2579        local ACTUAL="$EXPECTED"
2580    else
2581        local ACTUAL="$TMP/${FUNCNAME[0]}.out"
2582    fi
2583
2584    LS_COLORS="or=01;31:" invoke_bfs scratch/{,link,broken,nested,notdir,relative,absolute} -color -type l -ls \
2585        | sed 's/.* -> //' \
2586        | sort -o "$ACTUAL"
2587
2588    if [ ! "$UPDATE" ]; then
2589        diff -u "$EXPECTED" "$ACTUAL"
2590    fi
2591}
2592
2593function test_deep() {
2594    closefrom 4
2595
2596    ulimit -n 16
2597    bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash '{}' \;
2598}
2599
2600function test_deep_strict() {
2601    closefrom 4
2602
2603    # Not even enough fds to keep the root open
2604    ulimit -n 7
2605    bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash '{}' \;
2606}
2607
2608function test_exit() {
2609    invoke_bfs basic -name foo -exit 42
2610    if [ $? -ne 42 ]; then
2611        return 1
2612    fi
2613
2614    invoke_bfs basic -name qux -exit 42
2615    if [ $? -ne 0 ]; then
2616        return 1
2617    fi
2618
2619    bfs_diff basic/g -print -name g -exit
2620}
2621
2622function test_printx() {
2623    bfs_diff weirdnames -printx
2624}
2625
2626function test_and_purity() {
2627    # Regression test: (-a lhs(pure) rhs(always_false)) <==> rhs is only valid if rhs is pure
2628    bfs_diff basic -name nonexistent \( -print , -false \)
2629}
2630
2631function test_or_purity() {
2632    # Regression test: (-o lhs(pure) rhs(always_true)) <==> rhs is only valid if rhs is pure
2633    bfs_diff basic -name '*' -o -print
2634}
2635
2636function test_double_negation() {
2637    bfs_diff basic \! \! -name 'foo'
2638}
2639
2640function test_not_reachability() {
2641    bfs_diff basic -print \! -quit -print
2642}
2643
2644function test_comma_reachability() {
2645    bfs_diff basic -print -quit , -print
2646}
2647
2648function test_de_morgan_not() {
2649    bfs_diff basic \! \( -name 'foo' -o \! -type f \)
2650}
2651
2652function test_de_morgan_and() {
2653    bfs_diff basic \( \! -name 'foo' -a \! -type f \)
2654}
2655
2656function test_de_morgan_or() {
2657    bfs_diff basic \( \! -name 'foo' -o \! -type f \)
2658}
2659
2660function test_and_false_or_true() {
2661    # Test (-a lhs(always_true) -false) <==> (! lhs),
2662    # (-o lhs(always_false) -true) <==> (! lhs)
2663    bfs_diff basic -prune -false -o -true
2664}
2665
2666function test_comma_redundant_true() {
2667    # Test (, lhs(always_true) -true) <==> lhs
2668    bfs_diff basic -prune , -true
2669}
2670
2671function test_comma_redundant_false() {
2672    # Test (, lhs(always_false) -false) <==> lhs
2673    bfs_diff basic -print -not -prune , -false
2674}
2675
2676function test_data_flow_depth() {
2677    bfs_diff basic -depth +1 -depth -4
2678}
2679
2680function test_data_flow_group() {
2681    bfs_diff basic \( -group "$(id -g)" -nogroup \) -o \( -group "$(id -g)" -o -nogroup \)
2682}
2683
2684function test_data_flow_user() {
2685    bfs_diff basic \( -user "$(id -u)" -nouser \) -o \( -user "$(id -u)" -o -nouser \)
2686}
2687
2688function test_data_flow_hidden() {
2689    bfs_diff basic \( -hidden -not -hidden \) -o \( -hidden -o -not -hidden \)
2690}
2691
2692function test_data_flow_sparse() {
2693    bfs_diff basic \( -sparse -not -sparse \) -o \( -sparse -o -not -sparse \)
2694}
2695
2696function test_data_flow_type() {
2697    bfs_diff basic \! \( -type f -o \! -type f \)
2698}
2699
2700function test_data_flow_and_swap() {
2701    bfs_diff basic \! -type f -a -type d
2702}
2703
2704function test_data_flow_or_swap() {
2705    bfs_diff basic \! \( -type f -o \! -type d \)
2706}
2707
2708function test_print_error() {
2709    if [ -e /dev/full ]; then
2710        fail quiet invoke_bfs basic -maxdepth 0 >/dev/full
2711    fi
2712}
2713
2714function test_fprint_error() {
2715    if [ -e /dev/full ]; then
2716        fail quiet invoke_bfs basic -maxdepth 0 -fprint /dev/full
2717    fi
2718}
2719
2720function test_fprint_noerror() {
2721    # Regression test: /dev/full should not fail until actually written to
2722
2723    if [ -e /dev/full ]; then
2724        invoke_bfs basic -false -fprint /dev/full
2725    fi
2726}
2727
2728function test_fprint_error_stdout() {
2729    if [ -e /dev/full ]; then
2730        fail quiet invoke_bfs basic -maxdepth 0 -fprint /dev/full >/dev/full
2731    fi
2732}
2733
2734function test_fprint_error_stderr() {
2735    if [ -e /dev/full ]; then
2736        fail invoke_bfs basic -maxdepth 0 -fprint /dev/full 2>/dev/full
2737    fi
2738}
2739
2740function test_print0() {
2741    invoke_bfs basic/a basic/b -print0 >scratch/test_print0.out
2742
2743    if [ "$UPDATE" ]; then
2744        cp scratch/test_print0.out "$TESTS/test_print0.out"
2745    else
2746        cmp -s scratch/test_print0.out "$TESTS/test_print0.out"
2747    fi
2748}
2749
2750function test_fprint0() {
2751    invoke_bfs basic/a basic/b -fprint0 scratch/test_fprint0.out
2752
2753    if [ "$UPDATE" ]; then
2754        cp scratch/test_fprint0.out "$TESTS/test_fprint0.out"
2755    else
2756        cmp -s scratch/test_fprint0.out "$TESTS/test_fprint0.out"
2757    fi
2758}
2759
2760function test_closed_stdin() {
2761    bfs_diff basic <&-
2762}
2763
2764function test_ok_closed_stdin() {
2765    quiet bfs_diff basic -ok echo \; <&-
2766}
2767
2768function test_okdir_closed_stdin() {
2769    quiet bfs_diff basic -okdir echo {} \; <&-
2770}
2771
2772function test_closed_stdout() {
2773    fail quiet invoke_bfs basic >&-
2774}
2775
2776function test_closed_stderr() {
2777    fail invoke_bfs basic >&- 2>&-
2778}
2779
2780function test_unique() {
2781    bfs_diff links/{file,symlink,hardlink} -unique
2782}
2783
2784function test_unique_depth() {
2785    bfs_diff basic -unique -depth
2786}
2787
2788function test_L_unique() {
2789    bfs_diff -L links/{file,symlink,hardlink} -unique
2790}
2791
2792function test_L_unique_loops() {
2793    bfs_diff -L loops/deeply/nested -unique
2794}
2795
2796function test_L_unique_depth() {
2797    bfs_diff -L loops/deeply/nested -unique -depth
2798}
2799
2800function test_mount() {
2801    rm -rf scratch/*
2802    mkdir scratch/{foo,mnt}
2803    sudo mount -t tmpfs tmpfs scratch/mnt
2804    $TOUCH scratch/foo/bar scratch/mnt/baz
2805
2806    bfs_diff scratch -mount
2807    local ret=$?
2808
2809    sudo umount scratch/mnt
2810    return $ret
2811}
2812
2813function test_L_mount() {
2814    rm -rf scratch/*
2815    mkdir scratch/{foo,mnt}
2816    sudo mount -t tmpfs tmpfs scratch/mnt
2817    ln -s ../mnt scratch/foo/bar
2818    $TOUCH scratch/mnt/baz
2819    ln -s ../mnt/baz scratch/foo/qux
2820
2821    bfs_diff -L scratch -mount
2822    local ret=$?
2823
2824    sudo umount scratch/mnt
2825    return $ret
2826}
2827
2828function test_xdev() {
2829    rm -rf scratch/*
2830    mkdir scratch/{foo,mnt}
2831    sudo mount -t tmpfs tmpfs scratch/mnt
2832    $TOUCH scratch/foo/bar scratch/mnt/baz
2833
2834    bfs_diff scratch -xdev
2835    local ret=$?
2836
2837    sudo umount scratch/mnt
2838    return $ret
2839}
2840
2841function test_L_xdev() {
2842    rm -rf scratch/*
2843    mkdir scratch/{foo,mnt}
2844    sudo mount -t tmpfs tmpfs scratch/mnt
2845    ln -s ../mnt scratch/foo/bar
2846    $TOUCH scratch/mnt/baz
2847    ln -s ../mnt/baz scratch/foo/qux
2848
2849    bfs_diff -L scratch -xdev
2850    local ret=$?
2851
2852    sudo umount scratch/mnt
2853    return $ret
2854}
2855
2856function test_inum_mount() {
2857    rm -rf scratch/*
2858    mkdir scratch/{foo,mnt}
2859    sudo mount -t tmpfs tmpfs scratch/mnt
2860
2861    bfs_diff scratch -inum "$(inum scratch/mnt)"
2862    local ret=$?
2863
2864    sudo umount scratch/mnt
2865    return $ret
2866}
2867
2868function test_inum_bind_mount() {
2869    rm -rf scratch/*
2870    $TOUCH scratch/{foo,bar}
2871    sudo mount --bind scratch/{foo,bar}
2872
2873    bfs_diff scratch -inum "$(inum scratch/bar)"
2874    local ret=$?
2875
2876    sudo umount scratch/bar
2877    return $ret
2878}
2879
2880function test_type_bind_mount() {
2881    rm -rf scratch/*
2882    $TOUCH scratch/{file,null}
2883    sudo mount --bind /dev/null scratch/null
2884
2885    bfs_diff scratch -type c
2886    local ret=$?
2887
2888    sudo umount scratch/null
2889    return $ret
2890}
2891
2892function test_xtype_bind_mount() {
2893    rm -rf scratch/*
2894    $TOUCH scratch/{file,null}
2895    sudo mount --bind /dev/null scratch/null
2896    ln -s /dev/null scratch/link
2897
2898    bfs_diff -L scratch -type c
2899    local ret=$?
2900
2901    sudo umount scratch/null
2902    return $ret
2903}
2904
2905function set_acl() {
2906    case "$UNAME" in
2907        Darwin)
2908            chmod +a "$(id -un) allow read,write" "$1"
2909            ;;
2910        FreeBSD)
2911            if [ "$(getconf ACL_NFS4 "$1")" -gt 0 ]; then
2912                setfacl -m "u:$(id -un):rw::allow" "$1"
2913            else
2914                setfacl -m "u:$(id -un):rw" "$1"
2915            fi
2916            ;;
2917        *)
2918            setfacl -m "u:$(id -un):rw" "$1"
2919            ;;
2920    esac
2921}
2922
2923function test_acl() {
2924    rm -rf scratch/*
2925
2926    quiet invoke_bfs scratch -quit -acl || return 0
2927
2928    $TOUCH scratch/{normal,acl}
2929    set_acl scratch/acl || return 0
2930    ln -s acl scratch/link
2931
2932    bfs_diff scratch -acl
2933}
2934
2935function test_L_acl() {
2936    rm -rf scratch/*
2937
2938    quiet invoke_bfs scratch -quit -acl || return 0
2939
2940    $TOUCH scratch/{normal,acl}
2941    set_acl scratch/acl || return 0
2942    ln -s acl scratch/link
2943
2944    bfs_diff -L scratch -acl
2945}
2946
2947function test_capable() {
2948    rm -rf scratch/*
2949
2950    if fail quiet invoke_bfs scratch -quit -capable; then
2951        return 0
2952    fi
2953
2954    $TOUCH scratch/{normal,capable}
2955    sudo setcap all+ep scratch/capable
2956    ln -s capable scratch/link
2957
2958    bfs_diff scratch -capable
2959}
2960
2961function test_L_capable() {
2962    rm -rf scratch/*
2963
2964    if fail quiet invoke_bfs scratch -quit -capable; then
2965        return 0
2966    fi
2967
2968    $TOUCH scratch/{normal,capable}
2969    sudo setcap all+ep scratch/capable
2970    ln -s capable scratch/link
2971
2972    bfs_diff -L scratch -capable
2973}
2974
2975function make_xattrs() {
2976    rm -rf scratch/*
2977
2978    $TOUCH scratch/{normal,xattr,xattr_2}
2979    ln -s xattr scratch/link
2980    ln -s normal scratch/xattr_link
2981
2982    case "$UNAME" in
2983        Darwin)
2984            xattr -w bfs_test true scratch/xattr \
2985                && xattr -w bfs_test_2 true scratch/xattr_2 \
2986                && xattr -s -w bfs_test true scratch/xattr_link
2987            ;;
2988        FreeBSD)
2989            setextattr user bfs_test true scratch/xattr \
2990                && setextattr user bfs_test_2 true scratch/xattr_2 \
2991                && setextattr -h user bfs_test true scratch/xattr_link
2992            ;;
2993        *)
2994            # Linux tmpfs doesn't support the user.* namespace, so we use the security.*
2995            # namespace, which is writable by root and readable by others
2996            sudo setfattr -n security.bfs_test scratch/xattr \
2997                && sudo setfattr -n security.bfs_test_2 scratch/xattr_2 \
2998                && sudo setfattr -h -n security.bfs_test scratch/xattr_link
2999            ;;
3000    esac
3001}
3002
3003function test_xattr() {
3004    quiet invoke_bfs scratch -quit -xattr || return 0
3005    make_xattrs || return 0
3006    bfs_diff scratch -xattr
3007}
3008
3009function test_L_xattr() {
3010    quiet invoke_bfs scratch -quit -xattr || return 0
3011    make_xattrs || return 0
3012    bfs_diff -L scratch -xattr
3013}
3014
3015function test_xattrname() {
3016    quiet invoke_bfs scratch -quit -xattr || return 0
3017    make_xattrs || return 0
3018
3019    case "$UNAME" in
3020        Darwin|FreeBSD)
3021            bfs_diff scratch -xattrname bfs_test
3022            ;;
3023        *)
3024            bfs_diff scratch -xattrname security.bfs_test
3025            ;;
3026    esac
3027}
3028
3029function test_L_xattrname() {
3030    quiet invoke_bfs scratch -quit -xattr || return 0
3031    make_xattrs || return 0
3032
3033    case "$UNAME" in
3034        Darwin|FreeBSD)
3035            bfs_diff -L scratch -xattrname bfs_test
3036            ;;
3037        *)
3038            bfs_diff -L scratch -xattrname security.bfs_test
3039            ;;
3040    esac
3041}
3042
3043function test_help() {
3044    invoke_bfs -help | grep -E '\{...?\}' && return 1
3045    invoke_bfs -D help | grep -E '\{...?\}' && return 1
3046    invoke_bfs -S help | grep -E '\{...?\}' && return 1
3047    invoke_bfs -regextype help | grep -E '\{...?\}' && return 1
3048
3049    return 0
3050}
3051
3052function test_version() {
3053    invoke_bfs -version >/dev/null
3054}
3055
3056function test_typo() {
3057    invoke_bfs -dikkiq 2>&1 | grep follow >/dev/null
3058}
3059
3060function test_D_multi() {
3061    quiet bfs_diff -D opt,tree,unknown basic
3062}
3063
3064function test_D_all() {
3065    quiet bfs_diff -D all basic
3066}
3067
3068function test_O0() {
3069    bfs_diff -O0 basic -not \( -type f -not -type f \)
3070}
3071
3072function test_O1() {
3073    bfs_diff -O1 basic -not \( -type f -not -type f \)
3074}
3075
3076function test_O2() {
3077    bfs_diff -O2 basic -not \( -type f -not -type f \)
3078}
3079
3080function test_O3() {
3081    bfs_diff -O3 basic -not \( -type f -not -type f \)
3082}
3083
3084function test_Ofast() {
3085    bfs_diff -Ofast basic -not \( -xtype f -not -xtype f \)
3086}
3087
3088function test_S() {
3089    invoke_bfs -S "$1" -s basic >"$TMP/test_S_$1.out"
3090
3091    if [ "$UPDATE" ]; then
3092        cp {"$TMP","$TESTS"}/"test_S_$1.out"
3093    else
3094        diff -u {"$TESTS","$TMP"}/"test_S_$1.out"
3095    fi
3096}
3097
3098function test_S_bfs() {
3099    test_S bfs
3100}
3101
3102function test_S_dfs() {
3103    test_S dfs
3104}
3105
3106function test_S_ids() {
3107    test_S ids
3108}
3109
3110function test_exclude_name() {
3111    bfs_diff basic -exclude -name foo
3112}
3113
3114function test_exclude_depth() {
3115    bfs_diff basic -depth -exclude -name foo
3116}
3117
3118function test_exclude_mindepth() {
3119    bfs_diff basic -mindepth 3 -exclude -name foo
3120}
3121
3122function test_exclude_print() {
3123    fail quiet invoke_bfs basic -exclude -print
3124}
3125
3126function test_exclude_exclude() {
3127    fail quiet invoke_bfs basic -exclude -exclude -name foo
3128}
3129
3130function test_flags() {
3131    quiet invoke_bfs scratch -quit -flags offline || return 0
3132
3133    rm -rf scratch/*
3134
3135    $TOUCH scratch/{foo,bar}
3136    chflags offline scratch/bar || return 0
3137
3138    bfs_diff scratch -flags -offline,nohidden
3139}
3140
3141function test_files0_from_file() {
3142    cd weirdnames
3143    invoke_bfs -mindepth 1 -fprintf ../scratch/files0.in "%P\0"
3144    bfs_diff -files0-from ../scratch/files0.in
3145}
3146
3147function test_files0_from_stdin() {
3148    cd weirdnames
3149    invoke_bfs -mindepth 1 -printf "%P\0" | bfs_diff -files0-from -
3150}
3151
3152function test_files0_from_none() {
3153    printf "" | fail quiet invoke_bfs -files0-from -
3154}
3155
3156function test_files0_from_empty() {
3157    printf "\0" | fail quiet invoke_bfs -files0-from -
3158}
3159
3160function test_files0_from_nowhere() {
3161    fail quiet invoke_bfs -files0-from
3162}
3163
3164function test_files0_from_nothing() {
3165    fail quiet invoke_bfs -files0-from basic/nonexistent
3166}
3167
3168function test_files0_from_ok() {
3169    printf "basic\0" | fail quiet invoke_bfs -files0-from - -ok echo {} \;
3170}
3171
3172function test_stderr_fails_silently() {
3173    if [ -e /dev/full ]; then
3174        bfs_diff -D all basic 2>/dev/full
3175    fi
3176}
3177
3178function test_stderr_fails_loudly() {
3179    if [ -e /dev/full ]; then
3180        fail invoke_bfs -D all basic -false -fprint /dev/full 2>/dev/full
3181    fi
3182}
3183
3184function test_unexpected_operator() {
3185    fail quiet invoke_bfs \! -o -print
3186}
3187
3188BOL=
3189EOL='\n'
3190
3191function update_eol() {
3192    # Put the cursor at the last column, then write a space so the next
3193    # character will wrap
3194    EOL="\\033[${COLUMNS}G "
3195}
3196
3197if [ -t 1 -a ! "$VERBOSE" ]; then
3198    BOL='\r\033[K'
3199
3200    # Workaround for bash 4: checkwinsize is off by default.  We can turn it on,
3201    # but we also have to explicitly trigger a foreground job to finish so that
3202    # it will update the window size before we use $COLUMNS
3203    shopt -s checkwinsize
3204    (:)
3205
3206    update_eol
3207    trap update_eol WINCH
3208fi
3209
3210passed=0
3211failed=0
3212
3213for test in "${enabled_tests[@]}"; do
3214    printf "${BOL}${YLW}%s${RST}${EOL}" "$test"
3215
3216    ("$test")
3217    status=$?
3218
3219    if [ $status -eq 0 ]; then
3220        ((++passed))
3221    else
3222        ((++failed))
3223        printf "${BOL}${RED}%s failed!${RST}\n" "$test"
3224    fi
3225done
3226
3227if [ $passed -gt 0 ]; then
3228    printf "${BOL}${GRN}tests passed: %d${RST}\n" "$passed"
3229fi
3230if [ $failed -gt 0 ]; then
3231    printf "${BOL}${RED}tests failed: %s${RST}\n" "$failed"
3232    exit 1
3233fi
3234