. "$BST_LIB" # BST-TIMEOUT: 900 # TWO python3 starts: a bare `import' and the probe, which # # takes ONE start for 248 measurements. 400s per start is # # the budget the neighbouring python3 suites use; 900 is # # that pair plus headroom for the probe's ~30 protocol # # drives, every one of which is in-process arithmetic over # # byte strings -- no device, no peer, no fork. # # # # 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-xmodem 0.4.7 (meta-openembedded meta-python/recipes-devtools/python, # python3-xmodem_0.4.7.bb, `inherit pypi setuptools3', PyPI sdist) on the # wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE IS, AND WHY IT IS TESTABLE WITH NO DEVICE AT ALL. # ============================================================================ # # `xmodem' is a literal implementation of XMODEM.TXT, XMODEM1K.TXT and # XMODMCRC.TXT -- the 1981 framing protocols -- as ONE 762-line module, # `xmodem/__init__.py'. # # IT DOES NOT OWN A PORT. XMODEM(getc, putc) takes two callables and reaches the # outside world through nothing else: no serial device, no socket, no file # descriptor of its own. So the whole protocol can be driven in process against # byte strings the probe writes itself, and this port CLOSES ON ITS SUITE -- # non-interactive, no browser pass, no BRINTOS-780 follow-on. # # NO PATCH, NO BBAPPEND AND NO SITE ANSWER. The recipe is meta-openembedded's # exactly as written -- pure Python, wheel tag py3-none-any, no configure step # and no compile step, so there is nothing on this arm for an override to reach. # Its one deviation from a stock pypi recipe is a `do_install:append' that moves # setup.py's `data_files' docs out of ${prefix}/doc into ${docdir}/${PN}; that # move is what the doc-* cases below assert, and it is the only recipe-specific # behaviour there is to test. # # ============================================================================ # THE PORT TURNS ON ONE IMPORT, AND IT IS NOT THE OBVIOUS ONE. # ============================================================================ # # The recipe's RDEPENDS names `python3-logging', and the built .deb's line is # exactly "Depends: python3-core, python3-logging" (`pkg-depends-exact' asserts # it verbatim). But the module-scope import that decides whether this package # works here is # # import select # # on line 116 -- and `select' is a C EXTENSION MODULE. This image's python3 is # ONE STATIC BINARY with an empty lib-dynload and no dynamic loader # (BRINTOS-1476): python3-core ships `/usr/bin/python3.12' and NOT ONE `.so'. # So `select' can only be present as a BUILTIN compiled into that binary, and if # it is not, `import xmodem' fails outright and every case here is red at once. # # `select-builtin' is the case that says which. It is asserted from # `sys.builtin_module_names' rather than from a successful import, so a red # names the cause instead of leaving 247 unexplained failures behind it. # # The irony is worth recording: `select' is imported at module scope and used # ONLY inside `_send()', the argparse CLI helper, which nothing on this image # can reach -- see `pkg-no-bin' below. The one import that could have cost us # the port is the one the library does not need. # # ============================================================================ # THE MODULE'S OWN VERSION STRING IS WRONG, AND IT IS NOT A PACKAGING FAULT. # ============================================================================ # # `xmodem.__version__' is "0.4.5" while the distribution is 0.4.7. Upstream did # not bump the literal in the module; the dist-info, the .deb and the recipe all # say 0.4.7 and agree with each other. `version-attr' pins 0.4.5 and # `dist-info-version' pins 0.4.7, so the DISAGREEMENT ITSELF is the assertion -- # a caller doing version detection off `__version__' here is off by two # releases, and a future recipe bump that silently fixed one side without the # other would turn one of the two red. # # ============================================================================ # SEVEN BEHAVIOURS A CALLER WILL GET WRONG. THESE ARE THE POINT OF THE SUITE. # ============================================================================ # # AN EMPTY PAYLOAD DOES NOT ROUND-TRIP TO ZERO BYTES. send() on an empty # stream reads nothing, never enters its data loop, and emits # ONE byte: EOT. The whole wire is b"\x04" and send() returns # True. The receiving half is where it goes wrong -- recv() # treats a FIRST EOT as only "maybe empty", sets a flag and # goes round its start sequence again, so a sender that says # EOT once and stops is given up on and recv() returns NONE, # not 0. It is the SECOND EOT that yields the documented # empty-file result, and even then recv() answers with two # CANs rather than an ACK. All three are measured: # `empty-recv-single-eot' (None), `empty-recv-double-eot' (0) # and `empty-recv-cans' (2). # # A CAN DOES NOT ABORT A TRANSFER IN PROGRESS. Two consecutive CANs cancel # the send, but ONLY in the START SEQUENCE # (`can-start-send' -> False). Once blocks are flowing, the # sender's inner loop tests only `char == ACK', so a CAN # answering a data block is merely "not an ACK": it counts one # error and RETRANSMITS THE BLOCK. `can-midblock-send' is # True and `can-midblock-blocks' is 3 -- the receiver said # cancel twice and got the block three times. # # THE RETRY LIMIT IS OFF BY ONE, IN THE CALLER'S FAVOUR. Every limit is # `error_count > retry', so retry=N permits N+1 attempts. # Measured at three values: retry=0 still makes one attempt # (`retry0-reads' 1), retry=1 two, retry=4 five. # # THE TWO CALLBACKS HAVE DIFFERENT SIGNATURES. send()'s callback takes THREE # arguments and recv()'s takes FOUR -- recv() appends the # negotiated packet size, because the block size is read off # the wire rather than configured. One function passed to both # raises TypeError on whichever it was not written for. # `send-callback-arity' 3, `recv-callback-arity' 4. # # recv() IGNORES self.mode ENTIRELY. The constructor's mode picks the SEND # block size and nothing else; recv() takes 128 or 1024 from # the SOH/STX byte it reads. `recv-ignores-mode' decodes a # 2048-byte 1K stream through an instance built in the default # `xmodem' mode, and gets all 2048 bytes back. # # A BAD MODE IS ACCEPTED BY THE CONSTRUCTOR AND REJECTED BY send(). XMODEM(..., # mode="ymodem") constructs fine and keeps the string on # `.mode'; the ValueError arrives later, from inside send(), # after the caller has already wired everything up. # # recv() SLEEPS FOR `timeout' SECONDS PER JUNK BYTE. Its unexpected-byte path # ends in `time.sleep(timeout)', and the default timeout is # 60 with a default retry of 16 -- so a noisy line holds the # caller for over a quarter of an hour while reading nothing. # A caller must pass its own timeout and THIS SUITE ALWAYS # DOES; `junk-recv' drives that path at timeout=0 and asserts # it terminates by the retry limit rather than by the clock. # That is also why the suite's BST-TIMEOUT is not load-bearing # here: nothing in the probe can wait on a wall clock. # # ============================================================================ # WHAT THE PROBE DOES, AND WHY THERE ARE NO THREADS IN IT. # ============================================================================ # # A round trip needs a sender and a receiver. Running both live would need # concurrency, and this suite has none: it drives them SEQUENTIALLY. send() runs # first against a scripted receiver that ACKs and RECORDS THE WIRE; that # recorded wire is then replayed into recv(). Both engines really run, and # neither needs the other to be live, because the sender has committed the whole # wire before recv() reads its first byte. # # The replay is a LIST OF SEGMENTS rather than one buffer, and the seam between # two segments is an idle line -- one read there returns None. That is not a # convenience. recv()'s error path calls the PURGE subroutine the specification # describes, which reads until a read times out; on one flat buffer that purge # swallows the retransmission that was supposed to follow the NAK, and the # transfer dies with None. The first cut of this probe did exactly that and # reported a working library as broken. The seam is how a replay says "the line # went quiet", which is the condition PURGE is written against. # # Every check field is computed TWICE: once by the module's 256-entry table and # once by the probe, bitwise from the CRC-16/XMODEM polynomial (0x1021) with no # table at all. `crc-matches-polynomial' asserts the two agree over six inputs, # which is what makes every other CRC literal in this file a value a reader can # rederive rather than a number copied out of a run. P=python3-xmodem # The suite's own canary: a red here means the file did not start, and every # absent case below is unscorable rather than failing. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/xmodem MOD=$PKGDIR/__init__.py DI=$SITE/xmodem-0.4.7.dist-info PKGLIST=/var/lib/dpkg/info/python3-xmodem.list DOCDIR=/usr/share/doc/python3-xmodem PROBE=$BST_DATA/python3-xmodem-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-xmodem-suite.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---------------------------------------------------------------- layout ----- # A PACKAGE, not a flat module: setup.py declares packages=['xmodem'], so the # payload is a directory with __init__.py and __main__.py in it. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/module-file test -s "$MOD" check $P/main-file test -s "$PKGDIR/__main__.py" check $P/not-flat-module test ! -e "$SITE/xmodem.py" check $P/pycache-init test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" check $P/pycache-main test -s "$PKGDIR/__pycache__/__main__.cpython-312.pyc" # ------------------------------------------------------------- the .deb ------ cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # PURE PYTHON, AND NOT MERELY "NO .so OF ITS OWN". This image's python3 is ONE # STATIC BINARY with an empty lib-dynload and no dynamic loader (BRINTOS-1476), # so a module carrying a compiled extension could not be imported here at all. # xmodem ships no C -- no .c and no ext_modules in setup.py -- so this is a # REGRESSION TRIPWIRE on a version bump rather than a statement about today. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # NOTHING LANDS IN /usr/bin. setup.py declares no `entry_points' and no # `scripts', so the argparse `run()' in the module is reachable only through # `python3 -m xmodem'. That matters more here than in a neighbouring library: # `run()' is the ONLY caller of the `select' import this port turns on, and it # is unreachable as a command. check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/(s?bin)/' "$W/pkgf" 2>/dev/null)" # ...and nothing but Python in the runtime payload, which is the same claim from # the other side: the doc files setup.py declares as `data_files' belong to the # -doc package, and one of them appearing here would be the recipe's # do_install:append having failed to move it. _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" sed -n '/^Package: python3-xmodem$/,/^$/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: 0.4.7-r0" cat "$W/st" # THE DEPENDS LINE, VERBATIM. python3-logging is the recipe's own RDEPENDS; # python3-core is what setuptools3-base adds. Everything else the module imports # at scope -- select, platform, time, functools -- is inside the interpreter # binary, which is why nothing else appears here. See `select-builtin'. check_eq $P/pkg-depends-exact "Depends: python3-core, python3-logging" \ "$(sed -n 's/^Depends: /Depends: /p' "$W/st" | head -n 1)" # ---------------------------------------------------------------- dist-info -- # The distribution and the module share a name here, so the dist-info directory # is `xmodem-0.4.7.dist-info' with no underscore rewriting. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: xmodem" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 0.4.7" cat "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" 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-top-level "xmodem" cat "$DI/top_level.txt" # ------------------------------------------------------------------- docs ----- # THE ONE RECIPE-SPECIFIC BEHAVIOUR THERE IS. setup.py's `data_files' would put # the four protocol specifications in ${prefix}/doc, which is not a packaged # location; the recipe's do_install:append moves them to ${docdir}/${PN}. These # cases assert the move landed, and `doc-not-in-prefix' asserts the directory it # moved them OUT of is gone -- an rmdir that silently failed would leave the # files in both places and only this case would notice. # # They are in the SEPARATE python3-xmodem-doc package, so a skip here means the # image took the runtime package without the docs, not that the recipe is wrong. if [ -d "$DOCDIR" ]; then bst_ok $P/doc-dir check $P/doc-xmodem-txt test -s "$DOCDIR/XMODEM.TXT" check $P/doc-xmodem1k-txt test -s "$DOCDIR/XMODEM1K.TXT" check $P/doc-xmodmcrc-txt test -s "$DOCDIR/XMODMCRC.TXT" check $P/doc-ymodem-txt test -s "$DOCDIR/ymodem.txt" check $P/doc-not-in-prefix test ! -d /usr/doc else bst_skip $P/doc-dir "python3-xmodem-doc is not on this image" fi # ------------------------------------------------------------ bare import ----- # ITS OWN INTERPRETER START, AND ITS OWN CASE, because everything below depends # on it. A red here with `select-builtin' also red is the whole story; a red # here with select present is something else and the stderr says what. check $P/import python3 -c "import xmodem" # stderr must be EMPTY on import: a module that warns at import time on this arm # would be a platform signal, and it would also corrupt any caller piping # through `python3 -m xmodem'. check_eq $P/import-quiet "" "$(python3 -c "import xmodem" 2>&1)" # ----------------------------------------------------------------- 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 bst_probe $P/probe-run "$W/probe" 600 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 probe's own two bookends. `probe-complete' says it reached its last line # and `probe-count' says it emitted every key it meant to: a probe that died # two-thirds of the way through would otherwise leave the missing cases scoring # against an empty string, which check_eq would read as a wrong answer rather # than as an absent one. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "248" "$(pv emitted_count)" # -------------------------------------------------------------- identity ----- # WHICH COPY ANSWERED. Every behavioural case below is a statement about the # module named here. check_eq $P/module-answered "$MOD" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/license-attr "MIT" "$(pv license_attr)" # THE STALE VERSION LITERAL -- see the header. This and `dist-info-version' # (0.4.7) are deliberately in disagreement, and that disagreement is upstream's. check_eq $P/version-attr "0.4.5" "$(pv version_attr)" # ------------------------------------------------- the import that decides ---- # `select' is a C extension module and this image has no dynamic loader, so it # is here as a BUILTIN or not at all. See the header. check_eq $P/select-builtin "yes" "$(pv select_builtin)" check_eq $P/stdlib-select "yes" "$(pv stdlib_select)" check_eq $P/stdlib-platform "yes" "$(pv stdlib_platform)" check_eq $P/stdlib-logging "yes" "$(pv stdlib_logging)" check_eq $P/stdlib-time "yes" "$(pv stdlib_time)" check_eq $P/stdlib-functools "yes" "$(pv stdlib_functools)" # --------------------------------------------------------- protocol bytes ----- # The eight control bytes, against the specification rather than against # themselves. CRC is the ASCII letter 'C' (0x43), which is what a receiver sends # to ASK for CRC mode -- it is not a check value. check_eq $P/byte-soh "01" "$(pv byte_soh)" check_eq $P/byte-stx "02" "$(pv byte_stx)" check_eq $P/byte-eot "04" "$(pv byte_eot)" check_eq $P/byte-ack "06" "$(pv byte_ack)" check_eq $P/byte-dle "10" "$(pv byte_dle)" check_eq $P/byte-nak "15" "$(pv byte_nak)" check_eq $P/byte-can "18" "$(pv byte_can)" check_eq $P/byte-crc "43" "$(pv byte_crc)" # --------------------------------------------------------------- the API ------ check_eq $P/has-xmodem "yes" "$(pv has_xmodem)" check_eq $P/has-xmodem1k "yes" "$(pv has_xmodem1k)" check_eq $P/has-run "yes" "$(pv has_run)" check_eq $P/run-callable "yes" "$(pv run_callable)" check_eq $P/method-send "yes" "$(pv method_send)" check_eq $P/method-recv "yes" "$(pv method_recv)" check_eq $P/method-abort "yes" "$(pv method_abort)" check_eq $P/method-calc-crc "yes" "$(pv method_calc_crc)" check_eq $P/method-calc-checksum "yes" "$(pv method_calc_checksum)" # NO `__all__', so a star-import leaks every module-scope name including the # imported modules themselves. check_eq $P/has-dunder-all "no" "$(pv has_dunder_all)" # XMODEM1k IS A functools.partial, NOT A SUBCLASS. It cannot be subclassed and # isinstance() against it is a TypeError -- a caller writing # `class Mine(XMODEM1k)' gets a confusing failure rather than a clear one. check_eq $P/xmodem1k-is-partial "yes" "$(pv xmodem1k_is_partial)" check_eq $P/xmodem1k-is-class "no" "$(pv xmodem1k_is_class)" check_eq $P/xmodem1k-mode "xmodem1k" "$(pv xmodem1k_mode)" # A BAD MODE IS ACCEPTED BY THE CONSTRUCTOR -- see the header. check_eq $P/bad-mode-ctor "ok" "$(pv bad_mode_ctor)" check_eq $P/bad-mode-attr "ymodem" "$(pv bad_mode_attr)" check_eq $P/bad-mode-send "RAISED:ValueError" "$(pv bad_mode_send)" # ------------------------------------------------------------ arithmetic ------ # The two answers the module's own docstrings state, asserted against the code. check_eq $P/csum-hello "14" "$(pv csum_hello)" check_eq $P/csum-hello-world "3c" "$(pv csum_hello_world)" check_eq $P/csum-empty "00" "$(pv csum_empty)" check_eq $P/csum-wraps "fe" "$(pv csum_wraps)" check_eq $P/crc-hello "c362" "$(pv crc_hello)" check_eq $P/crc-hello-world "4ab3" "$(pv crc_hello_world)" check_eq $P/crc-empty "0000" "$(pv crc_empty)" check_eq $P/crc-128-nul "0000" "$(pv crc_128_nul)" check_eq $P/crc-128-pad "f8b0" "$(pv crc_128_pad)" # THE TABLE AGAINST THE POLYNOMIAL. The probe recomputes CRC-16/XMODEM bitwise # from 0x1021 with no table, over six inputs. This is what makes every CRC # literal above a value a reader can rederive. check_eq $P/crc-matches-polynomial "yes" "$(pv crc_matches_polynomial)" check_eq $P/crctable-len "256" "$(pv crctable_len)" check_eq $P/crctable-first "0000" "$(pv crctable_first)" check_eq $P/crctable-last "1ef0" "$(pv crctable_last)" # --------------------------------------------------------------- framing ------ # The header is [SOH|STX, seq, 0xff-seq]. Sequence starts at 1, wraps mod 0x100, # and block 256 therefore carries sequence 0 with complement 0xff. check_eq $P/header-128-seq1 "0101fe" "$(pv header_128_seq1)" check_eq $P/header-1024-seq1 "0201fe" "$(pv header_1024_seq1)" check_eq $P/header-128-seq255 "01ff00" "$(pv header_128_seq255)" check_eq $P/header-128-seq0 "0100ff" "$(pv header_128_seq0)" # A block size that is neither 128 nor 1024 trips an assert, so a build run with # -O would frame garbage instead of refusing. This image does not run -O. check_eq $P/header-bad-size "RAISED:AssertionError" "$(pv header_bad_size)" check_eq $P/checksum-field-csum "00" "$(pv checksum_field_csum)" check_eq $P/checksum-field-crc "0000" "$(pv checksum_field_crc)" # One 128-byte block of 'A', field by field, with the check value computed both # by the table and by the polynomial. check_eq $P/frame-crc-len "134" "$(pv frame_crc_len)" check_eq $P/frame-crc-soh "01" "$(pv frame_crc_soh)" check_eq $P/frame-crc-seq "01fe" "$(pv frame_crc_seq)" check_eq $P/frame-crc-data0 "41" "$(pv frame_crc_data0)" check_eq $P/frame-crc-check "1cce" "$(pv frame_crc_check)" check_eq $P/frame-crc-check-independent "1cce" "$(pv frame_crc_check_independent)" check_eq $P/frame-crc-eot "04" "$(pv frame_crc_eot)" # The same block in checksum mode is one byte shorter: an 8-bit sum, not a CRC. check_eq $P/frame-csum-len "133" "$(pv frame_csum_len)" check_eq $P/frame-csum-check "80" "$(pv frame_csum_check)" check_eq $P/frame-csum-check-independent "80" "$(pv frame_csum_check_independent)" check_eq $P/frame-csum-eot "04" "$(pv frame_csum_eot)" # A 1K block opens with STX, not SOH -- which is the only thing that tells a # receiver to read 1024 bytes instead of 128. check_eq $P/frame-1k-len "1030" "$(pv frame_1k_len)" check_eq $P/frame-1k-stx "02" "$(pv frame_1k_stx)" check_eq $P/frame-1k-seq "01fe" "$(pv frame_1k_seq)" check_eq $P/frame-1k-eot "04" "$(pv frame_1k_eot)" # THE PAD BYTE IS CPMEOF (0x1a) AND IT IS CONFIGURABLE. It is what makes a short # final block full length, and it is why a round trip is equal only modulo # padding -- the receiver cannot know where the payload ended. check_eq $P/pad-default-byte "1a" "$(pv pad_default_byte)" check_eq $P/pad-custom-byte "00" "$(pv pad_custom_byte)" # Sequence numbering over 258 blocks, through the wrap. check_eq $P/seq-first "1" "$(pv seq_first)" check_eq $P/seq-255th "255" "$(pv seq_255th)" check_eq $P/seq-256th "0" "$(pv seq_256th)" check_eq $P/seq-257th "1" "$(pv seq_257th)" check_eq $P/seq-wraps-to-zero "yes" "$(pv seq_wraps_to_zero)" check_eq $P/seq-complements-ok "yes" "$(pv seq_complements_ok)" # ------------------------------------------------------------ round trips ----- # THE BACKBONE. Each payload is sent through send(), the wire it produced is # replayed into recv(), and the bytes that come out are compared with the bytes # that went in PLUS the padding the protocol defines. `*-pad-len' names that # padding explicitly, so a case that passes only because both sides were empty # cannot hide among the ones that carried data. # # `*-wire-len' is the framed size and is arithmetic a reader can check: # ceil(n/block) * (3 + block + check) + 1 for the EOT. # # THE EMPTY CASES ARE NOT A ROUND TRIP AND DO NOT PRETEND TO BE. send() emits a # lone EOT (wire-len 1) and recv() returns None -- see the header, and see the # `empty-*' cases below which take that apart properly. check_eq $P/rt-empty-crc-send "yes" "$(pv rt_empty_crc_send)" check_eq $P/rt-empty-crc-wire-len "1" "$(pv rt_empty_crc_wire_len)" check_eq $P/rt-empty-crc-recv "None" "$(pv rt_empty_crc_recv)" check_eq $P/rt-empty-crc-bytes-match "yes" "$(pv rt_empty_crc_bytes_match)" check_eq $P/rt-empty-crc-pad-len "0" "$(pv rt_empty_crc_pad_len)" check_eq $P/rt-empty-csum-send "yes" "$(pv rt_empty_csum_send)" check_eq $P/rt-empty-csum-wire-len "1" "$(pv rt_empty_csum_wire_len)" check_eq $P/rt-empty-csum-recv "None" "$(pv rt_empty_csum_recv)" check_eq $P/rt-empty-csum-bytes-match "yes" "$(pv rt_empty_csum_bytes_match)" check_eq $P/rt-empty-csum-pad-len "0" "$(pv rt_empty_csum_pad_len)" check_eq $P/rt-one-crc-send "yes" "$(pv rt_one_crc_send)" check_eq $P/rt-one-crc-wire-len "134" "$(pv rt_one_crc_wire_len)" check_eq $P/rt-one-crc-recv "128" "$(pv rt_one_crc_recv)" check_eq $P/rt-one-crc-bytes-match "yes" "$(pv rt_one_crc_bytes_match)" check_eq $P/rt-one-crc-pad-len "127" "$(pv rt_one_crc_pad_len)" check_eq $P/rt-one-csum-send "yes" "$(pv rt_one_csum_send)" check_eq $P/rt-one-csum-wire-len "133" "$(pv rt_one_csum_wire_len)" check_eq $P/rt-one-csum-recv "128" "$(pv rt_one_csum_recv)" check_eq $P/rt-one-csum-bytes-match "yes" "$(pv rt_one_csum_bytes_match)" check_eq $P/rt-one-csum-pad-len "127" "$(pv rt_one_csum_pad_len)" check_eq $P/rt-block-crc-send "yes" "$(pv rt_block_crc_send)" check_eq $P/rt-block-crc-wire-len "134" "$(pv rt_block_crc_wire_len)" check_eq $P/rt-block-crc-recv "128" "$(pv rt_block_crc_recv)" check_eq $P/rt-block-crc-bytes-match "yes" "$(pv rt_block_crc_bytes_match)" check_eq $P/rt-block-crc-pad-len "0" "$(pv rt_block_crc_pad_len)" check_eq $P/rt-block-csum-send "yes" "$(pv rt_block_csum_send)" check_eq $P/rt-block-csum-wire-len "133" "$(pv rt_block_csum_wire_len)" check_eq $P/rt-block-csum-recv "128" "$(pv rt_block_csum_recv)" check_eq $P/rt-block-csum-bytes-match "yes" "$(pv rt_block_csum_bytes_match)" check_eq $P/rt-block-csum-pad-len "0" "$(pv rt_block_csum_pad_len)" check_eq $P/rt-block1-crc-send "yes" "$(pv rt_block1_crc_send)" check_eq $P/rt-block1-crc-wire-len "267" "$(pv rt_block1_crc_wire_len)" check_eq $P/rt-block1-crc-recv "256" "$(pv rt_block1_crc_recv)" check_eq $P/rt-block1-crc-bytes-match "yes" "$(pv rt_block1_crc_bytes_match)" check_eq $P/rt-block1-crc-pad-len "127" "$(pv rt_block1_crc_pad_len)" check_eq $P/rt-block1-csum-send "yes" "$(pv rt_block1_csum_send)" check_eq $P/rt-block1-csum-wire-len "265" "$(pv rt_block1_csum_wire_len)" check_eq $P/rt-block1-csum-recv "256" "$(pv rt_block1_csum_recv)" check_eq $P/rt-block1-csum-bytes-match "yes" "$(pv rt_block1_csum_bytes_match)" check_eq $P/rt-block1-csum-pad-len "127" "$(pv rt_block1_csum_pad_len)" check_eq $P/rt-multi-crc-send "yes" "$(pv rt_multi_crc_send)" check_eq $P/rt-multi-crc-wire-len "799" "$(pv rt_multi_crc_wire_len)" check_eq $P/rt-multi-crc-recv "768" "$(pv rt_multi_crc_recv)" check_eq $P/rt-multi-crc-bytes-match "yes" "$(pv rt_multi_crc_bytes_match)" check_eq $P/rt-multi-crc-pad-len "0" "$(pv rt_multi_crc_pad_len)" check_eq $P/rt-multi-csum-send "yes" "$(pv rt_multi_csum_send)" check_eq $P/rt-multi-csum-wire-len "793" "$(pv rt_multi_csum_wire_len)" check_eq $P/rt-multi-csum-recv "768" "$(pv rt_multi_csum_recv)" check_eq $P/rt-multi-csum-bytes-match "yes" "$(pv rt_multi_csum_bytes_match)" check_eq $P/rt-multi-csum-pad-len "0" "$(pv rt_multi_csum_pad_len)" check_eq $P/rt-k1-empty-crc-send "yes" "$(pv rt_k1_empty_crc_send)" check_eq $P/rt-k1-empty-crc-wire-len "1" "$(pv rt_k1_empty_crc_wire_len)" check_eq $P/rt-k1-empty-crc-recv "None" "$(pv rt_k1_empty_crc_recv)" check_eq $P/rt-k1-empty-crc-bytes-match "yes" "$(pv rt_k1_empty_crc_bytes_match)" check_eq $P/rt-k1-empty-crc-pad-len "0" "$(pv rt_k1_empty_crc_pad_len)" check_eq $P/rt-k1-empty-csum-send "yes" "$(pv rt_k1_empty_csum_send)" check_eq $P/rt-k1-empty-csum-wire-len "1" "$(pv rt_k1_empty_csum_wire_len)" check_eq $P/rt-k1-empty-csum-recv "None" "$(pv rt_k1_empty_csum_recv)" check_eq $P/rt-k1-empty-csum-bytes-match "yes" "$(pv rt_k1_empty_csum_bytes_match)" check_eq $P/rt-k1-empty-csum-pad-len "0" "$(pv rt_k1_empty_csum_pad_len)" check_eq $P/rt-k1-one-crc-send "yes" "$(pv rt_k1_one_crc_send)" check_eq $P/rt-k1-one-crc-wire-len "1030" "$(pv rt_k1_one_crc_wire_len)" check_eq $P/rt-k1-one-crc-recv "1024" "$(pv rt_k1_one_crc_recv)" check_eq $P/rt-k1-one-crc-bytes-match "yes" "$(pv rt_k1_one_crc_bytes_match)" check_eq $P/rt-k1-one-crc-pad-len "1023" "$(pv rt_k1_one_crc_pad_len)" check_eq $P/rt-k1-one-csum-send "yes" "$(pv rt_k1_one_csum_send)" check_eq $P/rt-k1-one-csum-wire-len "1029" "$(pv rt_k1_one_csum_wire_len)" check_eq $P/rt-k1-one-csum-recv "1024" "$(pv rt_k1_one_csum_recv)" check_eq $P/rt-k1-one-csum-bytes-match "yes" "$(pv rt_k1_one_csum_bytes_match)" check_eq $P/rt-k1-one-csum-pad-len "1023" "$(pv rt_k1_one_csum_pad_len)" check_eq $P/rt-k1-block-crc-send "yes" "$(pv rt_k1_block_crc_send)" check_eq $P/rt-k1-block-crc-wire-len "1030" "$(pv rt_k1_block_crc_wire_len)" check_eq $P/rt-k1-block-crc-recv "1024" "$(pv rt_k1_block_crc_recv)" check_eq $P/rt-k1-block-crc-bytes-match "yes" "$(pv rt_k1_block_crc_bytes_match)" check_eq $P/rt-k1-block-crc-pad-len "0" "$(pv rt_k1_block_crc_pad_len)" check_eq $P/rt-k1-block-csum-send "yes" "$(pv rt_k1_block_csum_send)" check_eq $P/rt-k1-block-csum-wire-len "1029" "$(pv rt_k1_block_csum_wire_len)" check_eq $P/rt-k1-block-csum-recv "1024" "$(pv rt_k1_block_csum_recv)" check_eq $P/rt-k1-block-csum-bytes-match "yes" "$(pv rt_k1_block_csum_bytes_match)" check_eq $P/rt-k1-block-csum-pad-len "0" "$(pv rt_k1_block_csum_pad_len)" check_eq $P/rt-k1-block1-crc-send "yes" "$(pv rt_k1_block1_crc_send)" check_eq $P/rt-k1-block1-crc-wire-len "2059" "$(pv rt_k1_block1_crc_wire_len)" check_eq $P/rt-k1-block1-crc-recv "2048" "$(pv rt_k1_block1_crc_recv)" check_eq $P/rt-k1-block1-crc-bytes-match "yes" "$(pv rt_k1_block1_crc_bytes_match)" check_eq $P/rt-k1-block1-crc-pad-len "1023" "$(pv rt_k1_block1_crc_pad_len)" check_eq $P/rt-k1-block1-csum-send "yes" "$(pv rt_k1_block1_csum_send)" check_eq $P/rt-k1-block1-csum-wire-len "2057" "$(pv rt_k1_block1_csum_wire_len)" check_eq $P/rt-k1-block1-csum-recv "2048" "$(pv rt_k1_block1_csum_recv)" check_eq $P/rt-k1-block1-csum-bytes-match "yes" "$(pv rt_k1_block1_csum_bytes_match)" check_eq $P/rt-k1-block1-csum-pad-len "1023" "$(pv rt_k1_block1_csum_pad_len)" check_eq $P/rt-k1-multi-crc-send "yes" "$(pv rt_k1_multi_crc_send)" check_eq $P/rt-k1-multi-crc-wire-len "3088" "$(pv rt_k1_multi_crc_wire_len)" check_eq $P/rt-k1-multi-crc-recv "3072" "$(pv rt_k1_multi_crc_recv)" check_eq $P/rt-k1-multi-crc-bytes-match "yes" "$(pv rt_k1_multi_crc_bytes_match)" check_eq $P/rt-k1-multi-crc-pad-len "0" "$(pv rt_k1_multi_crc_pad_len)" check_eq $P/rt-k1-multi-csum-send "yes" "$(pv rt_k1_multi_csum_send)" check_eq $P/rt-k1-multi-csum-wire-len "3085" "$(pv rt_k1_multi_csum_wire_len)" check_eq $P/rt-k1-multi-csum-recv "3072" "$(pv rt_k1_multi_csum_recv)" check_eq $P/rt-k1-multi-csum-bytes-match "yes" "$(pv rt_k1_multi_csum_bytes_match)" check_eq $P/rt-k1-multi-csum-pad-len "0" "$(pv rt_k1_multi_csum_pad_len)" # ------------------------------------------------------ the empty payload ----- check_eq $P/empty-send "yes" "$(pv empty_send)" check_eq $P/empty-wire "04" "$(pv empty_wire)" # ONE EOT is not enough -- recv() gives up and returns None. check_eq $P/empty-recv-single-eot "None" "$(pv empty_recv_single_eot)" # TWO EOTs produce the documented empty-file result: 0 bytes, and a CAN CAN # cancellation rather than an ACK. check_eq $P/empty-recv-double-eot "0" "$(pv empty_recv_double_eot)" check_eq $P/empty-recv-bytes "0" "$(pv empty_recv_bytes)" check_eq $P/empty-recv-cans "2" "$(pv empty_recv_cans)" # ----------------------------------------------- corruption, NAK and retry ---- # A flipped data byte fails the check, the receiver purges the line and NAKs, # and the retransmitted block lands. `nak-before-ack' is the ordering claim: the # NAK precedes every ACK, so the first block was rejected rather than the third. check_eq $P/nak-recv-bytes "256" "$(pv nak_recv_bytes)" check_eq $P/nak-payload-ok "yes" "$(pv nak_payload_ok)" check_eq $P/nak-count "1" "$(pv nak_count)" check_eq $P/nak-acks "3" "$(pv nak_acks)" check_eq $P/nak-before-ack "yes" "$(pv nak_before_ack)" # The same corruption against the 8-bit checksum, which is a different verifier. check_eq $P/nak-csum-recv-bytes "256" "$(pv nak_csum_recv_bytes)" check_eq $P/nak-csum-payload-ok "yes" "$(pv nak_csum_payload_ok)" check_eq $P/nak-csum-count "2" "$(pv nak_csum_count)" # A WRONG SEQUENCE NUMBER TAKES A DIFFERENT PATH ENTIRELY. There is no checksum # failure: the block is read, DISCARDED unexamined, and NAKed. The payload still # arrives once the correctly numbered copy follows. check_eq $P/badseq-recv-bytes "256" "$(pv badseq_recv_bytes)" check_eq $P/badseq-payload-ok "yes" "$(pv badseq_payload_ok)" check_eq $P/badseq-naks "1" "$(pv badseq_naks)" # The verifier on its own, both widths, both verdicts. check_eq $P/verify-crc-good "yes" "$(pv verify_crc_good)" check_eq $P/verify-crc-bad "no" "$(pv verify_crc_bad)" check_eq $P/verify-csum-good "yes" "$(pv verify_csum_good)" check_eq $P/verify-csum-bad "no" "$(pv verify_csum_bad)" # THE RETRY LIMIT IS `error_count > retry', SO retry=N ALLOWS N+1 ATTEMPTS. # Measured at three values rather than asserted once, because an off-by-one is # only visible as a pattern. check_eq $P/retry0-send "no" "$(pv retry0_send)" check_eq $P/retry0-reads "1" "$(pv retry0_reads)" check_eq $P/retry0-cans "2" "$(pv retry0_cans)" check_eq $P/retry1-send "no" "$(pv retry1_send)" check_eq $P/retry1-reads "2" "$(pv retry1_reads)" check_eq $P/retry1-cans "2" "$(pv retry1_cans)" check_eq $P/retry4-send "no" "$(pv retry4_send)" check_eq $P/retry4-reads "5" "$(pv retry4_reads)" check_eq $P/retry4-cans "2" "$(pv retry4_cans)" # Garbage in the start sequence is counted the same way silence is. check_eq $P/garbage-send "no" "$(pv garbage_send)" check_eq $P/garbage-reads "4" "$(pv garbage_reads)" # ------------------------------------------------------ giving up, safely ----- # THE PROPERTY THE TICKET ASKS FOR: a stream that returns nothing must end # through the caller's own getc rather than block forever. Asserted on BOTH # halves, at timeout=0 so nothing can be waiting on a clock. check_eq $P/none-send "no" "$(pv none_send)" check_eq $P/none-recv "None" "$(pv none_recv)" # recv() against silence: it returns None (not 0), having asked in CRC mode # first and FALLEN BACK to checksum mode at retry//2, then aborted with CAN CAN. check_eq $P/recv-silent "None" "$(pv recv_silent)" check_eq $P/recv-silent-crcs "2" "$(pv recv_silent_crcs)" check_eq $P/recv-silent-naks "2" "$(pv recv_silent_naks)" check_eq $P/recv-silent-cans "2" "$(pv recv_silent_cans)" # The fallback boundary itself, at retry=8: four 'C's then four NAKs. check_eq $P/recv-fallback-first "43" "$(pv recv_fallback_first)" check_eq $P/recv-fallback-crcs "4" "$(pv recv_fallback_crcs)" check_eq $P/recv-fallback-naks "4" "$(pv recv_fallback_naks)" # JUNK AT timeout=0. The path this drives ends in time.sleep(timeout); at the # library's default of 60s with 16 retries it would hold a caller for over # fifteen minutes. See the header -- this is why the suite passes its own # timeout everywhere. check_eq $P/junk-recv "None" "$(pv junk_recv)" check_eq $P/junk-terminates "yes" "$(pv junk_terminates)" # --------------------------------------------------------- cancellation ------- # TWO CANs CANCEL, BUT ONLY IN THE START SEQUENCE. check_eq $P/can-start-send "no" "$(pv can_start_send)" check_eq $P/can-start-reads "2" "$(pv can_start_reads)" # ONE CAN is remembered and not acted on -- the loop goes round and the transfer # proceeds normally when the next byte is a legitimate CRC request. check_eq $P/can-single-send "yes" "$(pv can_single_send)" # A CAN ANSWERING A DATA BLOCK DOES NOT ABORT -- see the header. It is counted # as one failed block and the block is sent again. check_eq $P/can-midblock-send "yes" "$(pv can_midblock_send)" check_eq $P/can-midblock-wire-len "400" "$(pv can_midblock_wire_len)" check_eq $P/can-midblock-blocks "3" "$(pv can_midblock_blocks)" # AN EOT IN THE START SEQUENCE CANCELS AT ONCE -- one read, no retries, and NO # abort sequence, which is the one cancellation path that emits no CAN at all. check_eq $P/eot-start-send "no" "$(pv eot_start_send)" check_eq $P/eot-start-reads "1" "$(pv eot_start_reads)" check_eq $P/eot-start-cans "0" "$(pv eot_start_cans)" check_eq $P/recv-can-start "None" "$(pv recv_can_start)" # abort() writes exactly `count' CAN bytes and nothing else. check_eq $P/abort-default-bytes "1818" "$(pv abort_default_bytes)" check_eq $P/abort-count5-len "5" "$(pv abort_count5_len)" # ----------------------------------------------------------- callbacks -------- # THE TWO SIGNATURES DIFFER -- see the header. 300 bytes is three 128-byte # blocks, and the last call reports (total, success, errors) = (3, 3, 0). check_eq $P/send-callback-calls "3" "$(pv send_callback_calls)" check_eq $P/send-callback-arity "3" "$(pv send_callback_arity)" check_eq $P/send-callback-last "3,3,0" "$(pv send_callback_last)" # recv()'s takes a fourth argument, the negotiated packet size, and fires once # more -- on the EOT, after the last block. check_eq $P/recv-callback-calls "4" "$(pv recv_callback_calls)" check_eq $P/recv-callback-arity "4" "$(pv recv_callback_arity)" check_eq $P/recv-callback-last "3,3,0,128" "$(pv recv_callback_last)" # recv() IGNORES self.mode: a default-mode instance decodes a 1K stream, because # the block size comes off the wire. check_eq $P/recv-ignores-mode "2048" "$(pv recv_ignores_mode)" rm -rf "$W"