. "$BST_LIB" # BST-TIMEOUT: 900 # TWO interpreter starts, and those starts are the whole # # cost: python3 on this image is a single ~63 MB static # # binary and a start has been minutes on a loaded guest, # # so the runner's 60s default would report the port as # # broken when what ran out was the clock. # # mercurial 6.5 (meta-openembedded meta-oe/recipes-devtools/mercurial, # `inherit python3native python3targetconfig', mercurial-scm.org release # tarball) on wasm32-hwjs -- the distributed source-control manager, running on # the ported Python 3 interpreter (BRINTOS-1476). # # WHY A DISTRIBUTED SCM IS TESTABLE ON AN IMAGE WITH NO NETWORK. Mercurial is # local-first: a repository is a directory, `hg init' writes a store into it, # and every command below reads and writes nothing outside that directory. No # path is configured in the fresh repository -- the `paths-empty' case asserts # exactly that -- so there is no peer to reach and no interface to bring up. # What is NOT covered here is the distributed half: clone, pull, push and the # wire protocol are out of this ticket's scope and no case below pretends to # reach them. # # --pure IS THE PORT, AND THE no-.so CASES ARE HOW IT IS CHECKED. Mercurial # ships C accelerators (mercurial/cext/parsers.c, bdiff.c, mpatch.c, base85.c, # osutil.c) and setup.py builds them by default. This image's python3 is one # static binary: lib-dynload is empty by construction and this arm has no # dynamic loader at all, so a .so in site-packages cannot be imported by # anything. The meta-brintos bbappend passes `PURE=--pure', which makes setup.py # skip the extensions AND write `modulepolicy = b"py"' into # mercurial/__modulepolicy__.py. Both halves are asserted below, over the # INSTALLED tree rather than over the recipe: a meta-oe bump that dropped the # flag would not fail the build -- the target clang compiles those .c files # quite happily -- it would ship an `hg' that dies at import on a user's target. # # EVERY EXPECTATION BELOW WAS MEASURED, not derived. The same probe was run # against the same 6.5 tarball (sha256 a5603d0d...be7e7) on an x86-64 control # built `--pure', CPython 3.12.3, recorded 2026-08-31, and the values pasted # here. The two commits are made at a FIXED user and a FIXED date (`0 0'), which # is what makes the node ids below reproducible rather than incidental: a # changeset hash is a function of the manifest, the parents, the user, the date # and the description, and all five are pinned. So `node0' is a claim about # mercurial's own hashing arithmetic being identical on this arm, not a # transcription of whatever it happened to produce. The target interpreter is # CPython 3.12.13 against the control's 3.12.3. # # TWO STARTS, NOT TWELVE. The guest does not return memory on process exit # (brintos/hardwarejs#20, brintos/linux#21), so every interpreter start is a # permanent cost to it; BRINTOS-1751's docutils suite was killed as a runaway at # six starts and reported NO case at all, because the runner buffers a suite's # output until completion. The repository lifecycle here is ten hg commands. One # start is /usr/bin/hg, run once, because the shipped script and its shebang are # part of the deliverable and nothing else proves they work. The other is # data/mercurial-probe.py, which drives the remaining nine commands through # mercurial.dispatch -- the same entry point /usr/bin/hg calls -- in one process # and writes its measurements as key=value lines. # # ONE PATCH, AND THE SUITE KNOWS WHY IT IS THERE. `--pure' alone would not give # a working hg on this image: mercurial/pure/osutil.py imports ctypes at module # scope, _ctypes is configured out of this interpreter (hwjs-cc#15), and # mercurial/util.py imports that module on every code path. The layer patches # the import onto the Windows arm that actually uses it, and the osutil-module / # ctypes-absent pair below asserts both halves of that. # # NOT COVERED HERE, on purpose: mercurial's own ~1000-file test suite (it is not # packaged and drives `hg' as a subprocess per test, which is a process start # per test on this guest), the chg command server, and the hgext extensions. require python3 mercurial/present require hg mercurial/present SITE=/usr/lib/python3.12/site-packages SCRATCH=${TMPDIR:-/tmp}/bst-mercurial.$$ mkdir -p "$SCRATCH" 2>/dev/null # --------------------------------------------------------- the deliverable --- # The two halves of this port land in two DIFFERENT packages, and the suite # keeps them apart so a red says which one went missing. `mercurial' is # /usr/bin/hg plus ${datadir}; `mercurial-python' is the whole of # site-packages/mercurial, because the stock recipe's `PACKAGES =+ # "${PN}-python"' puts it first and its FILES is the parent directory of the # ${PN} entry's. The bbappend declares the RDEPENDS that ties them together. check mercurial/bin-hg test -x /usr/bin/hg check_out mercurial/hg-shebang "#!/usr/bin/env python3" head -1 /usr/bin/hg check mercurial/env test -x /usr/bin/env check mercurial/pkg-dir test -d "$SITE/mercurial" check mercurial/pkg-init test -f "$SITE/mercurial/__init__.py" check mercurial/pkg-dispatch test -f "$SITE/mercurial/dispatch.py" check mercurial/pkg-commands test -f "$SITE/mercurial/commands.py" check mercurial/pkg-localrepo test -f "$SITE/mercurial/localrepo.py" check mercurial/pkg-revlog test -f "$SITE/mercurial/revlog.py" check mercurial/pkg-helptext test -d "$SITE/mercurial/helptext" check mercurial/pkg-hgext test -d "$SITE/hgext" check mercurial/pkg-demandimport test -d "$SITE/hgdemandimport" # mercurial/pure/ carries the Python implementations of the four accelerators. # It is packaged whether or not --pure was passed, so its presence is not on its # own evidence that the flag took -- the policy and no-.so cases below are. check mercurial/pure-dir test -d "$SITE/mercurial/pure" check mercurial/pure-parsers test -f "$SITE/mercurial/pure/parsers.py" check mercurial/pure-bdiff test -f "$SITE/mercurial/pure/bdiff.py" check mercurial/pure-mpatch test -f "$SITE/mercurial/pure/mpatch.py" check mercurial/pure-base85 test -f "$SITE/mercurial/pure/base85.py" # THE --pure ASSERTION, and the mechanism that makes it matter. The first pair # is the port's requirement: nothing mercurial owns may be a compiled extension, # and the generated policy must say so. The third is why -- lib-dynload is empty # on this image, so there is nowhere an extension could be loaded from even if # one shipped. # # The two `find' cases would pass VACUOUSLY against a directory that is not # there, which is the one way this could report a clean port on an image # carrying no mercurial at all. Each is preceded by the existence case that # makes its emptiness mean something. check mercurial/site-packages-dir test -d "$SITE" check_eq mercurial/no-so "" \ "$(find "$SITE/mercurial" -name '*.so' 2>/dev/null)" check mercurial/modulepolicy-file test -f "$SITE/mercurial/__modulepolicy__.py" check_out mercurial/modulepolicy-py 'modulepolicy = b"py"' \ cat "$SITE/mercurial/__modulepolicy__.py" check mercurial/lib-dynload-dir test -d /usr/lib/python3.12/lib-dynload check_eq mercurial/lib-dynload-empty "" \ "$(find /usr/lib/python3.12/lib-dynload -name '*.so' 2>/dev/null)" # ------------------------------------------------------- the shipped script --- # The ONE invocation of /usr/bin/hg. It proves the shebang resolves, the package # is importable from the interpreter's own default path, and the version the # ticket names is the version installed. Everything after this point runs in the # probe's single start instead. check_out mercurial/hg-version "Mercurial Distributed SCM (version 6.5)" \ /usr/bin/hg version # ------------------------------------------------------------- the probe --- # One interpreter start, forked, so a failure to stage the exec costs this file # its cases rather than the run its terminator. Everything below is a string # comparison against $SCRATCH/results.txt. ( python3 "$BST_DATA/mercurial-probe.py" "$SCRATCH" \ > "$SCRATCH/probe.out" 2>&1 ); probe_rc=$? if [ "$probe_rc" -ne 0 ]; then bst_notok mercurial/probe-run \ "rc=$probe_rc $(_bst_1line "$(cat "$SCRATCH/probe.out" 2>/dev/null)")" else bst_ok mercurial/probe-run fi # val : the probe's recorded value, or the empty string. A key the probe # never reached reads as "" and its case reds against the expectation, which is # the wanted behaviour -- an absent measurement is not a pass. val() { sed -n "s/^$1=//p" "$SCRATCH/results.txt" 2>/dev/null; } # The module resolves to the package the recipe installed, from the # interpreter's own side. `import mercurial' succeeding from some other path -- # a stray copy, a source tree left in the image -- would satisfy every case # below while the port had installed nothing. check_eq mercurial/import-path "$SITE/mercurial/__init__.py" "$(val import-path)" check_eq mercurial/version 6.5 "$(val version)" # Read out of the RUNNING interpreter, which is the only place the policy is # actually consulted: the file case above proves what shipped, this proves what # was obeyed. check_eq mercurial/policy-py py "$(val policy)" check_eq mercurial/pure-import True "$(val has-pure-parsers)" # THE ctypes GAP, AND THE PATCH THAT CLOSES IT. mercurial/util.py does # `osutil = policy.importmod("osutil")', which under the pure policy resolves to # mercurial.pure.osutil -- and upstream imports ctypes there at module scope # while using it only on the Windows arm. _ctypes is configured out of this # image's interpreter (hwjs-cc#15), so unpatched that import is fatal to `import # mercurial.util', and therefore to every hg command including `hg version'. # The layer's 0001-pure-osutil patch moves the import onto the arm that uses it. # # The two cases are the two halves of that statement, and each is useless # without the other. `osutil-module' says the pure module was reached and # imported; `ctypes-absent' says the condition that made the patch necessary is # still true of this image. If hwjs-cc#15 closes and _ctypes returns, the second # reds -- which is the wanted behaviour: it is the signal to re-read whether the # patch is still earning its place. It reds on an x86-64 control for the same # reason, and that is not a finding about the port. check_eq mercurial/osutil-module mercurial.pure.osutil "$(val osutil-module)" check_eq mercurial/ctypes-absent False "$(val ctypes-importable)" # ------------------------------------------------------------- hg init --- # The store is a directory, and the requirement sets are the two files that # describe the on-disk format. They are pinned because a repository this image # writes with a different format is one another Mercurial cannot read -- which # is the only way a local-first SCM can still be wrong about interoperability. check_eq mercurial/init-rc 0 "$(val init-rc)" check_eq mercurial/dot-hg True "$(val dot-hg)" check_eq mercurial/store-dir True "$(val store-dir)" check_eq mercurial/requires share-safe "$(val requires)" check_eq mercurial/store-requires \ dotencode,fncache,generaldelta,revlogv1,sparserevlog,store \ "$(val store-requires)" # NOTHING IS CONFIGURED TO TALK TO. `hg paths' on the fresh repository prints # nothing, which is the assertion that every case below is local-only rather # than a claim in this header. check_eq mercurial/paths-empty "" "$(val paths-empty)" # ------------------------------------------------------ add / commit / log --- # The three states of one file, in order: untracked, added, committed-clean. A # port whose dirstate did not work would pass "untracked" and fail the other # two, which is the distinction the three cases exist to draw. check_eq mercurial/status-untracked "? fixture.txt" "$(val status-untracked)" check_eq mercurial/add-rc 0 "$(val add-rc)" check_eq mercurial/status-added "A fixture.txt" "$(val status-added)" check_eq mercurial/commit1-rc 0 "$(val commit1-rc)" check_eq mercurial/status-clean "" "$(val status-clean)" # The description read back verbatim through --template, and the node id it # hashes to. The node is the strong case: it is sha1 over the manifest, the # parents, the fixed user, the fixed date and this description, so it holds only # if revlog storage, the manifest and the changelog all agree with x86-64. check_eq mercurial/log-desc "first revision" "$(val log-desc)" check_eq mercurial/node0 0764adf95c824f8bc0e49358f322b67494fbab4b "$(val node0)" # -------------------------------------------------------- a second revision --- check_eq mercurial/status-modified "M fixture.txt" "$(val status-modified)" check_eq mercurial/commit2-rc 0 "$(val commit2-rc)" check_eq mercurial/node1 280dc06e73c9fa0bf2e83626286886861b8c8f9e "$(val node1)" check_eq mercurial/log-count 2 "$(val log-count)" check_eq mercurial/log-revs "1:second revision|0:first revision" "$(val log-revs)" check_eq mercurial/tip-rev 1 "$(val tip-rev)" # ------------------------------------------------------------- hg diff --- # The unified diff the fixture edit produces. The sha1 pins the whole text # including the two node ids in its header; the body case pins the hunk and the # three changed lines on their own, so a diff that is merely reformatted is # distinguishable from one that computed the wrong change. bdiff runs pure here, # which is exactly what these cases exercise. check_eq mercurial/diff-rc 0 "$(val diff-rc)" check_eq mercurial/diff-lines 9 "$(val diff-lines)" check_eq mercurial/diff-body "@@ -1,3 +1,4 @@|-beta|+BETA|+delta" \ "$(val diff-body)" check_eq mercurial/diff-sha1 a8490bccf62b7d13c18dc12598d3cb132ae64c0b \ "$(val diff-sha1)" # ----------------------------------------------------------- hg update -r0 --- # Walking the history back restores the first revision's BYTES, not merely its # revision number -- both are asserted, because a working directory left at rev # 0 with the wrong content and one left at rev 1 with the right content are two # different defects and would otherwise read the same. check_eq mercurial/update-rc 0 "$(val update-rc)" check_eq mercurial/parent-rev 0 "$(val parent-rev)" check_eq mercurial/update-content "alpha|beta|gamma|" "$(val update-content)" check_eq mercurial/update-is-v0 True "$(val update-is-v0)" check_eq mercurial/update-status "" "$(val update-status)" # ...and forward again, so the last thing the lifecycle proves is that the # traversal is not one-way. check_eq mercurial/final-content "alpha|BETA|gamma|delta|" "$(val final-content)" rm -rf "$SCRATCH" 2>/dev/null