. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the single probe run's output; the # # probe itself draws about twenty small tables, which is # # nothing beside the interpreter load. The three starts are # # `import', `unicodedata-import' and the probe -- this # # package ships no script, so there is no fourth. # # # # 1200 is 99-python3-jstyleson's and 99-python3-elementpath's # # figure, for the same reason: the start dominates and the # # work after it is comparable. # # # # 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-texttable 1.7.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-texttable_1.7.0.bb, `inherit pypi setuptools3', # PyPI sdist) on the wasm32-hwjs glibc image -- ASCII TABLES WITH AN EXPLICIT, # IMPERATIVE SURFACE. Set the column widths, the alignments, the per-column # conversions and the decoration flags, add the rows, `draw()'. It is the module # to reach for when the layout has to be FIXED rather than inferred. # # ============================================================================ # WHAT IS BEING ASSERTED IS A RENDERING, WHICH IS WHY EACH ONE IS ASSERTED TWICE. # ============================================================================ # # texttable's whole deliverable is a block of text whose SPACING is the content: # column width, padding and alignment are the product, not incidental # formatting. So each drawing is asserted as # # -sha -- a 16-hex digest of the EXACT bytes draw() returned, trailing # spaces and all, which is the case that says nothing moved; # -norm -- the same lines with TRAILING whitespace stripped and joined # with `~', which is the case a human can read. # # Neither is sufficient alone. A change in padding INSIDE a row moves both; a # change in TRAILING padding moves only the digest, and trailing spaces are what # no editor, diff view or review UI preserves reliably -- so the digest is the # only place they can be asserted at all. The joined form is the one a reviewer # can actually check against the table they expected. # # `~' is the line separator because texttable's decoration alphabet is `-|+=' # and the fixture's cell text is chosen not to contain it. # # ============================================================================ # WHY IT PORTS AT ALL: PURE PYTHON, ONE MODULE, 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. # # texttable ships NO C: no .c, .pyx or .so in the sdist, and setup.py declares # `py_modules=["texttable"]' with no ext_modules at all. `pkg-no-so' below is a # REGRESSION TRIPWIRE on a version bump rather than a statement about today, and # `no-package-dir' says it is a FILE in site-packages rather than a package # directory -- a release that grew one would still import and still pass every # behavioural case while the shape this image ships had changed. # # THE ONE THING THAT IS NOT PURE PYTHON IS AN IMPORT IT MAKES, AND IT IS THE # REASON THIS PORT COULD HAVE FAILED. `import unicodedata' is at texttable's # MODULE SCOPE with no try/except around it, 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-import' and `unicodedata-builtin' below # assert it again from this package's side, because that is the import whose # failure would present as a broken texttable rather than as a broken python3. # # The two OPTIONAL imports are the other half of the same paragraph. texttable # prefers `cjkwrap' for wrapping and `wcwidth' for character width and falls back # to `textwrap' + `unicodedata' when neither is installed. On THIS image the two # answer differently: cjkwrap is absent, so the wrapper is `textwrap'; wcwidth is # PRESENT -- python3-wcwidth landed with BRINTOS-1961, which shares this image -- # so character width is `wcwidth.wcwidth', not the unicodedata fallback. # # That distinction costs the literals below nothing, and it was measured rather # than reasoned: wcwidth 0.2.13 and the fallback agree on every printable ASCII # character, this fixture is pure ASCII, and running the probe with wcwidth # importable and with it blocked differs in exactly one of its 80 keys -- # `has_wcwidth' itself. So every width and wrap literal below holds under either # branch, which is why this suite measured 100 of 101 green on the batch image # with only the stale `no-wcwidth' assertion red. # # `no-cjkwrap' and `has-wcwidth' assert that state directly, so an image that # grew cjkwrap or dropped wcwidth announces itself here rather than as a drift in # twenty unexplained renderings. # # `pkg-depends' records the consequence: `Depends: python3-core' and nothing # else, read back off the built .deb. textwrap and unicodedata are BOTH in # python3-core in poky's python3-manifest.json, so that dependency is complete # rather than merely lucky, and this port waits on no other package. # # ============================================================================ # THE FIXTURE IS ONE TABLE, AND THAT IS THE POINT OF THE PACKAGE. # ============================================================================ # # The ticket's shape is an IMPERATIVE surface, so the interesting statement is # what each setter does to ONE known table rather than a catalogue of unrelated # ones. Three columns, three rows, a header; `Nickname' is wider than every value # under it, so a CENTRED column is not accidentally equal to a left- or # right-aligned one -- without that, `align-lrc' would pass on an implementation # that ignored `c'. # # `set_cols_dtype' is the deliberate exception: it is a CONVERSION rather than a # layout knob, so it is driven as a second one-column table over the same five # input values, `a' / `t' / `e' / `f' / `i' in turn. A red then names the # conversion instead of a column of the fixture. # # ============================================================================ # WHAT `set_max_width(0)' HAS TO DO, AND WHY IT IS ASSERTED AS A PAIR. # ============================================================================ # # The default max_width is 80 and a cell wider than the table's share of it is # WRAPPED. `set_max_width(0)' must disable wrapping entirely. Asserting only the # disabled case would pass on a build that never wrapped anything, so the SAME # 123-character string is drawn both ways: `wrap-default-intact' is `no' (the # string was broken across lines) and `wrap-disabled-intact' is `yes' (it # survived as one contiguous run), and the line counts say so a second way. # # ============================================================================ # THE TWO DOCUMENTED FAILURES ARE ASSERTED, NOT TOLERATED. # ============================================================================ # # A row of the wrong arity, and a width list of the wrong length, must BOTH raise # `ArraySizeError' -- the module's own exception, one of the two names in its # `__all__'. A release that let either through, or that narrowed it to a bare # ValueError, would be a change in the package's contract and it announces itself # here. The exception TYPE and its message are asserted separately, because a # caller catching it prints the message and a `raise` that had lost its text # would still pass a type-only case. # # ============================================================================ # WHAT IS NOT COVERED. # ============================================================================ # # The recipe does NOT `inherit ptest'. Upstream's sdist DOES ship `tests.py' -- # unlike 99-python3-jstyleson's package, which ships none -- 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, and this note is here so the difference from a ptest-carrying port is # recorded rather than assumed. # # `set_chars' is asserted to EXIST and is not driven. Every drawing below uses # the default alphabet, and a suite that also re-drew each of them through a # custom one would double its length to assert the same layout engine through a # different four characters. # # ============================================================================ # WHERE THE ASSERTED VALUES COME FROM. # ============================================================================ # # Every literal below was produced by running data/python3-texttable-probe.py on # x86-64 against the sdist the recipe's SRC_URI sha256 pins (2d2068fb...2638, # verified by digest before measuring), and the payload cases were read back off # the built python3-texttable_1.7.0-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 on-target result is not yet measured -- the # batch verify is this port's first run on wasm32-hwjs. P=python3-texttable # THE CANARY, which asserts nothing about python3-texttable. # # 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 MOD=$SITE/texttable.py DI=$SITE/texttable-1.7.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-texttable.list PROBE=$BST_DATA/python3-texttable-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-texttable.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import texttable' 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/module-file test -f "$MOD" # ...AND IT IS A FILE, NOT A DIRECTORY. See the header: a release that grew a # package directory would still import and would still pass every behavioural # case below, while the shape this image ships had silently changed. check_eq $P/no-package-dir "absent" \ "$(if [ -d "$SITE/texttable" ]; then echo present; else echo absent; fi)" # 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 PYTHON OR ITS METADATA. The case above # names the failure that is expected; this one catches the shape nobody predicted # -- a binary, a .pyd, a stray README -- without having to enumerate what it # might be. This package ships no script at all, so there is no executable to # exempt: anything outside site-packages is unexpected here by construction. # # 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 ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ----------------------------------------------------------- the dist-info --- # The installed metadata. Unlike 99-python3-jstyleson's package this module DOES # declare a `__version__', so the version is stated three independent ways -- # here, through importlib.metadata (`dist-version'), and off the module itself # (`version-attr') -- and `version-agrees' below 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: 1.7.0" cat "$DI/METADATA" # top_level.txt names the ONE importable this package installs. It is what an # `import texttable' has to match, and a wheel that renamed the module would # change it here first. check_out $P/dist-info-top-level "texttable" cat "$DI/top_level.txt" # -------------------------------------------------------------- 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-texttable 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-texttable$/,/^$/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, and it is asserted as an EQUALITY # rather than as a substring. The recipe declares no RDEPENDS at all; what the # packaging derived is `python3-core' alone, and that is the whole claim this # port makes about waiting on nothing. A substring check would still pass if a # release had added a third-party dependency beside it -- which on this image is # the failure that matters, since the added package would have to be ported too. 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" \ "$(sed -e 's/[[:space:]]*$//' "$W/dep")" # ---------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If texttable 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 texttable' # ...and `unicodedata' separately. See the header: it is texttable's one # module-scope import that is a C extension in an ordinary CPython, so a # lib-dynload that had stopped being built in presents AS a broken texttable. # This case is what tells the two apart. check $P/unicodedata-import python3 -c 'import unicodedata' # ---------------------------------------------------------------- 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" 900 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 `base_sha' distinguishable # from `deco_all_sha' and `dtype_a_sha' from `dtype_a_norm'. 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 module answered out of the file 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 texttable. check_eq $P/module-answered "$MOD" "$(pv module_file)" # THE VERSION, three ways. See the dist-info block: the module's own attribute, # the installed dist-info read through importlib.metadata, and the directory name # asserted above. check_eq $P/version-attr "1.7.0" "$(pv version_attr)" check_eq $P/dist-version "1.7.0" "$(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. This module declares an `__all__' of exactly two, so the # case is an equality rather than a per-name probe: a release that exported a # third would be a change in the surface, and one that dropped `ArraySizeError' # would break the two failure cases at the end of this suite. check_eq $P/all-names "Texttable,ArraySizeError" "$(pv all_names)" # ------------------------------------------------ the platform-sensitive bit -- # See the header. unicodedata is BUILTIN here rather than a lib-dynload .so. # cjkwrap is absent, so `textwrap' is the wrapper. wcwidth IS on this image -- # python3-wcwidth landed with BRINTOS-1961 -- so `uchar_width' is the # wcwidth-backed branch, NOT the unicodedata fallback. # # That does not move a single literal below, and the reason is measured rather # than assumed: wcwidth 0.2.13 and the unicodedata fallback return the same # width for every printable ASCII character, and this fixture is pure ASCII. # Running the probe against the image's own texttable.py with wcwidth importable # and with it blocked differs in exactly ONE of its 80 keys -- has_wcwidth # itself -- so every rendering sha pinned below holds under either branch. # # Asserted as presence rather than absence for the reason the absence was # asserted before: the image's set of optional accelerators is a fact the # renderings depend on, so a change to it announces itself HERE rather than as # drift in twenty unexplained tables. An image that dropped wcwidth, or grew # cjkwrap, reds one of these two cases and names itself. check_eq $P/unicodedata-builtin "yes" "$(pv unicodedata_builtin)" check_eq $P/no-cjkwrap "no" "$(pv has_cjkwrap)" check_eq $P/has-wcwidth "yes" "$(pv has_wcwidth)" # THE DECORATION BITS, which are the public constants a caller ORs together. # Asserted as values rather than as existence: `set_deco' takes an int, so a # release that renumbered them would silently give every caller a different # decoration and pass an existence check. check_eq $P/deco-border "1" "$(pv deco_border)" check_eq $P/deco-header "2" "$(pv deco_header)" check_eq $P/deco-hlines "4" "$(pv deco_hlines)" check_eq $P/deco-vlines "8" "$(pv deco_vlines)" # THE IMPERATIVE SURFACE, one case each. These are the setters the ticket names # plus the three that drive the table; `set_chars' is here and is deliberately # not driven (see the header). for _n in set_cols_align set_cols_valign set_cols_dtype set_cols_width \ set_deco set_max_width set_precision set_chars \ header add_row add_rows draw reset; do _u=$(echo "$_n" | tr '_' '-') check_eq $P/api-$_u "yes" "$(pv has_$_n)" done # ------------------------------------------------------------- the fixture --- # The table every rendering case below is about, pinned by digest and shape, so a # red there names the common cause of the rest rather than leaving twenty # unexplained ones. check_eq $P/fixture-sha "2383e331e93b3b32" "$(pv fixture_sha)" check_eq $P/fixture-cols "3" "$(pv fixture_cols)" check_eq $P/fixture-rows "3" "$(pv fixture_rows)" # --------------------------------------------------- draw(), THE DELIVERABLE --- # The default drawing: all four decoration flags, automatic widths, automatic # alignment. Everything after this is the same table with one setter changed, so # this is the baseline a reader compares them against. check_eq $P/base-sha "e416e6f7faede43e" "$(pv base_sha)" check_eq $P/base-norm \ '+-----------+-----+----------+~| Name | Age | Nickname |~+===========+=====+==========+~| Huon | 32 | Xav |~+-----------+-----+----------+~| Clement | 1 | Baby |~+-----------+-----+----------+~| Bourgeois | 28 | Lou |~+-----------+-----+----------+' \ "$(pv base_norm)" # ------------------------------------------------- set_deco, one flag each --- # The flags are INDEPENDENT BITS, so each is drawn on its own. An implementation # that had collapsed two of them -- HLINES also drawing the header rule, say -- # would still pass a test of the default, where all four are set. # # `deco-all' is the explicit OR of all four and must equal the default `base' # above; that equality is the case that says the default IS all four rather than # something that merely looks like it. check_eq $P/deco-all-sha "e416e6f7faede43e" "$(pv deco_all_sha)" check_eq $P/deco-all-is-default "yes" \ "$(if [ "$(pv deco_all_sha)" = "$(pv base_sha)" ]; then echo yes; else echo no; fi)" # NOTHING AT ALL: no border, no rules, no vertical bars -- columns held apart by # padding only. This is the one a caller reaches for when the output is going # into something that will parse it. check_eq $P/deco-none-sha "d107a5ba6101c444" "$(pv deco_none_sha)" check_eq $P/deco-none-norm \ ' Name Age Nickname~Huon 32 Xav~Clement 1 Baby~Bourgeois 28 Lou' \ "$(pv deco_none_norm)" # BORDER ALONE: a box around the whole table and nothing inside it. Note the # columns are NOT separated -- that is VLINES, which is off here, and this case # is what says the two are independent. check_eq $P/deco-border-sha "08f8a654e0a71112" "$(pv deco_only_border_sha)" check_eq $P/deco-border-norm \ '+----------------------------+~| Name Age Nickname |~| Huon 32 Xav |~| Clement 1 Baby |~| Bourgeois 28 Lou |~+----------------------------+' \ "$(pv deco_only_border_norm)" # HEADER ALONE: the `=' rule under the header row and nothing else. The rule # character is what distinguishes it from HLINES, which draws `-'. check_eq $P/deco-header-sha "eb033e5ed36284ab" "$(pv deco_only_header_sha)" check_eq $P/deco-header-norm \ ' Name Age Nickname~==========================~Huon 32 Xav~Clement 1 Baby~Bourgeois 28 Lou' \ "$(pv deco_only_header_norm)" # HLINES ALONE: `-' rules BETWEEN data rows, and -- the part worth reading -- # none under the header, which is what HEADER would have drawn. Three rows give # two rules, not three: a rule after the last row would be a border. check_eq $P/deco-hlines-sha "b52aa924a4560fd9" "$(pv deco_only_hlines_sha)" check_eq $P/deco-hlines-norm \ ' Name Age Nickname~Huon 32 Xav~--------------------------~Clement 1 Baby~--------------------------~Bourgeois 28 Lou' \ "$(pv deco_only_hlines_norm)" # VLINES ALONE: `|' BETWEEN columns and no outer bars, which is the case that # separates it from BORDER above. check_eq $P/deco-vlines-sha "c63d1a5b8e258ba9" "$(pv deco_only_vlines_sha)" check_eq $P/deco-vlines-norm \ ' Name | Age | Nickname~Huon | 32 | Xav~Clement | 1 | Baby~Bourgeois | 28 | Lou' \ "$(pv deco_only_vlines_norm)" # --------------------------------------------------------- set_cols_align --- # Left, right, centre in one drawing. `Nickname' is wider than every value under # it (see the header), so the centred third column is genuinely distinguishable # -- on a column whose header matched its widest value, `c' and `l' would render # identically and this case would pass on an implementation that ignored `c'. check_eq $P/align-lrc-sha "42e2ac7e4855dc9d" "$(pv align_lrc_sha)" check_eq $P/align-lrc-norm \ '+-----------+-----+----------+~| Name | Age | Nickname |~+===========+=====+==========+~| Huon | 32 | Xav |~+-----------+-----+----------+~| Clement | 1 | Baby |~+-----------+-----+----------+~| Bourgeois | 28 | Lou |~+-----------+-----+----------+' \ "$(pv align_lrc_norm)" # ...and all three right, which moves the FIRST column -- the one `align-lrc' # leaves where the default already put it. Without this, an implementation that # only ever applied the alignment from the second column on would pass. check_eq $P/align-rrr-sha "a8393749a32dac2b" "$(pv align_rrr_sha)" check_eq $P/align-rrr-norm \ '+-----------+-----+----------+~| Name | Age | Nickname |~+===========+=====+==========+~| Huon | 32 | Xav |~+-----------+-----+----------+~| Clement | 1 | Baby |~+-----------+-----+----------+~| Bourgeois | 28 | Lou |~+-----------+-----+----------+' \ "$(pv align_rrr_norm)" # -------------------------------------------------------- set_cols_valign --- # valign only has an effect where a cell is TALLER than one line, so these two # drawings use rows with embedded newlines. Asserting valign over the flat # fixture would be asserting nothing: every vertical alignment renders # identically on a one-line cell. # # top/middle/bottom in one drawing: `32' sits on the SECOND of three lines and # `Xav' on the third, which is the whole statement. check_eq $P/valign-tmb-sha "6f9dbe240406e0d5" "$(pv valign_tmb_sha)" check_eq $P/valign-tmb-norm \ '+-----------+-----+----------+~| Name | Age | Nickname |~+===========+=====+==========+~| Mr | | |~| Xavier | 32 | |~| Huon | | Xav |~+-----------+-----+----------+~| Mme | | |~| Louise | 28 | |~| Bourgeois | | Lou |~+-----------+-----+----------+' \ "$(pv valign_tmb_norm)" # ...and all three bottom, which moves the short cells DOWN together. Read # against the case above it says `m' and `b' are different rather than both # meaning "not top". check_eq $P/valign-bbb-sha "67003ea886b92541" "$(pv valign_bbb_sha)" check_eq $P/valign-bbb-norm \ '+-----------+-----+----------+~| Name | Age | Nickname |~+===========+=====+==========+~| Mr | | |~| Xavier | | |~| Huon | 32 | Xav |~+-----------+-----+----------+~| Mme | | |~| Louise | | |~| Bourgeois | 28 | Lou |~+-----------+-----+----------+' \ "$(pv valign_bbb_norm)" # --------------------------------------------------------- set_cols_width --- # THE SETTER THE PACKAGE EXISTS FOR: the layout taken out of the library's hands. # Widths 12/5/4 against a table the default sized 9/3/8. # # The third column is set NARROWER than its own header, so this case also states # what happens when the requested width cannot hold the text: `Nickname' WRAPS # across two header lines rather than widening the column. A library that treated # the width as a minimum would render one header line and pass nothing here. check_eq $P/width-fixed-sha "fe0a81a314aef8ea" "$(pv width_fixed_sha)" check_eq $P/width-fixed-norm \ '+--------------+-------+------+~| Name | Age | Nick |~| | | name |~+==============+=======+======+~| Huon | 32 | Xav |~+--------------+-------+------+~| Clement | 1 | Baby |~+--------------+-------+------+~| Bourgeois | 28 | Lou |~+--------------+-------+------+' \ "$(pv width_fixed_norm)" # ---------------------------------------------------------- set_cols_dtype --- # THE CONVERSIONS, one case each over the SAME five values -- "12" (a numeric # STRING), 12 (an int), 1234.5678, 0.000128 and "lmn" (not numeric at all). Five # values through five codes is the only way each conversion's answer is # separable; a single mixed table would leave a red naming a column rather than a # code. # # `lmn' is in every one of them on purpose: it is what says a NON-NUMERIC cell is # passed through rather than raising, whatever the column's declared type. That # is `FallbackToText' in the implementation, and it is the behaviour a caller # relies on when a column is mostly numbers. # # `a', AUTOMATIC: the default. It converts what looks numeric and formats a float # at the default precision of 3 -- so 1234.5678 comes out `1234.568' and 0.000128 # comes out `0.000', which is the case that says `a' is not "leave it alone". check_eq $P/dtype-a-sha "2a5f36f880a7ed54" "$(pv dtype_a_sha)" check_eq $P/dtype-a-norm ' v~12~12~1234.568~0.000~lmn' "$(pv dtype_a_norm)" # `t', TEXT: no conversion at all, which is the one code that must NOT round. # 0.000128 survives here and is `0.000' under `a' and `f' -- that contrast is the # reason this case is worth a whole drawing. check_eq $P/dtype-t-sha "c9c4bfc05a3f7726" "$(pv dtype_t_sha)" check_eq $P/dtype-t-norm ' v~12~12~1234.5678~0.000128~lmn' "$(pv dtype_t_norm)" # `e', FLOAT-EXPONENT: everything numeric in scientific notation at the default # precision, INCLUDING the values that need no exponent -- 12 becomes `1.200e+01'. # The two-digit exponent field is part of what is asserted; it is CPython's own # formatting and a platform that emitted three digits would show up here first. check_eq $P/dtype-e-sha "0fd4e3d8165a287d" "$(pv dtype_e_sha)" check_eq $P/dtype-e-norm ' v~1.200e+01~1.200e+01~1.235e+03~1.280e-04~lmn' "$(pv dtype_e_norm)" # `f', FLOAT-DECIMAL: fixed point at the default precision. It differs from `a' # on the INTEGER inputs, which it renders `12.000' where `a' leaves `12' -- so # this case and `dtype-a' together say the two codes are not the same code. check_eq $P/dtype-f-sha "e3a7d7785113db27" "$(pv dtype_f_sha)" check_eq $P/dtype-f-norm ' v~12.000~12.000~1234.568~0.000~lmn' "$(pv dtype_f_norm)" # `i', INTEGER: ROUNDS rather than truncating -- 1234.5678 becomes `1235', not # `1234'. A truncating implementation would pass a case built on a value below # .5, so the fixture's value is above it deliberately. check_eq $P/dtype-i-sha "605ec19f4808b929" "$(pv dtype_i_sha)" check_eq $P/dtype-i-norm ' v~12~12~1235~0~lmn' "$(pv dtype_i_norm)" # set_precision, which is what the default of 3 above IS. Driven once at 1, so a # precision that was being ignored -- and would pass every case above, since they # all use the default -- reds here. check_eq $P/precision-1-sha "ce3c81079597461f" "$(pv precision_1_sha)" check_eq $P/precision-1-norm ' v~1234.6' "$(pv precision_1_norm)" # ---------------------------------------------------------- set_max_width --- # See the header: the pair is the assertion, not either half. The SAME # 123-character string is drawn at the default max_width of 80 and again after # `set_max_width(0)'. check_eq $P/long-len "123" "$(pv long_len)" # At the default it WRAPS: three lines out of an undecorated one-column table # (the header line plus two), and the string does not survive as one run. check_eq $P/wrap-default-lines "3" "$(pv wrap_default_lines)" check_eq $P/wrap-default-intact "no" "$(pv wrap_default_intact)" check_eq $P/wrap-default-sha "8289617467e5fb8b" "$(pv wrap_default_sha)" check_eq $P/wrap-default-norm \ ' text~alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron~pi rho sigma tau upsilon phi chi psi omega' \ "$(pv wrap_default_norm)" # set_max_width(0) DISABLES WRAPPING ENTIRELY, which is the ticket's `must'. Two # lines instead of three, and the 123 characters survive contiguously. check_eq $P/wrap-disabled-lines "2" "$(pv wrap_disabled_lines)" check_eq $P/wrap-disabled-intact "yes" "$(pv wrap_disabled_intact)" check_eq $P/wrap-disabled-sha "35ebcd46d3690b21" "$(pv wrap_disabled_sha)" check_eq $P/wrap-disabled-norm \ ' text~alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega' \ "$(pv wrap_disabled_norm)" # ------------------------------------------ THE TWO DOCUMENTED FAILURES ----- # Asserted rather than tolerated. See the header: both must raise the module's # OWN exception, and the message is asserted beside the type because a caller # catching it prints the message. # # A row of the wrong arity, against a 3-column header. check_eq $P/row-arity-raises "yes" "$(pv row_arity_raised)" check_eq $P/row-arity-exc "ArraySizeError" "$(pv row_arity_exc)" check_eq $P/row-arity-msg "array should contain 3 elements" "$(pv row_arity_msg)" # A width list of the wrong length. It raises at the SETTER, not at draw() -- # which is the useful behaviour, since the caller learns of it where the mistake # is rather than after the rows are in. check_eq $P/width-len-raises "yes" "$(pv width_len_raised)" check_eq $P/width-len-exc "ArraySizeError" "$(pv width_len_exc)" check_eq $P/width-len-msg "array should contain 3 elements" "$(pv width_len_msg)" # ...and it is an ordinary Exception subclass, which is what makes `except # Exception' around a caller's table-building code catch it. check_eq $P/arraysizeerror-is-exception "yes" "$(pv arraysizeerror_is_exception)" # The 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)" rm -rf "$W"