. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. Every # # other assertion below reads the filesystem or dpkg with # # shell builtins, or greps a line out of a transcript one # # of those three starts left behind. # # # # The three are: the probe under the parent the runner gave # # it, and the same probe twice more in `spawn' mode under a # # `/bin/sh -c' and a `/bin/bash -c' this suite starts on # # purpose. There is no fourth: a bare `import shellingham' # # start would say nothing the probe's own start does not, # # since the probe imports it as its first act and # # `probe-quiet' asserts that the whole run wrote nothing to # # stderr. # # # # The work inside a start is `os.path.basename' and a # # handful of `re.match' calls over argv strings, plus ten # # reads of /proc//stat and /proc//cmdline -- files # # of a few hundred bytes. Microseconds beside an interpreter # # start on this arm, which is tens of seconds before the # # first line of the program runs. # # # # 1200 is 400s per start, the same per-start budget # # 99-python3-imagesize sets for its three, and roughly six # # times the ~60s a python3 start has measured at here. # # Nothing here is quadratic in anything, so the start is the # # only thing the number has to cover. # # # # NOTHING IN THIS SUITE FORKS FROM PYTHON. shellingham's # # `ps' fallback would, and it is deliberately not exercised # # -- a fork+exec from CPython on this arm has had probes # # killed by the host's guest-growth watchdog before they # # reported a case (brintos/linux#21, brintos/hardwarejs#20). # # The two extra shells are started BY THIS SHELL, which is # # the ordinary cost the runner already pays per suite. # # # # MEASURED on the batch-2195 image, 2026-09-02: the three # # starts and everything around them took 80s of the 1200s # # bound -- about 27s per start, comfortably inside the ~60s # # the estimate above allowed for one. The bound stays as it # # is: it is sized for a slow start, not for the median. # # python3-shellingham (shellingham 1.5.4, meta-openembedded/meta-python # recipes-devtools/python/python3-shellingham_1.5.4.bb, `inherit pypi # setuptools3', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # `shellingham.detect_shell()' answers WHAT SHELL AM I RUNNING UNDER. It walks # the process tree upward from a pid -- ten ancestors at most -- and returns the # first one whose argv[0] BASENAME is in a fixed 15-name set, as a 2-tuple # `(name, command)'. `ShellDetectionFailure' is raised when the walk finds none. # # Two exported names, four small modules, no configure step, no compiled # extension and no C anywhere in the package. Poetry and Typer use it to decide # which shell to write a completion script for. # # ============================================================================ # THE COMMAND IS argv[0], NOT A RESOLVED PATH -- AND THAT IS THIS SUITE'S # SHARPEST STATEMENT. # ============================================================================ # # The second element of the tuple is whatever /proc//cmdline holds for the # ancestor, verbatim. It is an absolute path only if the shell was STARTED by # an absolute path. # # So the suite asserts BOTH shapes, and the difference between them is the # whole point: # # ambient-detect the probe's own parent is the `sh' that # brintos-selftest exec'd for this file, started as the # bare word `sh' -- so the answer is ('sh', 'sh'), a # command a caller cannot exec from another directory. # spawn-sh-detect a shell THIS SUITE started as `/bin/sh' -- ('sh', # '/bin/sh'). # spawn-bash-detect likewise `/bin/bash' -- ('bash', '/bin/bash'). # # A caller who assumes the ticket's phrasing -- "the command is an absolute # path to that shell's binary" -- is right two times in three here, which is # exactly the shape of assumption a suite is worth having for. # # ============================================================================ # $SHELL IS CONSULTED ON ONE BRANCH ONLY, AND IT IS NOT THE ONE README IMPLIES. # ============================================================================ # # `_get_shell' reads $SHELL if and only if the ancestor's argv[0] begins with a # hyphen -- the login-shell convention. For every other parent the environment # variable is ignored completely, however plainly it names a shell. # # login-no-env argv[0] '-bash', $SHELL unset -> ('bash', 'bash') # login-with-env argv[0] '-bash', $SHELL set -> ('fakeshell', # '/opt/fakeshell') # nonlogin-env-ignored argv[0] '/bin/sh', $SHELL still set -> ('sh', # '/bin/sh') # # The third is the case that matters. The README's "Notes for Application # Developers" points a caller at $SHELL as the sane default *after* detection # fails; it does not say that a successful detection ignores it, and a reader # who assumes otherwise will not understand why a `poetry' run under a spawned # `/bin/sh' says `sh' while $SHELL says `zsh'. # # ============================================================================ # AN UNKNOWN PID RAISES FileNotFoundError, NOT ShellDetectionFailure. # ============================================================================ # # `unknown-pid-detect' hands `detect_shell' a pid that /proc does not carry. # `proc._get_ppid' opens /proc//stat with no guard, and the # FileNotFoundError travels out through the generator untouched -- so the ONE # exception the README tells callers to catch is not the one they get. The case # asserts the measured behaviour, not the documented one. # # `depth1-detect' is the failure that IS documented: with max_depth=1 the walk # stops at this interpreter, which is not a shell, so `get_shell' returns None # and `detect_shell' converts that into ShellDetectionFailure. It reaches the # raise without needing a non-shell parent to exist on the machine. # # ============================================================================ # THE /proc READER IS THE ONLY PART THAT TOUCHES THE PLATFORM. # ============================================================================ # # Everything else is arithmetic over argv strings. The reader needs # /proc//stat (for the parent pid, field 3 on Linux) and # /proc//cmdline (for argv). THE TWO DO NOT BEHAVE THE SAME HERE, and the # difference is what this suite's reds are: # # /proc//stat present and correct -- 85-atop measured it, and # `ppid-matches-getppid' re-measures it here against # getppid(2). Every chain case is green on it. # /proc//cmdline READS BACK 0 BYTES for every process. That is # brintos/linux#112, open, and 99-python3-linux-procfs # (`live-cmdline-argv0 want:python3 got:') and # 85-smem (`child-cmdline-nonempty') are two of the # four suites that have already reproduced it. # # A per-pid /proc file reading correctly is NOT evidence about a different # per-pid /proc file on this kernel -- which is the mistake that has to be made # only once. `cmdline-raw-empty-today' below is the direct-`open' control that # keeps the distinction mechanical rather than remembered. # # `posix-iter-uses-proc' is the case that pins WHICH backend answered. # `posix._iter_process_parents' tries the /proc reader first and falls through # to running `ps' only when it raises EnvironmentError. The two are asserted to # agree on the first entry, which they can only do if /proc answered. # # ============================================================================ # THE WINDOWS BACKEND SHIPS AND CAN NEVER LOAD. # ============================================================================ # # `nt.py' is in the package -- `packages=find:' takes every module under # `shellingham' -- and its module scope reaches `ctypes.windll'. Importing it # here always fails; WHICH exception depends on whether ctypes itself imported, # so `nt-import-refused' scores the refusal and `nt-import-error' accepts any # of the three plausible types rather than pinning one this arm might not give. # # ============================================================================ # WHAT IS NOT ASSERTED, AND WHY. # ============================================================================ # # * THE `ps' FALLBACK. Reaching it means forking `ps -ww -o pid= -o ppid= -o # args=' from CPython, and a fork+exec from python on this arm is the cost # that has had probes killed before they reported a case # (brintos/linux#21, brintos/hardwarejs#20). What is asserted instead is # that it is never REACHED, which is the statement that matters for a # platform with a working /proc. # * A LOGIN SHELL WITH A REAL LEADING HYPHEN IN THE TREE. Putting one there # needs an exec with a forged argv[0], which sh cannot do portably. The # branch is driven directly instead, through `_get_shell("-bash")'. # * THE INTERACTIVE ANSWER. Nothing here needs a terminal, a device, a peer # or the network; every fixture is a string or a file the probe writes. # # ============================================================================ P=python3-shellingham # The canary. A suite whose first line ran is distinguishable from one the # runner never reached. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/shellingham DI=$SITE/shellingham-1.5.4.dist-info PKGLIST=/var/lib/dpkg/info/python3-shellingham.list PROBE=$BST_DATA/python3-shellingham-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-shellingham.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---- the shipped tree ------------------------------------------------------- # # `shellingham' is a PACKAGE with a `posix' subpackage, not a flat module, and # the file list below is the whole of it. The absence cases matter as much as # the presence ones: the sdist carries a `tests/' directory and `setup.cfg' # declares `packages=find: where=src', so a packaging change that started # shipping the test tree would land it under site-packages. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKG" check $P/posix-dir test -d "$PKG/posix" check $P/no-flat-module test ! -f "$SITE/shellingham.py" check $P/init-file test -s "$PKG/__init__.py" check $P/core-file test -s "$PKG/_core.py" check $P/nt-file test -s "$PKG/nt.py" check $P/posix-init-file test -s "$PKG/posix/__init__.py" check $P/posix-core-file test -s "$PKG/posix/_core.py" check $P/proc-file test -s "$PKG/posix/proc.py" check $P/ps-file test -s "$PKG/posix/ps.py" check $P/no-test-dir test ! -d "$SITE/tests" check $P/no-py-typed test ! -e "$PKG/py.typed" # Byte-compiled beside the source, for both levels of the package. check $P/pycache-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" check $P/pycache-core test -s "$PKG/__pycache__/_core.cpython-312.pyc" check $P/pycache-nt test -s "$PKG/__pycache__/nt.cpython-312.pyc" check $P/pycache-posix-init test -s "$PKG/posix/__pycache__/__init__.cpython-312.pyc" check $P/pycache-proc test -s "$PKG/posix/__pycache__/proc.cpython-312.pyc" check $P/pycache-ps test -s "$PKG/posix/__pycache__/ps.cpython-312.pyc" # ---- the package's own file list ------------------------------------------- # # Copied to tmpfs first: a stdin redirect performed by THIS shell on the # virtiofs root makes dup2(2) return 513 and ash exits, ending the suite while # every case already emitted reads as a pass. See bst_local. cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # PURE PYTHON, ASSERTED AS A NEGATIVE. Neither BRINTOS-2046 (the empty Py* # export surface that refuses a CPython extension at dlopen) nor BRINTOS-1476 # (the static interpreter with an empty lib-dynload) can reach a package that # ships no shared object -- so the negative is what says those two are out of # scope here, rather than a sentence in a design doc claiming it. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # NO EXECUTABLE. setup.cfg declares no `entry_points', so nothing lands under # /usr/bin and there is no shebang for this port to reach through. check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/(bin|sbin)/' "$W/pkgf" 2>/dev/null)" # ---- the wheel metadata ---------------------------------------------------- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 1.5.4" cat "$DI/METADATA" check_out $P/dist-info-name "Name: shellingham" cat "$DI/METADATA" check_out $P/dist-info-license "License: ISC License" cat "$DI/METADATA" check $P/dist-info-license-file test -s "$DI/LICENSE" check $P/dist-info-record test -s "$DI/RECORD" # `universal = 1' in setup.cfg's [bdist_wheel] is why BOTH tags are here, on a # package whose python_requires is >=3.7 -- the py2 tag is a lie the build # system was told and repeated. check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag-py3 "Tag: py3-none-any" cat "$DI/WHEEL" check_out $P/dist-info-tag-py2 "Tag: py2-none-any" cat "$DI/WHEEL" check_out $P/dist-info-top-level "shellingham" cat "$DI/top_level.txt" check $P/dist-info-zip-safe test -e "$DI/zip-safe" # ---- what dpkg thinks it installed ----------------------------------------- sed -n '/^Package: python3-shellingham$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" check_out $P/pkg-version "Version: 1.5.4-r0" cat "$W/st" # THE DEPENDENCY LINE, EXACT. The recipe declares no RDEPENDS at all; every # module shellingham imports -- importlib, os, re, io, sys, collections, errno, # subprocess -- is in poky's `core' split, so `setuptools3-base's implicit # python3-core is the whole of it and is correct. Checked against poky's own # python3-manifest.json, not assumed. An exact match rather than a substring: # a NEW dependency appearing here would mean the package grew an import, which # is the thing worth catching. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" check_eq $P/pkg-depends-exact \ "Depends: python3-core" \ "$(cat "$W/dep")" # ---- start 1 of 3: the probe, under the parent the runner gave it ----------- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi # The import is not given a start of its own; this is the assertion that would # have caught anything it would have. The probe imports shellingham, posix, # _core, proc and ps as its first act and writes nothing to stderr. check_eq $P/probe-quiet "" "$(cat "$OUT.err" 2>/dev/null)" # The probe ran to its last line, and emitted exactly the keys it was written # to emit -- a truncated transcript reds HERE rather than as fifty vague reds. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "60" "$(pv emitted_count)" # ---- what the package is --------------------------------------------------- check_eq $P/package-answered "$PKG/__init__.py" "$(pv package_file)" check_eq $P/posix-answered "$PKG/posix/__init__.py" "$(pv posix_file)" check_eq $P/proc-answered "$PKG/posix/proc.py" "$(pv proc_file)" check_eq $P/ps-answered "$PKG/posix/ps.py" "$(pv ps_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/version-attr "1.5.4" "$(pv version_attr)" # importlib.metadata reads the dist-info the wheel installed; equal to the # module's own __version__ is a statement about the PACKAGING, not the source. check_eq $P/dist-version "1.5.4" "$(pv dist_version)" # THE PUBLIC SURFACE IS NOT AN __all__. shellingham declares none, so # `importlib' and `os' -- module-scope imports of __init__.py -- are exported # beside the two names that were meant to be, and `from shellingham import *' # would bind all four. `posix' is in the list because the probe imported it. check_eq $P/public-names \ "ShellDetectionFailure,detect_shell,importlib,os,posix" "$(pv bare_dir)" # ShellDetectionFailure IS an OSError -- EnvironmentError is an alias for it on # Python 3 -- so a caller's blanket `except OSError' swallows a detection # failure it never meant to catch. check_eq $P/sdf-mro \ "ShellDetectionFailure,OSError,Exception,BaseException,object" "$(pv sdf_mro)" check_eq $P/sdf-is-oserror "yes" "$(pv sdf_is_oserror)" check_eq $P/process-fields "args,pid,ppid" "$(pv process_fields)" # THE NAME SET IS THE HEURISTIC. A shell absent from it is invisible to this # library however obviously it is a shell -- there is no /etc/shells lookup and # no exec-format check anywhere in the package. check_eq $P/shell-names \ "ash,bash,cmd,csh,dash,elvish,fish,ksh,nu,powershell,pwsh,sh,tcsh,xonsh,zsh" \ "$(pv shell_names)" check_eq $P/shell-names-count "15" "$(pv shell_names_count)" # ---- the Windows backend ships and cannot load ------------------------------ check_eq $P/nt-import-refused "yes" "$(pv nt_import_refused)" # WHICH exception depends on whether ctypes itself imported on this image, so # the case accepts any of the three that mean "this module cannot load here" # rather than pinning the one the build host happened to give (AttributeError, # from ctypes.windll being absent). _nte=$(pv nt_import_error) case "$_nte" in AttributeError|ImportError|ModuleNotFoundError|ValueError|OSError) bst_ok $P/nt-import-error ;; *) bst_notok $P/nt-import-error "want a load refusal; got:'$_nte'" ;; esac # ---- the heuristic, driven directly ---------------------------------------- # # _get_shell(cmd, *args) is the whole of the decision and it is pure -- it never # reads /proc. Fabricated argv is how the macOS, QEMU and login-shell branches # get asserted on an arm that has none of them. check_eq $P/login-no-env "bash|bash" "$(pv login_no_env)" check_eq $P/login-helper-no-env "bash|bash" "$(pv login_helper_no_env)" check_eq $P/login-with-env "fakeshell|/opt/fakeshell" "$(pv login_with_env)" check_eq $P/login-helper-with-env "fakeshell|/opt/fakeshell" "$(pv login_helper_with_env)" # THE PRECEDENCE CASE. $SHELL is still /opt/fakeshell here and the answer does # not mention it: a non-login parent is classified by its own argv[0] alone. check_eq $P/nonlogin-env-ignored "sh|/bin/sh" "$(pv nonlogin_env_ignored)" # The NAME is lowercased; the COMMAND is returned exactly as argv[0] carried it. check_eq $P/case-folded "sh|/bin/SH" "$(pv case_folded)" # A Rosetta or QEMU wrapper is stepped over and the WRAPPED command classified. check_eq $P/qemu-unwrap "bash|/bin/bash" "$(pv qemu_unwrap)" check_eq $P/rosetta-unwrap "zsh|/bin/zsh" "$(pv rosetta_unwrap)" check_eq $P/qemu-unwrap-nonshell "None" "$(pv qemu_unwrap_nonshell)" check_eq $P/qemu-matches-arm "yes" "$(pv qemu_matches_arm)" # fullmatch, not match: `qemu-armed' is not a QEMU binary and must not unwrap. check_eq $P/qemu-rejects-armed "yes" "$(pv qemu_rejects_armed)" check_eq $P/non-shell "None" "$(pv non_shell)" # All fifteen names, detected and detected identically. One case for fifteen # assertions: a set that grew a member the dispatch could not reach would be a # defect in exactly one of them, and the key carries which. check_eq $P/all-shell-names "yes" "$(pv all_shell_names_detected)" # The interpreter-shell path (sarugaku/shellingham#26): a shell written IN # Python is found by looking at what the interpreter was handed -- and only if # that argument is a file that EXISTS, which is the guard the next two cases # are about. check_eq $P/interp-xonsh "xonsh" "$(pv interp_xonsh_name)" check_eq $P/interp-xonsh-path "yes" "$(pv interp_xonsh_path_matches)" check_eq $P/interp-missing-file "None" "$(pv interp_missing_file)" check_eq $P/interp-wrong-name "None" "$(pv interp_wrong_name)" # ---- the /proc reader ------------------------------------------------------ check_eq $P/detect-proc "stat" "$(pv detect_proc)" check_eq $P/use-bsd-stat "no" "$(pv use_bsd_stat_format)" check_eq $P/uname-sysname "Linux" "$(pv uname_sysname)" check_eq $P/linux-ppid-field "3" "$(pv linux_stat_ppid_field)" check_eq $P/bsd-ppid-field "2" "$(pv bsd_stat_ppid_field)" # The parent pid shellingham parsed out of /proc//stat is the one the # KERNEL told this process it has, via getppid(2). Field 3 is reached through a # regex that keeps a parenthesised comm together, so a process whose name # contains a space or a bracket does not shift it. check_eq $P/ppid-matches-getppid "yes" "$(pv ppid_matches_getppid)" # --- /proc//cmdline READS BACK EMPTY ON THIS KERNEL ------------------- # # MEASURED on the batch-2195 image, 2026-09-02. These three assert the vector a # WORKING kernel produces -- `python3 ', two elements -- and the first # two are red here because the file returns 0 bytes. That is # brintos/linux#112, already tracked and open; nothing about this package is # wrong, and they flip green by themselves when #112 closes. # # `cmdline-type' stays green either way: an empty read still yields a tuple. check_eq $P/cmdline-argv0 "python3" "$(pv cmdline_argv0)" check_eq $P/cmdline-has-probe "yes" "$(pv cmdline_has_probe)" check_eq $P/cmdline-type "tuple" "$(pv cmdline_type)" check_eq $P/cmdline-len "2" "$(pv cmdline_len)" # THE CONTROL, and the case that says WHOSE fault the four above are. It reads # /proc//cmdline with plain `open' -- not through shellingham -- and # reports the byte count. 0 bytes means the kernel returned nothing and the # package read it correctly. GREEN TODAY: when #112 closes this one goes red, # which is the signal to come back and drop the `-today' cases as a group. check_eq $P/cmdline-raw-empty-today "0" "$(pv cmdline_raw_len)" check_eq $P/chain-first-is-self "yes" "$(pv chain_first_is_self)" check_eq $P/chain-len-ge-2 "yes" "$(pv chain_len_ge_2)" check_eq $P/chain-within-depth "yes" "$(pv chain_within_depth)" check_eq $P/chain-depth1-len "1" "$(pv chain_depth1_len)" # Each entry's ppid is the next entry's pid: a real ancestry walk, not a # re-read of one file. check_eq $P/chain-links "yes" "$(pv chain_links)" check_eq $P/chain-entry-type "Process" "$(pv chain_entry_type)" check_eq $P/proc-impl-available "yes" "$(pv proc_impl_available)" # WHICH BACKEND ANSWERED. posix._iter_process_parents falls through to running # `ps' only when the /proc reader raises EnvironmentError; the two agreeing on # the first entry is how this suite says /proc answered and no subprocess ran. check_eq $P/posix-iter-uses-proc "yes" "$(pv posix_iter_uses_proc)" check_eq $P/ps-fallback-unused "no" "$(pv ps_fallback_exercised)" # ---- detect_shell, end to end ---------------------------------------------- # # EVERY detect_shell CASE BELOW IS RED ON THIS IMAGE, AND ALL OF THEM FOR THE # ONE CAUSE ABOVE. With /proc//cmdline returning 0 bytes, # `proc._get_cmdline' returns an EMPTY TUPLE, so `posix.get_shell' -- which # spreads it, `_get_shell(*proc_args)' -- calls a function whose signature is # `_get_shell(cmd, *args)' with no arguments at all: # # TypeError: _get_shell() missing 1 required positional argument: 'cmd' # # So detect_shell does not fail to FIND a shell here, it raises before it can # look, and it raises TypeError rather than the ShellDetectionFailure the # README tells callers to catch. Reproduced off-target against this build's own # site-packages by feeding get_shell a chain entry with `args=()'. # # These are left asserting the TRUE answers -- what a kernel with a working # cmdline gives -- so they flip green by themselves when brintos/linux#112 # closes. They are NOT rewritten to expect the TypeError: that would pin a # platform gap as this package's contract. # # (The TypeError is also an upstream robustness defect: an empty cmdline is a # thing /proc can legitimately hand back for a zombie or a kernel thread, and # `_get_shell' should skip such an entry rather than crash. Nothing is filed # for it -- docs/conventions/no-public-upstream-reports.md -- and it is not # ours to carry a patch for while #112 is the reason we see it.) # # THE AMBIENT ANSWER. brintos-selftest runs each suite as `sh ', so the # first shell above this probe carries argv[0] `sh' -- a bare word. The command # a caller gets back is therefore NOT a path, which is the case the two spawns # below are the contrast to. check_eq $P/ambient-detect "sh|sh" "$(pv ambient_detect)" # ...and whatever the command turned out to be, its basename is the name. That # is an invariant of _get_shell rather than a fact about this machine, and it # is asserted separately so a surprise in the case above is readable. check_eq $P/ambient-basename "yes" "$(pv ambient_basename_matches)" check_eq $P/ambient-tuple-len "2" "$(pv ambient_tuple_len)" check_eq $P/ambient-name-lower "yes" "$(pv ambient_name_is_lower)" # A chain with no shell in it. max_depth=1 stops at this interpreter, get_shell # returns None, and detect_shell converts that into the documented failure. check_eq $P/depth1-detect "!ShellDetectionFailure" "$(pv depth1_detect)" # AN UNKNOWN PID DOES NOT RAISE THAT. _get_ppid opens /proc//stat with no # guard and the FileNotFoundError travels out through the generator untouched, # so the one exception the README tells callers to catch is not the one they # get. check_eq $P/unknown-pid-detect "!FileNotFoundError" "$(pv unknown_pid_detect)" # ---- start 2 of 3: a shell this suite started, named absolutely ------------- # # `; exit $?' is load-bearing. Both ash and bash exec the single command of a # `-c' string in place of themselves, which would remove the very shell this # case is about from the chain; a second command in the string defeats that. bst_probe $P/spawn-sh-run "$W/spawnsh" 600 \ /bin/sh -c 'python3 "$0" spawn; exit $?' "$PROBE" OUT="$W/spawnsh" check_eq $P/spawn-sh-done "yes" "$(pv spawn_done)" check_eq $P/spawn-sh-argv "spawn" "$(pv spawn_argv1)" check_eq $P/spawn-sh-detect "sh|/bin/sh" "$(pv spawn_detect)" # $SHELL is whatever the image's login environment left; the case above is the # proof it was ignored. This one only records that it was legible either way. _se=$(pv spawn_shell_env) case "$_se" in ""|/*) bst_ok $P/spawn-sh-shell-env ;; *) bst_notok $P/spawn-sh-shell-env "want '' or an absolute path; got:'$_se'" ;; esac # ---- start 3 of 3: the same, under bash ------------------------------------ # # bash is on this image (15-bash.test) and /bin/bash is the update-alternatives # link; the command returned is the LINK path, since that is what argv[0] # carried -- shellingham does not resolve it to /bin/bash.bash. if [ -x /bin/bash ]; then bst_probe $P/spawn-bash-run "$W/spawnbash" 600 \ /bin/bash -c 'python3 "$0" spawn; exit $?' "$PROBE" OUT="$W/spawnbash" check_eq $P/spawn-bash-done "yes" "$(pv spawn_done)" check_eq $P/spawn-bash-argv "spawn" "$(pv spawn_argv1)" check_eq $P/spawn-bash-detect "bash|/bin/bash" "$(pv spawn_detect)" else bst_skip $P/spawn-bash-run "/bin/bash not installed on this image" bst_skip $P/spawn-bash-done "/bin/bash not installed on this image" bst_skip $P/spawn-bash-argv "/bin/bash not installed on this image" bst_skip $P/spawn-bash-detect "/bin/bash not installed on this image" fi rm -rf "$W"