"""Drive humanfriendly's formatters and parsers over a fixed table, one key=value per line. ONE INTERPRETER START DOES THE WHOLE OF IT. Every python3 start on this arm is a fresh load of one large static image (99-python3 records the cost), so the suite that drives this asserts on the OUTPUT rather than calling python3 once per case. Every line is `key=value`; a key is emitted at most once. The suite reads the keys it names and ignores the rest, so adding a fact here cannot break it. Failure is reported as a key too -- `*_error' -- rather than only as an exit status, because the case that reds should say which stage stopped and why. WHAT THIS PACKAGE IS, AND WHY THE PARSERS ARE HALF OF IT. humanfriendly renders a number to text a person can read -- `1 KB', `1 hour and 30 minutes', `50 cm' -- and reads that text back to a number. A formatter alone can only be asserted against literals somebody transcribed; the PARSERS give the port a second, independent statement, and the two together give a ROUND TRIP that is a property of the library rather than of the transcription. `roundtrip_size_*' below is that. THE ROUND TRIP IS NOT UNIVERSAL, AND THAT IS MEASURED, NOT ASSUMED. format_size(1024) is `1.02 KB' and parse_size('1.02 KB') is 1020 -- the rendering is lossy by construction, because it rounds to two decimals. So the round trip is asserted over the values that format EXACTLY (`roundtrip_size_exact') and the lossy case is asserted as ITS OWN measured fact (`roundtrip_size_lossy'), rather than left as a gap a later reader would have to rediscover. TWO NAMES THIS PORT'S TICKET PREDICTED DO NOT EXIST IN 10.0, and the probe says so directly rather than by omission: * `humanfriendly.terminal.terminal_size' -- the function is `find_terminal_size'. * `humanfriendly.tables.format_table' -- the formatters are format_pretty_table / format_robust_table / format_rst_table / format_smart_table. `has_terminal_size_name' and `has_format_table_name' assert the ABSENCE, so a release that added either announces itself here instead of leaving the port's documentation quietly wrong. WHERE THE ASSERTED VALUES COME FROM. Every literal the suite checks was measured on x86-64 CPython 3.12 against the sdist the recipe's SRC_URI sha256 pins (6b0b831c...ddc, verified by digest before measuring) -- the same 3.12 the image ships, so there is no interpreter-version gap to state here. The on-target result is not yet measured: the batch verify is this port's first run on wasm32-hwjs. """ import hashlib import os import sys _seen = set() def emit(key, value): if key in _seen: return _seen.add(key) # One line per fact: the suite greps by key, and an embedded newline would # land as a second, unkeyed line. print("%s=%s" % (key, str(value).replace("\n", " ").replace("\r", " "))) def fail(key, exc): emit(key, "%s: %s" % (type(exc).__name__, exc)) raise SystemExit(1) def sha(text): return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16] # ------------------------------------------------------------------ the import try: import humanfriendly as hf from humanfriendly import ( format_size, format_number, format_length, format_timespan, parse_size, parse_length, parse_timespan, InvalidSize, InvalidLength, InvalidTimespan, ) except Exception as exc: # noqa: BLE001 - reported, not raised fail("import_error", exc) emit("module_file", getattr(hf, "__file__", "")) # THE VERSION, TWICE AND INDEPENDENTLY. The module carries `__version__' and the # installed dist-info carries `Version:'; a wheel that landed beside a different # module tree would import fine and disagree here. emit("mod_version", getattr(hf, "__version__", "")) try: from importlib.metadata import version as _dist_version emit("dist_version", _dist_version("humanfriendly")) except Exception as exc: # noqa: BLE001 emit("dist_version_error", "%s: %s" % (type(exc).__name__, exc)) # THE PUBLIC NAMES, ONE KEY EACH. Each is a name the port promises; a red here # names the missing entry point rather than leaving its cases to red one by one # with a different symptom each. for _n in ("format_size", "format_number", "format_length", "format_timespan", "parse_size", "parse_length", "parse_timespan", "InvalidSize", "InvalidLength", "InvalidTimespan"): emit("has_" + _n, "yes" if hasattr(hf, _n) else "no") # THE PACKAGE IS A DIRECTORY WITH A SUBPACKAGE, not a single module -- the # opposite shape from 99-python3-jstyleson. `humanfriendly.terminal' is itself a # package, and a release that flattened it would still import and still pass every # behavioural case below while the shape this image ships had changed. try: import humanfriendly.tables as _tables import humanfriendly.terminal as _terminal emit("terminal_is_package", "yes" if hasattr(_terminal, "__path__") else "no") except Exception as exc: # noqa: BLE001 fail("submodule_import_error", exc) # THE TWO NAMES THE TICKET PREDICTED AND 10.0 DOES NOT HAVE. See the header. The # absence is asserted so a release that grew either announces itself here. emit("has_find_terminal_size", "yes" if hasattr(_terminal, "find_terminal_size") else "no") emit("has_terminal_size_name", "yes" if hasattr(_terminal, "terminal_size") else "no") emit("has_format_pretty_table", "yes" if hasattr(_tables, "format_pretty_table") else "no") emit("has_format_table_name", "yes" if hasattr(_tables, "format_table") else "no") # ============================================================================ # THE FORMATTERS, OVER A FIXED TABLE. # ============================================================================ # # The inputs are chosen so that each names one rule: the singular/plural switch # at 1, the unit boundary at 1000 (decimal) and 1024 (binary), and the value where # the two disagree -- 1000 is `1 KB' decimally and `1000 bytes' in binary, which # is the single clearest statement that the `binary' flag is wired at all. SIZES = [0, 1, 512, 999, 1000, 1024, 1000000, 1048576, 1500000000] for _v in SIZES: try: emit("size_dec_%d" % _v, format_size(_v)) emit("size_bin_%d" % _v, format_size(_v, binary=True)) except Exception as exc: # noqa: BLE001 emit("size_error_%d" % _v, "%s: %s" % (type(exc).__name__, exc)) # `keep_width' pads the fraction rather than dropping a trailing `.00'. It is the # one formatting option this suite drives, because it is the one that changes the # TEXT of an otherwise identical value -- a column of sizes lines up under it. try: emit("size_keepwidth_1000", format_size(1000, keep_width=True)) emit("size_keepwidth_bin_1024", format_size(1024, binary=True, keep_width=True)) except Exception as exc: # noqa: BLE001 emit("size_keepwidth_error", "%s: %s" % (type(exc).__name__, exc)) # format_number groups thousands and keeps TWO decimals by default. The fraction # is TRUNCATED, not rounded -- 1234.567 is `1,234.56' and not `1,234.57' -- which # is a real property of 10.0 and the one thing here a reader would guess wrong. NUMBERS = [0, 1, 1234, 6789000, 1000000] for _v in NUMBERS: try: emit("number_%d" % _v, format_number(_v)) except Exception as exc: # noqa: BLE001 emit("number_error_%d" % _v, "%s: %s" % (type(exc).__name__, exc)) try: emit("number_trunc", format_number(1234.567)) emit("number_trunc_up", format_number(1234.565)) emit("number_decimals_0", format_number(1234.567, num_decimals=0)) emit("number_decimals_4", format_number(1234.567, num_decimals=4)) # The sign survives a rounding-to-zero: -0.5 at zero decimals is `-0', not # `0'. It is the one case where the grouping code has to keep something the # arithmetic threw away. emit("number_negative_zero", format_number(-0.5, num_decimals=0)) except Exception as exc: # noqa: BLE001 emit("number_option_error", "%s: %s" % (type(exc).__name__, exc)) # format_length works in METRES and picks the unit by magnitude: mm, cm, m, km. # The singular/plural switch is here too and it is irregular -- `1 metre', # `42 metres' -- so it is asserted rather than inferred from the size cases. LENGTHS = [("0", 0), ("0.001", 0.001), ("0.5", 0.5), ("1", 1), ("42", 42), ("1000", 1000), ("1500", 1500)] for _label, _v in LENGTHS: try: emit("length_%s" % _label.replace(".", "_"), format_length(_v)) except Exception as exc: # noqa: BLE001 emit("length_error_%s" % _label.replace(".", "_"), "%s: %s" % (type(exc).__name__, exc)) # format_timespan renders SECONDS as a sentence, joining units with `and' and # commas. The multi-unit forms are the interesting ones: they are what # parse_timespan cannot read back (see `roundtrip_span_multi_raises' below). SPANS = [("0", 0), ("1", 1), ("60", 60), ("90", 90), ("3600", 3600), ("5400", 5400), ("86399", 86399), ("86400", 86400), ("604800", 604800)] for _label, _v in SPANS: try: emit("span_%s" % _label, format_timespan(_v)) except Exception as exc: # noqa: BLE001 emit("span_error_%s" % _label, "%s: %s" % (type(exc).__name__, exc)) try: # Sub-second values: `detailed' is what makes milliseconds appear at all, and # without it anything under a second that is not a clean fraction reads `0 # seconds' -- which is worth pinning, because it looks like a bug and is not. emit("span_fractional", format_timespan(1.5)) emit("span_detailed", format_timespan(1.5, detailed=True)) emit("span_millisecond", format_timespan(0.001)) emit("span_max_units_2", format_timespan(86399, max_units=2)) except Exception as exc: # noqa: BLE001 emit("span_option_error", "%s: %s" % (type(exc).__name__, exc)) # ============================================================================ # THE PARSERS, OVER THE SAME GROUND. # ============================================================================ # # These are asserted against literals AND, below, against the formatters. A bare # number parses as the base unit -- bytes, metres, seconds -- which is the rule # that makes `parse_size("42")' 42 rather than an error. PARSE_SIZES = ["42", "0", "1 KB", "1 KiB", "5 MB", "5 MiB", "1.5 GB", "1 TB"] for _s in PARSE_SIZES: _k = _s.replace(" ", "_").replace(".", "_") try: emit("psize_%s" % _k, parse_size(_s)) emit("psize_bin_%s" % _k, parse_size(_s, binary=True)) except Exception as exc: # noqa: BLE001 emit("psize_error_%s" % _k, "%s: %s" % (type(exc).__name__, exc)) PARSE_LENGTHS = ["42", "1 km", "1 mm", "1 cm", "2.5 m"] for _s in PARSE_LENGTHS: _k = _s.replace(" ", "_").replace(".", "_") try: emit("plength_%s" % _k, parse_length(_s)) except Exception as exc: # noqa: BLE001 emit("plength_error_%s" % _k, "%s: %s" % (type(exc).__name__, exc)) PARSE_SPANS = ["42", "1s", "1 minute", "2h", "1 day", "1.5 hours"] for _s in PARSE_SPANS: _k = _s.replace(" ", "_").replace(".", "_") try: emit("pspan_%s" % _k, parse_timespan(_s)) except Exception as exc: # noqa: BLE001 emit("pspan_error_%s" % _k, "%s: %s" % (type(exc).__name__, exc)) # ============================================================================ # THE ROUND TRIP, WHICH IS THE PROPERTY RATHER THAN THE TRANSCRIPTION. # ============================================================================ # # A value formatted and read back must equal the value put in. This is the one # assertion here whose right-hand side nobody typed, so it survives an editing # mistake in the tables above -- and it is what says the formatter and the parser # are talking about the same units. # # It holds only where the rendering is EXACT. See the header: format_size rounds # to two decimals, so 1024 renders `1.02 KB' and reads back 1020. The exact set is # asserted as a round trip and the lossy one as its own measured value. _exact = [0, 1, 512, 999, 1000, 1500000000] _bad = [] for _v in _exact: try: if parse_size(format_size(_v)) != _v: _bad.append("%d->%s->%s" % (_v, format_size(_v), parse_size(format_size(_v)))) except Exception as exc: # noqa: BLE001 _bad.append("%d:%s" % (_v, type(exc).__name__)) emit("roundtrip_size_exact", "yes" if not _bad else "no: " + " ".join(_bad)) _exact_bin = [0, 1, 512, 1000, 1024, 1048576] _bad = [] for _v in _exact_bin: try: if parse_size(format_size(_v, binary=True), binary=True) != _v: _bad.append("%d->%s" % (_v, format_size(_v, binary=True))) except Exception as exc: # noqa: BLE001 _bad.append("%d:%s" % (_v, type(exc).__name__)) emit("roundtrip_size_binary", "yes" if not _bad else "no: " + " ".join(_bad)) # THE LOSSY CASE, STATED AS A VALUE. Not a failure -- a documented consequence of # rounding to two decimals, asserted so the exact set above is understood as a # restriction that was measured rather than one that was hoped for. try: emit("roundtrip_size_lossy_text", format_size(1024)) emit("roundtrip_size_lossy_back", parse_size(format_size(1024))) except Exception as exc: # noqa: BLE001 emit("roundtrip_size_lossy_error", "%s: %s" % (type(exc).__name__, exc)) _bad = [] for _v in [1, 42, 1000, 0.001, 0.5]: try: if parse_length(format_length(_v)) != _v: _bad.append("%s->%s" % (_v, format_length(_v))) except Exception as exc: # noqa: BLE001 _bad.append("%s:%s" % (_v, type(exc).__name__)) emit("roundtrip_length", "yes" if not _bad else "no: " + " ".join(_bad)) # SINGLE-UNIT TIMESPANS ROUND TRIP; MULTI-UNIT ONES DO NOT. format_timespan(5400) # is `1 hour and 30 minutes' and parse_timespan refuses it -- its tokenizer reads # `hour and' as one unit name. That is upstream's behaviour on x86-64 and it is # asserted as its own case, because a caller who round-trips a duration will meet # it and a silence here would not warn them. _bad = [] for _v in [1, 60, 3600, 86400, 604800]: try: if parse_timespan(format_timespan(_v)) != float(_v): _bad.append("%s->%s" % (_v, format_timespan(_v))) except Exception as exc: # noqa: BLE001 _bad.append("%s:%s" % (_v, type(exc).__name__)) emit("roundtrip_span_single", "yes" if not _bad else "no: " + " ".join(_bad)) try: parse_timespan(format_timespan(5400)) emit("roundtrip_span_multi_raises", "no") except InvalidTimespan: emit("roundtrip_span_multi_raises", "yes") except Exception as exc: # noqa: BLE001 emit("roundtrip_span_multi_raises", "other: %s" % type(exc).__name__) # ============================================================================ # THE DOCUMENTED FAILURE PATH: A MALFORMED STRING RAISES, IT DOES NOT RETURN. # ============================================================================ # # This is what separates a parser from a number scraper. `1 XB' has a unit that # does not exist, so parse_size must REFUSE it rather than answer 1 -- and the # exception type is part of the contract a caller catches on. # # `5 bogus' is NOT the malformed case and it is here to say so: parse_size # answers 5, because the tokenizer reads a bare number followed by an unknown # word as a byte count. That is measured, and a suite that asserted a raise there # would be asserting something upstream never promised. for _label, _s in (("bad_unit", "1 XB"), ("empty", ""), ("words", "not a size")): try: _r = parse_size(_s) emit("psize_%s_raises" % _label, "no: returned %r" % (_r,)) except InvalidSize: emit("psize_%s_raises" % _label, "yes") except Exception as exc: # noqa: BLE001 emit("psize_%s_raises" % _label, "other: %s" % type(exc).__name__) try: emit("psize_trailing_word", parse_size("5 bogus")) except Exception as exc: # noqa: BLE001 emit("psize_trailing_word", "%s" % type(exc).__name__) # THE EXCEPTION TYPES ARE DISTINCT, AND NONE OF THEM IS A ValueError. A caller # that wrote `except ValueError' would catch none of these; asserting the base # says which handler actually works. emit("invalid_size_base", InvalidSize.__mro__[1].__name__) emit("invalid_size_is_valueerror", "yes" if issubclass(InvalidSize, ValueError) else "no") for _f, _label, _exc in ((parse_length, "plength", InvalidLength), (parse_timespan, "pspan", InvalidTimespan)): try: _f("1 XB") emit("%s_bad_unit_raises" % _label, "no") except _exc: emit("%s_bad_unit_raises" % _label, "yes") except Exception as exc: # noqa: BLE001 emit("%s_bad_unit_raises" % _label, "other: %s" % type(exc).__name__) # ============================================================================ # THE TERMINAL HELPERS, IN THEIR NON-INTERACTIVE FORM. # ============================================================================ # # format_pretty_table over fixed rows. The output is a block of text, so it is # asserted three ways: by digest (exact), by line count (structure), and by the # header line (legible). A release that changed the box-drawing would move the # digest while the line count stayed, and a release that changed the padding # would move both while the header stayed -- so a red says WHICH changed. TABLE_DATA = [["eth0", "1.5 GB", "3 hours"], ["eth1", "512 KiB", "45 seconds"], ["lo", "0 bytes", "1 week"]] TABLE_COLS = ["Interface", "Traffic", "Uptime"] try: _t = _tables.format_pretty_table(TABLE_DATA, column_names=TABLE_COLS) emit("table_sha", sha(_t)) emit("table_len", len(_t)) emit("table_lines", len(_t.splitlines())) emit("table_header", _t.splitlines()[1]) emit("table_rule", _t.splitlines()[0]) emit("table_row0", _t.splitlines()[3]) except Exception as exc: # noqa: BLE001 emit("table_error", "%s: %s" % (type(exc).__name__, exc)) # find_terminal_size() AND THE DOCUMENTED FALLBACK. The function tries ioctl(2) # TIOCGWINSZ on each standard stream, then the external `stty size', then falls # back to (DEFAULT_LINES, DEFAULT_COLUMNS). Under BST none of that is # deterministic -- stdin may be a console in the guest and is a pipe on the # control host -- so the ASSERTABLE case is the fallback, and the probe forces it # rather than hoping for it: every standard stream is pointed at /dev/null (no # TIOCGWINSZ) and PATH is emptied (no `stty'). Both are restored immediately. # # The unforced call is emitted too, but only as a shape: two positive integers. # Pinning its VALUE would be pinning whatever terminal the verify happened to # attach, which is a property of the harness rather than of this package. emit("default_lines", getattr(_terminal, "DEFAULT_LINES", "")) emit("default_columns", getattr(_terminal, "DEFAULT_COLUMNS", "")) try: _devnull = open(os.devnull, "r+") _saved = (sys.stdin, sys.stdout, sys.stderr) _oldpath = os.environ.get("PATH", "") try: os.environ["PATH"] = "/nonexistent-bst" sys.stdin = sys.stdout = sys.stderr = _devnull _forced = _terminal.find_terminal_size() finally: sys.stdin, sys.stdout, sys.stderr = _saved os.environ["PATH"] = _oldpath _devnull.close() emit("terminal_fallback_lines", _forced[0]) emit("terminal_fallback_columns", _forced[1]) except Exception as exc: # noqa: BLE001 emit("terminal_fallback_error", "%s: %s" % (type(exc).__name__, exc)) try: _live = _terminal.find_terminal_size() emit("terminal_live_shape", "yes" if (len(_live) == 2 and all(isinstance(n, int) and n >= 1 for n in _live)) else "no: %r" % (_live,)) except Exception as exc: # noqa: BLE001 emit("terminal_live_error", "%s: %s" % (type(exc).__name__, exc)) emit("probe_complete", "yes")