. "$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 renders a five-row table eight ways and parses # # three small documents, which is nothing beside the # # interpreter load. The three starts are `import', # # `wcwidth-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-prettytable 3.10.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-prettytable_3.10.0.bb, `inherit pypi ptest # python_hatchling', PyPI sdist) on the wasm32-hwjs glibc image -- ASCII TABLES # WITH A MUTABLE OBJECT BEHIND THEM. # # ============================================================================ # WHAT THE PORT ADDS IS THE OBJECT, NOT THE `+---+' LINE. # ============================================================================ # # Drawing a box around some columns is a page of string formatting, so a reader # will ask what the package is for. The answer is that a PrettyTable is a # MUTABLE TABLE that happens to render: rows and columns are added, deleted and # cleared over its life, and between any two of those it can be sorted by a # column or by a callable, sliced by row, narrowed to a subset of columns, # restyled, and emitted as text, HTML, CSV or JSON -- and the renders have to # agree with the object at the moment they were taken. # # So the cases below are in two halves. The four renders are pinned exactly, and # the manipulations are asserted as the ORDER or the MEMBERSHIP they produce, # on one object driven through them in sequence. A suite that asserted only # `get_string' would pass on an implementation whose `sortby' did nothing. # # ============================================================================ # WIDTH IS wcwidth's, AND THAT IS THE ONE RDEPENDS WITH TEETH. # ============================================================================ # # prettytable measures a cell with `wcwidth.wcswidth', not with `len'. The two # agree on ASCII and disagree on everything else, so every case above the # `wide-*' group would pass on a build where python3-wcwidth had not landed and # the library had fallen back to counting code points. # # `wide-col-width' drives a column whose only content is U+6771 U+4EAC, two # characters occupying four terminal columns. A correct build renders the column # four wide; a len()-based one renders it two. python3-wcwidth is a SEPARATE # package on this image, landing in this same round (BRINTOS-1961), so this case # is also the one that fails if that port did not. # # ============================================================================ # WHY IT PORTS AT ALL: PURE PYTHON, NO EXTENSION, STDLIB-ONLY IMPORTS. # ============================================================================ # # 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. # # prettytable ships NO C: no .c, .pyx or .so in the sdist, and its wheel is # tagged `py3-none-any' with `Root-Is-Purelib: true', which `dist-info-wheel-tag' # below reads back off the image. Its imports are `io', `re', `html.parser', # `csv', `json', `random', `copy' and `textwrap' -- every one a split of the # ported python3 -- plus `wcwidth', which the recipe's RDEPENDS names and which # is itself pure Python. # # `csv' AND `json' ARE WORTH A SENTENCE, because both are C-accelerated in # CPython and would be the obvious thing to break here. `csv' imports `_csv' # unconditionally and `json' reaches `_json'; neither has a pure-Python # fallback. They work on this image because configure switches every stdlib # extension to a STATIC module on wasm32 and links it into the interpreter, so # `_csv' and `_json' are built in rather than absent (the python3 bbappend # records the mechanism, and _ctypes is the one exception). `get_csv_string' and # `get_json_string' below are therefore load-bearing beyond prettytable itself. # # ============================================================================ # THE UPSTREAM ptest IS NOT RUN, AND THAT IS THE DISTRO'S ANSWER, NOT A CHOICE. # ============================================================================ # # The recipe DOES `inherit ptest' and DOES ship upstream's tests (the sdist # carries tests/test_prettytable.py and tests/test_colortable.py, and # do_install_ptest copies them). None of that reaches this image, for a reason # that is settled before any preference could apply: brintos.conf sets a # DISTRO_FEATURES with no `ptest', so ptest.bbclass never adds ${PN}-ptest to # PACKAGES at all. The recipe-scope build produced exactly three .debs -- # python3-prettytable, -dev and -dbg -- and no -ptest, which is measured rather # than assumed. # # Had the package existed it would still not be installable: its RDEPENDS names # python3-pytest, python3-pytest-lazy-fixtures and python3-unittest-automake-output, # none of which is ported, and BRINTOS-1753 rules the first and third out of # scope. `pkg-no-ptest' below asserts the absence directly, so the day this # distro turns ptest on is a red here rather than a silent gap. # # The pass criterion for this port is therefore these cases, which is the same # standing this project gives 99-python3-netaddr and 99-python3-pyasn1. # # ============================================================================ # ONE OPTION THIS VERSION DOES NOT HAVE, AND DOES NOT REFUSE. # ============================================================================ # # `row_filter' is not among PrettyTable's options in 3.10.0 -- it arrived # upstream later. What makes that worth a case rather than a footnote is that an # unknown keyword is SILENTLY IGNORED: `get_string(row_filter=...)' raises # nothing and filters nothing, so a caller written against a newer prettytable # gets a full table and no indication anything was dropped. `row-filter-absent', # `row-filter-no-raise' and `row-filter-ignored' pin all three halves of that, # and a version bump that grew the option turns the last two red here rather # than somewhere a caller would find it. # # ============================================================================ # WHERE THE ASSERTED VALUES COME FROM. # ============================================================================ # # Every literal below was measured against the sdist the recipe's SRC_URI # sha256 pins (9665594d...568, verified by digest before measuring), and then # re-measured against THE BYTES OF THE BUILT python3-prettytable_3.10.0-r0_wasm32.deb # unpacked onto the build's own python3-native, which is CPython 3.12.13 -- the # same version this image ships. So there is no control-interpreter gap to # state: the control ran the shipped module on the shipped Python version, and # what is untested is the wasm32 arch alone. The on-target result is not yet # measured -- the batch verify is this port's first run on wasm32-hwjs. P=python3-prettytable # THE CANARY, which asserts nothing about python3-prettytable. # # 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/prettytable DI=$SITE/prettytable-3.10.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-prettytable.list PROBE=$BST_DATA/python3-prettytable-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-prettytable.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import prettytable' 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-init test -f "$PKGDIR/__init__.py" # IT IS A PACKAGE DIRECTORY, NOT A SINGLE MODULE, and the two submodules are # named. The import surface is `prettytable', so a release that flattened the # package to one file would import and would pass every behavioural case below # while the shape this image ships had changed. `colortable' is the one nothing # else here touches: it is exported by neither `__all__' nor any case, and it is # shipped, so it is named rather than left to be discovered. check $P/module-prettytable test -f "$PKGDIR/prettytable.py" check $P/module-colortable test -f "$PKGDIR/colortable.py" # py.typed is what makes the annotations visible to a type checker on the target. # It is a zero-byte marker, so `test -f' rather than `test -s'. check $P/py-typed test -f "$PKGDIR/py.typed" # 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 console script, so unlike 99-python3-netaddr's # equivalent there is no executable to exempt: anything outside site-packages is # unexpected here by construction. `py.typed' is the one non-.py file the # package itself installs. # # 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 ;; */py.typed) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ...AND IT INSTALLS NOTHING ON $PATH. prettytable's pyproject declares no # [project.scripts], so the package is importable-only. A release that grew a # console script would land it in /usr/bin unannounced, where it could collide # with a name another package owns. check_eq $P/pkg-no-script "" \ "$(grep -E '^/usr/(s?bin)/' "$W/pkgf" 2>/dev/null)" # ----------------------------------------------------------- the dist-info --- # The installed metadata. On this package it is not merely a second statement of # the version -- it is the ONLY one. prettytable's __init__ has no `__version__' # assignment; it defines a module-level `__getattr__' that answers the name by # calling importlib.metadata.version(), so the attribute IS this directory read # through another door. `version-attr-is-metadata' below asserts that # relationship rather than leaving two apparently independent readings. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 3.10.0" cat "$DI/METADATA" # THE WHEEL TAG, which is where "pure Python" is written down in a form the image # carries. `py3-none-any' with `Root-Is-Purelib: true' is a build that produced # no extension for any ABI; a release that grew one would be tagged for a # specific ABI and platform, and this is the case that would say so before any # import failed. check_out $P/dist-info-wheel-tag "Tag: py3-none-any" cat "$DI/WHEEL" check_out $P/dist-info-wheel-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" # The license the recipe's LIC_FILES_CHKSUM pins, as it landed. hatchling puts it # under licenses/ rather than at the top of the dist-info. check $P/dist-info-license test -s "$DI/licenses/LICENSE" # -------------------------------------------------------------- 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-prettytable 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 or -dev # stanza of the same family. sed -n '/^Package: python3-prettytable$/,/^$/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 ptest PACKAGE IS NOT HERE. See the header: the absent `ptest' feature # means ptest.bbclass never creates it, so the recipe's `inherit ptest' produces # nothing to install and the upstream suite it would have carried is not on this # image. Asserted directly, because the alternative is a silence a reader would # have to reconstruct from the recipe -- and because the day this distro turns # ptest on, this case is where that shows up. check_eq $P/pkg-no-ptest "" \ "$(grep -c '^Package: python3-prettytable-ptest$' /var/lib/dpkg/status 2>/dev/null | grep -v '^0$')" # THE RUNTIME DEPENDENCIES THE RECIPE DECLARES, as dpkg recorded them. Five of # the seven are stdlib splits of the ported python3 and come with it; the two # that are separate PORTS are python3-importlib-metadata -- which is what the # version attribute reads through -- and python3-wcwidth, which is what every # column width is measured with. Those two are named individually below because # a missing one presents as a broken prettytable rather than as a missing # package. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" check_out $P/pkg-depends-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-json "python3-json" cat "$W/dep" check_out $P/pkg-depends-html "python3-html" cat "$W/dep" check_out $P/pkg-depends-math "python3-math" cat "$W/dep" check_out $P/pkg-depends-compression "python3-compression" cat "$W/dep" check_out $P/pkg-depends-importlib "python3-importlib-metadata" cat "$W/dep" check_out $P/pkg-depends-wcwidth "python3-wcwidth" cat "$W/dep" # ---------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If prettytable 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 prettytable' # ...and `wcwidth' separately, because prettytable imports it at module scope -- # a missing python3-wcwidth presents AS a prettytable that will not import at # all. This case is what tells the two apart. check $P/wcwidth-import python3 -c 'import wcwidth' # ---------------------------------------------------------------- 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 `str_sha' distinguishable # from `style_sha', and `from_csv_fields' from `fields_header'. 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 version --- # The version the recipe pins, read two ways that CANNOT disagree -- see the # header. Both are asserted anyway, because the DOOR can break independently of # the value: a package whose dist-info had not landed would import fine, serve # every behavioural case below, and raise PackageNotFoundError on the attribute. check_eq $P/version-attr "3.10.0" "$(pv version_attr)" check_eq $P/dist-version "3.10.0" "$(pv dist_version)" # ...and that the attribute really is the metadata rather than a second, # independent statement. A release that grew a real `__version__' assignment # would make the two capable of drifting apart, which is a fact about the module # worth carrying rather than leaving for a reader to rediscover. check_eq $P/version-attr-is-metadata "yes" "$(pv version_attr_is_metadata)" # The module answered out of the package this image installed, not from a stray # copy on sys.path. This is what makes the payload cases above and the behaviour # cases below statements about the SAME prettytable. check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" # THE EXPORTED SURFACE, as upstream states it. This module DOES declare # `__all__', so this is a reading of a promise rather than an inventory of what # happens to be bound -- and a release that dropped a name from it would pass # every behavioural case below while narrowing what a `from prettytable import *' # gives a caller. check_eq $P/api-all-len "19" "$(pv all_len)" check_eq $P/api-all-names \ "ALL,DEFAULT,DOUBLE_BORDER,FRAME,HEADER,MARKDOWN,MSWORD_FRIENDLY,NONE,ORGMODE,PLAIN_COLUMNS,PrettyTable,RANDOM,SINGLE_BORDER,TableHandler,from_csv,from_db_cursor,from_html,from_html_one,from_json" \ "$(pv all_names)" for _n in PrettyTable TableHandler from_csv from_json from_html from_html_one from_db_cursor; do check_eq $P/api-$_n "yes" "$(pv has_$_n)" done # ------------------------------------------------------------- the fixture --- # The table the probe drove, pinned as DATA -- four columns, five rows, mixed # types -- so every case below is known to be about that table rather than about # an edited probe. The digest is load-bearing: one changed digit would move every # render and this case is what names the common cause of the reds that follow. check_eq $P/fixture-fields "City name,Area,Population,Annual Rainfall" \ "$(pv fixture_fields)" check_eq $P/fixture-nrows "5" "$(pv fixture_nrows)" check_eq $P/fixture-sha "96e0069411116218" "$(pv fixture_sha)" # ...and the object built from it agrees, BEFORE any render. A render that came # out right over the wrong number of rows would be a coincidence worth knowing # about, and `rows' is the accessor a caller reads. check_eq $P/table-field-names "City name,Area,Population,Annual Rainfall" \ "$(pv field_names)" check_eq $P/table-nrows "5" "$(pv nrows)" # The row came back with its TYPES intact -- an int and a float, not their # str()s. This is what the CSV round trip below loses and the JSON one keeps. check_eq $P/table-row0 '["Adelaide",1295,1158259,600.5]' "$(pv row0)" # =========================================================== get_string() ==== # The deliverable. Pinned by digest over the exact bytes -- which is what catches # a change in padding -- and then in the legible form, newlines shown as `/' and # runs of spaces collapsed, which is what a reader checks. check_eq $P/str-sha "54e66109a3c80fee" "$(pv str_sha)" check_eq $P/str-norm \ "+-----------+------+------------+-----------------+ / | City name | Area | Population | Annual Rainfall | / +-----------+------+------------+-----------------+ / | Adelaide | 1295 | 1158259 | 600.5 | / | Brisbane | 5905 | 1857594 | 1146.4 | / | Darwin | 112 | 120900 | 1714.7 | / | Hobart | 1357 | 205556 | 619.5 | / | Sydney | 2058 | 4336374 | 1214.8 | / +-----------+------+------------+-----------------+" \ "$(pv str_norm)" # THE GEOMETRY, which the collapsed form throws away. Nine lines is three rules, # one header and five data rows; a constant 51-column width is what a padding # change moves without changing any cell. check_eq $P/str-lines "9" "$(pv str_lines)" check_eq $P/str-width "51" "$(pv str_width)" check_eq $P/str-widths-equal "yes" "$(pv str_widths_equal)" # The rule line alone, because it encodes every column width in one string and is # the single most sensitive thing here to a width regression -- including the one # `wide-col-width' below is about. check_eq $P/str-rule "+-----------+------+------------+-----------------+" \ "$(pv str_rule)" # ====================================================== get_html_string() ==== check_eq $P/html-sha "b762fbda7379476f" "$(pv html_sha)" # THE ELEMENT COUNTS, which are the statement that survives an upstream # whitespace change: one table, one thead and one tbody, six (the header row # and five data rows), four and twenty . The digest above would red on a # re-indent; these say whether the DOCUMENT changed. check_eq $P/html-table "1" "$(pv html_count_table)" check_eq $P/html-thead "1" "$(pv html_count_thead)" check_eq $P/html-tbody "1" "$(pv html_count_tbody)" check_eq $P/html-tr "6" "$(pv html_count_tr)" check_eq $P/html-th "4" "$(pv html_count_th)" check_eq $P/html-td "20" "$(pv html_count_td)" # ======================================================= get_csv_string() ==== check_eq $P/csv-sha "d4bdb5e303644479" "$(pv csv_sha)" check_eq $P/csv-norm \ "City name,Area,Population,Annual Rainfall / Adelaide,1295,1158259,600.5 / Brisbane,5905,1857594,1146.4 / Darwin,112,120900,1714.7 / Hobart,1357,205556,619.5 / Sydney,2058,4336374,1214.8 /" \ "$(pv csv_norm)" check_eq $P/csv-header "City name,Area,Population,Annual Rainfall" "$(pv csv_header)" # THE LINE TERMINATOR IS THE csv MODULE'S `\r\n', NOT THE PLATFORM'S, and that is # a property a caller writing the text to a file depends on. It is counted rather # than left inside the digest, where a red would say only that something moved -- # six terminators for six lines, including one after the last. check_eq $P/csv-crlf "6" "$(pv csv_crlf)" check_eq $P/csv-ends-crlf "yes" "$(pv csv_ends_crlf)" # ====================================================== get_json_string() ==== # Both readings. The digest pins the exact text, including the four-space # indentation upstream chose; the canonical parse is the statement about CONTENT, # and it is the render that shows this one alone keeps the cell TYPES. check_eq $P/json-sha "ba6eeb82de076417" "$(pv json_sha)" check_eq $P/json-parses "yes" "$(pv json_parses)" check_eq $P/json-canon \ '[["City name","Area","Population","Annual Rainfall"],{"Annual Rainfall":600.5,"Area":1295,"City name":"Adelaide","Population":1158259},{"Annual Rainfall":1146.4,"Area":5905,"City name":"Brisbane","Population":1857594},{"Annual Rainfall":1714.7,"Area":112,"City name":"Darwin","Population":120900},{"Annual Rainfall":619.5,"Area":1357,"City name":"Hobart","Population":205556},{"Annual Rainfall":1214.8,"Area":2058,"City name":"Sydney","Population":4336374}]' \ "$(pv json_canon)" # THE SHAPE: a list whose FIRST element is the field names and whose remaining # five are row objects. That is prettytable's own JSON convention rather than a # list of records, and a caller reading it has to know. check_eq $P/json-len "6" "$(pv json_len)" check_eq $P/json-head '["City name","Area","Population","Annual Rainfall"]' "$(pv json_head)" # ...and the types survived. This is the difference from `csv' above, where 1295 # is the text "1295", and the reason both round trips below are asserted. check_eq $P/json-type-int "int" "$(pv json_row0_type_area)" check_eq $P/json-type-float "float" "$(pv json_row0_type_rain)" # ==================================== THE MANIPULATIONS, NOT THE FORMATTER === # Each is asserted as the ORDER or the MEMBERSHIP it produces rather than as # another whole render: a sort that worked is a sequence of city names, and # saying it that way keeps the case legible and independent of the padding the # render cases above already pin. # # sortby + reversesort, DESCENDING BY A NUMERIC COLUMN. That is the case which # separates a real sort key from a string sort: 4336374 leads 1857594 as a # number and trails it as text, so a `sortby' that stringified would give a # different answer here and the same one on the city names. check_eq $P/sortby-desc "Sydney,Brisbane,Adelaide,Hobart,Darwin" "$(pv sortby_pop_desc)" # ...and the same column ascending, which is what says `reversesort' is doing the # reversing rather than the column happening to be stored that way. check_eq $P/sortby-asc "Darwin,Hobart,Adelaide,Brisbane,Sydney" "$(pv sortby_pop_asc)" # sort_key, THE CALLABLE THIS VERSION HAS. Ordering the cities by the LENGTH of # their name is an order no column in the table holds, so a `sort_key' that were # ignored could not produce it by accident. (Ties are broken by the name itself, # so the expected order is a total one.) check_eq $P/sort-key "Darwin,Hobart,Sydney,Adelaide,Brisbane" "$(pv sort_key_len)" # fields, A COLUMN SUBSET. Asserted as the rendered HEADER and the column count, # because a `fields' that were ignored would still give the right ROWS -- which # is why the row list is asserted beside it rather than instead of it. check_eq $P/fields-header "| City name | Population |" "$(pv fields_header)" check_eq $P/fields-ncols "2" "$(pv fields_ncols)" check_eq $P/fields-rows "Adelaide,Brisbane,Darwin,Hobart,Sydney" "$(pv fields_rows)" # start/end, A ROW SLICE. Half-open, like a Python slice: start=1 end=3 is the # second and third rows and not the fourth. The count is asserted beside the # names because an off-by-one at the `end' is the failure this case exists for. check_eq $P/slice-rows "Brisbane,Darwin" "$(pv slice_rows)" check_eq $P/slice-nrows "2" "$(pv slice_nrows)" # THE OPTION THIS VERSION DOES NOT HAVE, AND DOES NOT REFUSE. See the header: # `row_filter' arrived upstream after 3.10.0, and an unknown keyword here raises # nothing and does nothing. All three halves are pinned -- the option is absent, # the call does not raise, and the result is byte-identical to the unfiltered # render -- because a caller written against a newer prettytable would otherwise # get a full table with no indication anything was dropped. check_eq $P/row-filter-absent "no" "$(pv row_filter_option)" check_eq $P/row-filter-no-raise "no" "$(pv row_filter_raises)" check_eq $P/row-filter-ignored "yes" "$(pv row_filter_ignored)" # ============================================================ STYLE FLAGS ==== # border=False, header=False and a per-column `align' on ONE object -- the # styling surface the ticket names, driven together because that is how a caller # reaches it. check_eq $P/style-sha "454fb9ca63e5af62" "$(pv style_sha)" check_eq $P/style-norm \ "Adelaide 1295 1158259 600.5 / Brisbane 5905 1857594 1146.4 / Darwin 112 120900 1714.7 / Hobart 1357 205556 619.5 / Sydney 2058 4336374 1214.8" \ "$(pv style_norm)" # THE FLAGS DID WHAT THEY SAY: no rules or junctions, no vertical bars, and five # lines -- the header and both border rules gone, the five data rows kept. check_eq $P/style-no-plus "yes" "$(pv style_no_plus)" check_eq $P/style-no-pipe "yes" "$(pv style_no_pipe)" check_eq $P/style-lines "5" "$(pv style_lines)" # A BORDERLESS RENDER LEAVES THE PADDING ON THE END OF EVERY LINE. That is # upstream's behaviour, and it is the reason `style-norm' above cannot stand # alone: collapsing whitespace throws away exactly this. check_eq $P/style-trailing-ws "5" "$(pv style_trailing_ws)" # ...and `align' IS PER COLUMN, which no borderless render can show. The same # styled object rendered WITH its border gives one row verbatim: "Darwin" pushed # to the left of its column, 112 pushed to the right of its own. A build that # ignored `align' centres both, which is what the default render above shows. check_eq $P/style-align-per-column \ "| Darwin | 112 | 120900 | 1714.7 |" \ "$(pv style_bordered_darwin)" check_eq $P/style-bordered-sha "c09c8e033ac84ad8" "$(pv style_bordered_sha)" # ========================================================= THE ROUND TRIPS === # from_csv and from_json rebuild the table the exporters wrote. Stated as # EQUALITY against the exporter's own output rather than against a transcribed # literal: the claim is that each pair is inverse, and a literal on either side # would only be re-asserting the render pinned above. check_eq $P/from-csv-fields "City name,Area,Population,Annual Rainfall" "$(pv from_csv_fields)" check_eq $P/from-csv-nrows "5" "$(pv from_csv_nrows)" check_eq $P/from-csv-csv-eq "yes" "$(pv from_csv_csv_eq)" check_eq $P/from-csv-str-eq "yes" "$(pv from_csv_str_eq)" # ...AND WHAT THE CSV ROUND TRIP LOSES, which is the honest half. CSV carries no # types, so every cell comes back a str: the rendered text is identical and the # DATA is not. Without this case a reader would take the two round trips for # interchangeable, and a caller doing arithmetic on a rebuilt column would find # out later. check_eq $P/from-csv-loses-types "str" "$(pv from_csv_area_type)" check_eq $P/from-json-fields "City name,Area,Population,Annual Rainfall" "$(pv from_json_fields)" check_eq $P/from-json-nrows "5" "$(pv from_json_nrows)" check_eq $P/from-json-json-eq "yes" "$(pv from_json_json_eq)" check_eq $P/from-json-str-eq "yes" "$(pv from_json_str_eq)" # THE TYPED ROUND TRIP. 1295 comes back an int here, and the contrast with the # case above is the whole reason both are asserted. check_eq $P/from-json-keeps-types "int" "$(pv from_json_area_type)" # from_html / from_html_one, the two remaining importers this port ships and the # only exported names no case above reaches. `from_html' answers a LIST, because # a document may hold several tables; `from_html_one' is the single-table form. # HTML carries no types either, so the loss is the same as CSV's. check_eq $P/from-html-ntables "1" "$(pv from_html_ntables)" check_eq $P/from-html-fields "City name,Area,Population,Annual Rainfall" "$(pv from_html_fields)" check_eq $P/from-html-nrows "5" "$(pv from_html_nrows)" check_eq $P/from-html-one-fields "City name,Area,Population,Annual Rainfall" \ "$(pv from_html_one_fields)" check_eq $P/from-html-one-loses-types "str" "$(pv from_html_one_area_type)" # =================================== WIDTH IS wcwidth's, NOT len's ========== # See the header: this is the group that fails if python3-wcwidth did not land, # and it is the only one that can. A column holding just U+6771 U+4EAC -- two # characters, four terminal columns -- must render four wide. check_eq $P/wcwidth-version "0.2.13" "$(pv wcwidth_version)" check_eq $P/wcwidth-wcswidth "4" "$(pv wcwidth_wcswidth)" # ...and `len' of the same string is 2, which is what a build without wcwidth # would have measured. Asserting both is what makes the next case a discriminator # rather than a number. check_eq $P/wide-len "2" "$(pv wide_len)" check_eq $P/wide-col-width "4" "$(pv wide_col_width)" check_eq $P/wide-rule "+------+" "$(pv wide_rule)" check_eq $P/wide-sha "b3e4a0b226944cb6" "$(pv wide_sha)" # ======================================= THE MUTABLE SURFACE, IN SEQUENCE ==== # The object is the deliverable, not the render: a caller builds a table # incrementally and renders it more than once. These drive the mutators on ONE # object in order, so a mutator that left stale internal width state behind shows # up in the render that follows it rather than in the row count. check_eq $P/mut-add-nrows "6" "$(pv mut_add_nrows)" check_eq $P/mut-add-last "Perth" "$(pv mut_add_last)" # The render AFTER the mutation, re-measured. "Perth" is shorter than "Brisbane" # so the widths are unchanged -- which is the point: the case says the render is # recomputed and agrees, not that it moved. check_eq $P/mut-add-rule "+-----------+------+------------+-----------------+" \ "$(pv mut_add_rule)" check_eq $P/mut-del-nrows "5" "$(pv mut_del_nrows)" check_eq $P/mut-del-first "Brisbane" "$(pv mut_del_first)" # add_column appends a FIELD as well as its cells, so both the header and the # row have to move together; a column added to only one of the two would pass a # count. check_eq $P/mut-col-fields "City name,Area,Population,Annual Rainfall,State" \ "$(pv mut_col_fields)" check_eq $P/mut-col-ncols "5" "$(pv mut_col_ncols)" check_eq $P/mut-col-row0 '["Brisbane",5905,1857594,1146.4,"QLD"]' "$(pv mut_col_row0)" # clear_rows KEEPS the field names; clear() drops them too. The pair is the # difference between the two calls, which a caller reusing one table depends on # -- and calling the wrong one is silent until the next add_row. check_eq $P/mut-clear-nrows "0" "$(pv mut_clear_nrows)" check_eq $P/mut-clear-fields "City name,Area,Population,Annual Rainfall,State" \ "$(pv mut_clear_fields)" check_eq $P/mut-clearall-fields "" "$(pv mut_clearall_fields)" # ================================== OVER A REAL FILE, NOT A StringIO ======== # from_csv's actual use is a path on disk, and this image's filesystem is the # part that is new. The probe writes under TMPDIR, never the virtiofs root # (brintos/linux#73). check_eq $P/file-csv-fields "City name,Area,Population,Annual Rainfall" "$(pv file_csv_fields)" check_eq $P/file-csv-str-eq "yes" "$(pv file_csv_str_eq)" # 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"