# bonnie++ 2.00a (coker.com.au, meta-oe recipes-benchmark) -- the classic # filesystem benchmark: five sequential-I/O phases against one file (per-char # write, block write, rewrite, per-char read, block read), a random-seek phase, # and a metadata half that creates, stats and deletes many small files in both # sequential and random order. Beside it the image already carries iozone3 (one # thread sweeping record and file sizes) and tiobench (fixed size, added # concurrency); bonnie++ is the one that puts a number on METADATA at volume, # and the only one whose output is a machine-readable CSV line with companion # renderers. # # BST-TIMEOUT: 1800 # # NOTHING HERE ASSERTS A THROUGHPUT NUMBER. The rate this guest achieves belongs # to the browser host it runs in, not to the port. What belongs to the port is # that a bounded pass COMPLETES and emits a well-formed CSV line -- which is the # ticket's stated closing criterion -- and that every phase that ran produced a # cell while every phase that could not run produced an EMPTY one rather than a # plausible number. # # SIX FACTS SHAPE THIS FILE. # # 0. THE WORK DIRECTORY IS THE SUBJECT, AND /tmp IS THE WRONG ONE. The kernel # mounts a tmpfs over /tmp for session-local scratch, so `mktemp -d` lands # off the filesystem under test: a run there measures RAM and still prints a # full healthy report. 84-iozone3 measured that gap at ~60x. So the work # directory is chosen from candidates that resolve to the / mount in # /proc/mounts, /tmp is asserted NOT to be one of them (the negative # control -- without it "the work directory is on /" could be true by # construction), and nothing is measured if no such directory can be had. # 1. EVERY PROGRAM'S CONSOLE OUTPUT IS CAPTURED ON tmpfs, NEVER IN THE WORK # DIRECTORY, AND THIS IS LOAD-BEARING RATHER THAN TIDY. A shell that waits # for a child whose stdout is a file on the virtiofs root is frequently # never woken -- the child runs to completion and writes every byte, and the # wait does not return (brintos/linux#74, whose own reproducer is the `wait` # builtin). Measured on this image: `ls -l /etc > /tmp/x 2>&1` returns rc=0 # in one second having written 7201 bytes, and the SAME command redirected # onto the / mount writes the same 7201 bytes and never returns -- so the # redirect target, not the program, decides it. That is what an earlier # draft of this file spent its whole 900s bound inside, and it is why # 84-tiobench keeps its capture directory on /tmp while measuring /. The # work directory below is still on /, because that is the filesystem being # benchmarked; what moved is only where the console goes. # 2. NOTHING ON THIS IMAGE COULD BOUND A COMMAND, so this file does it. # alarm(2) was ENOSYS before the brintos/linux#77 fix, so timeout(1) never # fired, and `sleep N` frequently does not advance the clock # (brintos/linux#48). run_bounded # below polls a sentinel FILE rather than the pid, because a finished child # is a zombie until reaped and `kill -0` succeeds on one; and the polling is # BUILTIN-ONLY, because an exec per iteration leaks a park slot per exec # (brintos/hwjs-cc#69) and grows the host process by gigabytes -- measured # at 2125 MiB/min while writing this file. # 3. THE SEEK PHASE IS THE ONLY THREADED PART OF bonnie++, and it runs LAST. # seek_test() calls Thread::go(NULL, SeekProcCount); pthread_create is # refused with EAGAIN on this arm (linux-in-the-browser#85, with # brintos/hardwarejs#12 behind it). Upstream called exit(1) there, which # discarded five already-completed sequential measurements along with the # report that carries them, so the bbappend's 0002 skips the phase instead # and leaves its cells empty. seeks-measured below is the anchor case that # says so, stated POSITIVELY: it goes green by itself the day #85 lands, and # nothing here needs editing then. # 4. getrusage(2) IS UNWIRED ON THIS ARM (brintos/linux#78 -- no [98] row in the # wasm32 syscall table). bonnie++ ignored its return value and formatted the # uninitialised struct, which on an x86_64 control with one LD_PRELOAD # ENOSYS printed per-char CPU columns of 86170 and 121006 percent. 0001 # reports 0.0 instead -- the honest answer when no counters were obtained -- # so the %CP columns read 0 on this image. cpu-accounting-available is the # other positively-stated anchor. # 5. THE PASS IS SMALL ON PURPOSE, AND IT IS THE BLOCK PASS. 1 MiB of file at # an 8 kB chunk. Guest memory is not returned to the kernel on process exit # (brintos/linux#25) and the virtiofs root is slow (84-iozone3 measured # 727 kB/s writing), so the hundreds of MiB bonnie++ is normally pointed at # would both starve everything scheduled behind it in this boot and cost # minutes. -r 0 is deliberate: it tells bonnie++ to make no claim about RAM, # which switches off every heuristic that would otherwise reject a file this # small as unrepresentative. # # THE PASS THAT CLOSES THE TICKET CARRIES -f AND -n 0, so the two per-char # phases and the metadata half are not in it. That is not a smaller sample # of the same thing -- it is two phases this arm cannot finish and one that # fails, and each gets its OWN positively-stated anchor case below instead of # being allowed to take the whole record down with it. Both anchors run the # work for real, on the same filesystem, and report what happened. The # ticket's criterion -- a bounded pass completes and emits a well-formed CSV # line -- is met by the block pass, which returns rc=0 in about two seconds. . "$BST_LIB" # EVERY bonnie++ AND getc_putc INVOCATION BELOW CARRIES -u, AND IT IS NOT # OPTIONAL HERE. Both refuse outright when geteuid() is 0 without it -- # "You must use the \"-u\" switch when running as root." followed by usage() -- # and the login on this guest IS root, so a suite written the way one is written # on a workstation measures nothing at all. -u then calls setgid(2)/setuid(2), # and every number in that family is NULL on wasm32 (brintos/linux#93), so # without the bbappend's 0004 there is no form of -u that works either. 0004 # honours the request only when the process already holds the identity asked # for; handing it this shell's own user is exactly that case, and the guard # itself is asserted at the foot of this file. U=$(id -un 2>/dev/null || id -u) # Absolute path, not a bare name: `require` uses `command -v`, so a bare # `bonnie++` would turn a PATH that lacks /usr/sbin into a whole-suite SKIP -- # coverage silently missing rather than reported. PATH placement is its own case # below instead. require /usr/sbin/bonnie++ bonnie/present # install-bin puts the four measuring programs in ${sbindir} and the two # renderers in ${bindir}; all six ship in this package, so an absence here is a # packaging failure rather than a legitimately-absent tool -- `check`, not # `require`. for p in /usr/sbin/bonnie++ /usr/sbin/zcav /usr/sbin/getc_putc \ /usr/sbin/getc_putc_helper /usr/bin/generate_randfile \ /usr/bin/bon_csv2html /usr/bin/bon_csv2txt; do n=${p##*/} check bonnie/installed-$n test -x "$p" done # The programs are reachable by name, which is how a user invokes them and how # every command below does. check bonnie/on-path command -v bonnie++ # --- capture directory (tmpfs) and the bound --------------------------------- # See fact 1: this is where every program's stdout and stderr go. It is NOT the # work directory and nothing in it is measured. RD=/tmp/.bst-bonnie rm -rf "${RD:?}" mkdir -p "$RD" || { bst_notok bonnie/capture-dir "cannot create $RD"; exit 0; } now() { read NOW _rest < /proc/uptime; NOW=${NOW%.*}; } spin() { _i=0; while [ $_i -lt 20000 ]; do _i=$((_i + 1)); done; } # run_bounded -- sets RB_RC to the command's status, or # 124 iff WE killed it, and RB_SECS to the wall clock it took. The command's # output lands in $RD/.out and $RD/.err. RB_RC=0; RB_SECS=0 run_bounded() { _lim=$1; _tag=$2; shift 2 rm -f "$RD/$_tag.done" { "$@" > "$RD/$_tag.out" 2> "$RD/$_tag.err"; echo $? > "$RD/$_tag.done"; } & _p=$! now; _s=$NOW while [ ! -f "$RD/$_tag.done" ]; do now if [ $((NOW - _s)) -ge "$_lim" ]; then kill -9 "$_p" 2>/dev/null RB_RC=124; RB_SECS=$((NOW - _s)); return 0 fi spin done now; RB_SECS=$((NOW - _s)) read RB_RC < "$RD/$_tag.done" return 0 } # --- the work directory is on the virtiofs root ------------------------------ # The longest mount point in /proc/mounts that is a prefix of $1, and its type. mount_of() { awk -v p="$1" ' { mp = $2; n = length(mp) if (substr(p,1,n) == mp && (n == 1 || length(p) == n || substr(p,n+1,1) == "/") && n >= best) { best = n; mp_best = mp; ty = $3 } } END { printf "%s %s", (mp_best == "" ? "?" : mp_best), (ty == "" ? "?" : ty) }' /proc/mounts } rty=$(awk '$2 == "/" { print $3; exit }' /proc/mounts) check_eq bonnie/rootfs-is-virtiofs virtiofs "$rty" W= for d in /home/root /var/lib /usr/lib/brintos-tests; do [ -d "$d" ] || continue c="$d/.bst-bonnie.$$" mkdir -p "$c" 2>/dev/null || continue cm=$(mount_of "$c") if [ "${cm%% *}" = "/" ]; then W=$c; break; fi rmdir "$c" 2>/dev/null done if [ -n "$W" ]; then bst_ok bonnie/workdir-on-virtiofs-root else bst_notok bonnie/workdir-on-virtiofs-root \ "no writable directory on the / mount -- measuring the /tmp tmpfs instead would be a false green, so nothing was measured" exit 0 fi # The negative control for the case above: if every path on this image resolved # to /, the check would be true by construction and would prove nothing. tm=$(mount_of /tmp); tmp_mp=${tm%% *}; tmp_ty=${tm#* } if [ "$tmp_mp" != "/" ]; then bst_ok bonnie/tmpfs-control else bst_notok bonnie/tmpfs-control \ "/tmp resolves to / ($tmp_ty), so the work-directory check above cannot distinguish the two" fi # The other half of fact 1, asserted rather than assumed: the capture directory # must NOT be on the filesystem under test, or every bounded run below inherits # the wait that does not return. cm=$(mount_of "$RD") if [ "${cm%% *}" != "/" ]; then bst_ok bonnie/capture-off-virtiofs else bst_notok bonnie/capture-off-virtiofs \ "$RD resolves to the / mount, so every captured run below can wedge on brintos/linux#74" fi # --- one bounded pass; every structural case below reads THIS run ------------ # Run once. Re-invoking per assertion would cost minutes on a cloud filesystem # and would let two cases disagree about which run they describe. -q sends the # human-readable report to stderr and leaves the CSV alone on stdout, which is # what makes the CSV assertions below exact rather than best-effort greps. run_bounded 300 run bonnie++ -u "$U" -d "$W" -s 1:8192 -f -n 0 -r 0 -q -m bst rc=$RB_RC if [ "$rc" = 124 ]; then bst_notok bonnie/run-rc "the bounded pass did not finish inside 300s and was killed" elif [ "$rc" -eq 0 ]; then bst_ok bonnie/run-rc else bst_notok bonnie/run-rc "rc=$rc after ${RB_SECS}s: $(_bst_1line "$(cat "$RD/run.err" 2>/dev/null)")" fi # Exactly one CSV record. bonnie++ prints one per test run (-x defaults to a # single run), and a second would mean the run repeated. lines=$(grep -c . "$RD/run.out" 2>/dev/null || echo 0) check_eq bonnie/csv-one-line 1 "$lines" csv=$(head -1 "$RD/run.out" 2>/dev/null) # 50 comma-separated fields: bonnie++'s CSV is positional and fixed-width, so a # short line is a truncated report and a long one is a format the renderers # below would misread. Asserted before any field is read out of it. check_eq bonnie/csv-field-count 50 "$(printf '%s' "$csv" | awk -F, '{print NF}')" # Field 1 is the CSV format version and field 2 the bonnie++ version. The format # version is what bon_csv2txt and bon_csv2html branch on, so it is pinned; the # program version is NOT pinned to an exact string, because the recipe # regenerates configure and @version@ has been seen to differ between the # shipped and the regenerated script. Non-empty is the honest assertion. check_eq bonnie/csv-format-version 1.98 "$(printf '%s' "$csv" | cut -d, -f1)" v2=$(printf '%s' "$csv" | cut -d, -f2) case "$v2" in 2.00*) bst_ok bonnie/csv-tool-version ;; *) bst_notok bonnie/csv-tool-version "field 2 is '$v2', expected a 2.00 series version" ;; esac # Field 3 is the machine name and comes from -m, so it proves the record is the # one this suite asked for rather than a stale file. Field 6 is the file size as # bonnie++ understood it, which is where -s 1 lands, and field 8 the chunk size, # which is the other half of the same argument -- both pinned so a misparsed -s # cannot pass as a measurement of something else. check_eq bonnie/csv-machine bst "$(printf '%s' "$csv" | cut -d, -f3)" check_eq bonnie/csv-file-size 1M "$(printf '%s' "$csv" | cut -d, -f6)" check_eq bonnie/csv-chunk-size 8192 "$(printf '%s' "$csv" | cut -d, -f8)" # --- the phases that ran produced numbers ------------------------------------ # Fields 10, 16 and 22 are the per-char write, per-char read and file-count # columns -- one from each of the three halves of the report, so a half that # silently did nothing cannot hide behind the other two. bonnie++ prints `+++++` # for a phase that finished under its minimum timing window, which is a # measurement that completed rather than one that is missing, so it passes. num_or_plus() { case "$1" in +++++|+++) return 0 ;; ''|*[!0-9.]*) return 1 ;; *) [ "${1%%.*}" -gt 0 ] 2>/dev/null || return 1; return 0 ;; esac } for f in 12:seq-block-write 14:seq-rewrite 18:seq-block-read; do i=${f%%:*}; nm=${f#*:} val=$(printf '%s' "$csv" | cut -d, -f"$i") if num_or_plus "$val"; then bst_ok "bonnie/measured-$nm" else bst_notok "bonnie/measured-$nm" "field $i is '$val', expected a positive rate or +++++"; fi done # No cell anywhere in the record is a stopped clock rendered as a rate. bonnie++ # divides elapsed time into its figures, so a zeroed timer surfaces as inf/nan # rather than as a zero -- the same trap 84-iozone3 names. badcell=$(printf '%s' "$csv" | awk -F, '{ for (i = 1; i <= NF; i++) if ($i ~ /inf|nan/) printf "col%d=%s ", i, $i }') if [ -z "$badcell" ]; then bst_ok bonnie/no-infinite-cells else bst_notok bonnie/no-infinite-cells "$(_bst_1line "non-finite cells: $badcell")"; fi # --- both halves of the report were rendered --------------------------------- # -q suppresses the per-phase progress lines and sends the human-readable report # to stderr, leaving stdout to the CSV alone -- which is what lets every case # above cut fields out of it exactly. What stderr carries is the rendered # report, and its section headers are the tool's own statement of which halves # it ran: a bonnie++ that had done the file work and skipped the directory work # would print the first block and not the second. for phrase in "Sequential Output" "Sequential Input" "Latency"; do id=$(printf '%s' "$phrase" | tr 'A-Z ' 'a-z-') check_out "bonnie/report-$id" "$phrase" cat "$RD/run.err" done # --- ANCHOR CASE: the seek phase, positively stated (linux-in-the-browser#85) - # Fields 20 and 21 are the random-seek rate and its %CP. On an arm that can # create a thread they carry a measurement; here 0002 leaves them empty so the # five sequential results above survive. Stated as "seeks were measured" so the # day #85 lands this goes green on its own. seek=$(printf '%s' "$csv" | cut -d, -f20) if num_or_plus "$seek"; then bst_ok bonnie/seeks-measured else bst_notok bonnie/seeks-measured \ "random-seek cells empty: seek_test() needs SeekProcCount threads and pthread_create is refused EAGAIN here -- linux-in-the-browser#85" fi # The consequence that matters, and the reason 0002 exists: the run still # finished and still reported. This one must be green on this arm -- it is the # ticket's closing criterion, and it is what upstream's exit(1) took away. if [ "$rc" -eq 0 ] && [ -n "$csv" ]; then bst_ok bonnie/completes-without-seekers else bst_notok bonnie/completes-without-seekers \ "rc=$rc with $(printf '%s' "$csv" | wc -c) bytes of CSV -- a refused thread must cost the seek phase, not the report" fi # --- ANCHOR CASE: CPU accounting, positively stated (brintos/linux#78) -------- # Field 11 is the per-char write %CP. 0001 makes it 0 where getrusage fails, so # a non-zero value here means the syscall came back -- which is exactly what # #78 landing looks like from user space. cpu=$(printf '%s' "$csv" | cut -d, -f11) if [ "$cpu" != "0" ] && [ -n "$cpu" ]; then bst_ok bonnie/cpu-accounting-available else bst_notok bonnie/cpu-accounting-available \ "per-char write %CP is '$cpu': getrusage(2) is unwired on wasm32 so no CPU counters were obtained -- brintos/linux#78" fi # --- ANCHOR CASE: the per-char phases, positively stated (brintos/linux#99) -- # -f above turned OFF the two single-byte phases, and that is the only reason # the pass above finishes. bonnie++ has no knob to make them smaller than 1 MiB: # -f takes an optional size in MiB, byte_io_size is min(file_size, that), and a # 0 means skip. So on this arm the choice is 1 MiB of putc/getc or none. # # MEASURED ON THIS IMAGE, same file, same chunk, same directory, one flag apart: # `-s 1:8192 -f -n 0` returns rc=0 in 2s and reports 1107 kB/s block write and # 1050 kB/s rewrite, and `-s 1:8192 -n 0` does not finish in 420s. So the block # path moves 1 MiB in about a second and the single-byte path does not move it # in seven minutes -- the gap is the per-call cost, not the filesystem, and it # is why the shipped pass takes -f. Filed as brintos/linux#99 with a # package-free reproducer: `dd bs=1 count=65536` onto / is killed at 300s having # written 63336 bytes (~211 B/s), `dd bs=8192 count=128` writes the whole MiB in # 1s, and the same bs=1 onto /tmp finishes in 4s -- so it is a per-CALL cost on # the virtiofs path, about 4.7 ms a write, and not a byte-rate. # # Stated as "the per-char phases complete", so it goes green by itself when the # per-call cost comes down and nothing here needs editing then. run_bounded 300 pc bonnie++ -u "$U" -d "$W" -s 1:8192 -n 0 -r 0 -q -m bstpc if [ "$RB_RC" -eq 0 ]; then bst_ok bonnie/per-char-phases-complete else bst_notok bonnie/per-char-phases-complete \ "rc=$RB_RC after ${RB_SECS}s: 1 MiB of putc/getc does not complete on this arm, while the same MiB of block I/O takes ~2s -- brintos/linux#99" fi # --- ANCHOR CASE: the metadata half, positively stated (brintos/linux#100) --- # One DirectoryUnit (1024 files) created, stat'd and deleted in sequential and # random order -- the half that is bonnie++'s reason to exist beside iozone3 and # tiobench, and the reason -n 1 is not simply dropped from this suite. # # MEASURED ON THIS IMAGE: it creates and stats the whole unit and then dies in # the delete phase -- "Bonnie: drastic I/O error (rmdir): Directory not empty" # after 22s, every time. It is NOT a plain unlink/rmdir fault: this file's own # neighbours do that constantly, and a package-free control on the same # filesystem -- 512 files created with shell redirects, removed with one rm, then # rmdir -- leaves 0 entries and returns 0. Filed as brintos/linux#100 with both # arms; it goes green here by itself the day that lands. run_bounded 300 meta bonnie++ -u "$U" -d "$W" -s 0 -n 1 -r 0 -q -m bstmeta if [ "$RB_RC" -eq 0 ]; then bst_ok bonnie/metadata-half-completes else bst_notok bonnie/metadata-half-completes \ "rc=$RB_RC after ${RB_SECS}s: $(_bst_1line "$(cat "$RD/meta.err" 2>/dev/null)") -- brintos/linux#100" fi # When the metadata half DOES run, every one of its twelve columns (sequential # and random create, read and delete, rate and %CP) must carry a cell -- the # half a "did it finish" check cannot see, since bonnie++ prints a full line # whether or not the file operations produced anything. Skipped rather than # failed while the run above cannot complete, because there is no record to read. mcsv=$(head -1 "$RD/meta.out" 2>/dev/null) if [ -n "$mcsv" ]; then meta_missing= i=27 while [ "$i" -le 38 ]; do val=$(printf '%s' "$mcsv" | cut -d, -f"$i") num_or_plus "$val" || meta_missing="$meta_missing $i='$val'" i=$((i + 1)) done if [ -z "$meta_missing" ]; then bst_ok bonnie/metadata-columns-complete else bst_notok bonnie/metadata-columns-complete "$(_bst_1line "empty or non-numeric metadata cells:$meta_missing")"; fi else bst_skip bonnie/metadata-columns-complete \ "the metadata run emitted no CSV record, so there are no columns to read -- see bonnie/metadata-half-completes" fi # --- the renderers read what bonnie++ wrote ---------------------------------- # bon_csv2txt is the perl front end and bon_csv2html the C++ one. Both are the # reason the CSV format version is pinned above: they parse it positionally. run_bounded 120 c2t sh -c "bon_csv2txt < '$RD/run.out'" check_eq bonnie/csv2txt-rc 0 "$RB_RC" check_out bonnie/csv2txt-renders "Version" cat "$RD/c2t.out" check_out bonnie/csv2txt-machine "bst" cat "$RD/c2t.out" run_bounded 120 c2h sh -c "bon_csv2html < '$RD/run.out'" check_eq bonnie/csv2html-rc 0 "$RB_RC" check_out bonnie/csv2html-renders "/dev/null | tr -d ' ') case "$rfsz" in 128) bst_ok bonnie/randfile-ilp32 ;; 256) bst_notok bonnie/randfile-ilp32 "32 numbers occupy 256 bytes: sizeof(unsigned long) is 8, not the ILP32 4 this arm uses" ;; *) bst_notok bonnie/randfile-ilp32 "32 numbers occupy $rfsz bytes, expected 128" ;; esac run_bounded 120 gr2 generate_randfile -s 7 -f "$W/r2" 32 if cmp -s "$W/r1" "$W/r2"; then bst_ok bonnie/randfile-seed-reproducible else bst_notok bonnie/randfile-seed-reproducible "the same -s 7 gave two different files"; fi # --- getc_putc --------------------------------------------------------------- # The per-character stdio benchmark, and the only program in this package that # reaches a SECOND one: it popen()s ${argv0}_helper to time putc from a process # with no threads linked in. That makes it coverage of fork+exec on this arm as # much as of stdio, which is why the helper's own presence is asserted above. # -s is in KiB and is rounded DOWN to a multiple of 64 before being shifted, so # anything under 64 becomes zero and the program prints its usage instead of # measuring. 64 is therefore the smallest honest request, not a preference. run_bounded 600 gp getc_putc -u "$U" -d "$W" -s 64 -q if [ "$RB_RC" -eq 0 ]; then bst_ok bonnie/getc-putc-rc else bst_notok bonnie/getc-putc-rc \ "rc=$RB_RC $(_bst_1line "$(cat "$RD/gp.err" 2>/dev/null)") -- \"Can't execute\" is getc_putc reporting that popen(3) returned NULL, so the failure is fork+pipe and not the helper, which is installed and asserted above -- brintos/linux#101" fi # -q leaves one CSV record on stdout: the machine name and the eight timings # (write, read, putc/getc with no threads linked, putc/getc, and the _unlocked # pair). A short line means a timing that produced nothing. check_eq bonnie/getc-putc-fields 9 "$(head -1 "$RD/gp.out" 2>/dev/null | awk -F, '{print NF}')" # The popen()'d helper is where the two NT columns come from, so a helper that # never ran leaves the parent reporting "Can't get results from child." rather # than a short line. Naming it separately is what tells fork+exec failing from # stdio failing. if grep -q "Can't get results from child" "$RD/gp.err" 2>/dev/null; then bst_notok bonnie/getc-putc-helper-ran "popen()'d getc_putc_helper produced no result -- fork+exec, not stdio" else bst_ok bonnie/getc-putc-helper-ran fi # --- ANCHOR CASE: zcav, positively stated (linux-in-the-browser#85) ----------- # zcav reads a file or device in constant-size chunks and reports the rate at # each offset. Every one of its readers is a thread and each IS a measurement, # so 0002 deliberately leaves it a hard failure rather than degrading it. That # makes this the second witness of #85 in this package, and it also goes green # on its own. run_bounded 300 zcd dd if=/dev/zero of="$W/zc.dat" bs=8192 count=64 run_bounded 300 zc zcav -f "$W/zc.dat" -b 1 if [ "$RB_RC" -eq 0 ]; then bst_ok bonnie/zcav-runs else bst_notok bonnie/zcav-runs \ "rc=$RB_RC -- zcav's readers are threads and pthread_create is refused EAGAIN here: $(_bst_1line "$(cat "$RD/zc.err" 2>/dev/null)") -- linux-in-the-browser#85" fi # --- ANCHOR CASE: the System V semaphore mode, positively stated (hwjs-cc#31) - # -p creates a semaphore set so N concurrent bonnie++ processes enter each phase # together, and -y joins one. Both call semctl(2) with a `union semun` by value # through its variadic tail -- POSIX's documented convention -- which the # coroutinize pass cannot lower, so the bbappend builds those two call sites out # (NO_SYSV_SEM) and they refuse in words instead of failing the whole-program # link. Stated positively: the day #31 lands and the define goes, this asks # whether the mode then works. run_bounded 120 sem bonnie++ -u "$U" -p 2 -d "$W" if [ "$RB_RC" -eq 0 ]; then bst_ok bonnie/sysv-semaphore-mode else bst_notok bonnie/sysv-semaphore-mode \ "rc=$RB_RC $(_bst_1line "$(cat "$RD/sem.err" 2>/dev/null)") -- semctl(2)'s union tail is unlowerable on this arm, so -p/-y are built out (hwjs-cc#31)" fi # The refusal is in words rather than a crash or a silent success: whatever a # user types, they get told. This one must be green on this arm. if grep -q "System V semaphores are not available" "$RD/sem.err" "$RD/sem.out" 2>/dev/null; then bst_ok bonnie/sysv-semaphore-refusal-is-explained else bst_notok bonnie/sysv-semaphore-refusal-is-explained \ "$(_bst_1line "neither stream said why -p is unavailable: $(cat "$RD/sem.err" "$RD/sem.out" 2>/dev/null)")" fi # --- error paths ------------------------------------------------------------- # A directory that does not exist, and a request with neither half of the work # asked for. Both must be refused rather than measured. run_bounded 120 e1 bonnie++ -u "$U" -d "$W/no-such-dir" -s 1 -n 0 -r 0 -q check_eq bonnie/missing-dir-rc 1 "$RB_RC" run_bounded 120 e2 bonnie++ -u "$U" -d "$W" -s 0 -n 0 -r 0 -q if [ "$RB_RC" -ne 0 ]; then bst_ok bonnie/no-work-refused else bst_notok bonnie/no-work-refused "rc=0 for a run asked to measure neither files nor directories"; fi # The guard that makes -u mandatory here, asserted rather than assumed: without # it, a root invocation must refuse. If this ever goes red the runs above are # no longer measuring what their options say, because the -u they carry has # stopped being load-bearing. if [ "$(id -u)" = "0" ]; then run_bounded 120 rg bonnie++ -d "$W" -s 1 -n 0 -r 0 -q if grep -q "You must use the" "$RD/rg.err" 2>/dev/null; then bst_ok bonnie/root-without-u-refused else bst_notok bonnie/root-without-u-refused \ "$(_bst_1line "a root run with no -u was not refused: rc=$RB_RC $(cat "$RD/rg.err" 2>/dev/null)")" fi else bst_skip bonnie/root-without-u-refused "this suite is not running as uid 0, so the guard is not reachable" fi rm -rf "${W:?}" "${RD:?}"