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 ]