|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Ecosystem verification for mcpp#596: the driver farm mirrors the sentinel, |
| 3 | +# and mcpp reports a dlopen surface it cannot satisfy. Against a PUBLISHED |
| 4 | +# mcpp, a PUBLISHED xim:libcuda-host-link and a PUBLISHED compat:sycl-runtime. |
| 5 | +# |
| 6 | +# # The sandbox has an EMPTY $HOME and a fresh /tmp, so this file is not |
| 7 | +# # visible from inside it. Pass the script itself in: |
| 8 | +# B64=$(base64 -w0 <this file>) |
| 9 | +# xlings subos use verify-596 --sandbox --cmd \ |
| 10 | +# "echo $B64 | base64 -d > /tmp/v.sh && MCPP_VERIFY_VERSION=2026.9.10.1 bash /tmp/v.sh" |
| 11 | +# |
| 12 | +# mcpp is addressed by its STORE path, which is the one thing the sandbox does |
| 13 | +# share: the xlings data directory. A bare `mcpp` is not on PATH in there. |
| 14 | +# |
| 15 | +# WHAT A SANDBOX CAN AND CANNOT DECIDE HERE. |
| 16 | +# |
| 17 | +# It decides everything about the PACKAGING, which is where the defect was: how |
| 18 | +# many driver sonames the sentinel publishes, and whether the farm carries all |
| 19 | +# of them. Those are properties of what gets installed, and a machine that has |
| 20 | +# built this before answers them from a directory that already existed. |
| 21 | +# |
| 22 | +# It cannot decide anything about a DEVICE. The sandbox's /dev has fourteen |
| 23 | +# entries and no NVIDIA node, so `libcuda.so.1` resolves to a dangling link |
| 24 | +# there exactly as it does on any machine without a driver -- which is a |
| 25 | +# supported configuration and is asserted as such below, not worked around. The |
| 26 | +# device leg is measured on the host and recorded in the design record. |
| 27 | +# |
| 28 | +# EVERY CRITERION NAMES THE OBJECT IT SELECTED, and every section that did not |
| 29 | +# run is listed again in the summary. "0 assertions failed" printed by a script |
| 30 | +# that skipped three sections is the failure mode this shape exists to prevent. |
| 31 | +set -u |
| 32 | + |
| 33 | +VER="${MCPP_VERIFY_VERSION:?set MCPP_VERIFY_VERSION}" |
| 34 | +STORE="${MCPP_VERIFY_BIN:-$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp}" |
| 35 | +SENTINEL_VER="${MCPP_VERIFY_SENTINEL:-0.0.2}" |
| 36 | +COMPAT_VER="${MCPP_VERIFY_COMPAT:-2026.09.10}" |
| 37 | + |
| 38 | +fails=0 |
| 39 | +skipped="" |
| 40 | +fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); } |
| 41 | +ok() { printf 'ok: %s\n' "$1"; } |
| 42 | +section() { printf '\n== %s ==\n' "$1"; } |
| 43 | +skip() { printf 'NOT RUN: %s\n' "$1"; skipped="$skipped |
| 44 | + - $1"; } |
| 45 | + |
| 46 | +work=$(mktemp -d) |
| 47 | +trap 'rm -rf "$work"' EXIT |
| 48 | + |
| 49 | +section "A. the published mcpp answers for itself" |
| 50 | +if [ ! -x "$STORE" ]; then |
| 51 | + fail "no mcpp at $STORE" |
| 52 | + printf '\nfails=%d (nothing else can run)\n' "$fails" |
| 53 | + exit 1 |
| 54 | +fi |
| 55 | +got=$("$STORE" --version 2>&1 | head -1) |
| 56 | +case "$got" in |
| 57 | + *"$VER"*) ok "mcpp --version says $got" ;; |
| 58 | + *) fail "mcpp --version says '$got', expected $VER" ;; |
| 59 | +esac |
| 60 | + |
| 61 | +# --------------------------------------------------------------------------- |
| 62 | +section "B. the sentinel publishes a SET, and the size is the assertion" |
| 63 | +# |
| 64 | +# The count is the criterion rather than the presence of one name: a list that |
| 65 | +# lost an entry passes every per-name test that only asks about the names it |
| 66 | +# still has. |
| 67 | +if "$STORE" self >/dev/null 2>&1 || true; then :; fi |
| 68 | +xl="$HOME/.xlings/data/xpkgs/xim-x-libcuda-host-link/$SENTINEL_VER/lib" |
| 69 | +if command -v xlings >/dev/null 2>&1; then |
| 70 | + xlings install "libcuda-host-link@$SENTINEL_VER" -y >"$work/sentinel.log" 2>&1 || true |
| 71 | +fi |
| 72 | +if [ -d "$xl" ]; then |
| 73 | + n=$(ls -1 "$xl" | wc -l) |
| 74 | + [ "$n" -ge 2 ] && ok "sentinel $SENTINEL_VER publishes $n sonames" \ |
| 75 | + || fail "sentinel publishes $n soname(s); the set is at least 2" |
| 76 | + for s in libcuda.so.1 libnvidia-ml.so.1; do |
| 77 | + # islink, not exists: a dangling link is the documented self-heal shape |
| 78 | + # on a machine with no driver, and the sandbox is such a machine. |
| 79 | + [ -L "$xl/$s" ] && ok "sentinel carries $s" || fail "sentinel has no $s" |
| 80 | + done |
| 81 | +else |
| 82 | + skip "B: the sentinel is not installed in this environment ($xl)" |
| 83 | +fi |
| 84 | + |
| 85 | +# --------------------------------------------------------------------------- |
| 86 | +section "C. the farm mirrors the sentinel" |
| 87 | +# |
| 88 | +# THE DEFECT ITSELF. The farm linked one hand-written name while enumerating |
| 89 | +# every other directory it draws from, so it carried libcuda.so.1 and not |
| 90 | +# libnvidia-ml.so.1, the CUDA adapter did not load, and the program aborted |
| 91 | +# with no message. |
| 92 | +cat >"$work/mcpp.toml" <<TOML |
| 93 | +[package] |
| 94 | +name = "farm-probe" |
| 95 | +version = "0.1.0" |
| 96 | +
|
| 97 | +[dependencies.compat] |
| 98 | +sycl-runtime = "$COMPAT_VER" |
| 99 | +TOML |
| 100 | +mkdir -p "$work/src" |
| 101 | +cat >"$work/src/main.cpp" <<'CPP' |
| 102 | +int main() { return 0; } |
| 103 | +CPP |
| 104 | +if (cd "$work" && "$STORE" build >"$work/build.log" 2>&1); then |
| 105 | + farm=$(find "$HOME" -path "*compat-x-sycl-runtime/$COMPAT_VER*/sycl_runtime/lib" \ |
| 106 | + -type d 2>/dev/null | head -1) |
| 107 | + if [ -n "$farm" ]; then |
| 108 | + for s in libcuda.so.1 libnvidia-ml.so.1; do |
| 109 | + [ -L "$farm/$s" ] && ok "farm carries $s" \ |
| 110 | + || fail "farm has no $s -- this is mcpp#596" |
| 111 | + done |
| 112 | + # The denominator: a farm that failed to build is empty, and every |
| 113 | + # per-name test above would then have failed for the wrong reason. |
| 114 | + n=$(ls -1 "$farm" | wc -l) |
| 115 | + [ "$n" -ge 20 ] && ok "farm has $n entries" \ |
| 116 | + || fail "farm has only $n entries; the payload half did not build" |
| 117 | + else |
| 118 | + fail "C: no farm directory for compat:sycl-runtime@$COMPAT_VER" |
| 119 | + fi |
| 120 | +else |
| 121 | + skip "C: the probe project did not build (see $work/build.log; the dpcpp payload is over a gigabyte)" |
| 122 | +fi |
| 123 | + |
| 124 | +# --------------------------------------------------------------------------- |
| 125 | +section "D. mcpp reports a dlopen surface it cannot satisfy" |
| 126 | +# |
| 127 | +# The record rather than the message: a test that greps a warning's wording |
| 128 | +# fails the next time the wording improves. Both denominators are asserted for |
| 129 | +# the reason they exist -- "no findings" and "nothing was examined" must not |
| 130 | +# read the same. |
| 131 | +res=$(find "$work" -name resolution.json 2>/dev/null | head -1) |
| 132 | +if [ -n "$res" ] && command -v python3 >/dev/null 2>&1; then |
| 133 | + python3 - "$res" <<'PY' |
| 134 | +import json, sys |
| 135 | +doc = json.load(open(sys.argv[1])) |
| 136 | +rec = doc.get("runtime", {}).get("dlopen_surface") |
| 137 | +if rec is None: |
| 138 | + print("ASSERT-FAIL: resolution.json has no runtime.dlopen_surface") |
| 139 | + sys.exit(1) |
| 140 | +members, walked = rec.get("members", 0), rec.get("walked", 0) |
| 141 | +if members <= 0: |
| 142 | + print(f"ASSERT-FAIL: dlopen_surface examined {members} members") |
| 143 | + sys.exit(1) |
| 144 | +print(f"ok: dlopen_surface examined {walked} of {members} members") |
| 145 | +missing = [f for f in rec.get("findings", []) if f.get("kind") == "missing"] |
| 146 | +for f in missing: |
| 147 | + print(f"note: {f['library']} needs {f['soname']} (declared unserved: libOpenCL.so.1)") |
| 148 | +bad = [f for f in missing if f.get("soname") == "libnvidia-ml.so.1"] |
| 149 | +if bad: |
| 150 | + print("ASSERT-FAIL: NVML is still missing from the farm -- mcpp#596") |
| 151 | + sys.exit(1) |
| 152 | +print("ok: no driver soname is missing from the farm") |
| 153 | +PY |
| 154 | + [ $? -eq 0 ] || fails=$((fails + 1)) |
| 155 | +else |
| 156 | + skip "D: no resolution.json (section C did not build) or no python3" |
| 157 | +fi |
| 158 | + |
| 159 | +# --------------------------------------------------------------------------- |
| 160 | +printf '\n== summary ==\n' |
| 161 | +printf 'assertions failed: %d\n' "$fails" |
| 162 | +if [ -n "$skipped" ]; then |
| 163 | + printf 'sections NOT RUN:%s\n' "$skipped" |
| 164 | + printf 'A pass with sections not run is not a pass for those sections.\n' |
| 165 | +fi |
| 166 | +exit $((fails > 0)) |
0 commit comments