Recently Written · git

sbm-suckless

sbm before the rewrite: bookmarks in a plain file, picked with dmenu. Suckless style.

git clone https://github.com/equwal/sbm-suckless

Log | Files | Refs


test.sh (10270 bytes)

1 #!/bin/sh
2 
3 # Tests for bm and bm-sync: sh test.sh, or dash test.sh. The shell that runs
4 # this file also runs bm and bm-sync. Stand-ins for curl, dmenu, xclip and
5 # bm-sync play the server, the menu, the clipboard and the background sync.
6 # Thus the tests do not use the network or your files.
7 
8 here=$(cd "$(dirname "$0")" && pwd)
9 # Where /proc exists, it gives the name of this shell.
10 sh=$(tr '\000' '\n' 2>/dev/null < /proc/$$/cmdline | sed -n 1p)
11 sh=${sh:-sh}
12 t=${TMPDIR:-/tmp}/sbm-test.$$
13 mkdir -m 700 "$t" || exit 1
14 trap 'rm -rf "$t"' EXIT
15 trap 'exit 1' HUP INT TERM
16 mkdir "$t/bin" "$t/srv"
17 srv=$t/srv
18 PATH=$t/bin:$PATH
19 export T="$t" BOOKMARKS="$t/bookmarks" USERTAGS="$t/usertags" SBM_SYNC_CONFIG="$t/sync"
20 pass=0
21 fail=0
22 
23 # eq <name> <result> <expected>
24 eq () {
25     if [ "$2" = "$3" ]; then
26         pass=$((pass + 1))
27         printf 'ok   %s\n' "$1"
28     else
29         fail=$((fail + 1))
30         printf 'FAIL %s: expected [%s], got [%s]\n' "$1" "$3" "$2"
31     fi
32 }
33 
34 # dmenu: each call takes the next line of $T/answers. It prints the first
35 # menu line that holds the answer. Else it prints the answer, as typed text.
36 cat > "$t/bin/dmenu" <<'EOF'
37 #!/bin/sh
38 a=$(sed -n 1p "$T/answers")
39 sed 1d "$T/answers" > "$T/answers.new"
40 mv "$T/answers.new" "$T/answers"
41 awk -v a="$a" 'index($0, a) && !n { print; n = 1 } END { if (!n) print a }'
42 EOF
43 # xclip: -o prints the clipboard, -i fills it.
44 cat > "$t/bin/xclip" <<'EOF'
45 #!/bin/sh
46 if [ "$1" = -o ]; then cat "$T/clip" 2>/dev/null; else cat > "$T/clip"; fi
47 EOF
48 # bm-sync: records its arguments. The tests of bm-sync use the real one.
49 cat > "$t/bin/bm-sync" <<'EOF'
50 #!/bin/sh
51 printf '%s\n' "$*" >> "$T/bm-sync.log"
52 EOF
53 # curl: plays an sbm-sync server that keeps its file in $T/srv/file. Lines
54 # in $T/srv/other come from another device, once. With $T/srv/code, the
55 # server refuses with that status. With $T/srv/touch, bm adds a line during
56 # the request. With $T/srv/broken, bm-sync cannot read the answer. With
57 # $T/srv/hold, a process takes the write lock of bm during the request and
58 # keeps it; its pid goes to $T/srv/holder.
59 cat > "$t/bin/curl" <<'EOF'
60 #!/bin/sh
61 srv=$T/srv
62 printf '%s\n' "$*" >> "$srv/argv"
63 out= head= data= auth= url= password=
64 while [ $# -gt 0 ]; do
65     case $1 in
66         -o) out=$2; shift ;;
67         -D) head=$2; shift ;;
68         -w|--max-time|-X) shift ;;
69         -H) case $2 in @*) auth=$(cat "${2#@}") ;; esac; shift ;;
70         --data-binary) data=${2#@}; shift ;;
71         --data-urlencode) case $2 in password@-) password=$(cat) ;; esac; shift ;;
72         -*) ;;
73         *) url=$1 ;;
74     esac
75     shift
76 done
77 answer () { printf '%s\n' "$2" > "$out"; printf '%s' "$1"; exit 0; }
78 case $url in
79     */api/login)
80         [ "$password" = 'secret pass ' ] || answer 401 'wrong email or password'
81         answer 200 tok123 ;;
82     */api/logout)
83         echo logout >> "$srv/log"
84         exit 0 ;;
85 esac
86 [ "$auth" = 'Authorization: Bearer tok123' ] || answer 401 'not signed in'
87 [ ! -e "$srv/code" ] || answer "$(cat "$srv/code")" 'sync is paused'
88 printf '%s\n' "${url#*base=}" >> "$srv/bases"
89 cat "$data" "$srv/other" > "$srv/file" 2>/dev/null
90 rm -f "$srv/other"
91 version=v$(cksum < "$srv/file" | cut -d ' ' -f 1)
92 echo "$version" >> "$srv/versions"
93 cp "$srv/file" "$out"
94 [ ! -e "$srv/broken" ] || { rm -f "$out"; mkdir "$out"; }
95 printf 'HTTP/1.1 200 OK\r\nsbm-version: %s\r\n\r\n' "$version" > "$head"
96 if [ -e "$srv/touch" ]; then
97     rm -f "$srv/touch"
98     echo 'https://c.example C | ' >> "$BOOKMARKS"
99 fi
100 if [ -e "$srv/hold" ]; then
101     sleep 30 >/dev/null 2>&1 &
102     echo $! > "$srv/holder"
103     mkdir "$BOOKMARKS.lock" && echo $! > "$BOOKMARKS.lock/pid"
104 fi
105 printf 200
106 EOF
107 chmod +x "$t/bin/dmenu" "$t/bin/xclip" "$t/bin/bm-sync" "$t/bin/curl"
108 echo "shell: $sh"
109 
110 # ---- bm ----
111 
112 echo 'code  | code repos, docs' > "$USERTAGS"
113 printf 'https://old.example Old site | code\nhttps://new.example\tNew site\tcode\n' > "$BOOKMARKS"
114 echo old.example > "$t/answers"
115 $sh "$here/bm" -c
116 eq 'copy takes the URL of a line of bm' "$(cat "$t/clip")" https://old.example
117 echo new.example > "$t/answers"
118 $sh "$here/bm" -c
119 eq 'copy takes the URL of a line of the app' "$(cat "$t/clip")" https://new.example
120 
121 : > "$BOOKMARKS"
122 printf '%s\n' https://add.example 'Add site' code done > "$t/answers"
123 $sh "$here/bm" -a
124 eq 'add writes the bookmark' "$(cut -d ' ' -f 1 "$BOOKMARKS")" https://add.example
125 # bm runs bm-sync in the background: wait for it.
126 i=0
127 while [ ! -s "$t/bm-sync.log" ] && [ $i -lt 10 ]; do
128     sleep 1
129     i=$((i + 1))
130 done
131 eq 'add runs bm-sync -q' "$(cat "$t/bm-sync.log" 2>/dev/null)" -q
132 
133 # The write lock. The other process adds a line after two seconds, and then
134 # frees the lock.
135 : > "$BOOKMARKS"
136 mkdir "$BOOKMARKS.lock"
137 ( sleep 2; echo 'https://first.example First | ' >> "$BOOKMARKS"
138   rm -rf "$BOOKMARKS.lock" ) &
139 first=$!
140 echo "$first" > "$BOOKMARKS.lock/pid"
141 printf '%s\n' https://second.example Second done > "$t/answers"
142 SBM_LOCK_WAIT=10 $sh "$here/bm" -a
143 wait "$first"
144 eq 'add waits for the write lock, and adds after the other process' \
145     "$(cut -d ' ' -f 1 "$BOOKMARKS" | tr '\n' ' ')" 'https://first.example https://second.example '
146 
147 sleep 30 >/dev/null 2>&1 &
148 holder=$!
149 mkdir "$BOOKMARKS.lock" && echo "$holder" > "$BOOKMARKS.lock/pid"
150 printf '%s\n' https://new.example New done > "$t/answers"
151 SBM_LOCK_WAIT=1 $sh "$here/bm" -a 2>"$t/err"
152 eq 'add stops when the lock stays, says why, and adds nothing' \
153     "$?:$(grep -c 'in use' "$t/err"):$(grep -c new.example "$BOOKMARKS")" '1:1:0'
154 kill "$holder"
155 rm -rf "$BOOKMARKS.lock"
156 
157 true &
158 wait $!
159 mkdir "$BOOKMARKS.lock" && echo $! > "$BOOKMARKS.lock/pid"
160 printf '%s\n' https://after.example After done > "$t/answers"
161 SBM_LOCK_WAIT=1 $sh "$here/bm" -a
162 eq 'add takes over the lock of a process that is gone, and frees it' \
163     "$(grep -c after.example "$BOOKMARKS"):$(ls -d "$BOOKMARKS.lock" 2>/dev/null)" '1:'
164 
165 # ---- bm-sync ----
166 
167 bs () {
168     $sh "$here/bm-sync" "$@"
169 }
170 
171 (unset BOOKMARKS; bs) 2>"$t/err"
172 eq 'without BOOKMARKS, bm-sync fails and says so' "$?:$(grep -c BOOKMARKS "$t/err")" 1:1
173 
174 echo 'https://a.example A | code' > "$BOOKMARKS"
175 bs 2>"$t/err"
176 eq 'without an account, bm-sync fails and says how to sign in' \
177     "$?:$(cat "$t/err")" '1:bm-sync: not signed in: run bm-sync login'
178 
179 : > "$SBM_SYNC_CONFIG"
180 chmod 644 "$SBM_SYNC_CONFIG"
181 printf 'me@example.org\nsecret pass \n' | bs login https://sync.example/ >/dev/null 2>&1
182 eq 'login keeps the server and the token in a file that only you can read' \
183     "$(ls -l "$SBM_SYNC_CONFIG" | cut -c 1-10) $(tr '\n' ' ' < "$SBM_SYNC_CONFIG")" \
184     '-rw------- server=https://sync.example token=tok123 '
185 eq 'the password and the token are not in the arguments of curl' \
186     "$(grep -c -e secret -e tok123 "$srv/argv")" 0
187 eq 'login syncs at once, without a version' \
188     "$(cat "$srv/file")|$(cat "$srv/bases")" 'https://a.example A | code|'
189 
190 printf 'https://b.example\tB\torg\n' > "$srv/other"
191 bs -q
192 eq 'sync writes the merged file' \
193     "$(cat "$BOOKMARKS")" "$(printf 'https://a.example A | code\nhttps://b.example\tB\torg')"
194 eq 'sync keeps the version' "$(cat "$BOOKMARKS.sync")" "$(sed -n '$p' "$srv/versions")"
195 v=$(cat "$BOOKMARKS.sync")
196 bs -q
197 eq 'the next sync sends that version' "$(sed -n '$p' "$srv/bases")" "$v"
198 
199 : > "$srv/touch"
200 bs -q
201 eq 'a bookmark that bm adds during a sync goes to the server too' \
202     "$(grep -c c.example "$srv/file") $(grep -c c.example "$BOOKMARKS")" '1 1'
203 
204 echo 402 > "$srv/code"
205 cp "$BOOKMARKS" "$t/file.before"
206 cp "$BOOKMARKS.sync" "$t/version.before"
207 bs -q 2>"$t/err"
208 eq 'on 402, bm-sync fails, says why, and keeps the file and the version' \
209     "$?:$(cat "$t/err"):$(cmp -s "$t/file.before" "$BOOKMARKS" &&
210         cmp -s "$t/version.before" "$BOOKMARKS.sync" && echo same)" \
211     '1:bm-sync: sync is paused:same'
212 rm -f "$srv/code"
213 
214 # While bm holds the write lock, bm-sync neither reads nor writes the file.
215 export SBM_LOCK_WAIT=1
216 echo 'https://other.example Other | ' > "$srv/other"
217 sleep 30 >/dev/null 2>&1 &
218 holder=$!
219 mkdir "$BOOKMARKS.lock" && echo "$holder" > "$BOOKMARKS.lock/pid"
220 bs -q 2>"$t/err"
221 rc=$?
222 kill "$holder"
223 rm -rf "$BOOKMARKS.lock"
224 eq 'bm-sync does not read the file while bm holds the lock, and says why' \
225     "$rc:$(grep -c 'in use' "$t/err"):$(cmp -s "$t/file.before" "$BOOKMARKS" && echo same)" \
226     '1:1:same'
227 : > "$srv/hold"
228 bs -q 2>"$t/err"
229 rc=$?
230 kill "$(cat "$srv/holder")"
231 rm -rf "$BOOKMARKS.lock" "$srv/hold"
232 eq 'bm-sync does not write while bm holds the lock, and keeps the version' \
233     "$rc:$(cmp -s "$t/file.before" "$BOOKMARKS" &&
234         cmp -s "$t/version.before" "$BOOKMARKS.sync" && echo same)" '1:same'
235 unset SBM_LOCK_WAIT
236 
237 rm -f "$BOOKMARKS"
238 bs -q
239 eq 'bm-sync makes a missing file and sends it without a version' \
240     "$([ -f "$BOOKMARKS" ] && echo made):$(sed -n '$p' "$srv/bases")" 'made:'
241 
242 echo 'https://d.example D | ' >> "$BOOKMARKS"
243 : > "$srv/broken"
244 bs -q 2>/dev/null
245 eq 'after a failed write, bm-sync has no version, so the next sync deletes nothing' \
246     "$?:$([ -e "$BOOKMARKS.sync" ] || echo none)" '1:none'
247 rm -f "$srv/broken"
248 
249 mkdir "$BOOKMARKS.sync.lock"
250 echo $$ > "$BOOKMARKS.sync.lock/pid"
251 n=$(grep -c '' "$srv/bases")
252 bs -q
253 eq 'while a sync runs, bm-sync leaves a note for it and sends nothing' \
254     "$?:$([ -e "$BOOKMARKS.sync.lock/again" ] && echo note):$(grep -c '' "$srv/bases")" \
255     "0:note:$n"
256 true &
257 wait $!
258 echo $! > "$BOOKMARKS.sync.lock/pid"
259 bs -q
260 eq 'bm-sync takes a lock that a killed bm-sync left' \
261     "$?:$(grep -c '' "$srv/bases")" "0:$((n + 1))"
262 
263 eq 'status shows the server' "$(bs status | sed -n 1p)" 'server: https://sync.example'
264 
265 bs -q logout
266 eq 'logout removes the config and signs out on the server' \
267     "$([ -e "$SBM_SYNC_CONFIG" ] || echo gone) $(cat "$srv/log")" 'gone logout'
268 eq 'bm-sync leaves no lock' "$(ls -d "$BOOKMARKS.sync.lock" 2>/dev/null)" ''
269 
270 # ---- make ----
271 
272 if command -v make >/dev/null 2>&1; then
273     (cd "$here" && make -s install DESTDIR="$t/dest" PREFIX=/usr) >/dev/null
274     eq 'make install installs bm and bm-sync' \
275         "$(cd "$t/dest/usr/bin" && for f in *; do [ -x "$f" ] && printf '%s ' "$f"; done)" \
276         'bm bm-sync '
277     (cd "$here" && make -s uninstall DESTDIR="$t/dest" PREFIX=/usr) >/dev/null
278     eq 'make uninstall removes them' "$(ls "$t/dest/usr/bin")" ''
279 fi
280 
281 echo "$pass passed, $fail failed"
282 [ "$fail" -eq 0 ]