. "$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 builds sets of at most five elements, which is # # nothing beside the interpreter load. # # # # WHERE THE THREE GO: the probe, one import case, and one # # case asserting the module resolves out of site-packages # # rather than a stray. This package ships NO executable, so # # there is no script group and no start per script -- # # 99-python3-jsonpointer carries 1200 for six starts, which # # is the nearest comparable and leaves this one generous. # # # # 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-ordered-set 4.1.0 (meta-openembedded # meta-python/recipes-devtools/python/python3-ordered-set_4.1.0.bb, `inherit pypi # python_flit_core ptest', PyPI sdist) on the wasm32-hwjs glibc image -- a # MutableSet that remembers insertion order, so every entry also has an index. # # ============================================================================ # WHAT THE TYPE IS, AND THE ONE PROPERTY THAT DISTINGUISHES IT. # ============================================================================ # # An OrderedSet is a set AND a sequence at once: it de-duplicates like a `set', # and it iterates, slices and indexes like a `list'. Its MRO is # OrderedSet -> MutableSet -> Set -> Sequence, and both isinstance answers are # asserted, because which mixin supplies a method the class does not define # itself is decided by that order. # # EVERY BEHAVIOURAL CASE BELOW ASSERTS THE FULL CONTENTS IN ORDER, not membership. # Order is the entire difference from `set', and it is the first thing a wrong # implementation loses while every membership assertion goes on passing. The probe # therefore dumps each result as a JSON LIST in iteration order and never sorts # it. # # TWO KINDS OF ORDER-BEARING ARGUMENT ARE KEPT APART. Over a list or another # OrderedSet, every result here is a fact about this package. Over a plain `set' # the argument HAS no order of its own, so the tail of a `union' or a # `symmetric_difference' is CPython's set iteration order rather than anything # this package chose -- those two are asserted as a pinned head plus a SORTED # tail, and the operator/method agreement over a set is asserted separately. The # other two, `intersection' and `difference', take their order from self alone # and are pinned whole. # # ============================================================================ # THREE THINGS THE TICKET AND THE DOCSTRING GET WRONG, MEASURED. # ============================================================================ # # 1. `index' OF AN ABSENT ELEMENT RAISES KeyError. The ticket predicted # ValueError (which is what `list.index' raises) and the method's own # docstring promises IndexError. It is neither: `index' resolves through # `self.map[key]' and the dict's KeyError comes straight out. A caller's # `except' clause written from either source catches nothing. # # 2. THE OPERATOR AND METHOD FORMS ACCEPT THE SAME THINGS. The ticket has them # "differing in what they accept"; measured over a set, a list and another # OrderedSet they agree element for element and in order, every time. What # differs is the ERROR on a NON-iterable -- the operator returns # NotImplemented and Python raises "unsupported operand", the method reaches # `set(...)' and raises "not iterable" -- and `&' is the one operator that # does not follow that rule, because the class overrides it onto the method. # # 3. THERE IS NO ptest PACKAGE AND UPSTREAM'S SUITE IS NOT ON THE IMAGE. The # recipe does `inherit ptest' and does carry a `do_install_ptest', but this # distro's DISTRO_FEATURES carries no "ptest", so ptest.bbclass never adds # ${PN}-ptest and the build produced three debs -- the package, -dev and # -dbg -- and no -ptest. The ticket's "the port runs the upstream suite # as-is" is not what this image can do; the cases below are the port's own # statement instead, and pkg-no-ptest asserts the absence rather than # leaving it to be discovered. # # ============================================================================ # A DEFECT IN 4.1.0 THAT THIS SUITE ASSERTS AS SHIPPED BEHAVIOUR. # ============================================================================ # # `discard' walks the index map and decrements every entry at or after the hole # it made. `pop' DOES NOT -- it deletes the item and its own map entry and leaves # every later index one too high. So after `pop(0)' on OrderedSet([1, 2, 3]) the # map answers `index(2) == 1' for an element sitting at position 0, and the round # trip the whole package rests on -- `oset[oset.index(x)]' -- returns the WRONG # ELEMENT (3, not 2) or raises IndexError for the last one. # # It is invisible in ordinary use because `pop()' with no argument takes the LAST # element, where there is nothing after the hole to renumber. Iteration and # membership read `items' directly and stay correct throughout, so only an # index-based read can see it at all. # # THIS IS UPSTREAM'S, NOT THIS PLATFORM'S -- it reproduces identically on x86-64 # CPython 3.12 against the same sdist. It is asserted here as the behaviour this # image ships, beside the `discard' and `pop()' cases that are correct, so that a # release fixing it reds these three cases and is READ rather than absorbed in # silence. Nothing is filed: ordered-set is a third-party public repository and # this project does not report to those. # # PURE PYTHON, WHICH IS WHY IT PORTS. This image's python3 is ONE STATIC BINARY # with an empty lib-dynload and no dynamic loader (BRINTOS-1476), so a module # carrying a compiled extension could not be imported here at all. ordered-set # ships no C -- no .c, .pyx or .so in the sdist and no ext_modules -- and its only # non-Python file is the zero-byte PEP 561 `py.typed' marker. The built package's # control stanza reads `Depends: python3-core' and NOTHING ELSE, so this port # waits on no other package; `python3-pytest-runner-native' in the recipe is a # build-time DEPENDS and reaches no target package. P=python3-ordered-set bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/ordered_set MOD=$PKGDIR/__init__.py # The dist-info directory is spelled with an UNDERSCORE while the distribution is # named with a HYPHEN -- flit normalises one way for the directory and not for # the metadata, and this is the one packaging fact a caller can get wrong without # the import telling them. DI=$SITE/ordered_set-4.1.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-ordered-set.list PROBE=$BST_DATA/python3-ordered-set-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-ordered-set.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # --------------------------------------------------------------- the artifact check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/module-file test -f "$MOD" # A PACKAGE directory, not a single module -- and the `py.typed' marker only has # meaning inside one. A flattened `ordered_set.py' would import identically and # ship no marker at all, so both halves are asserted. check_eq $P/no-flat-module "absent" \ "$(if [ -f "$SITE/ordered_set.py" ]; then echo present; else echo absent; fi)" check $P/py-typed-present test -f "$PKGDIR/py.typed" # PEP 561 makes it a marker by EXISTING; upstream's is empty and a non-empty one # would mean a partial-stub declaration nobody wrote. check_eq $P/py-typed-empty "" "$(cat "$PKGDIR/py.typed" 2>/dev/null)" # The file list is COPIED to $TMPDIR before it is read, because the `while read' # below takes it on stdin: a top-level stdin redirect on the virtiofs root makes # dup2(2) return 513 and `ash' exits, ending the suite in silence. # # BRINTOS-WORKAROUND: brintos/linux#73 remove 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)" # Everything shipped is Python source, its bytecode, dist-info metadata or the # typing marker. A file outside that set is a compiled artifact or a stray, and # either is worth a red on an arm with no dynamic loader. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; "$PKGDIR/py.typed") continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 4.1.0" cat "$DI/METADATA" # The HYPHENATED distribution name, beside the underscored directory holding it. check_out $P/dist-info-name "Name: ordered-set" cat "$DI/METADATA" # flit, not setuptools -- and the difference is visible in the shipped metadata: # a pure-Python wheel tagged py3-none-any, with NO top_level.txt, which # setuptools would have written. A port reading the module name out of that file # would find nothing here. check_out $P/dist-info-wheel-generator "flit" cat "$DI/WHEEL" check_out $P/dist-info-wheel-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-wheel-tag "Tag: py3-none-any" cat "$DI/WHEEL" check_eq $P/dist-info-no-top-level "absent" \ "$(if [ -f "$DI/top_level.txt" ]; then echo present; else echo absent; fi)" # RECORD names the two shipped payload files and nothing else -- the check that # the wheel this .deb was cut from held exactly what the package holds. check_out $P/dist-info-record-module "ordered_set/__init__.py" cat "$DI/RECORD" check_out $P/dist-info-record-pytyped "ordered_set/py.typed" cat "$DI/RECORD" # ---------------------------------------------------------------- the package sed -n '/^Package: python3-ordered-set$/,/^$/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: 4.1.0-r0" cat "$W/st" 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" # THE WHOLE DEPENDS LINE, pinned. The point of this port is that it waits on # nothing: `python3-core' is the only entry, and a second one appearing would # mean the package had acquired a runtime dependency between releases. check_eq $P/pkg-depends-only-core "Depends: python3-core" \ "$(sed 's/[[:space:]]*$//' "$W/dep")" # NO -ptest PACKAGE. The recipe inherits ptest and carries a do_install_ptest, # but this distro's DISTRO_FEATURES carries no "ptest", so ptest.bbclass adds # no ${PN}-ptest and the build produced three debs and not four. Upstream's # test/test_ordered_set.py is therefore NOT on this image, and the behavioural # cases below are this port's own statement rather than a run of it. check_eq $P/pkg-no-ptest "absent" \ "$(if [ -d /usr/lib/python3-ordered-set/ptest ]; then echo present; else echo absent; fi)" check_eq $P/pkg-no-ptest-status "absent" \ "$(if grep -q '^Package: python3-ordered-set-ptest$' /var/lib/dpkg/status 2>/dev/null; \ then echo present; else echo absent; fi)" check_eq $P/pkg-no-upstream-tests "absent" \ "$(if [ -f "$SITE/test_ordered_set.py" ]; then echo present; else echo absent; fi)" # ----------------------------------------------------------------- the import check $P/import python3 -c 'import ordered_set; ordered_set.OrderedSet' # The module resolves out of site-packages rather than a stray on the path -- the # one case that would separate "the package shipped" from "something importable # is called ordered_set". check_out $P/import-from-site "$PKGDIR" \ python3 -c 'import ordered_set, os; print(os.path.dirname(ordered_set.__file__))' # ------------------------------------------------------------------ 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 bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi check_eq $P/version "4.1.0" "$(pv version)" check_eq $P/module-answered "$MOD" "$(pv module_file)" check_eq $P/module-is-package "yes" "$(pv module_is_package)" # The metadata read through importlib rather than off the filesystem, under BOTH # spellings: `ordered-set' is the distribution and `ordered_set' the module, and # importlib normalises the two to the same distribution. A caller asking for # either must get an answer. check_eq $P/dist-version "4.1.0" "$(pv dist_version)" check_eq $P/dist-version-underscore "4.1.0" "$(pv dist_version_underscore)" check_eq $P/dist-name "ordered-set" "$(pv dist_name)" check_eq $P/python-version "3.12" "$(pv python_version)" # ------------------------------------------------------------------ the surface # No __all__, so `from ordered_set import *' would export the typing imports too. # Stated rather than corrected -- it is upstream's shape. check_eq $P/no-dunder-all "no" "$(pv has_all)" for _n in OrderedSet SLICE_ALL SetLike OrderedSetInitializer; do check_eq $P/module-has-$_n "yes" "$(pv has_$_n)" done for _n in add append update index get_loc get_indexer pop discard remove clear \ copy union intersection difference symmetric_difference \ issubset issuperset difference_update intersection_update \ symmetric_difference_update; do check_eq $P/api-$_n "yes" "$(pv meth_$_n)" done # Three aliases that are the SAME function object, not wrappers: `append' for # `add', and pandas' `get_loc'/`get_indexer' for `index'. Nothing behavioural # would notice one being rebound to a lookalike. check_eq $P/alias-append-is-add "yes" "$(pv append_is_add)" check_eq $P/alias-get-loc-is-index "yes" "$(pv get_loc_is_index)" check_eq $P/alias-get-indexer-is-index "yes" "$(pv get_indexer_is_index)" # A set AND a sequence -- the whole claim of the package, and the MRO order that # decides which mixin wins where the class defines nothing itself. check_eq $P/is-mutableset "yes" "$(pv is_mutableset)" check_eq $P/is-sequence "yes" "$(pv is_sequence)" check_eq $P/mro \ "OrderedSet|MutableSet|Set|Sequence|Reversible|Collection|Sized|Iterable|Container|Generic|object" \ "$(pv mro)" # ------------------------------------- the mutating sequence, contents in order # ONE set driven through add / discard / remove / update / pop, with the FULL # contents asserted after every step. A membership-only assertion would pass # through all of it with the order destroyed. check_eq $P/seq-init '["a", "b", "c"]' "$(pv seq_00_init)" # `add' returns the INDEX -- MutableSet.add promises None, and this deviation is # the package's own convenience. check_eq $P/seq-add-new "3" "$(pv seq_01_add_new)" check_eq $P/seq-add-new-after '["a", "b", "c", "d"]' "$(pv seq_01_after)" # Re-adding an existing key returns the index it ALREADY had and does not move it # to the end -- the case that separates "remembers insertion order" from "records # the most recent add". check_eq $P/seq-add-existing "1" "$(pv seq_02_add_existing)" check_eq $P/seq-add-existing-after '["a", "b", "c", "d"]' "$(pv seq_02_after)" check_eq $P/seq-discard '["a", "c", "d"]' "$(pv seq_03_after)" # `discard' renumbered the map, so "c" now answers 1 and not its old 2. This is # the claim the pop-vs-discard group below shows `pop' does NOT satisfy. check_eq $P/seq-discard-renumbered "1" "$(pv seq_03_index_c)" check_eq $P/seq-discard-absent-noop '["a", "c", "d"]' "$(pv seq_04_after)" check_eq $P/seq-remove '["a", "d"]' "$(pv seq_05_after)" # `update' returns the index of the LAST element inserted, and a re-offered key # does not move -- so this is "f"'s index, not "a"'s. check_eq $P/seq-update "2" "$(pv seq_06_update)" check_eq $P/seq-update-after '["a", "d", "e", "f"]' "$(pv seq_06_after)" check_eq $P/seq-pop "f" "$(pv seq_07_pop)" check_eq $P/seq-pop-after '["a", "d", "e"]' "$(pv seq_07_after)" check_eq $P/seq-len "3" "$(pv seq_08_len)" check_eq $P/seq-index "2" "$(pv seq_08_index_e)" check_eq $P/seq-contains "yes" "$(pv seq_08_contains)" check_eq $P/seq-contains-absent "no" "$(pv seq_08_contains_absent)" # ---------------------------------------------------------- the sequence half check_eq $P/get-int "b" "$(pv get_int)" check_eq $P/get-negative "d" "$(pv get_negative)" check_eq $P/get-slice '["b", "c"]' "$(pv get_slice)" # A slice gives an OrderedSet back; a LIST of indices gives a plain list, because # the caller may ask for an index twice and a set could not hold the answer. The # duplicate is in the fixture for exactly that reason. check_eq $P/get-slice-type "OrderedSet" "$(pv get_slice_type)" check_eq $P/get-fancy '["c", "a", "c"]' "$(pv get_fancy)" check_eq $P/get-fancy-type "list" "$(pv get_fancy_type)" # `oset[:]' is special-cased to copy() rather than falling through the slice path: # equal, and a DIFFERENT object. check_eq $P/get-all '["a", "b", "c", "d"]' "$(pv get_all)" check_eq $P/get-all-is-copy "no" "$(pv get_all_is_same)" check_eq $P/get-all-equal "yes" "$(pv get_all_equal)" check_eq $P/index-present "2" "$(pv index_present)" check_eq $P/index-fancy "[2, 0]" "$(pv index_fancy)" # A str or a tuple is ATOMIC to `index' -- looked up whole rather than iterated, # which is what `_is_atomic' exists for and the one place `index' and the # CONSTRUCTOR disagree about what a string is (see init-str below). check_eq $P/index-str-atomic "0" "$(pv index_str_atomic)" check_eq $P/index-tuple-atomic "0" "$(pv index_tuple_atomic)" check_eq $P/reversed '["d", "c", "b", "a"]' "$(pv reversed)" check_eq $P/iter '["a", "b", "c", "d"]' "$(pv iter)" check_eq $P/len "4" "$(pv len)" # --------------------------------------------------------------- the set half # Over a LIST and over another ORDERED SET, both forms, with the order pinned. # Every one of these is a fact about this package and nothing else. check_eq $P/union-method-list "[3, 1, 4, 5, 7, 9, 2]" "$(pv union_m_list)" check_eq $P/union-operator-list "[3, 1, 4, 5, 7, 9, 2]" "$(pv union_o_list)" check_eq $P/union-method-oset "[3, 1, 4, 5, 7, 9, 2]" "$(pv union_m_oset)" check_eq $P/union-operator-oset "[3, 1, 4, 5, 7, 9, 2]" "$(pv union_o_oset)" check_eq $P/inter-method-list "[3, 1, 7]" "$(pv inter_m_list)" check_eq $P/inter-operator-list "[3, 1, 7]" "$(pv inter_o_list)" check_eq $P/inter-method-oset "[3, 1, 7]" "$(pv inter_m_oset)" check_eq $P/inter-operator-oset "[3, 1, 7]" "$(pv inter_o_oset)" check_eq $P/diff-method-list "[4, 5]" "$(pv diff_m_list)" check_eq $P/diff-operator-list "[4, 5]" "$(pv diff_o_list)" check_eq $P/diff-method-oset "[4, 5]" "$(pv diff_m_oset)" check_eq $P/diff-operator-oset "[4, 5]" "$(pv diff_o_oset)" # self's survivors FIRST, then other's -- the documented order, and the half a # plain set could not reproduce. check_eq $P/symdiff-method-list "[4, 5, 9, 2]" "$(pv symdiff_m_list)" check_eq $P/symdiff-operator-list "[4, 5, 9, 2]" "$(pv symdiff_o_list)" check_eq $P/symdiff-method-oset "[4, 5, 9, 2]" "$(pv symdiff_m_oset)" check_eq $P/symdiff-operator-oset "[4, 5, 9, 2]" "$(pv symdiff_o_oset)" check_eq $P/union-returns-oset "OrderedSet" "$(pv type_union_list)" # Over a PLAIN SET, where the argument brings no order. `intersection' and # `difference' take their order from self alone, so they are pinned whole. check_eq $P/inter-method-set "[3, 1, 7]" "$(pv inter_m_set)" check_eq $P/diff-method-set "[4, 5]" "$(pv diff_m_set)" # `union' and `symmetric_difference' append the argument's members, so their # TAILS are CPython's set iteration order and not this package's answer. Head # pinned, tail sorted -- asserting the pinned tail would be asserting the # interpreter's hash table. check_eq $P/union-set-head "[3, 1, 4, 5, 7]" "$(pv union_set_head)" check_eq $P/union-set-tail "[2, 9]" "$(pv union_set_tail_sorted)" check_eq $P/symdiff-set-head "[4, 5]" "$(pv symdiff_set_head)" check_eq $P/symdiff-set-tail "[2, 9]" "$(pv symdiff_set_tail_sorted)" # ...and the two forms agree over a set exactly as they do over everything else, # which is the claim the two unpinned keys above would otherwise leave unmade. check_eq $P/set-forms-agree "yes" "$(pv set_forms_agree)" check_eq $P/union-variadic "[3, 1, 4, 5, 2, 0]" "$(pv union_multi)" check_eq $P/inter-variadic "[2, 3]" "$(pv inter_multi)" check_eq $P/diff-variadic "[3, 5]" "$(pv diff_multi)" # Called with NO arguments each returns a copy of self rather than raising -- the # identity element, and the shape a caller reaches by splatting an empty list. check_eq $P/union-noargs "[3, 1, 4, 5, 7]" "$(pv union_noargs)" check_eq $P/inter-noargs "[3, 1, 4, 5, 7]" "$(pv inter_noargs)" check_eq $P/diff-noargs "[3, 1, 4, 5, 7]" "$(pv diff_noargs)" # `&' is the ONE operator the class overrides. The Set mixin's version computes # the intersection the other way round and would answer in the ARGUMENT's order; # the second case is what makes the first a statement rather than a tautology. check_eq $P/and-is-intersection "yes" "$(pv and_equals_intersection)" check_eq $P/and-not-mixin-order "yes" "$(pv and_not_reversed_order)" check_eq $P/issubset-true "yes" "$(pv issubset_yes)" check_eq $P/issubset-false "no" "$(pv issubset_no)" check_eq $P/issuperset-true "yes" "$(pv issuperset_yes)" check_eq $P/issuperset-false "no" "$(pv issuperset_no)" # ------------------------------------------------------ the in-place updates # Each of the three rebuilds the index map wholesale through `_update_items', so # each is followed by a full index sweep: it is the same map `pop' leaves stale # below, and these are the paths that get it right. check_eq $P/difference-update "[3, 5]" "$(pv difference_update)" check_eq $P/difference-update-index "[0, 1]" "$(pv difference_update_index)" check_eq $P/intersection-update "[1, 3, 7]" "$(pv intersection_update)" check_eq $P/intersection-update-index "[0, 1, 2]" "$(pv intersection_update_index)" check_eq $P/symdiff-update "[4, 5, 9, 2]" "$(pv symdiff_update)" check_eq $P/symdiff-update-index "[0, 1, 2, 3]" "$(pv symdiff_update_index)" check_eq $P/clear "[]" "$(pv clear)" check_eq $P/clear-len "0" "$(pv clear_len)" # ------------------------------------------------------ equality, asymmetric # THE SAME TWO COLLECTIONS COMPARE EQUAL OR UNEQUAL DEPENDING ON WHICH CONTAINER # THE CALLER REACHED FOR. Against a Sequence -- list, tuple, another OrderedSet -- # order is compared; against a set or frozenset it is ignored, because those have # no order to compare. Documented, and the single most surprising thing on the # surface. check_eq $P/eq-set-reordered "yes" "$(pv eq_set_reordered)" check_eq $P/eq-frozenset-reordered "yes" "$(pv eq_frozenset_reordered)" check_eq $P/eq-list-same-order "yes" "$(pv eq_list_same_order)" check_eq $P/eq-list-reordered "no" "$(pv eq_list_reordered)" check_eq $P/eq-tuple-same-order "yes" "$(pv eq_tuple_same_order)" check_eq $P/eq-tuple-reordered "no" "$(pv eq_tuple_reordered)" # Two OrderedSets compare BY ORDER, because an OrderedSet IS a Sequence -- the # answer a reader is most likely to get backwards, since both sides are sets. check_eq $P/eq-oset-same-order "yes" "$(pv eq_oset_same_order)" check_eq $P/eq-oset-reordered "no" "$(pv eq_oset_reordered)" # The reflected direction: set.__eq__ returns NotImplemented against a type it # does not know, so Python falls back to this class and the order-free answer # holds from the left-hand side too. check_eq $P/eq-set-reflected "yes" "$(pv eq_set_reflected)" check_eq $P/eq-missing-member "no" "$(pv eq_missing_member)" # A non-iterable answers False rather than raising, which is what `==' must do. check_eq $P/eq-noniterable "no" "$(pv eq_noniterable)" # ----------------------------------------------------------- the failure paths check_eq $P/fail-remove-absent "KeyError" "$(pv fail_remove_absent)" # NOT ValueError (which is `list.index') AND NOT IndexError (which is what this # method's own docstring promises). `index' resolves through `self.map[key]' and # the dict's KeyError comes straight out, so a caller's `except' clause written # from either source catches nothing. Asserted as the class this image ships. check_eq $P/fail-index-absent "KeyError" "$(pv fail_index_absent)" check_eq $P/fail-index-absent-msg "KeyError: 'zz'" "$(pv fail_index_absent_msg)" # The empty-set case carries the class's OWN message; one line below, an # out-of-range index on a NON-empty set raises a different class entirely, # straight out of the backing list. check_eq $P/fail-pop-empty "KeyError: 'Set is empty'" "$(pv fail_pop_empty)" check_eq $P/fail-pop-out-of-range "IndexError" "$(pv fail_pop_out_of_range)" # The only place in the class that CONVERTS an exception: TypeError in, ValueError # out, with a message of its own. check_eq $P/fail-update-noniterable \ "ValueError: Argument needs to be an iterable, got " \ "$(pv fail_update_noniterable)" # The module's OWN "Don't know how to index" message, reachable only for an # argument that is neither iterable nor integer-like... check_eq $P/fail-getitem-float \ "TypeError: Don't know how to index an OrderedSet by 1.5" "$(pv fail_getitem_float)" check_eq $P/fail-getitem-none \ "TypeError: Don't know how to index an OrderedSet by None" "$(pv fail_getitem_none)" # ...and NOT for a str, which IS iterable, so the fancy-indexing branch wins and # the error arrives from indexing the backing list with a character. The module's # own message is unreachable for the argument a caller is likeliest to pass. check_eq $P/fail-getitem-str \ "TypeError: list indices must be integers or slices, not str" "$(pv fail_getitem_str)" # Mutable, therefore unhashable: it cannot be a MEMBER of another set, though # every set operation above takes it happily as an ARGUMENT. check_eq $P/fail-hash "TypeError" "$(pv fail_hash)" # THE OPERATOR/METHOD DIFFERENCE, WHICH IS ONLY HERE. Over any iterable the two # forms agree exactly (the set-half group above). Handed a NON-iterable the # operator returns NotImplemented and Python raises "unsupported operand", while # the method reaches `set(...)' and raises "not iterable". check_eq $P/fail-or-int \ "TypeError: unsupported operand type(s) for |: 'OrderedSet' and 'int'" \ "$(pv fail_or_int)" check_eq $P/fail-union-int "TypeError: 'int' object is not iterable" \ "$(pv fail_union_int)" check_eq $P/fail-sub-int \ "TypeError: unsupported operand type(s) for -: 'OrderedSet' and 'int'" \ "$(pv fail_sub_int)" check_eq $P/fail-difference-int "TypeError: 'int' object is not iterable" \ "$(pv fail_difference_int)" # `&' is overridden onto the method, so it is the one operator that raises the # METHOD's error -- the asymmetry above does not hold for it. check_eq $P/fail-and-int "TypeError: 'int' object is not iterable" \ "$(pv fail_and_int)" # ----------------------------------------- pop(index) leaves the map stale # UPSTREAM DEFECT IN 4.1.0, ASSERTED AS THE BEHAVIOUR THIS IMAGE SHIPS. `pop' # deletes the item and its own map entry and does NOT renumber what follows, so # every later index is one too high. See the header for why nothing is filed and # why the three cases here are worth a red when a release fixes it. check_eq $P/pop0-returned "1" "$(pv pop0_returned)" check_eq $P/pop0-items "[2, 3]" "$(pv pop0_items)" check_eq $P/pop0-len "2" "$(pv pop0_len)" # 2 is at position 0 and the map answers 1... check_eq $P/pop0-index-stale "1" "$(pv pop0_index_2)" # ...so the round trip the whole package rests on hands back the WRONG element... check_eq $P/pop0-roundtrip-wrong-element "3" "$(pv pop0_roundtrip_2)" # ...and for the last element it walks off the end of the backing list. check_eq $P/pop0-roundtrip-out-of-range "IndexError" "$(pv pop0_roundtrip_3)" # Iteration and membership read `items' directly, so they are correct throughout # -- which is why only an index-based read can see any of this. check_eq $P/pop0-contains "yes" "$(pv pop0_contains_2)" check_eq $P/pop0-iter "[2, 3]" "$(pv pop0_iter)" # The same removal through `discard', which IS correct. The pair is what makes # the group a statement about `pop' rather than about the class. check_eq $P/discard-items "[2, 3]" "$(pv discard_items)" check_eq $P/discard-index-fresh "0" "$(pv discard_index_2)" check_eq $P/discard-roundtrip "2" "$(pv discard_roundtrip_2)" # ...and `pop()' of the LAST element, which is also correct, and is why the defect # stays invisible in ordinary use. check_eq $P/poplast-items "[1, 2]" "$(pv poplast_items)" check_eq $P/poplast-index-fresh "1" "$(pv poplast_index_2)" check_eq $P/poplast-roundtrip "2" "$(pv poplast_roundtrip_2)" # --------------------------------------------------------- construction, repr check_eq $P/init-none "[]" "$(pv init_none)" check_eq $P/init-empty "[]" "$(pv init_empty)" # The CONSTRUCTOR iterates a str character by character and de-duplicates it -- # the opposite of what `index' does with one, and the only place the two # disagree about what a string is. check_eq $P/init-str '["h", "e", "l", "o"]' "$(pv init_str)" check_eq $P/init-dedup "[1, 2, 3]" "$(pv init_dedup)" # A dict is iterated by KEY, in the dict's own insertion order -- so this is # order-preserving rather than sorted. check_eq $P/init-dict-keys '["b", "a"]' "$(pv init_dict_keys)" check_eq $P/repr-empty "OrderedSet()" "$(pv repr_empty)" check_eq $P/repr-nonempty "OrderedSet([1, 2, 3])" "$(pv repr_nonempty)" check_eq $P/str-is-repr "OrderedSet([1, 2, 3])" "$(pv str_nonempty)" check_eq $P/bool-empty "no" "$(pv bool_empty)" # A one-element set holding a FALSY element is truthy: truth comes from __len__, # and 0 is a perfectly good member. check_eq $P/bool-zero-member "yes" "$(pv bool_zero_member)" check_eq $P/copy-equal "yes" "$(pv copy_equal)" check_eq $P/copy-is-new "no" "$(pv copy_is_same)" check_eq $P/copy-type "OrderedSet" "$(pv copy_type)" # Mutating the copy leaves the original alone -- a `copy' that returned self # would satisfy both cases above and fail this one. check_eq $P/copy-independent "[1, 2]" "$(pv copy_independent)" # ------------------------------------------------------------------- pickling # The class hand-writes __getstate__/__setstate__ for ONE reason: an empty set's # natural state is `[]', which is FALSY, and pickle skips __setstate__ entirely # for a falsy state -- so an empty OrderedSet would come back uninitialised and # unusable. The `(None,)' sentinel is that workaround, asserted directly because # a round trip alone would not say which mechanism carried it. check_eq $P/getstate-empty "[null]" "$(pv getstate_empty)" check_eq $P/getstate-nonempty "[1, 2, 3]" "$(pv getstate_nonempty)" check_eq $P/pickle-roundtrip "[3, 1, 2]" "$(pv pickle_roundtrip)" check_eq $P/pickle-roundtrip-type "OrderedSet" "$(pv pickle_roundtrip_type)" check_eq $P/pickle-empty-len "0" "$(pv pickle_empty_len)" # The unpickled empty set is USABLE -- `add' returns index 0, which it could only # do with `items' and `map' actually initialised. check_eq $P/pickle-empty-usable "0" "$(pv pickle_empty_usable)" check_eq $P/probe-complete "yes" "$(pv probe_complete)" rm -rf "$W"