# BST-TIMEOUT: 300 . "$BST_LIB" # ceres-solver 2.2.0 (upstream ceres-solver/ceres-solver at SRCREV 85331393, # meta-oe recipes-support/ceres-solver plus this layer's bbappend) -- Google's # non-linear least-squares minimiser. Numbered with 76-libeigen, whose headers # it is built against, and 76-gsl, the image's other numerical library. # # WHY THIS SUITE IS ABOUT ARITHMETIC AND NOT ABOUT FILES. That ceres builds is # established by do_compile on the build host -- 158 translation units of # template-heavy C++ through this distro's driver. What that does not establish, # and what a consumer of a SOLVER actually needs, is whether the answers are # right. A file listing cannot say; only a solve can. # # THE PORT'S HEADLINE, WHICH THIS SUITE IS THE STANDING GUARD FOR. ceres 2.2 # removed its NO_THREADS backend (upstream 06bfe6ff) on the reasoning that C++ # threads are always available since C++11. brintos-libcxx is built threads-off # because pthread_create traps on this arm (linux-in-the-browser#85), so # and declare nothing at all and stock ceres 2.2 does not # compile here -- 18 errors across five headers. The bbappend carries a patch # restoring a -DTHREADS=OFF build. Sections 2 and 4 are what keep that honest: # section 2 pins the switch as it was MATERIALISED into the installed config.h # rather than as the recipe asked for it, and section 4 proves the answers did # not move when the threading went away. # # THE ORACLE. Every number below is compared against data/ceres-native.txt -- # the same probe source, from the same patched tree, built and run on x86-64 # through the recipe's `native` arm, where THREADS is left ON. So the comparison # is not "does this look plausible" but "does the wasm32 no-threads build agree # with an x86-64 WITH-threads build of the same source". That is a stronger # statement than a same-arm regression check, and it is the one the port needs: # it says the patch did not change the mathematics. # # WITHIN A TOLERANCE, NOT BYTE-EXACTLY, and that is deliberate rather than # lenient. Floating point is the one place byte-equality is the wrong test: two # correct builds may sum a dense QR in different orders, and -Os on one arm # against -O2 on the other is licence to contract an fma. The bar is # |a - b| <= 1e-12 + 1e-9*|b|, which is ~7 orders of magnitude tighter than any # real numerical fault and still leaves that freedom. A cost or a parameter that # is wrong for a REASON -- a broken Jet, a mis-sized block, a solver that quietly # did not iterate -- misses by far more than 1e-9 relative. # # NO COMPILE HAPPENS ON TARGET. This image ships no compiler, so nothing here # can write a ceres programme; the probe was cross-linked in the same build that # installed the headers, which makes do_compile the port's first consumer and # this suite its second. PROBE=$BST_DATA/ceres-probe NATIVE=$BST_DATA/ceres-native.txt INC=/usr/include/ceres CFG=$INC/internal/config.h # --------------------------------------- 0. the decoy that absorbs the loss --- # A suite's FIRST BST result line does not reach the host: the kernel prints its # benign `[k] wasm32: the region holds pending signal(s) ...` audit line # (BRINTOS-783) immediately after ==BST-BEGIN== and displaces the result beside # it -- BRINTOS-1149 for the host-side parser, brintos/linux#96 and # linux-in-the-browser#138 for the console loss itself. # # THE HARNESS COUNTS A DROPPED LINE AS A PASS (it computes passes as total minus # failures), so the eaten position must never carry an assertion anyone depends # on -- it would be reported green without ever having been read. Measured on # this port before the decoy existed: the lost line was ceres-solver/present, # which is a real assertion, and three green runs reported it as passing. # # Same remedy 76-function2.test (BRINTOS-1138) and 76-microsoft-gsl.test carry. # Remove when BRINTOS-1149 is fixed. bst_ok ceres-solver/suite-canary if [ ! -f /usr/lib/libceres.a ]; then bst_skip ceres-solver/present "ceres-solver not installed on this image" exit 0 fi bst_ok ceres-solver/present # ------------------------------------------------------- 1. what shipped ---- # All builtin `test`, so this block costs no process. Exec is a fresh wasm # module instantiation here, so the suite spends its budget on the one probe # run and nothing else. check_rc ceres-solver/inc-dir 0 test -d "$INC" check_rc ceres-solver/hdr-ceres 0 test -s "$INC/ceres.h" check_rc ceres-solver/hdr-problem 0 test -s "$INC/problem.h" check_rc ceres-solver/hdr-solver 0 test -s "$INC/solver.h" check_rc ceres-solver/hdr-autodiff 0 test -s "$INC/autodiff_cost_function.h" check_rc ceres-solver/hdr-rotation 0 test -s "$INC/rotation.h" check_rc ceres-solver/hdr-version 0 test -s "$INC/version.h" check_rc ceres-solver/hdr-config 0 test -s "$CFG" check_rc ceres-solver/ar-ceres 0 test -s /usr/lib/libceres.a check_rc ceres-solver/cmake-config 0 test -f /usr/lib/cmake/Ceres/CeresConfig.cmake check_rc ceres-solver/cmake-targets 0 test -f /usr/lib/cmake/Ceres/CeresTargets.cmake # MINIGLOG=ON installs upstream's logging substitute where ceres's own PUBLIC # headers can reach it -- ordered_groups.h, rotation.h, loss_function.h and # seven more `#include "glog/logging.h"`, so a consumer that cannot resolve this # path cannot compile against ceres at all. Its INSTALL_INTERFACE include dir is # exported through CeresTargets.cmake, so a cmake consumer gets it for free and # anything else needs -I on it explicitly. Asserted because it is the one # installed file whose absence would be invisible until a consumer tried. check_rc ceres-solver/miniglog-hdr 0 test -s "$INC/internal/miniglog/glog/logging.h" # NO shared object, asserted as an ABSENCE so that a distro change which starts # emitting one is a red rather than a silent change. This arm links whole-program # bitcode; -DBUILD_SHARED_LIBS=OFF is the recipe's half of the same statement. check_eq ceres-solver/no-so "0" "$(find /usr/lib -maxdepth 1 -name 'libceres*.so*' 2>/dev/null | grep -c .)" # The installed payload, counted rather than sampled -- so a packaging change # that drops half the headers is a red here and not a consumer's problem later. # Both figures were checked against the packaged .deb before they were written # down (65 .h under include/ceres, 4 cmake files), which is the BRINTOS-167 # discipline: assert the ARTIFACT, not the build tree it came from. check_eq ceres-solver/hdr-count "65" "$(find "$INC" -name '*.h' 2>/dev/null | grep -c .)" check_eq ceres-solver/cmake-count "4" "$(find /usr/lib/cmake/Ceres -maxdepth 1 -name '*.cmake' 2>/dev/null | grep -c .)" # The version, read from the shipped header rather than from the recipe. check_eq ceres-solver/ver-major "#define CERES_VERSION_MAJOR 2" "$(grep '^#define CERES_VERSION_MAJOR' "$INC/version.h" 2>/dev/null)" check_eq ceres-solver/ver-minor "#define CERES_VERSION_MINOR 2" "$(grep '^#define CERES_VERSION_MINOR' "$INC/version.h" 2>/dev/null)" check_eq ceres-solver/ver-patch "#define CERES_VERSION_REVISION 0" "$(grep '^#define CERES_VERSION_REVISION' "$INC/version.h" 2>/dev/null)" # ------------------------------- 2. the configuration it actually shipped ---- # THIS IS THE TRIPWIRE for everything below it. These lines are read out of the # INSTALLED config.h, not out of the recipe: the recipe states an intent and # config.h states what cmake concluded, and the port's whole subject is the gap # between those two. A red here means the flags or the driver moved and every # number in section 4 would have to be re-measured rather than merely re-read. # The switch the patch adds, and the reason this port needed one. Its presence # in an INSTALLED header is also what lets a consumer discover the decision -- # ceres_probe.cc reads exactly this macro rather than being told. check_eq ceres-solver/cfg-no-threads "#define CERES_NO_THREADS" \ "$(grep '^#define CERES_NO_THREADS' "$CFG" 2>/dev/null)" # The back-ends this image has none of. CUDA in particular defaults ON upstream, # so its absence here is a decision and not an accident. check_eq ceres-solver/cfg-no-cuda "#define CERES_NO_CUDA" \ "$(grep '^#define CERES_NO_CUDA' "$CFG" 2>/dev/null)" check_eq ceres-solver/cfg-no-lapack "#define CERES_NO_LAPACK" \ "$(grep '^#define CERES_NO_LAPACK' "$CFG" 2>/dev/null)" check_eq ceres-solver/cfg-no-suitesparse "#define CERES_NO_SUITESPARSE" \ "$(grep '^#define CERES_NO_SUITESPARSE' "$CFG" 2>/dev/null)" # The one sparse back-end this arm CAN have, because it is Eigen's own and # header-only, and libeigen is ported (tests/76-libeigen.test). Asserted as a # PRESENCE rather than left implied: if this went away ceres would still build # and would silently lose every sparse solver, since CERES_NO_SPARSE is derived # from the three of them together. check_eq ceres-solver/cfg-eigen-sparse "#define CERES_USE_EIGEN_SPARSE" \ "$(grep '^#define CERES_USE_EIGEN_SPARSE' "$CFG" 2>/dev/null)" # THERE IS DELIBERATELY NO CASE HERE for the negative that follows -- "with Eigen # sparse in, ceres must not have concluded it has no sparse back-end at all". One # was written and went red on the first run, and the guard was the thing at fault: # config.h carries a LITERAL `#define CERES_NO_SPARSE` at line 66, inside # `#if defined(CERES_NO_SUITESPARSE) && ... && !defined(CERES_USE_EIGEN_SPARSE)`, # so grep finds the text in every build while the preprocessor defines the macro # in none of ours. A text search cannot answer a question only the preprocessor # decides, and no rephrasing of the grep changes that. # # It is not re-asserted another way because it is already covered twice over: the # condition it is derived from is asserted directly above, and config.h's own # lines 90-98 `#error` if the two are ever true together -- so the probe having # COMPILED at all is the proof, and the probe runs in section 4. # ------------------------------------------------- 3. the oracle is usable ---- # Proves the READER before anything is read with it. Without this, a missing or # truncated oracle file would make every comparison in section 4 vacuous in the # direction nobody checks -- an empty expectation compares equal to an empty # actual. The same reasoning as catch2/strings-usable. if [ ! -s "$NATIVE" ]; then bst_notok ceres-solver/oracle-usable "data/ceres-native.txt missing or empty" exit 0 fi check_eq ceres-solver/oracle-usable "done" "$(tail -1 "$NATIVE")" # The x86-64 oracle was built with threads ON. That is not an oversight, it is # what makes section 4 a cross-arm statement rather than a same-configuration # regression check -- and it is the control for the patch's OTHER arm, which is # otherwise never exercised: a patch that had quietly broken the threaded build # could not have produced this file. check_eq ceres-solver/oracle-is-threaded "no_threads 0" \ "$(grep '^no_threads ' "$NATIVE")" # ---------------------------------------------------------- 4. it SOLVES ---- # One exec for four solves. Everything below reads this one capture. if ! out=$("$PROBE" 2>&1); then if bst_execfail "$out"; then bst_unrun ceres-solver/probe-runs "$out" else bst_notok ceres-solver/probe-runs "probe exited non-zero: $(echo "$out" | tr '\n' ' ')" fi exit 0 fi bst_ok ceres-solver/probe-runs # The probe reached its last line, so no solve aborted part-way. Asserted before # the numbers, because a truncated capture would otherwise show up as a handful # of missing keys rather than as the crash it was. check_eq ceres-solver/probe-complete "done" "$(echo "$out" | tail -1)" check_eq ceres-solver/probe-version "version 2.2.0" "$(echo "$out" | grep '^version ')" # The target build is the no-threads one. Paired with oracle-is-threaded above, # these two lines are the whole claim: two different threading configurations of # one source, agreeing on every number below. check_eq ceres-solver/probe-no-threads "no_threads 1" "$(echo "$out" | grep '^no_threads ')" # THE COMPARISON. One awk over both captures -- a process apiece per key would # be ~24 fresh module instantiations on this arm, which is what the suite's # budget cannot afford. # # num_threads_used is deliberately EXCLUDED from the value compare and asserted # separately below: it is the one field that MUST differ between the arms, so # comparing it would either fail correctly for the wrong reason or force a # tolerance wide enough to hide a real disagreement. verdicts=$(awk ' function abs(v) { return v < 0 ? -v : v } FNR == NR { if ($1 == "version" || $1 == "no_threads" || $1 == "max_threads_available") next if ($2 == "num_threads_used" || $2 == "termination") next if (NF == 3) want[$1 " " $2] = $3 next } { if (NF != 3) next k = $1 " " $2 if (!(k in want)) next seen[k] = 1 d = abs($3 - want[k]) tol = 1e-12 + 1e-9 * abs(want[k]) if (d <= tol) printf "ok %s %s\n", $1, $2 else printf "notok %s %s want=%s got=%s delta=%g tol=%g\n", $1, $2, want[k], $3, d, tol } END { for (k in want) if (!(k in seen)) { split(k, a, " "); printf "notok %s %s missing-from-probe-output\n", a[1], a[2] } } ' "$NATIVE" - <= 20" from a guess and went red against a perfectly good run: # # helloworld initial_cost, final_cost, param0 3 # powell initial_cost, final_cost, param0..param3 6 # curve initial_cost, final_cost, param0, param1 4 # curve-t4 initial_cost, final_cost, param0, param1 4 # -- # 17 # # An exact count is also the stronger assertion. Mutation-tested on the host # against the real oracle rather than adopted because it passed: # # oracle truncated to 8 lines count 3 red <- what it is for # oracle grew a field count 18 red # oracle field renamed count 17 red via `missing-from-probe-output` # one value off by 1e-6 relative count 17 red via that key # one value off by 1e-11 relative count 17 green (inside tolerance, as intended) # # ONE TRAP FOR WHOEVER EDITS THIS awk. An EMPTY oracle does not produce zero # comparisons -- `FNR == NR` is true for every line of the SECOND file when the # first is empty, so the probe output is read as the oracle and the END block # emits 17 `missing` lines. The count is then 17 and this case passes. That is # not a hole because ceres-solver/oracle-usable above exits the suite before the # comparison when the file is empty or truncated, but the guard against an empty # oracle is THAT case, not this one; do not delete it thinking this covers it. nverdict=$(echo "$verdicts" | grep -c .) check_eq ceres-solver/compare-ran "17" "$nverdict" echo "$verdicts" | while read -r verdict tag field detail; do [ -n "$verdict" ] || continue case "$verdict" in ok) bst_ok "ceres-solver/$tag-$field" ;; notok) bst_notok "ceres-solver/$tag-$field" "$detail" ;; esac done # Every solve converged, on both arms. A solver that hit the iteration cap can # still land within tolerance of an oracle that also hit it, so this is checked # rather than inferred from the costs agreeing. for tag in helloworld powell curve curve-t4; do check_eq "ceres-solver/$tag-termination" "$tag termination CONVERGENCE" \ "$(echo "$out" | grep "^$tag termination ")" done # THE ARM THIS PORT EXISTS TO MEASURE. curve-t4 asked for four threads. On this # build it must get exactly one -- silently, without failing the solve -- and it # must reach the same fit as curve, which asked for one. The value compare above # already checked curve-t4 against the x86-64 oracle that really did use four; # this pins the local consequence a consumer sees. check_eq ceres-solver/threads-used-1 "curve num_threads_used 1" \ "$(echo "$out" | grep '^curve num_threads_used ')" check_eq ceres-solver/threads-used-t4 "curve-t4 num_threads_used 1" \ "$(echo "$out" | grep '^curve-t4 num_threads_used ')" # ...and the two fits are the same fit. Asserted as an exact string match rather # than within a tolerance, because both ran the identical code path on the # identical data in the identical process: anything but bit-equality here would # mean the thread count reached the arithmetic, which is precisely what # -DTHREADS=OFF is supposed to make impossible. check_eq ceres-solver/t4-matches-t1 \ "$(echo "$out" | sed -n 's/^curve final_cost //p;s/^curve param//p')" \ "$(echo "$out" | sed -n 's/^curve-t4 final_cost //p;s/^curve-t4 param//p')"