. "$BST_LIB" # BST-TIMEOUT: 1500 # FOUR python3 starts, and they are the whole cost: `import', # # the main probe, the monitor probe, and the `tqdm' console # # script. Every other assertion below reads the filesystem or # # dpkg with shell builtins, or greps a transcript the probes # # already wrote. # # # # 1200 is 99-python3-texttable's and 99-python3-jstyleson's # # figure for THREE starts; this suite adds a fourth, and the # # start dominates on this arm, so 1500 is that figure plus one # # start's share rather than a new estimate. # # # # NOT MEASURED ON TARGET YET: this port's first on-target run # # is the batch verify, and the measured elapsed replaces this # # note in the test plan rather than in this comment, which # # would cost an image build to keep the shipped suite # # byte-identical. # # python3-tqdm 4.66.3 (meta-openembedded/meta-python # recipes-devtools/python/python3-tqdm_4.66.3.bb, `inherit pypi # python_setuptools_build_meta', PyPI sdist) on the wasm32-hwjs glibc image -- # THE PROGRESS METER. Wrap any iterable and get a bar; the formatting layer under # it is a set of documented static methods, and a `tqdm' console script meters a # pipe without a line of Python being written. # # ============================================================================ # THE BAR IS A CR-REWOUND STREAM, WHICH IS WHY EACH ONE IS ASSERTED TWICE. # ============================================================================ # # tqdm does not print lines. It writes a frame, rewinds with a CARRIAGE RETURN, # and writes the next one over it -- so the CRs are the content, not formatting # around it. A build that emitted the same frames on separate lines would look # identical in a transcript and be useless to a caller. Each stream is therefore # asserted as # # -sha -- a 16-hex digest of the EXACT characters written, CRs and # trailing padding included, which is the case that says # nothing moved; and # -norm -- the same text with CR shown as `~' and LF as `$', which is # the case a human can read. # # Neither is sufficient alone: the digest is exact and illegible, the visualised # form is legible and is the only one a reviewer can check against the frames # they expected. # # ============================================================================ # EVERY BAR BELOW IS DRAWN THROUGH AN EXPLICIT `bar_format'. IT HAS TO BE. # ============================================================================ # # tqdm's DEFAULT template carries the elapsed time and the rate, which are # wall-clock and cannot be pinned in a suite. `{n_fmt}/{total_fmt} # {percentage:3.0f}%' keeps the count and the percentage -- which is what this # port is asked to assert -- and drops nothing else that is deterministic. # # The timing layer is NOT thereby left unasserted. It is asserted where it is a # PURE FUNCTION instead: `format_interval' over its own table, and `format_meter' # called with an EXPLICIT `elapsed' argument, which makes the ETA and the rate # arithmetic over the caller's numbers rather than a reading of the clock. # # ============================================================================ # WHY IT PORTS AT ALL: PURE PYTHON, AND ONE IMPORT THAT IS NOT. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476; 99-python3 asserts both directly). A module # carrying a compiled extension could not be imported here -- not "would be # slow", could not be imported. # # tqdm ships NO C: no .c, .pyx or .so anywhere in the sdist and no ext_modules. # `pkg-no-so' below is a REGRESSION TRIPWIRE on a version bump rather than a # statement about today. # # THE ONE THING THAT IS NOT PURE PYTHON IS AN IMPORT IT MAKES. tqdm/utils.py does # `from unicodedata import east_asian_width' at MODULE SCOPE with no try/except, # and in an ordinary CPython unicodedata is a lib-dynload .so -- exactly the shape # this interpreter cannot load. It works here because this python3 builds it in: # 99-python3's `builtin-modules' case names unicodedata in the set it asserts # against sys.builtin_module_names. `unicodedata-builtin' and `east-asian-width' # below assert it again from this package's side, because that is the import # whose failure would present as a broken tqdm rather than as a broken python3. # # `pkg-depends' records the rest, read back off the built .deb rather than # assumed: `python3-core, python3-logging, python3-numbers' and nothing else. # Both named sub-packages belong to the ported python3 recipe (BRINTOS-1476), so # this port waits on no other package. It is asserted as an EQUALITY, because a # substring check would still pass if a release had added a third-party # dependency beside them -- which on this image is the failure that matters, # since the added package would have to be ported too. # # ============================================================================ # THE MONITOR THREAD IS THE PLATFORM RISK, AND IT HAS ITS OWN PROBE AND BOUND. # ============================================================================ # # `tqdm.__new__' starts a daemon TMonitor thread on the FIRST construction -- # `monitor_interval' defaults to 10 -- and this arm refuses a thread-mode clone # (brintos/linux-in-the-browser#85, which 99-python3's `thread-start-refused' # case asserts directly). tqdm has a documented path for that: `except Exception' # around the TMonitor construction, a TqdmMonitorWarning on stderr, and # `monitor_interval = 0' thereafter, so a refusal costs a caller the warning and # nothing else. # # That path is measured in its own file, data/python3-tqdm-monitor-probe.py, run # under its own SHORT bound, for a reason that is not tidiness. # `TMonitor.__init__' calls `atexit.register(self.exit)' BEFORE `self.start()', # and `exit()' JOINS. So a platform on which `start()' neither raises nor produces # a running thread would hang the interpreter at exit -- after every measurement # had been printed. Bounding it separately means that case reds alone, names the # bound, and leaves the twenty rendering cases standing. # # data/python3-tqdm-probe.py, which is where those twenty live, sets # `monitor_interval = 0' BEFORE it constructs anything, so no thread is # attempted there and neither the warning nor an atexit traceback can land in # the middle of its transcript. # # `monitor-interval-zeroed', `monitor-warned' and `monitor-not-alive' are THREE # CASES FOR ONE PREDICTION, and they are three so that a surprise names its own # mechanism: a refusal shows interval 0 / warned / not alive, while a thread that # started and never ran would show interval 10 / not warned / ALIVE. They are # EXPECTED, NOT YET MEASURED -- see "where the values come from" below. # # `monitor-render' is the case that is not a prediction: whatever the monitor # did, the bar must be the same bar, and it is asserted against a literal so that # a monitor gap which HAD perturbed the rendering could not hide behind a # "monitor disabled" pass. # # ============================================================================ # THE CONSOLE SCRIPT, AND WHY ITS STDERR IS GREPPED RATHER THAN PINNED. # ============================================================================ # # `/usr/bin/tqdm' meters a pipe: stdin to stdout unchanged, the bar to stderr. # STDOUT is asserted BYTE-IDENTICAL to the fixture, which is the whole contract # of a pass-through meter and is unaffected by anything above. # # STDERR is asserted by GREP, not by equality, and that is deliberate rather than # lax. The script cannot zero `monitor_interval' -- there is no flag for it -- so # on this arm its stderr is expected to carry the TqdmMonitorWarning ahead of the # bar and an `atexit' traceback after it, neither of which is a defect in the # port and both of which are noise around the measurement. The bar's final frame # is what is asserted, and `cli-status' asserts separately that all of that # leaves the exit status at 0. # # ============================================================================ # WHAT IS NOT COVERED. # ============================================================================ # # The recipe does not `inherit ptest'. Upstream's sdist DOES ship a `tests/' # directory, so there is a suite that could be run and this image does not run # it: nothing installs it, and adding a ptest to a meta-openembedded recipe is a # change to that recipe rather than a port. The cases below are the only # statement about this package on this image. # # The optional back ends -- tqdm.notebook, tqdm.gui, tqdm.rich, tqdm.keras, # tqdm.dask, tqdm.tk, tqdm.contrib.telegram/slack/discord -- ship as files and # are NOT imported. Each needs a package this image does not carry (ipywidgets, # matplotlib, rich, keras, dask, tkinter, requests), so importing one would # assert the absence of another package rather than anything about tqdm. # `pkg-optional-backends-present' asserts they SHIPPED, which is the statement # this suite can honestly make about them. # # ============================================================================ # WHERE THE ASSERTED VALUES COME FROM. # ============================================================================ # # Every literal below was produced by running data/python3-tqdm-probe.py and # data/python3-tqdm-monitor-probe.py on x86-64 against the sdist the recipe's # SRC_URI sha256 pins (23097a41...7e5, verified by digest before measuring), and # the payload cases were read back off the built # python3-tqdm_4.66.3-r0_wasm32.deb. The control interpreter is CPython 3.10 and # the image ships 3.12; that gap is stated here and in the test plan rather than # papered over. # # THE THREE MONITOR CASES ARE THE ONE EXCEPTION AND ARE A PREDICTION. Threads are # not refused on x86-64, so their expected values were obtained by SIMULATING the # refusal -- `_thread.start_new_thread' replaced by one that raises RuntimeError # -- which measures tqdm's code path exactly and says nothing about which path # this arm takes. The on-target result is not yet measured; the batch verify is # this port's first run on wasm32-hwjs. P=python3-tqdm # THE CANARY, which asserts nothing about python3-tqdm. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). The # harness computes its pass count as total-minus-failures, so THE DROPPED LINE # IS COUNTED AS A PASS. A nothing-case therefore stands in that position and the # eaten line costs nothing. It sits ahead of the `require' guards deliberately, # because a `require' that skips would emit its skip as the first line and be # eaten too. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/tqdm MOD=$PKGDIR/__init__.py DI=$SITE/tqdm-4.66.3.dist-info SCRIPT=/usr/bin/tqdm PKGLIST=/var/lib/dpkg/info/python3-tqdm.list PROBE=$BST_DATA/python3-tqdm-probe.py MPROBE=$BST_DATA/python3-tqdm-monitor-probe.py FIXTURE=$BST_DATA/python3-tqdm-fixture.txt BF='{n_fmt}/{total_fmt} {percentage:3.0f}%' tmp=${TMPDIR:-/tmp} W=$tmp/bst-tqdm.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import tqdm' further down is the functional # statement; these are the ones that say WHICH copy answered, so a package that # imported out of some other path is distinguishable from one that imported out # of what this image installed. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/module-file test -f "$MOD" # ...AND IT IS A DIRECTORY, NOT A FILE. The opposite of 99-python3-texttable's # `no-package-dir': tqdm is a PACKAGE of ~20 modules, and a release that had # collapsed it into one file would still import and still pass every behavioural # case below while the shape this image ships had changed. check_eq $P/is-package "yes" \ "$(if [ -f "$PKGDIR/__init__.py" ] && [ ! -f "$SITE/tqdm.py" ]; then echo yes; else echo no; fi)" # THE CONSOLE SCRIPT, which is half the ticket's deliverable and is the only # thing this package puts outside site-packages. check $P/script-file test -f "$SCRIPT" check $P/script-executable test -x "$SCRIPT" # NO COMPILED EXTENSION ANYWHERE THE PACKAGE PUT ANYTHING. See the header: on # this image a .so could not be loaded at all, so this is the case that fails a # version bump which grew one -- loudly, here, instead of at `import' later. # # The list is copied to tmpfs first and never redirected from the rootfs: a # top-level stdin redirect on the virtiofs root makes dup2(2) return 513 and ash # EXITS, ending the suite silently (brintos/linux#73). cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # EVERY REGULAR FILE THE PACKAGE SHIPS IS ONE THIS PORT ACCOUNTED FOR. The case # above names the failure that is expected; this one catches the shape nobody # predicted -- a binary, a .pyd, a stray data file -- without having to enumerate # what it might be. The four exemptions are each a file this package really does # ship and each is asserted in its own right elsewhere: the console script # (`script-file'), the man page and the bash completion that ride ALONG INSIDE # site-packages (`pkg-manpage', `pkg-completion'), and the dist-info metadata. # # A dpkg .list names DIRECTORIES too, and without a trailing slash, so a # name-only filter would report every directory in the package as an unexpected # shape; each entry is therefore tested for being a regular file first. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; "$SCRIPT") continue ;; */tqdm/tqdm.1) continue ;; */tqdm/completion.sh) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # THE TWO NON-PYTHON FILES, NAMED. See above: they are exempted from the sweep, # so they are asserted here rather than merely tolerated there. check $P/pkg-manpage test -s "$PKGDIR/tqdm.1" check $P/pkg-completion test -s "$PKGDIR/completion.sh" # THE OPTIONAL BACK ENDS SHIPPED. See the header: none of them is imported -- # each needs a package this image does not carry -- so their PRESENCE is the # statement this suite can honestly make. A release that had dropped one is a # change in what this image installs even though nothing here imports it. _missing="" for _m in notebook gui rich keras dask tk asyncio auto autonotebook; do [ -f "$PKGDIR/$_m.py" ] || _missing="$_missing $_m" done check_eq $P/pkg-optional-backends-present "" "$_missing" # ----------------------------------------------------------- the dist-info --- # The installed metadata. The version is stated three independent ways -- here, # through importlib.metadata (`dist-version'), and off the module's own # `__version__' (`version-attr') -- and `version-agrees' is what says the three # are one fact rather than three that happen to match today. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 4.66.3" cat "$DI/METADATA" # THE ENTRY POINT the console script is generated from. `/usr/bin/tqdm' exists # because of this line; a wheel that had renamed the callable would change it # here first, and the script would then be a two-line file that raises # ImportError at run time. check_out $P/dist-info-entry-point "tqdm = tqdm.cli:main" cat "$DI/entry_points.txt" # top_level.txt NAMES TWO, AND ONE OF THEM IS AN UPSTREAM PACKAGING ARTEFACT. # `build' is the wheel builder's own scratch directory leaking into the metadata # at 4.66.3; NO `build' module is installed, and `no-build-module' below asserts # that directly. This case pins the quirk so that a later release which fixed it # is visible as a change rather than passing unnoticed. check_out $P/dist-info-top-level "tqdm" cat "$DI/top_level.txt" check_eq $P/no-build-module "absent" \ "$(if [ -e "$SITE/build" ]; then echo present; else echo absent; fi)" # -------------------------------------------------------------- the package --- # dpkg KNOWS the package. Every case above would look identical if some other # package had installed the same files, so this is the one that says # python3-tqdm is a real package this image installed rather than an # IMAGE_INSTALL line that resolved to nothing. # # The stanza is read from a tmpfs copy for the same reason as the file list, and # the anchor is exact -- an unanchored match would also take a -ptest, -dev or # -dbg stanza of the same family. sed -n '/^Package: python3-tqdm$/,/^$/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" # THE RUNTIME DEPENDENCY, as dpkg recorded it, asserted as an EQUALITY. See the # header: both named sub-packages are the ported python3's own, so this line is # the whole claim this port makes about waiting on nothing. 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 "Depends: python3-core, python3-logging, python3-numbers" \ "$(sed -e 's/[[:space:]]*$//' "$W/dep")" # THE TWO NAMED SUB-PACKAGES ARE ACTUALLY INSTALLED. The Depends line is what the # packaging DERIVED; this is what the image has. A dependency recorded and not # satisfied would present as an ImportError deep inside tqdm rather than here. for _sub in python3-logging python3-numbers; do check_out $P/rdep-$_sub "Status: install ok installed" \ sh -c "sed -n '/^Package: $_sub\$/,/^\$/p' /var/lib/dpkg/status" done # ---------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE ANY PROBE. If tqdm cannot be imported then every # probe case below reds at once and each names a different symptom; this case is # what says the cause is the import. It is also the cheapest possible statement # of the deliverable. check $P/import python3 -c 'import tqdm' # ...and `unicodedata' separately. See the header: it is the one module-scope # import in tqdm's dependency closure that is a C extension in an ordinary # CPython, so a lib-dynload that had stopped being built in presents AS a broken # tqdm. This case is what tells the two apart. check $P/unicodedata-import python3 -c 'from unicodedata import east_asian_width' # ---------------------------------------------------------------- the probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present # ONE interpreter start for everything that follows. bst_probe bounds it and # keeps BOTH streams: a probe that dies early otherwise leaves every case below # reporting got:'' and naming a symptom each, with the actual traceback nowhere. # The bound is generous because a python3 start on this arm is tens of seconds # before any work begins. bst_probe $P/probe-run "$W/probe" 600 python3 "$PROBE" OUT="$W/probe" # Read a single key. The probe emits each key at most once, so head -n 1 is # defensive rather than load-bearing; the anchors keep `meter_ascii' distinct # from `meter_ascii_sha' and `iter_norm' from `iter_sha'. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # THE EVIDENCE PLANE. If the probe emitted nothing, every case below is reading # an absent file: each would red with got:'' and the set would read exactly like # a broken library, when what actually happened is that no measurement was taken. # The word `unscorable' is PARSED -- brintos_local_verify re-measures a suite that # prints it on a fresh guest rather than trusting a shared boot's numbers # (BRINTOS-1644) -- so it goes in this structural check, not in a comment. # # The cases below still print their reds: withdraw, do not hide. This case is # what says what those reds measure. 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 identity --- # The package answered out of the tree this image installed, not from a stray # copy on sys.path. This is what makes the payload cases above and the rendering # cases below statements about the SAME tqdm. check_eq $P/module-answered "$MOD" "$(pv module_file)" check_eq $P/module-is-package "yes" "$(pv package_dir)" check_eq $P/version-attr "4.66.3" "$(pv version_attr)" check_eq $P/dist-version "4.66.3" "$(pv dist_version)" check_eq $P/version-agrees "yes" \ "$(if [ "$(pv version_attr)" = "$(pv dist_version)" ]; then echo yes; else echo no; fi)" # THE EXPORTED NAMES, as an equality over the whole `__all__'. tqdm exports # sixteen names and five of them are the exception types a caller catches; a # release that dropped one would break callers silently, and one that added a # name is a change in the surface this image offers. check_eq $P/all-names \ "tqdm,tqdm_gui,trange,tgrange,tqdm_pandas,tqdm_notebook,tnrange,main,TMonitor,TqdmTypeError,TqdmKeyError,TqdmWarning,TqdmDeprecationWarning,TqdmExperimentalWarning,TqdmMonitorWarning,TqdmSynchronisationWarning,__version__" \ "$(pv all_names)" # ------------------------------------------------ the platform-sensitive bit -- # See the header. unicodedata is BUILTIN here rather than a lib-dynload .so, and # `east-asian-width' drives the one function tqdm actually imports from it -- # `N' for an ASCII letter, `W' for a wide CJK one, which is the distinction the # width layer is built on. check_eq $P/unicodedata-builtin "yes" "$(pv unicodedata_builtin)" check_eq $P/east-asian-width "NaW" "$(pv east_asian_width)" # THE SURFACE, one case each. These are the methods the ticket names plus the # ones that drive a bar; a red here names a missing method rather than leaving # the behavioural case below to red with an AttributeError inside the probe. for _n in format_meter format_interval format_sizeof format_num status_printer \ update close clear refresh reset write set_description set_postfix \ get_lock iter trange; do _u=$(echo "$_n" | tr '_' '-') check_eq $P/api-$_u "yes" "$(pv has_$_n)" done # ------------------------------------------- format_meter, THE PURE FUNCTION --- # Given n, total, elapsed and ncols it is arithmetic over its arguments, so every # value here is exact rather than timing-dependent. This is where the ETA and the # rate ARE asserted -- see the header on why no bar below carries them. # # `ascii=True' pins the fill alphabet to `#' and the digits 1-9. The `7' at the # end of `#######7' is a PARTIAL cell: the bar is 35% of 22 columns, and the # fractional remainder is drawn as a digit rather than rounded away. A build that # rounded would still produce a plausible bar and would fail here. check_eq $P/meter-ascii-sha "ccc8cebb2cbcbe4c" "$(pv meter_ascii_sha)" check_eq $P/meter-ascii \ ' 35%|#######7 | 35/100 [00:07<00:13, 5.00it/s]' \ "$(pv meter_ascii)" # THE DEFAULT ALPHABET is the Unicode eighth-blocks, and the same 35% renders the # same partial cell as U+258B. Asserted beside the ASCII form because it is the # one a caller gets without asking, and because it is the only case in this suite # whose correctness depends on the width layer above. check_eq $P/meter-default-sha "7a406827ae257e1a" "$(pv meter_default_sha)" check_eq $P/meter-default \ ' 35%|███████▋ | 35/100 [00:07<00:13, 5.00it/s]' \ "$(pv meter_default)" # NO TOTAL: no bar, no percentage and no ETA -- the count, the elapsed and the # rate alone. This is what a caller wrapping an UNSIZED iterable gets, and it is # a different code path rather than the same one with a blank bar. check_eq $P/meter-no-total "35it [00:07, 5.00it/s]" "$(pv meter_nototal)" # THE TWO ENDS. At n=0 with elapsed=0 the rate is unknown and renders `?it/s' # with `?' for the ETA -- a build that divided by zero would fail here rather # than in a caller's first bar. At n==total the ETA is `00:00' rather than # absent, and the bar is full. check_eq $P/meter-zero \ ' 0%| | 0/100 [00:00 3 is deliberate -- a build that incremented # by one per call regardless of its argument would still pass a test made of # update(1) calls, and would fail here on the missing `2/3' frame. check_eq $P/manual-sha "2664453adf95e3ba" "$(pv manual_sha)" check_eq $P/manual-norm '~0/3 0%~1/3 33%~3/3 100%~3/3 100%$' "$(pv manual_norm)" # `leave=False' is the other documented close(): the bar is ERASED rather than # left behind -- a blank frame the width of the last one, then a final CR and NO # newline, so the caller's next write lands on the same line. The trailing spaces # in that blank frame are exactly what no diff view preserves, which is why the # digest is asserted beside the visualised form. check_eq $P/leave-false-sha "7e3461f60e509ac4" "$(pv leave_false_sha)" check_eq $P/leave-false-norm '~0/3 0%~3/3 100%~ ~' "$(pv leave_false_norm)" check_eq $P/leave-false-no-newline "no" "$(pv leave_false_ends_newline)" # The main probe ran to the end. Every case above greps a key, so a probe that # died halfway would leave the remaining keys absent and each case would red with # got:'' -- this case is the one that says the cause was a truncated run. check_eq $P/probe-complete "yes" "$(pv probe_complete)" # ------------------------------------------------------- the monitor thread --- # See the header. This is the platform half, in its own file and under its own # SHORT bound because the failure it guards against is a HANG at interpreter # exit, and a hang inside the main probe would have cost every case above. if [ ! -f "$MPROBE" ]; then bst_notok $P/monitor-probe-present "$MPROBE did not ship" else bst_ok $P/monitor-probe-present bst_probe $P/monitor-probe-run "$W/mon" 300 python3 "$MPROBE" MOUT="$W/mon" mpv() { grep "^$1=" "$MOUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # 10 is upstream's default and the reason a thread is attempted at all. A # release shipping 0 would make every case below vacuous rather than red. check_eq $P/monitor-default-interval "10" "$(mpv default_interval)" # THE THREE-CASE PREDICTION. See the header: expected, NOT yet measured, and # three so that a surprise names its own mechanism. # refusal (expected): interval 0 / warned yes / alive no # started but never runs: interval 10 / warned no / alive YES # threads actually work: interval 10 / warned no / alive yes # The second and third are told apart by the rendering case below and by # 99-python3's own `thread-start-refused'. check_eq $P/monitor-interval-zeroed "0" "$(mpv interval_after)" check_eq $P/monitor-warned "yes" "$(mpv monitor_warning)" check_eq $P/monitor-not-alive "no" "$(mpv monitor_alive)" # THE CASE THAT IS NOT A PREDICTION. Whatever the monitor did, the bar must be # the same bar -- asserted against a literal so a monitor gap that HAD # perturbed the rendering could not hide behind a "monitor disabled" pass. check_eq $P/monitor-render '~0/2 0%~2/2 100%~2/2 100%$' "$(mpv render)" check_eq $P/monitor-probe-complete "yes" "$(mpv monitor_probe_complete)" fi # --------------------------------------------------------- the console script --- # `/usr/bin/tqdm' meters a pipe. The fixture is copied to tmpfs first and the # redirect happens inside ( ) -- a top-level stdin redirect on the virtiofs root # ends the suite silently (brintos/linux#73). if [ ! -f "$FIXTURE" ]; then bst_notok $P/cli-fixture-present "$FIXTURE did not ship" else bst_ok $P/cli-fixture-present FIX=$(bst_local "$FIXTURE") ( "$SCRIPT" --total 5 --bar_format "$BF" < "$FIX" > "$W/cli.out" 2> "$W/cli.err" ) _cli_rc=$? # ALL THE NOISE ON STDERR STILL LEAVES THE EXIT STATUS AT 0. See the header: # the TqdmMonitorWarning and the atexit traceback this arm is expected to # produce are written by paths that do not fail the process, and a caller # putting `tqdm' in a pipeline depends on exactly that. check_eq $P/cli-status "0" "$_cli_rc" # STDOUT IS THE CONTRACT: byte-identical to stdin. Nothing above can affect it # and nothing about the meter is allowed to. check_eq $P/cli-stdout-identical "$(cat "$FIX")" "$(cat "$W/cli.out")" check_eq $P/cli-stdout-bytes "31" "$(wc -c < "$W/cli.out" | tr -d ' ')" # THE BAR REACHED ITS LAST FRAME on stderr. Grepped rather than pinned -- see # the header -- and the frames are CR-separated, so they are split onto lines # first. `--total 5' is what makes the percentage meaningful: without it the # script does not know the length of a pipe and every frame reads `0%'. tr '\r' '\n' < "$W/cli.err" > "$W/cli.errlines" 2>/dev/null check_out $P/cli-bar-final "5/5 100%" cat "$W/cli.errlines" check_out $P/cli-bar-initial "0/5 0%" cat "$W/cli.errlines" # AND NOTHING OF THE BAR LEAKED INTO STDOUT. The two streams being separate is # what makes `tqdm' usable mid-pipeline at all, and a build that had written # the meter to stdout would still pass `cli-bar-final' above. check_eq $P/cli-stdout-clean "0" "$(grep -c '%' "$W/cli.out" 2>/dev/null)" # WITHOUT --total THERE IS NO PERCENTAGE, because the script cannot know the # length of a pipe. This is the documented default and it is asserted so that a # release which started buffering stdin to count it -- which would break # metering a stream -- is visible here. ( "$SCRIPT" --bar_format "$BF" < "$FIX" > "$W/cli2.out" 2> "$W/cli2.err" ) tr '\r' '\n' < "$W/cli2.err" > "$W/cli2.errlines" 2>/dev/null check_out $P/cli-no-total-unknown "5/?" cat "$W/cli2.errlines" check_eq $P/cli-no-total-stdout-identical "$(cat "$FIX")" "$(cat "$W/cli2.out")" rm -f "$FIX" fi rm -rf "$W"