# sbase-test.sh: checks suckless sbase (milestone M4), run by dash. # The self-test runs it as `dash /scripts/sbase-test.sh`. sbase lives in # /usr/lib/sbase, OFF the default PATH, so every tool is called by its full # path ($S/sort, ...); the Foundry OS Rust tools stay the default. # # Each check prints one line: # sbase: ok NAME it worked # sbase: FAIL NAME it did not (the test fails) # sbase: xfail NAME (WHY) a known gap, failing as expected; WHY names the # kernel request in docs/kernel-requests.md # sbase: XPASS NAME (WHY) a known gap that suddenly works: fixed? Move it # to the plain checks (counts as a failure) # sbase: absent NAME a tool that sbase 0.1 doesn't have at all # The exit status is the number of failures (FAIL + XPASS). # # To try it on a Linux host against a host-built sbase: # SBASE=/path/to/sbase WORK=/tmp/sbase-work dash root/scripts/sbase-test.sh # (there `uname`, `du` block sizes and the known gaps differ, of course). S=${SBASE:-/usr/lib/sbase} W=${WORK:-/sbase-work} fails=0 passed=0 gaps=0 absent=0 check() { # check NAME COMMAND... (passes when COMMAND succeeds) name=$1; shift if "$@"; then echo "sbase: ok $name"; passed=$((passed + 1)) else echo "sbase: FAIL $name"; fails=$((fails + 1)); fi } xfail() { # xfail NAME WHY COMMAND... (a known gap: COMMAND should fail) name=$1 why=$2; shift 2 if "$@" > /dev/null 2>&1; then echo "sbase: XPASS $name ($why)"; fails=$((fails + 1)) else echo "sbase: xfail $name ($why)"; gaps=$((gaps + 1)); fi } notin() { # notin NAME WHERE: a tool sbase doesn't have if [ -e "$S/$1" ]; then echo "sbase: FAIL $1 is in sbase now: test it"; fails=$((fails + 1)) else echo "sbase: absent $1 (not part of sbase 0.1; $2)"; absent=$((absent + 1)); fi } eq() { [ "$1" = "$2" ] || { echo " got '$1', want '$2'"; return 1; }; } # j: join standard input's lines with single spaces (plain sh, no tools). j() { r=; while IFS= read -r l; do r="$r${r:+ }$l"; done; printf '%s' "$r"; } # lsl FILE: `ls -l FILE` split into words: $1 mode, $2 links, $3 owner, # $4 group, $5 size, $6-$8 date. Result in $L (e.g. "$L" = "-rw-r--r-- 1 ..."). lsl() { L=$($S/ls -l "$1") || return 1; } $S/rm -rf "$W" && $S/mkdir -p "$W" && cd "$W" || { echo "sbase: FAIL cannot use $W"; exit 1; } printf '%s\n' 1 2 3 > nums # 6 bytes printf 'hello\n' > hello # 6 bytes # ---- text ---- check sort eq "$(printf 'b\na\nc\nb\n' | $S/sort | j)" "a b b c" check sort-n eq "$(printf '10\n9\n100\n' | $S/sort -n | j)" "9 10 100" check sort-u-r eq "$(printf 'b\na\nc\nb\n' | $S/sort -u -r | j)" "c b a" check sort-k eq "$($S/sort -t: -k3 -n /etc/passwd | $S/cut -d: -f1 | j)" "root tyler" check uniq-c eq "$(printf 'a\na\nb\n' | $S/uniq -c | $S/sed 's/^ *//' | j)" "2 a 1 b" check uniq-d eq "$(printf 'a\na\nb\n' | $S/uniq -d)" "a" check head eq "$($S/seq 10 | $S/head -n 3 | j)" "1 2 3" check tail eq "$($S/seq 10 | $S/tail -n 2 | j)" "9 10" check tail-plus eq "$($S/seq 10 | $S/tail -n +9 | j)" "9 10" check cut-f eq "$($S/cut -d: -f1 /etc/passwd | j)" "root tyler" check cut-c eq "$(echo hello | $S/cut -c2-4)" "ell" check tr eq "$(echo hello | $S/tr a-z A-Z)" "HELLO" check tr-d-s eq "$(echo hello | $S/tr -d e)/$(echo hello | $S/tr -s l)" "hllo/helo" check sed-s eq "$(echo a:b:c | $S/sed 's/:/-/g')" "a-b-c" check sed-d eq "$($S/seq 3 | $S/sed 2d | j)" "1 3" check sed-n-p eq "$($S/sed -n '/^t/p' /etc/passwd | $S/cut -d: -f1)" "tyler" check sed-y eq "$(echo abc | $S/sed 'y/abc/xyz/')" "xyz" check grep eq "$($S/grep -c '^[rt]' /etc/passwd)/$($S/grep -v root /etc/passwd | $S/cut -d: -f1)" "2/tyler" check wc eq "$(set -- $(printf 'a b\nc d e\n' | $S/wc); printf '%s' "$*")" "2 5 10" check od eq "$(set -- $(printf 'AB\n' | $S/od -t c); printf '%s' "$*")" '0000000 A B \n 0000003' check printf eq "$($S/printf '%05d|%s|%x|%c|%5.2f' 42 hi 255 A 3.14159)" "00042|hi|ff|A| 3.14" check expr eq "$($S/expr 6 \* 7)/$($S/expr abc : 'a\(.\)')/$($S/expr 5 \> 3)" "42/b/1" check seq eq "$($S/seq 2 2 8 | j)/$($S/seq -s, 1 3)/$($S/seq -w 8 10 | j)" "2 4 6 8/1,2,3/08 09 10" check basename eq "$($S/basename /a/b/c.txt .txt)" "c" check dirname eq "$($S/dirname /a/b/c.txt)" "/a/b" check paste-comm eq "$($S/paste -s -d, nums)/$(printf 'a\nb\n' > c1; printf 'b\nc\n' > c2; $S/comm -12 c1 c2)" "1,2,3/b" check rev-fold-nl eq "$(echo abc | $S/rev)/$(echo abcdef | $S/fold -w 2 | j)/$($S/nl -b a nums | $S/sed -n 1p | $S/tr -d ' \t')" "cba/ab cd ef/11" check cat eq "$($S/cat nums hello | j)" "1 2 3 hello" # ---- checksums ---- check cksum eq "$($S/cksum < hello)" "3015617425 6" check sha256sum eq "$($S/sha256sum hello)" "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 hello" check sha256sum-c eval '$S/sha256sum hello > sums && $S/sha256sum -c sums > /dev/null' check md5sum eq "$($S/md5sum hello)" "b1946ac92492d2347c6235b4d2611184 hello" check sha1sum eq "$($S/sha1sum hello)" "f572d396fae9206628714fb2ce00f72e94f2258f hello" # ---- files, modes, links, times (A:, ext2) ---- check ls-l eval 'lsl nums && set -- $L && eq "$1 $2 $3 $4 $5" "-rw-r--r-- 1 root root 6"' check ls-l-owner eval 'lsl /etc/passwd && set -- $L && eq "$3:$4" "root:root"' check ls-ld eval 'set -- $($S/ls -ld /etc) && eq "$1" drwxr-xr-x' check chmod eval '$S/chmod 640 nums && lsl nums && set -- $L && eq "$1" -rw-r-----' check chmod-sym eval '$S/chmod u+x,g-r nums && lsl nums && set -- $L && eq "$1" -rwx------ && $S/chmod 644 nums' check ln-s eval '$S/ln -s nums sym && eq "$($S/readlink sym)" nums && eq "$($S/cat sym | j)" "1 2 3" && lsl sym && case $L in l*) ;; *) false;; esac' check ln-hard eval '$S/ln nums hard && lsl nums && set -- $L && eq "$2" 2 && eq "$($S/ls -i nums | $S/cut -d" " -f1)" "$($S/ls -i hard | $S/cut -d" " -f1)"' check touch-new eval '$S/touch empty && [ -e empty ] && [ ! -s empty ]' check touch-t eval '$S/touch -t 200001020304 nums && lsl nums && set -- $L && eq "$6 $7 $8" "Jan 02 2000"' check touch-d eval '$S/touch -d 2001-02-03T04:05:06 hello && lsl hello && set -- $L && eq "$6 $7 $8" "Feb 03 2001"' check cp-p eval '$S/chmod 600 nums && $S/cp -p nums keep && lsl keep && set -- $L && eq "$1 $6 $7 $8" "-rw------- Jan 02 2000"' check cp-plain eval '$S/cp nums fresh && lsl fresh && set -- $L && [ "$8" != 2000 ]' check cp-R-mv-rm eval '$S/mkdir -p d/e && $S/cp nums d/e/ && $S/cp -R d d2 && $S/mv d2 d3 && [ -f d3/e/nums ] && $S/rm -r d d3 && [ ! -e d3 ]' check rmdir eval '$S/mkdir -p r/s && ! $S/rmdir r 2>/dev/null && $S/rmdir r/s r && [ ! -e r ]' check dd eval '$S/dd if=/dev/zero of=zeros bs=512 count=6 2> dd.err && eq "$($S/wc -c < zeros)" 3072 && $S/grep -q "6+0 records in" dd.err' check dd-skip eq "$($S/dd if=/etc/passwd bs=1 skip=5 count=4 2>/dev/null)" "x:0:" check du eval 'set -- $($S/du -k zeros) && eq "$1" 3' check find eval '$S/mkdir -p tree/a/b && $S/touch tree/x.txt tree/a/y.txt && eq "$($S/find tree -name "*.txt" | $S/sort | j)" "tree/a/y.txt tree/x.txt"' check find-type eq "$($S/find tree -type d | $S/sort | j)" "tree tree/a tree/a/b" check find-newer eval '$S/touch -t 200001010000 tree/x.txt && eq "$($S/find tree -name "*.txt" -newer tree/x.txt)" tree/a/y.txt' check xargs eq "$(printf 'a b\nc\n' | $S/xargs $S/echo)" "a b c" check xargs-n eq "$(printf 'a b c\n' | $S/xargs -n 1 $S/echo | $S/wc -l)" "3" check tee eval 'echo hi | $S/tee t1 > t2 && echo ho | $S/tee -a t1 > /dev/null && eq "$($S/cat t1 | j)/$($S/cat t2)" "hi ho/hi"' check test eval '$S/test -d / && $S/test -f /etc/passwd && ! $S/test -e /nope && $S/test 3 -lt 5 && $S/test -L sym && $S/test -s nums && ! $S/test -s empty && $S/test abc = abc' check tar eval '$S/tar -cf t.tar tree && $S/rm -r tree && $S/tar -xf t.tar && [ -f tree/a/y.txt ] && eq "$($S/tar -tf t.tar | $S/grep -c txt)" 2' check cmp-split eval '$S/cmp -s nums keep && ! $S/cmp -s nums hello && $S/split -l 1 nums part. && eq "$($S/cat part.ab)" 2' check readlink-f eq "$($S/readlink -f sym)" "$W/nums" check mktemp eval 'f=$($S/mktemp "$W/tmp.XXXXXX") && [ -f "$f" ] && d=$($S/mktemp -d "$W/dir.XXXXXX") && [ -d "$d" ]' # ---- environment, processes, system ---- check env-i eq "$($S/env -i FOO=bar $S/env)" "FOO=bar" check env-u eval '! $S/env -u PATH $S/printenv PATH > /dev/null' check date eq "$($S/date -u -d 0 '+%Y-%m-%d %H:%M:%S')" "1970-01-01 00:00:00" check date-now eval '[ "$($S/date +%Y)" -ge 2025 ]' check yes-head eq "$($S/yes | $S/head -n 3 | j)/$($S/yes ab | $S/head -n 2 | j)" "y y y/ab ab" check sleep eval 'a=$($S/date +%s); $S/sleep 1; b=$($S/date +%s); [ $((b - a)) -ge 1 ] && [ $((b - a)) -le 3 ]' check kill-TERM eval '$S/sleep 30 & p=$!; $S/kill $p; wait $p; eq $? 143' check kill-9 eval '$S/sleep 30 & p=$!; $S/kill -9 $p; wait $p; eq $? 137' check kill-l eq "$($S/kill -l 15)" "TERM" check nice eq "$($S/nice -n 5 $S/echo niced)" "niced" check renice eval '$S/renice -n 2 -p $$ > /dev/null' check uname eq "$($S/uname -s)/$($S/uname -m)" "Rustbelt/x86_64" echo "sbase: uname -sr: $($S/uname -sr)" check whoami eq "$($S/whoami)" "root" check hostname eval '[ -n "$($S/hostname)" ]' check tail-f eval '$S/tail -f nums > tailed & p=$!; $S/sleep 1; $S/kill $p; wait $p; eq "$? $($S/cat tailed | j)" "143 1 2 3"' check true-false eval '$S/true && ! $S/false' check getconf eq "$($S/getconf PAGESIZE)" "4096" # ---- known gaps: the kernel (docs/kernel-requests.md K14) ---- xfail mkfifo "K14: mknodat 259 is ENOSYS" $S/mkfifo fifo xfail mknod-p "K14: mknodat 259 is ENOSYS" $S/mknod fifo2 p xfail chroot "K14: chroot 161 is ENOSYS" $S/chroot / $S/true xfail ls-a-dots "K14: getdents64 has no . and .. entries" eval '$S/ls -a "$W" | $S/grep -qx "\.\."' xfail touch-on-FAT "K14: FAT32 and exFAT keep no file times" eval '$S/touch -t 200001020304 /mnt/disk0/sbase-t && lsl /mnt/disk0/sbase-t && set -- $L && $S/rm -f /mnt/disk0/sbase-t && eq "$8" 2000' $S/rm -f /mnt/disk0/sbase-t # ---- tools sbase 0.1 doesn't have (its sister project ubase does) ---- notin df "ubase; busybox at M5" notin id "ubase; busybox at M5" cd / && $S/rm -r "$W" check cleanup [ ! -e "$W" ] if [ $fails = 0 ]; then echo "sbase-test: all $((passed + gaps + absent)) checks as expected ($passed passed, $gaps known gaps, $absent tools not in sbase)" else echo "sbase-test: $fails unexpected result(s) ($passed passed, $gaps known gaps)" fi exit $fails