. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. # # Everything else reads the filesystem or dpkg with shell # # builtins, or greps one probe transcript. # # # # The three: a bare `import iso3166', a second import whose # # STDERR is the assertion, and the probe -- which takes ONE # # start for 110 measurements. This package ships NO # # executable, so there is no shipped program to run as a # # real process and no exit status to assert: nothing here # # needs a fourth start. # # # # The work inside a start is a few hundred dict lookups # # over a 250-entry table, plus one sha256 of ~20 KB. # # Microseconds beside an interpreter start on this arm, # # which is tens of seconds before the first line of the # # program runs. # # # # 1200 is 400s per start, against the ~60s a python3 start # # has measured at here. The start dominates and nothing in # # this file is quadratic in anything. # # # # NOT MEASURED ON TARGET YET: this port's first on-target # # run is the batch verify. The measured elapsed goes in the # # test plan rather than in this comment, which would cost an # # image build to keep the shipped suite byte-identical. # # python3-iso3166 2.1.1 (meta-openembedded/meta-python # recipes-devtools/python/python3-iso3166_2.1.1.bb, `inherit pypi # python_setuptools_build_meta ptest', PyPI sdist) on the wasm32-hwjs glibc # image -- SELF-CONTAINED ISO 3166-1 COUNTRY DEFINITIONS. # # WHAT THE PACKAGE IS. One module file holding 250 `Country' namedtuples as a # Python LITERAL, and five dicts indexing them by name, apolitical name, alpha-2, # alpha-3 and numeric code. There is no data file, no download and no parsing at # import: the table IS the source. That shape is why this port has no runtime # fixture to stage and why every case below is about the table's content rather # than about a reader for it. # # WHY PURENESS IS THE LOAD-BEARING PROPERTY HERE, not a footnote. This image's # python3 is ONE STATIC BINARY: configure picks MODULE_BUILDTYPE=static from # host_cpu=wasm32, lib-dynload is empty by construction, and this arm has no # dynamic loader at all (BRINTOS-1476). Nothing on this image can import an # out-of-tree .so. iso3166 ships none -- its wheel says `Root-Is-Purelib: true' # -- so a version bump that grew a compiled speedup would not fail at build time, # it would install into site-packages and fail at `import iso3166' on target. # The no-.so case below asserts that negative directly, and the empty # lib-dynload beside it is the MECHANISM, so a reader can see why the negative # matters without leaving the file. # # NO BBAPPEND AND NO PATCH OF OURS. The recipe is taken from meta-python exactly # as written -- the port is one IMAGE_INSTALL line, this suite and its probe. # There is no configure, no compile step and no C in the package, so there is # nothing on this arm for an override or a site answer to reach. # # THE ONE RUNTIME DEPENDENCY IS ALREADY HERE. The recipe's RDEPENDS names only # `python3-numbers', a sub-package of the ported python3 recipe (BRINTOS-1476); # `python3-core' is added by the packaging. Neither waits on anything, which is # why this port waited on nothing. Both are asserted below as dpkg recorded # them, so a recipe that grew a third dependency is visible here rather than at # the next image build. # # ============================================================================ # WHERE THE EXPECTED VALUES CAME FROM. # ============================================================================ # # Every literal below was measured on x86-64 against the sdist verified against # the recipe's own SRC_URI sha256 (fcd551b8...1719) before it was read, and # against the built .deb for the payload cases. The control interpreter is # CPython 3.12.3 and the image ships 3.12, the same minor. # # Nothing in the probe depends on pointer width, byte order, the platform # string, the clock, the zone, the locale or hash randomisation, which is what # makes "identical to x86-64" a claim rather than a transcription of whatever # this arm happened to do. # # ============================================================================ # THE FIXTURES, AND WHY THESE THREE. # ============================================================================ # # AF / AFG / 004 / Afghanistan # The LOW-CODE case: a numeric code with fewer than three significant # digits. It is stored as the string "004", so a table that had kept it as # an int, or a lookup that formatted the caller's int without the zero # pad, lands nowhere. Both spellings are asserted, and they take different # branches of `get'. # # BO / BOL / 068 / "Bolivia, Plurinational State of" # The COMMA case. A comma is the separator a CSV-derived table splits on, # so a truncated name is the expected corruption; the whole name is # asserted, not a prefix of it. # # CI / CIV / 384 / "Côte d'Ivoire" # The NORMALISATION case, carrying BOTH an accent and an apostrophe. The # accent makes the index key a `str.upper()' over a non-ASCII codepoint -- # the one operation in this package that reaches the interpreter's Unicode # database -- and the apostrophe is the character a quoting bug eats. # # The three are asserted in every direction the API offers and then asserted to # AGREE: each direction must return the same Country object, which is the # ticket's requirement and is stronger than each direction returning something # correct on its own. # # ============================================================================ # HOW THE TABLE'S OWN INTEGRITY IS ASSERTED. # ============================================================================ # # Not by transcribing 250 records. Every whole-table property is reduced by the # probe to ONE number this file compares against ONE literal -- the count of # records violating it, which is 0 -- plus a sha256 DIGEST over the whole table # in table order. The counts catch a record whose SHAPE broke and name the # field; the digest catches a record whose VALUE moved while every shape still # held. Neither is satisfied by a table that is merely the right size, and the # pair costs eight cases instead of 1250. # # ============================================================================ # WHAT IS NOT COVERED, ON PURPOSE. # ============================================================================ # # - UPSTREAM'S OWN TEST SUITE (tests/test_lookup.py, tests/test_listings.py). # It CANNOT run on this image, for two independent reasons: the files are # pytest modules -- bare `def test_*' functions using `pytest.raises', with # no unittest.TestCase, so `python3 -m unittest' cannot collect them -- and # the recipe installs them into the SEPARATE `python3-iso3166-ptest' # package, whose RDEPENDS are python3-pytest and # python3-unittest-automake-output. This image carries none of those three, # and adding pytest to it is a materially larger change than this port. # The same call as 99-python3-xmltodict, for the same reason. # # Their SUBSTANCE is translated into the probe instead, as the assertions # they make rather than the harness they make them in -- see # `upstream-*' below. So what upstream checks is checked; what is not run is # their runner. # # - THE `-dev', `-dbg' AND `-ptest' PACKAGES. The image installs # `python3-iso3166' only. Nothing here asserts their absence, because they # are ordinary Yocto split packages and their absence is the default rather # than a property of this port. # # - `py.typed' AS A TYPING FACT. The marker is asserted as a SHIPPED FILE # below, because it is payload and the packaging must carry it. What it # means -- that a type checker may read the inline annotations -- is not # checkable on target: this image ships no type checker, and running one # would be a statement about mypy rather than about the port. # # THIS PORT CLOSES ON THIS SUITE: non-interactive, no manual browser pass, no # follow-on ticket under BRINTOS-780. P=python3-iso3166 # THE CANARY, which asserts nothing about python3-iso3166. # # 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/iso3166 DI=$SITE/iso3166-2.1.1.dist-info PKGLIST=/var/lib/dpkg/info/python3-iso3166.list PROBE=$BST_DATA/python3-iso3166-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-iso3166.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import iso3166' further down is the functional # statement; these say WHICH copy answered, so a package importing out of some # other path on sys.path is distinguishable from one importing out of what this # image installed. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/package-init test -s "$PKGDIR/__init__.py" # A PACKAGE, NOT A FLAT MODULE. The sdist uses a `src/' layout with # `packages = find:', so what installs is a DIRECTORY. A build that had # flattened it to `iso3166.py' would still import, and every behavioural case # below would pass, while the packaging had changed shape -- which is what # `py.typed' and the __pycache__ path depend on. check $P/no-flat-module test ! -f "$SITE/iso3166.py" # The bytecode Yocto compiles at install time. It is NOT in the wheel's RECORD # -- python3targetconfig's do_install adds it -- so this asserts the IMAGE's # packaging step ran, not upstream's. check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # THE TYPING MARKER, which is the package's one non-Python payload file. It is # zero bytes BY DESIGN -- PEP 561 defines it as a marker whose presence is the # whole signal -- so it is asserted with `test -f', never `test -s', and it is # named explicitly in the only-python case below for exactly that reason. check $P/py-typed test -f "$PKGDIR/py.typed" check_eq $P/py-typed-empty "0" \ "$(wc -c < "$PKGDIR/py.typed" 2>/dev/null | tr -d ' ')" # ------------------------------------------------------- what dpkg installed --- # NO COMPILED EXTENSION ANYWHERE THE PACKAGE PUT ANYTHING. On this image a .so # could not be loaded at all, so this is the case that fails a version bump # which grew one -- at install rather than at `import'. # # BRINTOS-WORKAROUND: brintos/linux#73 remove -- read `$PKGLIST' directly when # the redirect stops failing. 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. 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)" # ...AND NOTHING IN ${bindir}. iso3166 declares no `scripts' and no # `console_scripts': it is a library with no CLI, so there is no executable and # no shebang to assert. Stated as a fact about the package rather than left to # be inferred from a case nobody wrote. check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/s?bin/' "$W/pkgf" 2>/dev/null)" # EVERY REGULAR FILE THE PACKAGE SHIPS IS PYTHON, ITS METADATA, OR THE ONE # TYPING MARKER. The two cases above name the failures that are expected; this # one catches the shape nobody predicted -- a binary, a .pyd, a stray data file # -- without having to enumerate what it might be. # # `py.typed' is allowed BY NAME rather than by a pattern. The package has # exactly one non-Python file and naming it means a SECOND one would red here, # which a `*.typed' glob or a blanket data-file exemption would let through. # # A dpkg .list names DIRECTORIES too, and without a trailing slash, so each # entry is 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 ;; */iso3166/py.typed) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # The .so negative above is a grep over a LIST; this is the same statement made # against the filesystem, which is what catches a file present on disk but # absent from dpkg's manifest. check $P/lib-dynload-dir test -d /usr/lib/python3.12/lib-dynload check_eq $P/lib-dynload-empty "" \ "$(find /usr/lib/python3.12/lib-dynload -name '*.so' 2>/dev/null)" check_eq $P/no-so-on-disk "" \ "$(find "$PKGDIR" -name '*.so' 2>/dev/null)" # ----------------------------------------------------------- the dist-info --- # The installed metadata, and here it is the ONLY statement of the version: # iso3166 defines no `__version__', so there is no second reading to cross-check # against -- which is itself asserted, below, as `has-dunder-version'. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 2.1.1" cat "$DI/METADATA" check_out $P/dist-info-name "Name: iso3166" cat "$DI/METADATA" check_out $P/dist-info-top-level "iso3166" cat "$DI/top_level.txt" # PURE-PYTHON, AS THE WHEEL ITSELF DECLARES IT. This is upstream's own statement # of the property the whole port depends on, and it is independent of our # find-for-.so above: that one says none shipped, this one says none was ever # meant to. check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag "Tag: py3-none-any" cat "$DI/WHEEL" # THE LICENCE IS SHIPPED, unlike python3-isodate's, whose dist-info carries # none. The recipe's LIC_FILES_CHKSUM pins LICENSE.txt at build time; this says # the file reached the target, which is a packaging fact the checksum cannot # make. check $P/dist-info-license test -s "$DI/LICENSE.txt" check_out $P/dist-info-license-mit "MIT" cat "$DI/METADATA" # ------------------------------------------------------------- the package --- # dpkg KNOWS the package. Every case above would look identical if some other # recipe -- or a stray copy -- had put these files here, so this is what ties # the payload to THIS port. # # BRINTOS-WORKAROUND: brintos/linux#73 remove -- as above, the status file is # read with `sed' rather than redirected into. sed -n '/^Package: python3-iso3166$/,/^$/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: 2.1.1-r0" cat "$W/st" check_out $P/pkg-arch "Architecture: wasm32" cat "$W/st" # THE RUNTIME DEPENDENCIES THE RECIPE DECLARES, as dpkg recorded them. The # recipe names `python3-numbers' only; `python3-core' is the packaging's own. sed -n 's/^Depends: //p' "$W/st" > "$W/dep" 2>/dev/null 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-numbers "python3-numbers" cat "$W/dep" # ...AND NOT ON pytest. The recipe DOES declare python3-pytest, but only on the # `-ptest' package, which this image does not install. A dependency leaking from # that package onto this one would pull a test framework into a product image, # so the negative is asserted rather than assumed. check_eq $P/pkg-no-pytest-depend "" \ "$(grep -E 'python3-pytest|unittest-automake' "$W/dep" 2>/dev/null)" # --------------------------------------------------------- the interpreter --- # The package imports AT ALL, as its own process and its own exit status. Every # probe case below is downstream of this one, so a red here explains all of them # at once. check $P/import python3 -c 'import iso3166' # ...AND SILENTLY. A DeprecationWarning or a fallback notice on stderr is not an # import failure and would not move the case above, but it is the first sign of # a package drifting out of step with the interpreter -- so stderr is the # assertion, and it must be empty. check_eq $P/import-quiet "" \ "$(python3 -c 'import iso3166' 2>&1)" # ---------------------------------------------------------------- the probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present # ONE interpreter start for everything that follows. bst_probe bounds it and # keeps BOTH streams: a probe that dies early otherwise leaves every case below # reporting got:'' and naming a symptom each, with the actual traceback nowhere. # The bound is generous because a python3 start on this arm is tens of seconds # before any work begins. bst_probe $P/probe-run "$W/probe" 600 python3 "$PROBE" OUT="$W/probe" # Read a single key. The probe emits each key at most once, so head -n 1 is # defensive rather than load-bearing; the `^' and `=' anchors keep `get_AF' # distinguishable from `get_AFG'. 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 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 # ...and it ran to the END. Every key is emitted in file order, so a probe that # died two thirds of the way through would leave the earlier cases green and the # later ones reading got:'' -- which looks like a partial library rather than a # partial run. This is the case that tells those apart, and `emitted-count' is # what catches a key skipped in the middle. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/emitted-count "110" "$(pv emitted_count)" # The import succeeded INSIDE the probe too. It is emitted as its own key so a # probe that ran but could not import is one case rather than 100 reds. check_eq $P/probe-import "no" "$(pv import_failed)" # The module answered out of the file this image installed, not from a stray # copy on sys.path -- which is what makes the payload cases above and the # behavioural cases below statements about the SAME iso3166. check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" # ------------------------------------------------------------- the identity --- # The version, from the ONE place that states it. iso3166 defines no # `__version__' -- asserted here as the negative it is, so a release that grew # one is visible rather than silently giving this suite a second reading it does # not cross-check. check_eq $P/has-dunder-version "no" "$(pv has_dunder_version)" check_eq $P/dist-version "2.1.1" "$(pv dist_version)" # `__all__' names ONE symbol. The five index dicts and `Country' are reachable # and documented in the README but deliberately not star-exported, which is a # fact about the API surface a consumer relies on. check_eq $P/all-names "countries" "$(pv all_names)" check_eq $P/has-country-type "yes" "$(pv has_country_type)" check_eq $P/country-fields "name,alpha2,alpha3,numeric,apolitical_name" \ "$(pv country_fields)" # ------------------------------------------------------------ the container --- # `countries' is a lookup OBJECT, not a dict and not a list. A reimplementation # returning a plain dict would satisfy every lookup case below and fail all of # these, which is why the container protocol gets its own block. check_eq $P/countries-type "_CountryLookup" "$(pv countries_type)" check_eq $P/len "250" "$(pv len)" check_eq $P/iter-count "250" "$(pv iter_count)" check_eq $P/iter-all-country "yes" "$(pv iter_all_country)" # It returns `iter(_records)', so it must be re-iterable: a change to a # generator would exhaust and break every second consumer. check_eq $P/iter-repeatable "yes" "$(pv iter_repeatable)" # Table ORDER, at both ends. The records are alphabetical by name, so these two # also pin that the literal was not re-sorted or rotated. check_eq $P/iter-first "AF" "$(pv iter_first)" check_eq $P/iter-last "ZW" "$(pv iter_last)" # --------------------------------------------------------------- the indexes --- # All five, by size. They are built by one `_build_index(i)' over the same # records, so a size BELOW the record count means two records collided on that # field -- which would silently make one country unreachable by one of its keys, # and is invisible to any single lookup. check_eq $P/index-alpha2-len "250" "$(pv index_alpha2_len)" check_eq $P/index-alpha3-len "250" "$(pv index_alpha3_len)" check_eq $P/index-numeric-len "250" "$(pv index_numeric_len)" check_eq $P/index-name-len "250" "$(pv index_name_len)" check_eq $P/index-apolitical-len "250" "$(pv index_apolitical_name_len)" # The documented accessors are the SAME objects as the internal ones, not # copies. A copy would drift silently and no lookup case would notice. check_eq $P/index-alias-identity "yes" "$(pv index_alias_identity)" # EVERY INDEX KEY IS UPPER-CASE, because `_build_index' calls .upper() on the # field. That is the mechanism behind the case-insensitive lookups below, so it # is asserted directly rather than inferred from them. The value is a COUNT of # keys that are not, and it is 0. check_eq $P/index-keys-upper "0" "$(pv index_keys_upper)" # ------------------------------------------------------- whole-table integrity --- # The ticket's requirement. Each value is the number of records VIOLATING the # rule, so each is 0 and a red says how many broke and which field. check_eq $P/bad-alpha2 "0" "$(pv bad_alpha2)" check_eq $P/bad-alpha3 "0" "$(pv bad_alpha3)" check_eq $P/bad-numeric "0" "$(pv bad_numeric)" check_eq $P/bad-name-empty "0" "$(pv bad_name_empty)" check_eq $P/bad-apolitical-empty "0" "$(pv bad_apolitical_empty)" # THE THREE INDEXES AGREE WITH EACH OTHER, ENTRY FOR ENTRY -- the ticket's # words. The probe compares by identity, not equality: two equal-but-distinct # Country tuples would compare equal and would still mean the table had been # built twice. check_eq $P/index-disagreements "0" "$(pv index_disagreements)" # The two NAME indexes likewise, which is a SEPARATE statement: name and # apolitical_name differ for exactly two records, so these are not the same # mapping, and a port that aliased them would pass every case written against # the other three. check_eq $P/name-index-disagreements "0" "$(pv name_index_disagreements)" check_eq $P/name-differs-count "2" "$(pv name_differs_count)" check_eq $P/name-differs-pairs \ "Palestine, State of>Palestine;Taiwan, Province of China>Taiwan" \ "$(pv name_differs_pairs)" # Key uniqueness across the whole table, which is the index-size statement made # from the other side and the one that catches a duplicate record appended to # the end. check_eq $P/unique-alpha2 "250" "$(pv unique_alpha2)" check_eq $P/unique-alpha3 "250" "$(pv unique_alpha3)" check_eq $P/unique-numeric "250" "$(pv unique_numeric)" # THE DIGEST over all 250 records in table order, sha256 of the fields joined # with an explicit separator and encoded UTF-8. This is the case that fails when # a VALUE changed but every shape still holds -- a renamed country, a reassigned # numeric code, a record inserted in the middle. It is a property of the DATA: # reproducible on any interpreter, independent of pointer width, dict ordering # and hash randomisation. check_eq $P/table-sha256 \ "173e9029153c840339ffb71ae0671e65dcfa1cb1a5173d0e2664bc09ab025aee" \ "$(pv table_sha256)" # ------------------------------------------------------------ lookup: alpha-2 --- check_eq $P/get-AF "AFG" "$(pv get_AF)" check_eq $P/get-BO "BOL" "$(pv get_BO)" check_eq $P/get-CI "CIV" "$(pv get_CI)" # Lower case, because `get' upper-cases its argument before every lookup. This # is the case-insensitivity the index-keys-upper case explains the mechanism of. check_eq $P/get-af-lower "AFG" "$(pv get_af_lower)" check_eq $P/get-ci-lower "CIV" "$(pv get_ci_lower)" # ------------------------------------------------------------ lookup: alpha-3 --- check_eq $P/get-AFG "AF" "$(pv get_AFG)" check_eq $P/get-BOL "BO" "$(pv get_BOL)" check_eq $P/get-CIV "CI" "$(pv get_CIV)" check_eq $P/get-civ-lower "CI" "$(pv get_civ_lower)" # ------------------------------------------------- lookup: numeric, both ways --- # The ticket names both spellings and they take DIFFERENT branches of `get': an # int is formatted "%03d", a str is matched against `[0-9]{3}'. Two code paths # to the same record, not one case written twice -- and the AF pair is the # zero-pad case, where an int path that dropped the pad lands nowhere. check_eq $P/get-int-4 "AFG" "$(pv get_int_4)" check_eq $P/get-str-004 "AFG" "$(pv get_str_004)" check_eq $P/get-int-68 "BOL" "$(pv get_int_68)" check_eq $P/get-str-068 "BOL" "$(pv get_str_068)" check_eq $P/get-int-384 "CIV" "$(pv get_int_384)" check_eq $P/get-str-384 "CIV" "$(pv get_str_384)" # A THREE-DIGIT STRING IS AMBIGUOUS AND THE DISPATCH ORDER RESOLVES IT: length-3 # keys are tried as a NUMERIC code before an alpha-3, so a country whose alpha-3 # were three digits could not be reached by it. None is -- which is what # `bad-alpha3' establishes -- so the order is safe, and it is pinned here as the # behaviour it is. 724 is Spain. check_eq $P/get-str-724 "ESP" "$(pv get_str_724)" # "000" IS THE ONE THREE-DIGIT STRING EXCLUDED BY NAME: `get' guards # `k != "000"', so it skips the numeric branch, misses alpha-3, and falls # through to the name indexes where it is absent. A quirk of the implementation # rather than of the standard, pinned so a refactor that dropped the guard is # visible. check_eq $P/get-000-default "'DEFAULT'" "$(pv get_000_default)" # A one-character key matches no code branch and falls through to the name # lookup, so a bare "4" is NOT the same query as the integer 4. Pinned because # it is the mistake a caller makes, and the library's answer is `not found' # rather than Afghanistan. check_eq $P/get-str-4-default "'DEFAULT'" "$(pv get_str_4_default)" # --------------------------------------------------------------- lookup: name --- # By full name, in three cases, for the two fixtures chosen for their awkward # spelling. The comma name is asserted WHOLE -- a table split on the comma would # answer to "Bolivia" and not to this. check_eq $P/get-name-afghanistan "AFG" "$(pv get_name_afghanistan)" check_eq $P/get-name-comma "BOL" "$(pv get_name_comma)" check_eq $P/get-name-comma-upper "BOL" "$(pv get_name_comma_upper)" check_eq $P/get-name-comma-lower "BOL" "$(pv get_name_comma_lower)" # The accented name, and its lower-case spelling -- which only resolves if # `str.upper()' round-trips the non-ASCII codepoint the way the index was built. check_eq $P/get-name-accent "CIV" "$(pv get_name_accent)" check_eq $P/get-name-accent-lower "CIV" "$(pv get_name_accent_lower)" check_eq $P/get-name-aland "ALA" "$(pv get_name_aland)" check_eq $P/get-name-turkiye "TUR" "$(pv get_name_turkiye)" # ---------------------------------------------------- lookup: apolitical name --- # The fifth field, and the only lookup that can DISTINGUISH the two name # indexes: for these two records the name and the apolitical name differ, so a # query by one must not be answerable by the other. Four cases, two records, # both directions. check_eq $P/get-apolitical-palestine "PSE" "$(pv get_apolitical_palestine)" check_eq $P/get-apolitical-taiwan "TWN" "$(pv get_apolitical_taiwan)" check_eq $P/get-name-palestine-full "PSE" "$(pv get_name_palestine_full)" check_eq $P/get-name-taiwan-full "TWN" "$(pv get_name_taiwan_full)" # ------------------------------------------------- the fixtures agree, whole --- # THE TICKET'S ACTUAL REQUIREMENT: every direction returns the SAME Country # tuple. The probe compares by identity across all five directions at once, so # this is stronger than the individual cases above -- each of those could pass # against a table built twice. check_eq $P/fixture-agreement-AF "yes" "$(pv fixture_agreement_AF)" check_eq $P/fixture-agreement-BO "yes" "$(pv fixture_agreement_BO)" check_eq $P/fixture-agreement-CI "yes" "$(pv fixture_agreement_CI)" # ...and WHAT they agree on, field by field. The cases above say a lookup # ARRIVES; these say what it arrived at, so a table whose rows had shifted by # one would be caught even though every lookup still resolved. check_eq $P/record-AF "Afghanistan|AF|AFG|004|Afghanistan" "$(pv record_AF)" check_eq $P/record-BO \ "Bolivia, Plurinational State of|BO|BOL|068|Bolivia, Plurinational State of" \ "$(pv record_BO)" check_eq $P/record-CI "Côte d'Ivoire|CI|CIV|384|Côte d'Ivoire" "$(pv record_CI)" # ---------------------------------------------------------- __getitem__ / in --- # `countries[k]' IS `get' -- the class assigns `__getitem__ = get' -- and `in' # is `get' with the exception swallowed. Both are documented API and neither is # reached by the cases above. check_eq $P/getitem-AF "AFG" "$(pv getitem_AF)" check_eq $P/getitem-int-4 "AFG" "$(pv getitem_int_4)" check_eq $P/getitem-is-get "yes" "$(pv getitem_is_get)" check_eq $P/contains-AF "True" "$(pv contains_AF)" check_eq $P/contains-AFG "True" "$(pv contains_AFG)" check_eq $P/contains-int-4 "True" "$(pv contains_int_4)" check_eq $P/contains-name "True" "$(pv contains_name)" check_eq $P/contains-ZZ "False" "$(pv contains_ZZ)" check_eq $P/contains-int-999 "False" "$(pv contains_int_999)" # ------------------------------------------------------------ the failure path --- # ITS OWN SECTION, because "not found" is a deliverable of this port and a # library that quietly returned None for an unknown code would satisfy every # positive case above. # # The exception CLASS and its ARGUMENT are both asserted. The argument is the # caller's ORIGINAL key, not the upper-cased one -- which is what a caller # printing the error actually sees, and is pinned by `raise-keeps-key-case'. check_eq $P/raise-unknown-alpha2 "KeyError:'ZZ'" "$(pv raise_unknown_alpha2)" check_eq $P/raise-unknown-alpha3 "KeyError:'ZZZ'" "$(pv raise_unknown_alpha3)" check_eq $P/raise-unknown-numeric-int "KeyError:999" \ "$(pv raise_unknown_numeric_int)" check_eq $P/raise-unknown-name "KeyError:'Atlantis'" "$(pv raise_unknown_name)" check_eq $P/raise-empty-string "KeyError:''" "$(pv raise_empty_string)" check_eq $P/raise-getitem-unknown "KeyError:'ZZ'" "$(pv raise_getitem_unknown)" check_eq $P/raise-keeps-key-case "KeyError:'zz'" "$(pv raise_keeps_key_case)" # ...AND THE DEFAULT SUPPRESSES IT -- the ticket's second half. Same queries with # a default supplied: the default comes back and nothing is raised. check_eq $P/default-unknown-alpha2 "'DEFAULT'" "$(pv default_unknown_alpha2)" check_eq $P/default-unknown-numeric "'DEFAULT'" "$(pv default_unknown_numeric)" # THE FALSY DEFAULTS, which are the ones an implementation gets wrong. `get' # decides by `r == NotFound', so None, 0, False and "" must each come back AS # THEMSELVES; a `default or NotFound' spelling would turn every one of them back # into a raise. None is first because it is the default a caller actually # passes. check_eq $P/default-none "None" "$(pv default_none)" check_eq $P/default-zero "0" "$(pv default_zero)" check_eq $P/default-false "False" "$(pv default_false)" check_eq $P/default-empty-str "''" "$(pv default_empty_str)" # ...and a default must NOT displace a key that IS present. check_eq $P/default-ignored-when-found "AFG" \ "$(pv default_ignored_when_found)" # ------------------------------------------------------------------- Unicode --- # `str.upper()' builds all five indexes, so it is the one operation in this # package that reaches the interpreter's Unicode database -- and the only # plausibly arm-sensitive step in an otherwise pure table. Asserted directly on # the accented names rather than left implicit in a lookup that happened to work. # # The codepoints are asserted as HEX so a red shows WHICH character moved. A # decomposed source -- 'A' + U+030A instead of U+00C5 -- would upper-case to # something the index could not match, and reads differently here. This is the # case that would catch a .py or .pyc that had been re-encoded in transit. check_eq $P/aland-codepoints \ "00C5,006C,0061,006E,0064,0020,0049,0073,006C,0061,006E,0064,0073" \ "$(pv aland_codepoints)" check_eq $P/aland-upper "ÅLAND ISLANDS" "$(pv aland_upper)" check_eq $P/civ-codepoints \ "0043,00F4,0074,0065,0020,0064,0027,0049,0076,006F,0069,0072,0065" \ "$(pv civ_codepoints)" check_eq $P/civ-upper "CÔTE D'IVOIRE" "$(pv civ_upper)" # `Türkiye'.upper() is the classic dotted/dotless-i trap. Python's str.upper() # is locale-INDEPENDENT, so this is TÜRKIYE on every image and the length is # unchanged at 7; a red here would mean the interpreter had grown a # locale-sensitive case mapping, which would break the name index wholesale. check_eq $P/turkiye-upper "TÜRKIYE" "$(pv turkiye_upper)" check_eq $P/turkiye-upper-len "7" "$(pv turkiye_upper_len)" # The whole non-ASCII set, so a name that lost its accent is caught even if it # is not one of the fixtures. check_eq $P/nonascii-count "6" "$(pv nonascii_count)" check_eq $P/nonascii-names \ "Åland Islands;Côte d'Ivoire;Curaçao;Réunion;Saint Barthélemy;Türkiye" \ "$(pv nonascii_names)" # The source is NFC, computed rather than assumed: the whole name index turns on # the exact codepoints the .py -- or the .pyc this image actually loads -- # carries. check_eq $P/names-are-nfc "yes" "$(pv names_are_nfc)" # ------------------------------------------- what upstream's own tests assert --- # Upstream's suite is NOT run here and cannot be -- pytest files in a `-ptest' # package this image does not carry; see WHAT IS NOT COVERED at the top. What # follows is its SUBSTANCE: the assertions test_lookup.py and test_listings.py # make that the sections above do not already cover, translated into cases. check_eq $P/upstream-len-over-100 "True" "$(pv up_len_over_100)" check_eq $P/upstream-by-name-afghanistan "Afghanistan" \ "$(pv up_by_name_afghanistan)" check_eq $P/upstream-by-alt-name-taiwan "Taiwan" "$(pv up_by_alt_name_taiwan)" check_eq $P/upstream-tables-cover-records "yes" \ "$(pv up_tables_cover_records)" check_eq $P/upstream-data-types-ok "yes" "$(pv up_data_types_ok)" rm -rf "$W" 2>/dev/null