Repository navigation
Expand file tree
/
Copy pathlib.sh
More file actions
183 lines (156 loc) · 5.33 KB
/
Copy pathlib.sh
File metadata and controls
183 lines (156 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# Helpers for the files in t/unit, which run under bash and zsh both. Keep this
# file to what both shells read identically: no arrays, no [[ ]], no $'...'.
#
# A test file looks like:
#
# . "$(dirname "$0")/../lib.sh"
# t_setup
# t_mkdir a b/c
# t_seed "$T_ROOT/a|10|0"
# t_z a
# is "jumped" "$PWD" "$T_ROOT/a"
# t_done
# zsh points $0 at the file being sourced, i.e. at this one; $ZSH_ARGZERO is
# still the test file, which is the name worth reporting
T_ARGV0=${ZSH_ARGZERO:-$0}
T_NAME=$(basename "$T_ARGV0")
T_REPO=${T_REPO:-$(cd "$(dirname "$T_ARGV0")/../.." && pwd)}
T_SHELL=${T_SHELL:-$(basename "${BASH_VERSION:+bash}${ZSH_VERSION:+zsh}")}
T_PASS=0
T_FAIL=0
T_SKIP=0
# --- assertions -------------------------------------------------------------
t_pass() {
T_PASS=$((T_PASS + 1))
[ "$T_VERBOSE" ] && printf ' ok %s\n' "$1"
return 0
}
t_block() {
printf ' %s\n' "$1"
printf '%s\n' "$2" | \sed 's/^/ | /'
}
t_fail() { # label [detail...]
T_FAIL=$((T_FAIL + 1))
printf ' FAIL [%s %s] %s\n' "$T_SHELL" "$T_NAME" "$1"
shift
while [ "$#" -gt 0 ]; do t_block "$1" "$2"; shift; shift; done
return 0
}
is() { # label got want
if [ "$2" = "$3" ]; then t_pass "$1"; else t_fail "$1" "got:" "$2" "want:" "$3"; fi
}
isnt() { # label got unwanted
if [ "$2" != "$3" ]; then t_pass "$1"; else t_fail "$1" "did not want:" "$3"; fi
}
like() { # label got substring
case "$2" in
*"$3"*) t_pass "$1";;
*) t_fail "$1" "got:" "$2" "want a substring:" "$3";;
esac
}
unlike() { # label got substring
case "$2" in
*"$3"*) t_fail "$1" "got:" "$2" "did not want the substring:" "$3";;
*) t_pass "$1";;
esac
}
t_skip() { # label why
T_SKIP=$((T_SKIP + 1))
printf ' skip [%s %s] %s (%s)\n' "$T_SHELL" "$T_NAME" "$1" "$2"
}
# --- fixtures ---------------------------------------------------------------
# Fixtures live one level down from $T_TMP so that a test's "siblings" are only
# ever what it created itself: $TMPDIR is full of other people's directories,
# and --fzf-list offers siblings.
t_setup() {
T_TMP=$(mktemp -d "${TMPDIR:-/tmp}/z-t.XXXXXX")
# $TMPDIR ends in a slash on macOS, and cd would quietly fold the resulting
# // away, so every $PWD comparison in every test would miss by one slash
T_TMP=$(builtin cd "$T_TMP" && pwd)
T_ROOT=$T_TMP/root
mkdir -p "$T_ROOT"
T_HOME=$T_TMP/home
mkdir -p "$T_HOME"
_Z_DATA=$T_TMP/.z
_Z_NO_PROMPT_COMMAND=1
export _Z_DATA _Z_NO_PROMPT_COMMAND
unset _Z_CMD _Z_OWNER _Z_MAX_SCORE _Z_NO_RESOLVE_SYMLINKS
unset _Z_NO_CD_FALLBACK _Z_NO_FZF _Z_EXCLUDE_DIRS _Z_FZF_PRUNE_DIRS
. "$T_REPO/z.sh"
builtin cd "$T_ROOT"
}
t_done() {
builtin cd /
[ "$T_KEEP" ] || \env rm -rf "$T_TMP"
printf '%-6s %-16s %2d passed' "$T_SHELL" "$T_NAME" "$T_PASS"
[ "$T_FAIL" -gt 0 ] && printf ', %d FAILED' "$T_FAIL"
[ "$T_SKIP" -gt 0 ] && printf ', %d skipped' "$T_SKIP"
printf '\n'
[ "$T_FAIL" -eq 0 ]
exit $?
}
t_mkdir() { # names relative to $T_ROOT
local d
for d in "$@"; do mkdir -p "$T_ROOT/$d"; done
}
t_seed() { # entries as path|rank|age-in-seconds, replacing the datafile
local now spec p r a
now=$(\date +%s)
: > "$_Z_DATA"
for spec in "$@"; do
p=${spec%%|*}
a=${spec##*|}
r=${spec#*|}
r=${r%%|*}
printf '%s|%s|%s\n' "$p" "$r" "$((now - a))" >> "$_Z_DATA"
done
}
t_datafile() { \sort "$_Z_DATA" 2>/dev/null; }
# strip the timestamp, which no test can predict
t_ranks() { \awk -F"|" '{ print $1 "|" $2 }' "$_Z_DATA" 2>/dev/null | \sort; }
# --- running ----------------------------------------------------------------
# Redirect rather than capture in a subshell: _z's whole job is to cd, and a
# subshell would throw that away. $T_RC, $T_OUT and $T_ERR are the results, kept
# apart because on the go path stdout is the return channel and everything
# user-facing goes to stderr.
t_run() {
"$@" > "$T_TMP/.stdout" 2> "$T_TMP/.stderr"
T_RC=$?
T_OUT=$(\cat "$T_TMP/.stdout")
T_ERR=$(\cat "$T_TMP/.stderr")
return 0
}
t_z() { t_run _z "$@"; }
# --- the fake fzf -----------------------------------------------------------
# Point PATH at t/bin/fzf and script its answers, one act per invocation. See
# t/bin/fzf for the act names.
t_fzf() {
PATH="$T_REPO/t/bin:$PATH"
FAKE_FZF_LOG=$T_TMP/fzf.log
FAKE_FZF_N=$T_TMP/fzf.n
FAKE_FZF_ACTS="$*"
export PATH FAKE_FZF_LOG FAKE_FZF_N FAKE_FZF_ACTS
: > "$FAKE_FZF_LOG"
\env rm -f "$FAKE_FZF_N"
}
# awk, not grep -c: grep exits 1 on no match, and the caller would then run its
# own fallback and print a second count
t_fzf_calls() {
[ -f "$FAKE_FZF_LOG" ] || { echo 0; return 0; }
\awk 'index($0, "--- call ") == 1 { n++ } END { print n + 0 }' "$FAKE_FZF_LOG"
}
t_fzf_field() { # call-number field-name
\awk -v c="--- call $1" -v f="$2=" '
$0 == c { on = 1; next }
on && index($0, f) == 1 { print substr($0, length(f) + 1); exit }
on && index($0, "--- call ") == 1 { exit }
' "$FAKE_FZF_LOG"
}
t_fzf_list() { # call-number: the candidate list that invocation was handed
\awk -v c="--- call $1" '
$0 == c { on = 1; next }
on && index($0, "--- call ") == 1 { exit }
on && $0 == "list:" { list = 1; next }
list { print }
' "$FAKE_FZF_LOG"
}