. "$BST_LIB" # BST-TIMEOUT: 900 # 202 cvt(1) execs plus the controls; measured, see the doc # libxcvt -- cvt(1) compared BYTE FOR BYTE against a native x86-64 oracle. # # WHY THIS SUITE IS A COMPARISON AND NOT A SET OF ASSERTIONS. cvt(1) takes a # resolution and a refresh rate on argv and prints a modeline. It opens nothing -- # no config, no device, no clock -- so every answer it can give is knowable in # advance, and writing them down by hand is the one thing not worth doing: a # hand-written expectation is a snapshot of one upstream version that rots into a # false red the day poky bumps libxcvt, and gets re-blessed from whatever the target # printed, which is the comparison inverted. So the expectation is generated at # build time by libxcvt-native -- the SAME recipe version, same meson options, built # for the build host -- and installed as /usr/libexec/libxcvt-tests. This suite # re-runs the identical argv here and requires identical bytes. # # WHAT MAKES THE COMPARISON WORTH MAKING. The computation is floating point at # every step that decides an integer: hperiod is a float; hblank, # vsync_and_back_porch and vblank_interval_lines are int truncations of float # expressions; dot_clock is a uint64 truncation of a double; hsync and vrefresh are # floats printed at two decimals. A wasm32 build one ULP away from x86-64 anywhere # in that chain prints a modeline that is ALMOST right -- correct resolution, # correct sync polarity, one integer wrong in a field nobody reads first. That is # why nothing here compares a prefix, a first field, or a summary: it is stdout, # stderr and exit status, whole, for every case. # # AND WHY THE CONTROLS AT THE END ARE NOT DECORATION. A comparator with a bug in it # passes everything, and a suite of 202 green comparisons looks exactly the same # either way. The controls take a known-good oracle answer, change ONE character of # it -- the last digit of the vertical total, a character of the comment line, the # sync flag, the trailing newline -- and require the comparator to say so. Each # mutation reports whether it actually applied, so a control that poses no question # is a named failure rather than a quiet pass. bst_ok libxcvt/suite-canary # first line: absorbs brintos/linux#96, which drops it require cvt libxcvt/present T=/usr/libexec/libxcvt-tests C=$T/cases if [ ! -f "$T/manifest" ] || [ ! -f "$T/oracle-info" ]; then bst_notok libxcvt/corpus "no oracle corpus at $T -- libxcvt-tests is not installed, so every comparison in this suite is unrunnable" exit 0 fi W=${TMPDIR:-/tmp}/bst-libxcvt.$$ if ! mkdir -p "$W" 2>/dev/null; then bst_notok libxcvt/stage "cannot create working directory $W" exit 0 fi # The manifest is READ FROM A COPY IN /tmp, never redirected from the rootfs. A # top-level `while read ... done < /usr/...` on the virtiofs root hits # brintos/linux#73: dup2(2) returns 513, ash prints "Unknown error 513" and exits, # and the runner then reports every case emitted so far as a pass and nothing at # all about the ones never reached -- a clean-looking run that is simply shorter. if ! cp "$T/manifest" "$W/manifest" 2>/dev/null; then bst_notok libxcvt/stage "cannot copy $T/manifest to $W" rm -rf "$W"; exit 0 fi info_pv=$(sed -n 's/^pv //p' "$T/oracle-info") info_arch=$(sed -n 's/^oracle-arch //p' "$T/oracle-info") info_cases=$(sed -n 's/^cases //p' "$T/oracle-info") info_fields=$(sed -n 's/^fields //p' "$T/oracle-info") info_bytes=$(sed -n 's/^bytes //p' "$T/oracle-info") # The ticket's criterion names the oracle's architecture, so the suite asserts it # rather than assuming the build host was one. A corpus generated on any other # architecture is still a valid self-consistency check and is NOT the comparison # this port was asked for. check_eq libxcvt/oracle-arch x86_64 "$info_arch" # cmp is the byte-exact comparator; the fallback exists so a stripped image reports # a real comparison rather than skipping the whole suite. `$(cat)` drops trailing # newlines on both sides, so the byte count is what closes that hole -- and # control-trailing-newline below proves the pair actually does. if command -v cmp >/dev/null 2>&1; then LX_CMP=1; else LX_CMP=; fi lx_same() { # lx_same if [ -n "$LX_CMP" ]; then cmp -s "$1" "$2" else [ "$(cat "$1")" = "$(cat "$2")" ] && [ "$(wc -c < "$1")" = "$(wc -c < "$2")" ] fi } # A readable case name built from the argv, with no fork per case. lx_label() { lx_lbl= for _a in $1; do case "$_a" in -r) _a=r ;; -i) _a=i ;; -v) _a=v ;; -h) _a=h ;; --help) _a=help ;; esac if [ -z "$lx_lbl" ]; then lx_lbl=$_a; else lx_lbl=$lx_lbl-$_a; fi done } # --------------------------------------------------------------------------- # The comparison. Three kinds of case, reported two ways. # # CURATED cases are one per distinct code path, so a red names which path broke. # # BOUNDARY cases are the ones this suite's sensitivity actually rests on. Each is a # grid point where perturbing one of libxcvt's three float results by a single ULP # was MEASURED to change the printed output -- see libxcvt-oracle.sh for the sweep # and its densities. Without them a one-ULP divergence in hperiod, the value every # integer truncation reads, would show up in 0.022% of the parameter space and this # suite would almost certainly not contain a case that noticed. Both kinds get one # named result line each. # # SWEPT cases are 16 resolutions x 6 timing modes over paths the curated set already # visited, differing only in the numbers flowing through them. They are reported as # ONE aggregate carrying the count and the first disagreement, because 96 # individually-named arithmetic cases would bury the 106 that localise. # --------------------------------------------------------------------------- n_all=0 n_cur=0 n_bnd=0 n_swp=0 swp_bad=0 swp_first= bnd_classes= : > "$W/list" while read -r id kind rc args; do [ -n "$id" ] || continue n_all=$((n_all + 1)) # One path per line, into a file -- never onto an argv. 404 paths is ~19 kB # against the guest's 8192-byte __wasm32_args_buf, and an exec that exceeds it # never returns: the suite is reported as guest-wedged, with no case and no # cause (brintos/hardwarejs#15). `printf >>` is a builtin and forks nothing. printf '%s\n%s\n' "$C/$id.out" "$C/$id.err" >> "$W/list" cvt $args > "$W/o" 2> "$W/e" < /dev/null arc=$? diff= lx_same "$C/$id.out" "$W/o" || diff=stdout # Both empty is equality, and `[ -s ]` is a builtin -- worth the branch, since # most cases write no stderr at all and this halves the suite's fork count. if [ -s "$C/$id.err" ] || [ -s "$W/e" ]; then lx_same "$C/$id.err" "$W/e" || diff="${diff:+$diff,}stderr" fi [ "$arc" = "$rc" ] || diff="${diff:+$diff,}rc(oracle $rc, target $arc)" if [ "$kind" != sweep ]; then case "$kind" in curated) n_cur=$((n_cur + 1)); probes= ;; boundary/*) n_bnd=$((n_bnd + 1)) probes=" [boundary case for ${kind#boundary/}]" case " $bnd_classes " in *" ${kind#boundary/} "*) : ;; *) bnd_classes="$bnd_classes ${kind#boundary/}" ;; esac ;; *) probes=" [$kind]" ;; esac lx_label "$args" if [ -z "$diff" ]; then bst_ok "libxcvt/$id-$lx_lbl" else bst_notok "libxcvt/$id-$lx_lbl" "cvt $args differs from the x86-64 oracle in $diff$probes -- oracle: $(_bst_1line "$(cat "$C/$id.out" "$C/$id.err")") target: $(_bst_1line "$(cat "$W/o" "$W/e")")" fi else n_swp=$((n_swp + 1)) if [ -n "$diff" ]; then swp_bad=$((swp_bad + 1)) [ -n "$swp_first" ] || swp_first="cvt $args ($diff) oracle: $(_bst_1line "$(cat "$C/$id.out")") target: $(_bst_1line "$(cat "$W/o")")" fi fi done < "$W/manifest" if [ "$swp_bad" -eq 0 ] && [ "$n_swp" -gt 0 ]; then bst_ok libxcvt/sweep else bst_notok libxcvt/sweep "$swp_bad of $n_swp swept cases differ from the x86-64 oracle; first: $swp_first" fi # The corpus must be the whole corpus. A partially-installed one would make every # comparison above pass over fewer cases, which is the one regression a green run # cannot show. check_eq libxcvt/case-count "$info_cases" "$n_all" # ...and the boundary set in particular, because it is the whole of this suite's # ability to see a one-ULP divergence and it is the part that would go missing # without any other symptom. check_eq libxcvt/boundary-count "$(sed -n 's/^boundary //p' "$T/oracle-info")" "$n_bnd" # ...and it must contain what the oracle says it contains. This recomputes the word # and byte totals HERE, from the installed files, rather than trusting the header; # awk opens the paths itself, out of the list built above. set -- $(awk -v L="$W/list" ' BEGIN { w = 0; b = 0 while ((getline f < L) > 0) { while ((getline line < f) > 0) { b += length(line) + 1; w += split(line, a, " ") } close(f) } print w, b }' < /dev/null) check_eq libxcvt/corpus-fields "$info_fields" "${1:-}" check_eq libxcvt/corpus-bytes "$info_bytes" "${2:-}" # --------------------------------------------------------------------------- # Controls. Every case above compares two files and passes when they agree, so a # comparator that always agrees produces exactly the report seen above. These take # a known-good oracle answer and change ONE character of it. # # c12 is `cvt 1920 1080` -- two lines, a comment and a modeline, and the case whose # expected bytes a reader of this transcript is most likely to recognise. # --------------------------------------------------------------------------- ORACLE=$C/c12.out if [ ! -s "$ORACLE" ]; then bst_notok libxcvt/control-corpus "no $ORACLE to mutate -- the controls cannot run, so every comparison above is unverified" else cp "$ORACLE" "$W/ctl.same" if lx_same "$ORACLE" "$W/ctl.same"; then bst_ok libxcvt/control-identity else bst_notok libxcvt/control-identity "the comparator reports an unmodified copy of $ORACLE as different -- every result above is meaningless" fi # Each mutation program exits 0 only if it actually changed something, so a # control that posed no question says so instead of reading as a comparator # failure. A control written in the same sitting as the thing it judges is more # often wrong than the thing is. lx_ctl() { # lx_ctl if awk "$2" "$ORACLE" > "$W/ctl.mut"; then if lx_same "$ORACLE" "$W/ctl.mut"; then bst_notok "libxcvt/$1" "the comparator did not see $3 -- a target modeline wrong in that place would have been reported as a pass" else bst_ok "libxcvt/$1" fi else bst_notok "libxcvt/$1" "the mutation ($3) did not apply to $ORACLE, so this control posed no question" fi } # The failure this port is least able to notice: a modeline right everywhere # except one integer. The digit flipped is the last of the vertical total, the # last number before the sync flags. lx_ctl control-vtotal-digit ' NR==2 { i = match($0, / [-+]hsync/) if (i > 1) { c = substr($0, i-1, 1); r = (c == "0" ? "1" : "0") $0 = substr($0, 1, i-2) r substr($0, i); ch = 1 } } { print } END { exit(ch ? 0 : 1) }' "one digit of the vertical total changed" # The comment line is compared too, not just the Modeline -- it carries the # computed hsync and pixel clock, the two float-printed values. lx_ctl control-comment-line ' NR==1 { c = substr($0, length($0), 1); r = (c == "0" ? "1" : "0") $0 = substr($0, 1, length($0)-1) r; ch = 1 } { print } END { exit(ch ? 0 : 1) }' "the last character of the comment line changed" # ...and so is the tail of the modeline, where the sync polarity lives. lx_ctl control-sync-flag ' NR==2 { c = substr($0, length($0), 1); r = (c == "0" ? "1" : "0") $0 = substr($0, 1, length($0)-1) r; ch = 1 } { print } END { exit(ch ? 0 : 1) }' "the last character of the modeline changed" # Byte-exact, not line-content-equal. This is the one the `$(cat)` fallback # comparator would miss on its own; the byte count is what closes it, and this # is the case that proves the pair actually does. awk '{ if (NR > 1) printf "\n"; printf "%s", $0 }' "$ORACLE" > "$W/ctl.nonl" if [ "$(wc -c < "$W/ctl.nonl")" = "$(wc -c < "$ORACLE")" ]; then bst_notok libxcvt/control-trailing-newline "the mutation did not remove the trailing newline, so this control posed no question" elif lx_same "$ORACLE" "$W/ctl.nonl"; then bst_notok libxcvt/control-trailing-newline "the comparator reports a copy with the trailing newline removed as identical -- the comparison is not byte-exact" else bst_ok libxcvt/control-trailing-newline fi fi # --------------------------------------------------------------------------- # The static-only shape of the port, asserted rather than assumed. libxcvt calls # shared_library() explicitly, so -Ddefault_library=static reaches it only through # the recipe's patch; a dropped patch would fail the build loudly, but a .so # arriving by some other route would not. # --------------------------------------------------------------------------- check libxcvt/staticlib test -f /usr/lib/libxcvt.a check libxcvt/header test -f /usr/include/libxcvt/libxcvt.h bst_lib_shape libxcvt/no-shared "/usr/lib/libxcvt.so" "/usr/lib/libxcvt.so.0" # Free-form evidence lines: outside the BST protocol, ignored by the report # parsers, and the reason a reader of the transcript does not have to take the # booleans above on trust. echo "libxcvt-evidence oracle pv=$info_pv arch=$info_arch cases=$info_cases fields=$info_fields bytes=$info_bytes" echo "libxcvt-evidence compared $n_all cases ($n_cur curated + $n_bnd boundary + $n_swp swept), $swp_bad swept mismatches, comparator=$(if [ -n "$LX_CMP" ]; then echo cmp; else echo shell; fi)" echo "libxcvt-evidence boundary classes:$bnd_classes" echo "libxcvt-evidence 1920x1080 oracle: $(sed -n '2p' "$C/c12.out")" echo "libxcvt-evidence 1920x1080 target: $(cvt 1920 1080 2>/dev/null | sed -n '2p')" echo "libxcvt-evidence 1920x1080R oracle: $(sed -n '2p' "$C/c25.out")" echo "libxcvt-evidence 1920x1080R target: $(cvt -r 1920 1080 2>/dev/null | sed -n '2p')" rm -rf "$W"