Files
willtools-demo/WillTools.extension/tests/test_yamlkit.py
T
will 25592fa6c2
ci / test (push) Successful in 6m14s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
History-free snapshot of the private development repository at 77a82eb.
Code, docs and the public catalogue only: no plant or job knowledge
layers, no client, job or producer names (tools/scrub.py clean against
the widened term list; a second word-level grep clean). The intro deck is
left out because its slide images cannot be checked for job drawings.
2026-09-09 08:35:15 -05:00

402 lines
14 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for vibe.yamlkit: the hand-rolled YAML subset emit()/parse() round
trips through, and nothing else.
No Revit dependency at all -- see conftest.py -- so every function here is
in scope.
"""
from vibe import yamlkit
# ------------------------------------------------------------------ scalar
def test_scalar_plain_string_is_unquoted():
assert yamlkit.scalar('left_rail') == 'left_rail'
def test_scalar_quotes_a_reserved_word():
assert yamlkit.scalar('true') == "'true'"
assert yamlkit.scalar('null') == "'null'"
def test_scalar_quotes_a_number_looking_string_so_it_reads_back_as_text():
# A part number like '4.10' must not come back as the float 4.1.
assert yamlkit.scalar('4.10') == "'4.10'"
def test_scalar_quotes_a_string_with_a_leading_or_trailing_space():
assert yamlkit.scalar(' x') == "' x'"
assert yamlkit.scalar('x ') == "'x '"
def test_scalar_quotes_a_string_ending_in_colon():
assert yamlkit.scalar('note:') == "'note:'"
def test_scalar_escapes_an_embedded_quote_by_doubling_it():
assert yamlkit.scalar("it's") == "'it''s'"
def test_scalar_empty_string_is_quoted():
assert yamlkit.scalar('') == "''"
def test_scalar_none_true_false():
assert yamlkit.scalar(None) == 'null'
assert yamlkit.scalar(True) == 'true'
assert yamlkit.scalar(False) == 'false'
def test_scalar_float_drops_a_trailing_dot_zero():
assert yamlkit.scalar(270.0) == '270'
assert yamlkit.scalar(3.5) == '3.5'
def test_scalar_int_is_bare():
assert yamlkit.scalar(28920) == '28920'
# --------------------------------------------------------------- emit/parse
def test_round_trip_flat_dict():
doc = {'a': 1, 'b': 'text', 'c': True, 'd': None, 'e': 2.5}
assert yamlkit.parse('\n'.join(yamlkit.emit(doc))) == doc
def test_round_trip_nested_dict():
doc = {'outer': {'inner': {'x': 1, 'y': [1, 2, 3]}}}
assert yamlkit.parse('\n'.join(yamlkit.emit(doc))) == doc
def test_round_trip_flat_list_stays_on_one_line():
doc = {'columns_in': [3.5, 16.5, 40.5]}
lines = yamlkit.emit(doc)
assert lines == ['columns_in: [3.5, 16.5, 40.5]']
assert yamlkit.parse('\n'.join(lines)) == doc
def test_round_trip_list_of_dicts():
doc = {'rows': [{'from_bottom_in': 1.75, 'strands': ['X', 'X', '.']},
{'from_bottom_in': 6.25, 'strands': ['X', 'X', 'X']}]}
text = '\n'.join(yamlkit.emit(doc))
assert yamlkit.parse(text) == doc
def test_round_trip_empty_dict_and_list():
doc = {'confidence': {}, 'notes': []}
text = '\n'.join(yamlkit.emit(doc))
assert yamlkit.parse(text) == doc
def test_round_trip_string_containing_a_colon_stays_a_scalar():
# A note that happens to contain ':' must not be misread as a nested key.
doc = {'text': 'STRAND PATTERN: see detail'}
text = '\n'.join(yamlkit.emit(doc))
assert yamlkit.parse(text) == doc
def test_parse_ignores_comments_and_blank_lines():
text = '# a header comment\n\nkey: 1\n# inline-ish comment on its own line\nother: 2\n'
assert yamlkit.parse(text) == {'key': 1, 'other': 2}
def test_parse_ignores_a_comment_indented_above_a_section():
text = ('reinforcing:\n'
' # a note about this field\n'
' mild_grade_ksi: 60\n')
assert yamlkit.parse(text) == {'reinforcing': {'mild_grade_ksi': 60}}
def test_deterministic_key_order_is_alphabetical():
doc = {'z': 1, 'a': 2, 'm': 3}
lines = yamlkit.emit(doc)
assert [ln.split(':')[0] for ln in lines] == ['a', 'm', 'z']
def test_is_flat_respects_the_width_budget():
short = [1, 2, 3]
long_list = list(range(40))
assert yamlkit.is_flat(short) is True
assert yamlkit.is_flat(long_list) is False
def test_is_flat_false_for_a_list_containing_a_dict():
assert yamlkit.is_flat([{'a': 1}]) is False
def test_loads_is_an_alias_for_parse():
text = 'a: 1\n'
assert yamlkit.loads(text) == yamlkit.parse(text)
# ------------------------------------------------ hand-written truncation
#
# EVERY TEST BELOW ASSERTS ON WHAT COMES AFTER THE AWKWARD CONSTRUCT, and
# that is the whole point of them. `parse()` did not raise on any of these
# shapes and did not return a wrong value for them either -- it returned a
# tree that was simply MISSING everything below, and a test that checked
# only the construct itself would have passed against the broken parser.
#
# The one that cost a real press: a job knowledge file wrapped
# `parts: [P76, P89,` onto a second line two thirds of the way down, and
# the three sections under it vanished. The config editor compares its tree
# against the Revit-side reader's key by key, so a WHOLE-FILE failure was
# reported as a disagreement about whichever innocent key was being
# written. 16 top-level keys in the file, 13 in the tree.
WRAPPED = (
'first: 1\n'
'middle:\n'
' parts: [P76, P89, P92,\n'
' SP29, SP30,\n'
" '0010001']\n"
' after_the_list: yes it is read\n'
'last:\n'
' deep: 2\n'
)
def test_a_wrapped_flow_list_does_not_end_the_document():
doc = yamlkit.parse(WRAPPED)
# The keys AFTER the wrapped list are the assertion that matters.
assert sorted(doc) == ['first', 'last', 'middle']
assert doc['last'] == {'deep': 2}
assert doc['middle']['after_the_list'] == 'yes it is read'
def test_a_wrapped_flow_list_reads_back_as_the_whole_list():
doc = yamlkit.parse(WRAPPED)
assert doc['middle']['parts'] == ['P76', 'P89', 'P92', 'SP29', 'SP30',
'0010001']
def test_a_wrapped_flow_list_keeps_a_quoted_bracket_or_comma_intact():
doc = yamlkit.parse("a: ['P76, spare', '[not a list]',\n"
" 'it''s']\nb: 2\n")
assert doc['a'] == ['P76, spare', '[not a list]', "it's"]
assert doc['b'] == 2
def test_a_hash_inside_a_quoted_flow_value_is_not_a_comment():
doc = yamlkit.parse("bars: ['#4 BAR (TYP.)',\n"
" '#9 (80ksi)']\nafter: ok\n")
assert doc['bars'] == ['#4 BAR (TYP.)', '#9 (80ksi)']
assert doc['after'] == 'ok'
def test_a_comment_inside_a_wrapped_flow_list_is_dropped():
doc = yamlkit.parse('a: [1, 2, # why these two\n'
' 3]\nb: 4\n')
assert doc == {'a': [1, 2, 3], 'b': 4}
def test_a_double_quote_that_is_an_inch_mark_does_not_open_a_string():
# knowledge/catalogue.yaml really writes this. Treating the inch marks
# as quotes hides the closing bracket and the file ends there.
doc = yamlkit.parse('dims: [3", 3", 3"]\nafter: ok\n')
assert doc == {'dims': ['3"', '3"', '3"'], 'after': 'ok'}
def test_an_unterminated_flow_list_raises_instead_of_returning_what_it_had():
# THE DECISION, pinned: running off the end of the file and returning
# the part it managed to read is the exact failure this whole path
# exists to end.
import pytest
with pytest.raises(yamlkit.YamlKitError):
yamlkit.parse('a: 1\nb: [1, 2,\nc: 3\n')
def test_the_unterminated_message_names_the_line_to_go_and_look_at():
import pytest
with pytest.raises(yamlkit.YamlKitError) as caught:
yamlkit.parse('a: 1\nb: 2\nparts: [P76,\n')
assert 'line 3' in str(caught.value)
def test_yamlkit_error_is_a_value_error():
# A caller already guarding a parse with `except ValueError` keeps
# working; one that wants to name this failure still can.
assert issubclass(yamlkit.YamlKitError, ValueError)
def test_a_block_sequence_at_its_keys_own_indent_does_not_end_the_document():
doc = yamlkit.parse('one:\n'
' notes:\n'
' - the first note\n'
' - the second\n'
' after: read\n'
'two: 2\n')
assert doc['one']['notes'] == ['the first note', 'the second']
assert doc['one']['after'] == 'read'
assert doc['two'] == 2
def test_a_trailing_comment_on_a_key_line_does_not_end_the_document():
doc = yamlkit.parse('prefixes: # dead sheets, skipped outright\n'
' - zvoid\n'
' - zzvoid\n'
'after: read\n')
assert doc == {'prefixes': ['zvoid', 'zzvoid'], 'after': 'read'}
def test_a_trailing_comment_is_stripped_from_a_value():
assert yamlkit.parse('a: 1 # one\nb: two # and two\n') == {
'a': 1, 'b': 'two'}
def test_a_hash_that_is_part_of_a_value_is_not_a_comment():
# A '#' with no whitespace in front of it is text, which is what YAML
# says and what a pattern like this needs (checked against PyYAML).
assert yamlkit.parse('sheet_re: ^WP#\\d+\nafter: ok\n') == {
'sheet_re': '^WP#\\d+', 'after': 'ok'}
assert yamlkit.parse("note: '# 4 bar'\n") == {'note': '# 4 bar'}
def test_a_hash_after_a_space_is_a_comment_even_when_it_looks_like_a_value():
# `size: #4` really is a null with a comment in YAML -- agreeing with
# the other reader matters more than guessing the writer's intent.
assert yamlkit.parse('size: #4\nafter: ok\n') == {'size': None,
'after': 'ok'}
def test_a_wrapped_plain_scalar_does_not_end_the_document():
doc = yamlkit.parse('one:\n'
' - a note that ran long and wrapped\n'
' onto a second indented line\n'
' - a shorter one\n'
'two: 2\n')
assert doc['one'] == ['a note that ran long and wrapped onto a second '
'indented line', 'a shorter one']
assert doc['two'] == 2
def test_a_wrapped_scalar_does_not_swallow_a_list_items_second_field():
# The guard that keeps `_fold_plain` from eating structure: the second
# field of a `- key: value` item is a mapping line, not a continuation.
doc = yamlkit.parse('rows:\n'
' - qty: 14\n'
' dia_in: 0.5\n'
' - qty: 6\n'
' dia_in: 0.5\n'
'after: read\n')
assert doc['rows'] == [{'qty': 14, 'dia_in': 0.5},
{'qty': 6, 'dia_in': 0.5}]
assert doc['after'] == 'read'
def test_a_block_scalars_body_keeps_its_hashes_and_indentation():
doc = yamlkit.parse('note: >-\n'
' a sentence with a # in it\n'
' and a second line\n'
'after: read\n')
assert doc['note'] == 'a sentence with a # in it and a second line'
assert doc['after'] == 'read'
def test_a_folded_block_scalar_clips_one_trailing_newline_by_default():
# `>` clips (one newline), `>-` strips. Reading them the same way is a
# disagreement with every real YAML reader on the value of the key.
assert yamlkit.parse('note: >\n one line\n')['note'] == 'one line\n'
assert yamlkit.parse('note: >-\n one line\n')['note'] == 'one line'
# ------------------------------------------------------ flow mappings
def test_a_flow_mapping_is_a_dict_and_not_a_string():
doc = yamlkit.parse("bar_notes: {SR356: OPPOSING PAIRS}\nafter: read\n")
assert doc['bar_notes'] == {'SR356': 'OPPOSING PAIRS'}
assert doc['after'] == 'read'
def test_a_flow_mapping_reads_a_quoted_key():
doc = yamlkit.parse("part_notes: {'0010001': (TYP.), SR7: 2}\n")
assert doc['part_notes'] == {'0010001': '(TYP.)', 'SR7': 2}
def test_a_wrapped_flow_mapping_does_not_end_the_document():
doc = yamlkit.parse('m: {a: 1,\n'
' b: 2}\n'
'after: read\n')
assert doc == {'m': {'a': 1, 'b': 2}, 'after': 'read'}
def test_a_nested_flow_list_is_not_split_on_its_inner_commas():
doc = yamlkit.parse('a: [[1, 2], [3, 4]]\nb: 5\n')
assert doc == {'a': [[1, 2], [3, 4]], 'b': 5}
def test_an_empty_flow_collection_still_reads_as_empty():
assert yamlkit.parse('a: []\nb: {}\nc: 1\n') == {'a': [], 'b': {}, 'c': 1}
def test_a_double_quoted_scalar_is_unquoted():
# emit() never writes one; a person editing the file by hand does, and
# a value the two readers disagree about is a value the config editor
# refuses to write beside.
assert yamlkit.parse('note: "quoted by hand"\n') == {
'note': 'quoted by hand'}
def test_a_realistic_hand_edited_file_reads_to_its_last_key():
# The shape of the file that caused this: several sections, an awkward
# construct partway down, and the sections that follow it are the
# thing under test.
text = (
'job:\n'
' name: sample_job\n'
'details:\n'
' 05/X4:\n'
' bar_notes: {SR356: OPPOSING PAIRS}\n'
' parts: [P76, P89, P92, P98,\n'
' SP29, SP30, SP32,\n'
" '0010001']\n"
' notes:\n'
' - a note at the key\'s own indent that also wraps\n'
' onto a second line\n'
' dims: [3", 3"] # inch marks, not quotes\n'
'takeoff:\n'
' enabled: true\n'
'snapshot:\n'
' detached_dir: somewhere\n'
'catalogue:\n'
' checked: true\n'
)
doc = yamlkit.parse(text)
assert sorted(doc) == ['catalogue', 'details', 'job', 'snapshot',
'takeoff']
# The innocent key the old failure was reported against.
assert doc['snapshot']['detached_dir'] == 'somewhere'
detail = doc['details']['05/X4']
assert detail['parts'][-1] == '0010001'
assert detail['bar_notes'] == {'SR356': 'OPPOSING PAIRS'}
assert detail['dims'] == ['3"', '3"']
assert detail['notes'] == ["a note at the key's own indent that also "
'wraps onto a second line']
def test_round_trip_matches_a_realistic_spec_shaped_document():
doc = {
'schema': 1,
'design_number': 'WP1',
'source': {'extractor': 'regex', 'reviewed_by': None},
'concrete': {'fc_psi': 6000, 'fci_psi': 3500},
'reinforcing': {
'strand_groups': [
{'qty': 14, 'dia_in': 0.5, 'grade_ksi': 270, 'it_lb': 28920,
'role': None, 'note': None},
],
'strand_layout': {
'datum': 'left_rail',
'columns_in': [3.5, 16.5, 40.5, 59.5, 87.5, 109.5, 132.5],
'rows': [
{'from_bottom_in': 1.75, 'strands': ['X'] * 7},
{'from_bottom_in': 6.25, 'strands': ['X'] * 7},
],
'total_in': 143.0,
},
},
'unresolved': [{'text': 'STRAND PATTERN', 'reason': 'no rule matched'}],
}
text = '\n'.join(yamlkit.emit(doc))
assert yamlkit.parse(text) == doc