#!/usr/bin/env python3 """One interpreter start for every measurement 99-python3-webencodings makes. WHY ONE PROCESS. A python3 start on wasm32-hwjs is tens of seconds before the first line of a program runs, and this suite has ~200 things to say about webencodings. Run as separate `python3 -c` invocations that would be hours of interpreter starts and nothing else; run here it is ONE start, and the work inside is a few thousand dict lookups and `codecs` calls over byte strings under 40 bytes. THE OUTPUT CONTRACT. One `key=value` line per measurement, keys emitted in file order, `probe_done=yes` last and `emitted_count=N` beside it. The suite asserts both, so a probe that died two thirds of the way through is distinguishable from a library that returned the wrong answers -- without them the early cases stay green and the later ones read got:'' , which looks like a partial library. NO VALUE ON A KEY LINE CARRIES AN ADDRESS. `repr(lookup('utf-8'))` is `` -- webencodings defines __repr__ and it is stable -- but a `codecs.CodecInfo` repr carries the object's address and would red at random. Every codec is therefore reported by its NAME, and every Encoding by `.name`. THE LABEL TABLE IS ASSERTED IN BULK, NOT CASE BY CASE. 214 labels is too many to write out in a suite, and a suite that wrote out twenty of them would pass with the other 194 truncated at packaging time -- which is the failure this port is actually exposed to, since `labels.py` is a generated file. So the whole table is reduced to a SHA-256 over its sorted `label\\tname` pairs and the digest is the assertion, with a hand-written sample beside it to say what a digest mismatch is about. NOTHING HERE READS THE CLOCK, THE ZONE, THE NETWORK OR THE LOCALE. Every fixture is a literal. `mklabels.py` -- the only module in this package that opens a socket -- is never imported; see `mklabels-not-imported` below, which is the statement that it is not. """ import codecs import hashlib import sys import traceback COUNT = 0 def emit(key, value): global COUNT COUNT += 1 sys.stdout.write("%s=%s\n" % (key, value)) def val(key, fn): """Emit fn()'s `ascii()`, or `raised:` -- never a traceback. `ascii`, NOT `repr`: repr of a str leaves printable non-ASCII as itself, so the curly quotes windows-1252 decodes to would land in the transcript as UTF-8 bytes and the suite would have to carry them too -- through a busybox ash whose locale on this image is not something this port controls. `ascii` escapes them, so every key line here is 7-bit and the suite's expectation is a literal a shell cannot misread. A case that expects a value and gets an exception name still reds, and the class name is what says which. The traceback goes to stderr, which bst_probe keeps at .err, so nothing is lost by not putting it on a key line the runner would parse as N stray results. """ try: emit(key, ascii(fn())) except Exception: traceback.print_exc() emit(key, "raised:%s" % sys.exc_info()[0].__name__) def sval(key, fn): """Emit fn()'s value as plain text -- for answers that are already strings. `repr` would wrap them in quotes and, for the label names, double the file's size for nothing. Used only where the value cannot contain a newline, which every encoding name satisfies. """ try: emit(key, fn()) except Exception: traceback.print_exc() emit(key, "raised:%s" % sys.exc_info()[0].__name__) def raises(key, exc, fn): """Emit `yes` iff fn() raises exc -- the failure paths, stated positively. `no` when it returned, and the class name when it raised something else: three outcomes, one key, and the red says which of the two wrong ones happened. """ try: fn() except exc: emit(key, "yes") return except Exception: emit(key, "raised:%s" % sys.exc_info()[0].__name__) return emit(key, "no") import webencodings as we # noqa: E402 from webencodings import (lookup, decode, encode, iter_decode, # noqa: E402 iter_encode, IncrementalDecoder, IncrementalEncoder, UTF8) from webencodings.labels import LABELS # noqa: E402 def name_of(label): """The canonical name a label resolves to, or the string `None`. Every `lookup-*` case goes through this rather than through `repr` of the Encoding: an Encoding's repr is ``, which says the same thing in more characters, and `None` is a first-class answer here rather than an error -- the spec's `get an encoding` returns it for every label it does not know. """ e = lookup(label) return "None" if e is None else e.name def _has_python_codec(name): """True iff Python's OWN codec registry knows `name`. Used for exactly one statement -- that `x-user-defined` is NOT in it, which is why webencodings ships its own implementation. `codecs.lookup` raises for an unknown codec rather than returning None, and a bare `except` here would swallow a genuinely broken registry, so the failure is narrowed to the class it is documented to raise. """ try: codecs.lookup(name) except LookupError: return False return True # ---------------------------------------------------------------- identity --- # WHICH COPY ANSWERED. Every behavioural key below is a statement about the # webencodings this image installed only if this one is. emit("module_file", getattr(we, "__file__", "none")) emit("is_package", "yes" if hasattr(we, "__path__") else "no") emit("version", we.VERSION) # THE VERSION HAS TWO READINGS AND THEY MUST AGREE. Unlike isodate, this # package DOES define a version -- `VERSION`, not `__version__` -- and setup.py # greps that same literal out of __init__.py to build the sdist. So the # dist-info and the module are two independent renderings of one line, and a # disagreement means the wheel was built from a different tree than the one # installed. emit("has_dunder_version", "yes" if hasattr(we, "__version__") else "no") try: from importlib.metadata import version as _dist_version emit("dist_version", _dist_version("webencodings")) except Exception: traceback.print_exc() emit("dist_version", "raised") # NO `__all__`. The published surface is whatever __init__.py defines, so a # suite cannot assert an export list -- it asserts the fourteen names a caller # actually reaches, below. Stated rather than left to be inferred. emit("has_all", "yes" if hasattr(we, "__all__") else "no") # THE MODULES THAT LOADED, AND THE ONE THAT DID NOT. emit("labels_module", "yes" if "webencodings.labels" in sys.modules else "no") emit("x_user_defined_imported", "yes" if "webencodings.x_user_defined" in sys.modules else "no") # `mklabels` IS THE ONLY MODULE HERE THAT OPENS A SOCKET, and it is a developer # tool: it regenerates labels.py from the WHATWG spec over HTTP. It ships in # the package because find_packages() takes the whole directory, and its # `import json` is why the recipe RDEPENDS on python3-json -- see the header of # the suite. Importing it is never part of using this library, and this key is # what says the library did not. emit("mklabels_not_imported", "yes" if "webencodings.mklabels" not in sys.modules else "no") # The fourteen names a caller reaches for, each pinned present. for _n in ("lookup", "decode", "encode", "iter_decode", "iter_encode", "IncrementalDecoder", "IncrementalEncoder", "Encoding", "UTF8", "LABELS", "PYTHON_NAMES", "CACHE", "ascii_lower", "VERSION"): emit("has_" + _n, "yes" if hasattr(we, _n) else "no") # ------------------------------------------------------------ the table ----- # THE WHOLE LABEL TABLE, AS ONE DIGEST. 214 labels onto 41 canonical names is # the payload this package exists to carry, and the failure it is exposed to is # a TRUNCATION -- labels.py is a generated file, and a packaging step that # copied it short would leave `lookup('utf-8')` working while a few hundred # legacy aliases silently answered None. Twenty hand-written cases would not # catch that; the digest does, and the counts beside it say which way it moved. emit("labels_count", str(len(LABELS))) _names = sorted(set(LABELS.values())) emit("names_count", str(len(_names))) emit("names_sorted", ",".join(_names)) _pairs = "".join("%s\t%s\n" % (k, LABELS[k]) for k in sorted(LABELS)) emit("labels_sha256", hashlib.sha256(_pairs.encode("utf-8")).hexdigest()) emit("python_names_count", str(len(we.PYTHON_NAMES))) emit("python_names", ",".join("%s>%s" % (k, we.PYTHON_NAMES[k]) for k in sorted(we.PYTHON_NAMES))) # EVERY CANONICAL NAME RESOLVES TO A CODEC ON THIS IMAGE -- the one case that # is genuinely about the PLATFORM rather than about the package. # # webencodings resolves lazily: `lookup` calls `codecs.lookup(python_name)` the # first time a name is asked for, so a codec this image's python3-codecs does # not ship is a LookupError at first use and NOT at import. A caller that only # ever asks for utf-8 would never find out. This sweeps all 41 and names the # ones that failed, so a partial codec set is one red naming the gap rather # than an intermittent failure somewhere in a caller. _missing = [] for _n in _names: try: lookup(_n) except Exception: _missing.append(_n) emit("codecs_missing", ",".join(_missing) if _missing else "none") # ------------------------------------------------------------- the lookup --- # THE SPEC'S OWN EXAMPLES, canonical name first. sval("lookup_utf8", lambda: name_of("utf-8")) sval("lookup_utf8_noh", lambda: name_of("utf8")) sval("lookup_unicode11", lambda: name_of("unicode-1-1-utf-8")) # THE FIVE ALIASES THE TICKET NAMES, and the answer is the surprise: every one # of them is windows-1252, NOT latin-1. The standard says a document labelled # `iso-8859-1` is to be decoded as windows-1252, because that is what browsers # have always done and what the bytes 0x80-0x9F in real content mean. A caller # who used `bytes.decode('iso-8859-1')` gets C1 control characters where the # web means curly quotes and the euro sign, and this is the single most useful # thing this package does. sval("lookup_latin1", lambda: name_of("latin1")) sval("lookup_iso88591", lambda: name_of("iso-8859-1")) sval("lookup_cp1252", lambda: name_of("cp1252")) sval("lookup_windows1252", lambda: name_of("windows-1252")) sval("lookup_ansi_x3", lambda: name_of("ansi_x3.4-1968")) # ...and `us-ascii` and `ascii` land there too, which is the same rule seen # from the other side: there is no ASCII encoding in the standard at all. sval("lookup_ascii", lambda: name_of("ascii")) sval("lookup_us_ascii", lambda: name_of("us-ascii")) # CASE IS FOLDED, AND ONLY OVER ASCII. `ascii_lower` lowercases through a utf-8 # round trip precisely so that a non-ASCII character which `str.lower()` would # fold into the ASCII range does NOT fold here. The Kelvin sign is the standard # example: `'K'.lower()` is `'k'`, so `str.lower` would turn a label # containing it into one that matches, and `ascii_lower` must not. sval("lookup_upper", lambda: name_of("UTF-8")) sval("lookup_mixed", lambda: name_of("UtF-8")) sval("lookup_shift_jis_upper", lambda: name_of("SHIFT_JIS")) # Through `val`, not `sval`: the WHOLE POINT of the first is that the Kelvin # sign survives, so its answer is not ASCII and would put a codepoint no # reviewer can see into the suite's expectation. `ascii()` makes it # `'bac\u212aground'`, which is the same fact written where it can be read. val("ascii_lower_kelvin", lambda: we.ascii_lower("Bac\u212aground")) val("str_lower_kelvin", lambda: "Bac\u212aground".lower()) sval("lookup_kelvin", lambda: name_of("\u212aOI8-R")) sval("lookup_kelvin_lowered", lambda: name_of("\u212aOI8-R".lower())) # ASCII WHITESPACE IS STRIPPED, AND ONLY THESE FIVE. The spec names U+0009, # U+000A, U+000C, U+000D and U+0020 -- so a vertical tab (U+000B), which every # other definition of "whitespace" in Python includes, is NOT stripped and the # label does not match. `str.strip()` would have stripped it, which is why the # code passes the five explicitly and why this pair is asserted. sval("lookup_ws_all", lambda: name_of("\t\n\f\r utf-8 \r\n\f\t")) sval("lookup_ws_tab", lambda: name_of("\tutf-8")) sval("lookup_ws_vtab", lambda: name_of("\x0butf-8")) sval("lookup_ws_nbsp", lambda: name_of("\xa0utf-8")) # INNER WHITESPACE IS NOT TOUCHED -- stripping is at the ends only. sval("lookup_inner_space", lambda: name_of("utf 8")) # THE UNKNOWN LABEL IS `None`, NOT AN EXCEPTION. `lookup` is the spec's "get an # encoding", which is defined to return failure as a value; the raising # entry point is `decode`/`encode`, below. A caller that wrote # `try: lookup(x) except LookupError:` catches nothing and reads None as an # encoding. sval("lookup_unknown", lambda: name_of("banana")) sval("lookup_empty", lambda: name_of("")) sval("lookup_ws_only", lambda: name_of(" ")) sval("lookup_utf9", lambda: name_of("utf-9")) raises("lookup_bytes_raises", Exception, lambda: lookup(b"utf-8")) # THE ENCODING OBJECT IS CACHED AND SHARED. `lookup` memoises by canonical # name, so two different LABELS for one encoding answer the SAME object -- and # a caller may therefore compare with `is`. Asserted because it is a documented # consequence of the CACHE dict and would break silently if that went. emit("lookup_identity_same_label", "yes" if lookup("utf-8") is lookup("utf-8") else "no") emit("lookup_identity_alias", "yes" if lookup("latin1") is lookup("cp1252") else "no") emit("lookup_is_UTF8", "yes" if lookup("utf-8") is UTF8 else "no") val("encoding_repr", lambda: repr(lookup("utf-8"))) sval("encoding_codec_name", lambda: lookup("utf-8").codec_info.name) # THE FOUR REMAPPED NAMES. `Encoding.name` is the STANDARD's name and the codec # behind it is Python's, and for these four they differ -- so this is the pair # of keys that says the remap happened and did not leak into the name. sval("remap_88598i_name", lambda: name_of("iso-8859-8-i")) sval("remap_88598i_codec", lambda: lookup("iso-8859-8-i").codec_info.name) sval("remap_maccyrillic_name", lambda: name_of("x-mac-cyrillic")) sval("remap_maccyrillic_codec", lambda: lookup("x-mac-cyrillic").codec_info.name) sval("remap_macintosh_name", lambda: name_of("macintosh")) sval("remap_macintosh_codec", lambda: lookup("macintosh").codec_info.name) sval("remap_win874_name", lambda: name_of("windows-874")) sval("remap_win874_codec", lambda: lookup("windows-874").codec_info.name) # `x-user-defined` IS THE ONE ENCODING PYTHON DOES NOT HAVE, and this package # ships it. It maps bytes 0x80-0xFF onto the private-use area U+F780-U+F7FF, # which is how a browser holds bytes it was told not to interpret. A red on # `xud-codec-absent` would mean Python grew it and the remap should be # revisited; a red on the decode says this package's own codec is broken. emit("xud_not_in_codecs", "yes" if not _has_python_codec("x-user-defined") else "no") sval("xud_name", lambda: name_of("x-user-defined")) val("xud_decode_low", lambda: decode(b"abc", "x-user-defined")[0]) val("xud_decode_80", lambda: decode(b"\x80", "x-user-defined")[0]) val("xud_decode_ff", lambda: decode(b"\xff", "x-user-defined")[0]) val("xud_encode_roundtrip", lambda: encode(decode(bytes(range(256)), "x-user-defined")[0], "x-user-defined") == bytes(range(256))) # ------------------------------------------------------------- the decode --- # NO BOM: THE DECLARED LABEL DECIDES, and the answer is a TUPLE -- the text and # the Encoding that produced it. A caller that treated the return value as a # string gets a tuple, so both halves are asserted separately throughout. val("decode_plain_text", lambda: decode(b"abc", "utf-8")[0]) sval("decode_plain_enc", lambda: decode(b"abc", "utf-8")[1].name) val("decode_latin1_text", lambda: decode(b"\x93hi\x94", "iso-8859-1")[0]) sval("decode_latin1_enc", lambda: decode(b"\x93hi\x94", "iso-8859-1")[1].name) # ...and that is the windows-1252 rule from the lookup section, MEASURED as a # decode: 0x93/0x94 are curly quotes, where a plain # `b'\x93hi\x94'.decode('iso-8859-1')` gives the C1 controls U+0093/U+0094. val("decode_python_iso88591", lambda: b"\x93hi\x94".decode("iso-8859-1")) # THE BOM OVERRIDES THE DECLARED LABEL. This is the rule the standard states # and the one a plain `bytes.decode` gets wrong: the fallback encoding is a # FALLBACK, used only when the input carries no BOM. All three BOMs are # asserted against a declared label that is deliberately something else, so a # green case cannot be the fallback having been used by accident. val("bom_utf8_text", lambda: decode(b"\xef\xbb\xbfab", "iso-8859-1")[0]) sval("bom_utf8_enc", lambda: decode(b"\xef\xbb\xbfab", "iso-8859-1")[1].name) val("bom_utf16le_text", lambda: decode(b"\xff\xfea\x00b\x00", "utf-8")[0]) sval("bom_utf16le_enc", lambda: decode(b"\xff\xfea\x00b\x00", "utf-8")[1].name) val("bom_utf16be_text", lambda: decode(b"\xfe\xff\x00a\x00b", "utf-8")[0]) sval("bom_utf16be_enc", lambda: decode(b"\xfe\xff\x00a\x00b", "utf-8")[1].name) # THE BOM IS CONSUMED, NOT DECODED. Three bytes for utf-8 and two for the # utf-16 pair are stripped before the codec sees them, so no U+FEFF survives # into the output. A codec-level `utf-8-sig` would strip it too; `utf-16le` # would not, and that difference is what this case pins. emit("bom_stripped_utf8", "yes" if "\ufeff" not in decode(b"\xef\xbb\xbfab", "utf-8")[0] else "no") emit("bom_stripped_utf16le", "yes" if "\ufeff" not in decode(b"\xff\xfea\x00", "utf-8")[0] else "no") # A BOM-LOOKING PREFIX THAT IS NOT ONE. `\xff\xfe` is the utf-16le BOM, so # `\xff` alone is not -- it falls through to the fallback and, under the # default `errors='replace'`, becomes U+FFFD rather than raising. val("bom_partial_text", lambda: decode(b"\xff", "utf-8")[0]) sval("bom_partial_enc", lambda: decode(b"\xff", "utf-8")[1].name) # ...WHICH IS THE DEFAULT ERROR HANDLER, and it is `replace`, not `strict` -- # the opposite of `bytes.decode`. Undecodable content in a web document is # meant to survive as U+FFFD rather than to abort the parse, and a caller who # assumed the stdlib default gets no exception where they expected one. val("decode_bad_default", lambda: decode(b"a\xffb", "utf-8")[0]) raises("decode_bad_strict", UnicodeDecodeError, lambda: decode(b"a\xffb", "utf-8", "strict")) val("decode_bad_ignore", lambda: decode(b"a\xffb", "utf-8", "ignore")[0]) # AN UNKNOWN LABEL RAISES HERE, where `lookup` returned None. `decode` resolves # the label through `_get_encoding`, which turns the None into a LookupError -- # so the two entry points report an unknown label in two different ways, and # which one a caller used decides what they must handle. raises("decode_unknown_raises", LookupError, lambda: decode(b"a", "banana")) raises("encode_unknown_raises", LookupError, lambda: encode("a", "banana")) # ...and the label is resolved BEFORE the input is looked at, so an unknown # label raises even for input that carries a BOM naming a known encoding. That # is the docstring's "fail early" and it is worth pinning: the BOM would # otherwise have made the fallback irrelevant. raises("decode_unknown_raises_with_bom", LookupError, lambda: decode(b"\xef\xbb\xbfa", "banana")) # AN Encoding OBJECT IS ACCEPTED WHERE A LABEL IS, which is what # `_get_encoding`'s `hasattr(x, 'codec_info')` is for -- a caller that resolved # the label once may pass the object and skip the table. sval("decode_by_object", lambda: decode(b"ab", lookup("utf-8"))[1].name) # ------------------------------------------------------------- the encode --- # THE DEFAULT ENCODING IS UTF-8 and the default error handler is `strict` -- # the opposite pair from `decode`, which defaults to `replace`. Both defaults # are asserted rather than described. val("encode_default", lambda: encode("h\xe9")) val("encode_latin1", lambda: encode("h\xe9", "iso-8859-1")) val("encode_by_object", lambda: encode("h\xe9", lookup("iso-8859-1"))) raises("encode_bad_strict", UnicodeEncodeError, lambda: encode("\u20ac\u4e2d", "iso-8859-1")) val("encode_bad_replace", lambda: encode("\u4e2d", "iso-8859-1", "replace")) # NO BOM IS EVER WRITTEN. `encode` is the codec's encoder and nothing more, so # a round trip through `encode` then `decode` relies on the DECLARED label -- # there is no BOM to carry the encoding for it. emit("encode_writes_no_bom", "yes" if not encode("a", "utf-8").startswith(b"\xef\xbb\xbf") else "no") val("encode_utf16le_no_bom", lambda: encode("a", "utf-16le")) # -------------------------------------------------------- the incremental --- # `iter_decode` AND `iter_encode` MUST AGREE WITH THE ONE-SHOT CALLS, and the # way to find out is not one split but every split. The fixture below is # deliberately multi-byte in three different ways -- a 2-byte, a 3-byte and a # 4-byte utf-8 sequence -- so that a chunk boundary falls INSIDE a sequence at # most offsets, which is the whole of what an incremental decoder is for. # # The keys are counts, not transcripts: `iter_decode_splits_ok=N` against # `iter_decode_splits=N` says every one of them agreed, and # `iter_decode_first_bad` names the offset of the first that did not, so a # failure is one red carrying the offset rather than a transcript nobody reads. _TEXT = "a\xe9\u4e2d\U0001f600z" _BYTES = _TEXT.encode("utf-8") def _split_check(nbytes, run): """Drive `run(chunks)` at every offset and count the agreements.""" ok = 0 bad = "none" for i in range(len(nbytes) + 1): chunks = [nbytes[:i], nbytes[i:]] try: got = run(chunks) except Exception: got = "raised:%s" % sys.exc_info()[0].__name__ if got == _TEXT: ok += 1 elif bad == "none": bad = "%d:%r" % (i, got) return ok, bad def _iter_decode_join(chunks): out, _enc = iter_decode(chunks, "utf-8") return "".join(out) _ok, _bad = _split_check(_BYTES, _iter_decode_join) emit("iter_decode_splits", str(len(_BYTES) + 1)) emit("iter_decode_splits_ok", str(_ok)) emit("iter_decode_first_bad", _bad) def _incremental_join(chunks): d = IncrementalDecoder("utf-8") out = "".join(d.decode(c) for c in chunks) return out + d.decode(b"", final=True) _ok, _bad = _split_check(_BYTES, _incremental_join) emit("incremental_splits_ok", str(_ok)) emit("incremental_first_bad", _bad) # THE ENCODER, SPLIT THE SAME WAY -- over the TEXT this time, so a chunk # boundary falls between a surrogate pair's halves on a narrow build and inside # nothing on a wide one. The assertion is against the one-shot `encode`. _enc_ok = 0 _enc_bad = "none" for _i in range(len(_TEXT) + 1): try: _got = b"".join(iter_encode([_TEXT[:_i], _TEXT[_i:]], "utf-8")) except Exception: _got = "raised:%s" % sys.exc_info()[0].__name__ if _got == _BYTES: _enc_ok += 1 elif _enc_bad == "none": _enc_bad = "%d:%r" % (_i, _got) emit("iter_encode_splits", str(len(_TEXT) + 1)) emit("iter_encode_splits_ok", str(_enc_ok)) emit("iter_encode_first_bad", _enc_bad) # THE BOM IS SNIFFED ACROSS CHUNKS, which is the incremental case the one-shot # decoder cannot have: the decoder holds back input until it has three bytes or # is told the input has ended, so a BOM split down the middle is still a BOM. # `iter_decode` returns the Encoding BEFORE the first output chunk, so this # also pins what a caller may read from it and when. _bom_chunks = [b"\xef", b"\xbb", b"\xbfab"] _out, _e = iter_decode(_bom_chunks, "iso-8859-1") sval("iter_decode_bom_enc", lambda: _e.name) val("iter_decode_bom_text", lambda: "".join(_out)) # ...AND A SHORT INPUT THAT NEVER REACHES THREE BYTES still resolves, because # `final=True` forces the decision. Without it the buffered bytes would be # dropped in silence -- one to two bytes of a document, discarded. val("iter_decode_short", lambda: "".join(iter_decode([b"a"], "utf-8")[0])) val("iter_decode_empty", lambda: "".join(iter_decode([], "utf-8")[0])) sval("iter_decode_empty_enc", lambda: iter_decode([], "iso-8859-1")[1].name) # The incremental decoder's `encoding` attribute is None until it has decided, # which is the documented shape and the reason `iter_decode` yields the # Encoding rather than exposing the decoder. _d = IncrementalDecoder("utf-8") emit("incremental_encoding_before", "None" if _d.encoding is None else "set") _d.decode(b"abc", final=True) sval("incremental_encoding_after", lambda: _d.encoding.name) # ------------------------------------------------------- upstream's tests --- # THE AUTHOR'S OWN SUITE, RUN ON THIS IMAGE. setup.py's find_packages() takes # the whole directory, so `webencodings/tests.py` lands in site-packages beside # the library and its eight test functions are callable here. # # They are PLAIN FUNCTIONS with bare `assert` statements -- no pytest, no # unittest -- so nothing outside the package is needed to run them, which is # why this works on an image that has neither. That is a different kind of # evidence from everything above: the ~180 keys before this are this port's # statements about the API a caller reaches; these are the author's statements # about the implementation, `test_all_labels` among them, which walks every one # of the 214 labels and is the broadest single thing that runs here. # # The COUNT is asserted, not just the failure total: a discovery mechanism that # collected nothing would report zero failures and read as green. try: from webencodings import tests as _wt _fns = sorted(n for n in dir(_wt) if n.startswith("test_")) emit("upstream_count", str(len(_fns))) emit("upstream_names", ",".join(_fns)) _failed = [] for _n in _fns: try: getattr(_wt, _n)() except Exception: traceback.print_exc() _failed.append("%s:%s" % (_n, sys.exc_info()[0].__name__)) emit("upstream_failed", ",".join(_failed) if _failed else "none") emit("upstream_ran", "yes") except Exception: traceback.print_exc() emit("upstream_count", "raised") emit("upstream_names", "raised") emit("upstream_failed", "raised") emit("upstream_ran", "no") # ------------------------------------------------------------------- done --- # `probe_done` LAST AND `emitted_count` BESIDE IT. Together they are what makes # a partial run distinguishable from a wrong answer: without them a probe that # died two thirds of the way through leaves the early cases green and the later # ones reading got:'' , which looks like a partial library. emit("emitted_count", str(COUNT + 2)) # this line, and probe_done sys.stdout.write("probe_done=yes\n")