"""Round-trip the TOML 0.5 example document through `toml` and print key=value. One interpreter start does the whole of it. Every python3 start on this arm is a fresh load of one ~63 MB 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 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. WHY THE COMPARISON HAPPENS HERE AND NOT IN THE SUITE. What the ticket asks for is that the parsed document equal, FIELD BY FIELD, the dict the fixture denotes. That is a structural comparison over nested dicts, lists, datetimes and strings carrying escapes, and shell can neither build the expectation nor compare it. So the literal is written here, `==` does the comparison, and the suite asserts on the verdicts -- one per field, so a red names the field that mis-parsed rather than reporting the whole document as unequal. WHERE THE FIXTURE COMES FROM, EXACTLY. The base is the example document the TOML 0.5 specification puts at the head of its own README -- title, [owner] with a first-class offset date-time, [database], the nested [servers.alpha]/[servers.beta] sub-tables, and [clients] with an array of arrays and a line-broken array -- copied unchanged. Two constructs the ticket names are NOT in that example, so each is taken verbatim from the section of the same specification that introduces it: the [[products]] array of tables (including the empty middle table, which is part of the spec's own example), and the multi-line basic strings. Nothing here is invented; what is added is added because the base example does not exercise it. """ import datetime import os import sys W = sys.argv[1] _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. The fixture deliberately carries newlines # and tabs inside its strings, so this substitution is load-bearing here in a # way it is not in every probe. print("%s=%s" % (key, str(value).replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t"))) def fail(key, exc): emit(key, "%s: %s" % (type(exc).__name__, exc)) raise SystemExit(1) # ------------------------------------------------------------------ the import try: import toml except Exception as exc: # noqa: BLE001 - reported, not raised fail("import_error", exc) emit("version", toml.__version__) # The SPECIFICATION LEVEL the library implements, which is the fact that # distinguishes this package from python3-tomli on the same image: tomli is a # TOML 1.0.0 parser, this is 0.5. toml exposes it as a module-level constant, so # it is read rather than inferred from the version number. emit("spec", getattr(toml, "_spec_", "")) emit("module_file", toml.__file__) emit("module_dir", os.path.dirname(os.path.abspath(toml.__file__))) # toml's documented public surface. Reported as facts rather than asserted here # so the suite can name which one is absent. Both directions are named because # this package, unlike tomli, is a reader AND a writer -- that is the whole of # why the round trip below is possible at all. for name in ("loads", "load", "dumps", "dump", "TomlDecodeError", "TomlDecoder", "TomlEncoder"): emit("has_" + name, "yes" if getattr(toml, name, None) is not None else "no") emit("decode_error_is_exception", "yes" if isinstance(getattr(toml, "TomlDecodeError", None), type) and issubclass(toml.TomlDecodeError, Exception) else "no") # --------------------------------------------------------------- the fixture # The document and the dict it denotes sit side by side, as a matched pair. A # fixture shipped in a separate file from its expectation is how the two drift # apart into a silent false green. # # A RAW string with ''' delimiters, for two reasons that are not style: the # backslashes below are TOML escapes and must reach the parser unchanged, and the # fixture itself contains `"""`, which would close a Python `"""` literal. DOC = r'''# This is a TOML document. title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00-08:00 # First class dates [database] server = "192.168.1.1" ports = [ 8001, 8001, 8002 ] connection_max = 5000 enabled = true [servers] # Indentation (tabs and/or spaces) is allowed but not required [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" [clients] data = [ ["gamma", "delta"], [1, 2] ] # Line breaks are OK when inside arrays hosts = [ "alpha", "omega" ] [[products]] name = "Hammer" sku = 738594937 [[products]] # empty table within the array [[products]] name = "Nail" sku = 284758393 color = "gray" [strings] ml_basic = """ Roses are red Violets are blue""" ml_continuation = """ The quick brown \ fox jumps over \ the lazy dog.""" ml_escapes = """ I'm a string. \"You can quote me\". Name\tJosé Location\tSF.""" ''' # THE DICT THE FIXTURE DENOTES, as a Python literal, so the assertion is # `parsed == EXPECT` rather than a walk that could agree with a wrong parse. # # `dob` carries a -08:00 offset and NOT UTC: TOML 0.5 makes the offset part of # the value, and a parser that normalised it to UTC would still satisfy an # equality test on the instant -- which is why the offset is also emitted on its # own below. EXPECT = { "title": "TOML Example", "owner": { "name": "Tom Preston-Werner", "dob": datetime.datetime( 1979, 5, 27, 7, 32, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=-8))), }, "database": { "server": "192.168.1.1", "ports": [8001, 8001, 8002], "connection_max": 5000, "enabled": True, }, "servers": { "alpha": {"ip": "10.0.0.1", "dc": "eqdc10"}, "beta": {"ip": "10.0.0.2", "dc": "eqdc10"}, }, "clients": { "data": [["gamma", "delta"], [1, 2]], "hosts": ["alpha", "omega"], }, "products": [ {"name": "Hammer", "sku": 738594937}, {}, {"name": "Nail", "sku": 284758393, "color": "gray"}, ], "strings": { # A multi-line basic string: the newline IMMEDIATELY after the opening # delimiter is trimmed, the one inside is kept. "ml_basic": "Roses are red\nViolets are blue", # THE LINE-ENDING BACKSLASH. It swallows the newline and every space that # follows it, so three indented source lines denote one unbroken # sentence. This is the construct in the whole fixture most likely to be # got wrong, and it fails by producing a string that merely LOOKS # plausible -- which is why it is compared against a literal. "ml_continuation": "The quick brown fox jumps over the lazy dog.", # The escapes the spec lists: an escaped quote, a tab, and a \\u code # point outside ASCII, inside a multi-line string that also has a real # newline in it. "ml_escapes": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF.", }, } path = os.path.join(W, "example.toml") try: with open(path, "w") as fh: fh.write(DOC) except Exception as exc: # noqa: BLE001 fail("fixture_error", exc) emit("fixture_ok", "yes") # ------------------------------------------------------------- load(path) # THE TICKET'S ENTRY POINT. toml.load takes a path (or a file object, or a list # of either), decodes it, and returns the document -- and it is the call every # consumer of a pyproject.toml or a config file actually makes. Driven from the # file rather than from the string so a red here with loads() green is an I/O or # encoding fault rather than a parser fault. try: parsed = toml.load(path) except Exception as exc: # noqa: BLE001 fail("load_error", exc) emit("load_ok", "yes") # The same document through the string entry point, which is what the round trip # below has to use. Kept as a separate fact so "load and loads disagree" is # distinguishable from "both are wrong the same way". try: parsed_str = toml.loads(DOC) except Exception as exc: # noqa: BLE001 fail("loads_error", exc) emit("loads_ok", "yes") emit("load_matches_loads", "yes" if parsed_str == parsed else "no") # THE WHOLE-DOCUMENT VERDICT. The per-field verdicts below say WHICH field # disagreed; this one is the statement that nothing else did -- an extra key the # fixture never wrote would show up here and nowhere else. emit("doc_equal", "yes" if parsed == EXPECT else "no") emit("top_level_keys", ",".join(sorted(parsed.keys()))) # ------------------------------------------------------- field by field ------ # One verdict per field the ticket names, so a red names the field. The Python # type is emitted beside each because `1 == True` and `1.0 == 1` in Python: a # bool parsed as an int, or an integer widened to a float, would satisfy a # value-only comparison and is exactly the class of bug a TOML parser gets wrong. FIELDS = ( ("title", ("title",)), ("owner_name", ("owner", "name")), ("owner_dob", ("owner", "dob")), ("database_server", ("database", "server")), ("database_ports", ("database", "ports")), ("database_max", ("database", "connection_max")), ("database_enabled", ("database", "enabled")), ("servers_alpha", ("servers", "alpha")), ("servers_beta", ("servers", "beta")), ("clients_data", ("clients", "data")), ("clients_hosts", ("clients", "hosts")), ("products", ("products",)), ("ml_basic", ("strings", "ml_basic")), ("ml_continuation", ("strings", "ml_continuation")), ("ml_escapes", ("strings", "ml_escapes")), ) _MISSING = object() def dig(obj, keys): for k in keys: if not isinstance(obj, dict) or k not in obj: return _MISSING obj = obj[k] return obj for label, keys in FIELDS: got = dig(parsed, keys) want = dig(EXPECT, keys) ok = got is not _MISSING and got == want and type(got) is type(want) emit("field_" + label, "yes" if ok else "no") emit("pytype_" + label, "" if got is _MISSING else type(got).__name__) # THE NESTED SHAPE, stated as counts rather than as verdicts, so a red above has # something to be read against. `products` is an ARRAY OF TABLES with an empty # table in the middle -- a parser that dropped the empty one would give 2 here # and still parse the two populated entries correctly. emit("products_len", len(parsed.get("products", [])) if isinstance( parsed.get("products"), list) else "") emit("products_middle_empty", "yes" if isinstance(parsed.get("products"), list) and len(parsed["products"]) == 3 and parsed["products"][1] == {} else "no") emit("servers_subtables", ",".join(sorted(parsed.get("servers", {}).keys())) if isinstance(parsed.get("servers"), dict) else "") # THE OFFSET DATE-TIME, as values rather than as a verdict. TOML 0.5 makes the # offset part of the value, so a parser that normalised -08:00 to UTC, or dropped # the zone entirely, shows it here even if some type check passed. _dob = dig(parsed, ("owner", "dob")) emit("dob_utcoffset", _dob.utcoffset() if hasattr(_dob, "utcoffset") else "") # The same offset as a NUMBER. `str(timedelta(hours=-8))` is "-1 day, 16:00:00", # which is correct and unreadable; the suite asserts on this key instead, and the # one above stays as the fact a human reads when it reds. emit("dob_utcoffset_seconds", _dob.utcoffset().total_seconds() if hasattr(_dob, "utcoffset") else "") emit("dob_iso", _dob.isoformat() if hasattr(_dob, "isoformat") else "") # The multi-line strings as LENGTHS beside the verdicts above. A string that is # wrong by a swallowed-or-kept newline differs by exactly one character, and a # length says that where a diff of two multi-line values in a shell case cannot. _ml = parsed.get("strings", {}) for _k in ("ml_basic", "ml_continuation", "ml_escapes"): _v = _ml.get(_k) if isinstance(_ml, dict) else None emit("len_" + _k, len(_v) if isinstance(_v, str) else "") # ------------------------------------------------------------- the round trip - # THE OTHER HALF OF THE TICKET. dumps() the parsed object, parse THAT, and assert # the two parses are equal. This is the only statement that exercises the writer # at all, and it is stronger than comparing the emitted TEXT to a golden file: # the text is not canonical (key order, quoting style and array layout are the # encoder's choice), while the DOCUMENT it denotes is. try: emitted = toml.dumps(parsed) except Exception as exc: # noqa: BLE001 fail("dumps_error", exc) emit("dumps_ok", "yes") emit("dumps_nonempty", "yes" if emitted.strip() else "no") emit("dumps_len", len(emitted)) try: reparsed = toml.loads(emitted) except Exception as exc: # noqa: BLE001 fail("roundtrip_parse_error", exc) emit("roundtrip_parse_ok", "yes") # THE DELIVERABLE. What came back out and went back in denotes the same document. emit("roundtrip_equal", "yes" if reparsed == parsed else "no") # ...and against the literal too, which is the stronger of the two: `reparsed == # parsed` would still be satisfied if the reader and the writer were wrong in # matching ways. emit("roundtrip_equal_expect", "yes" if reparsed == EXPECT else "no") emit("roundtrip_keys", ",".join(sorted(reparsed.keys()))) # dump() to a real file, which is the writer's other entry point and the one that # touches the filesystem. Separated so a read-only or full tmpfs is not reported # as an encoder failure. outp = os.path.join(W, "roundtrip.toml") try: with open(outp, "w") as fh: toml.dump(parsed, fh) with open(outp) as fh: from_file = toml.loads(fh.read()) except Exception as exc: # noqa: BLE001 fail("dump_error", exc) emit("dump_ok", "yes") emit("dump_matches_dumps", "yes" if from_file == reparsed else "no") # ----------------------------------------------------- the malformed fixtures - # THE TICKET NAMES TWO, wrong in two different ways: one STRUCTURAL (a key # defined twice, which TOML makes an error rather than an overwrite) and one # LEXICAL (a bare integer with a leading zero, which TOML forbids so that `01` # can never be read as octal). A parser that merely overwrote, or that accepted # the leading zero, would pass every case above. # # Each reports the exception's CLASS NAME as well as the verdict, so a red # distinguishes "did not raise" from "raised the wrong class". try: toml.loads('a = 1\na = 2\n') emit("dup_key_raises", "no") emit("dup_key_exc", "") except toml.TomlDecodeError as exc: emit("dup_key_raises", "yes") emit("dup_key_exc", "TomlDecodeError") emit("dup_key_msg_nonempty", "yes" if str(exc).strip() else "no") except Exception as exc: # noqa: BLE001 emit("dup_key_raises", "wrong-class") emit("dup_key_exc", type(exc).__name__) try: toml.loads('n = 01\n') emit("leading_zero_raises", "no") emit("leading_zero_exc", "") except toml.TomlDecodeError as exc: emit("leading_zero_raises", "yes") emit("leading_zero_exc", "TomlDecodeError") emit("leading_zero_msg_nonempty", "yes" if str(exc).strip() else "no") except Exception as exc: # noqa: BLE001 emit("leading_zero_raises", "wrong-class") emit("leading_zero_exc", type(exc).__name__) emit("done", "yes")