WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s
ci / test (push) Successful in 6m18s
History-free snapshot of the private development repository at cbe45c5 (2026-09-09). Code, docs and public catalogue only; no plant or job knowledge layers, no client or job names (tools/scrub.py clean). The intro deck is left out of this snapshot because its slide images could not be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for tools/knowledgelint.py -- the host-side gate on the layer files.
|
||||
|
||||
Loaded by path, like `test_knowledge_drift.py`: the tool is host CPython and
|
||||
must be (it reads YAML with PyYAML, on purpose, because that is the only
|
||||
reader on this side as strict as the YamlDotNet one inside Revit).
|
||||
|
||||
What matters about a gate is the two answers it gives and that it never
|
||||
gives a third by accident: a file that will not load has to FAIL and set the
|
||||
exit code, and a file that loads has to pass without anybody learning to
|
||||
ignore it. A gate that cannot tell them apart is the silence this whole tool
|
||||
exists against -- 2026-09-01, one duplicate key, one whole plant layer gone,
|
||||
every report normal.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__))))
|
||||
TOOLS = os.path.join(ROOT, 'tools')
|
||||
if TOOLS not in sys.path:
|
||||
sys.path.insert(0, TOOLS)
|
||||
|
||||
pytest.importorskip('yaml')
|
||||
lint = pytest.importorskip('knowledgelint')
|
||||
|
||||
|
||||
def _write(tmp_path, name, text):
|
||||
path = tmp_path / name
|
||||
path.write_text(text, encoding='utf-8')
|
||||
return str(path)
|
||||
|
||||
|
||||
# --------------------------------------------------------------- one file
|
||||
|
||||
def test_a_clean_file_passes(tmp_path):
|
||||
path = _write(tmp_path, 'ok.yaml', u'a:\n b: 1\n c: [1, 2]\n')
|
||||
assert lint.check_file(path) == (True, 'ok')
|
||||
|
||||
|
||||
def test_a_duplicate_key_fails_with_the_line_to_delete(tmp_path):
|
||||
"""The incident's exact shape: nested, not top level."""
|
||||
path = _write(tmp_path, 'dup.yaml',
|
||||
u'checks:\n'
|
||||
u' rules:\n'
|
||||
u' cap_label_mismatch_is_view_specific: true\n'
|
||||
u' other: 1\n'
|
||||
u' cap_label_mismatch_is_view_specific: false\n')
|
||||
ok, message = lint.check_file(path)
|
||||
assert ok is False
|
||||
assert message.startswith('Duplicate key')
|
||||
assert 'cap_label_mismatch_is_view_specific' in message
|
||||
assert 'line 5' in message # the SECOND statement of the key
|
||||
|
||||
|
||||
def test_an_ordinary_syntax_error_fails_with_its_position(tmp_path):
|
||||
path = _write(tmp_path, 'broken.yaml', u'a:\n - [1, 2\n')
|
||||
ok, message = lint.check_file(path)
|
||||
assert ok is False
|
||||
assert 'line' in message
|
||||
|
||||
|
||||
def test_the_same_key_under_two_different_parents_is_not_a_duplicate(tmp_path):
|
||||
path = _write(tmp_path, 'notes.yaml', u'a:\n note: x\nb:\n note: y\n')
|
||||
assert lint.check_file(path)[0] is True
|
||||
|
||||
|
||||
def test_a_file_that_is_not_there_is_a_failure_not_a_pass(tmp_path):
|
||||
ok, message = lint.check_file(str(tmp_path / 'nope.yaml'))
|
||||
assert ok is False
|
||||
assert 'cannot open' in message
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- the run
|
||||
|
||||
def test_main_exits_zero_on_a_clean_tree(tmp_path, capsys):
|
||||
_write(tmp_path, 'a.yaml', u'a: 1\n')
|
||||
_write(tmp_path, 'b.yaml', u'b: 2\n')
|
||||
assert lint.main([str(tmp_path)]) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert '2 layer file(s) parse.' in out
|
||||
assert 'FAILED' not in out
|
||||
|
||||
|
||||
def test_main_exits_one_and_names_the_file(tmp_path, capsys):
|
||||
_write(tmp_path, 'a.yaml', u'a: 1\n')
|
||||
bad = _write(tmp_path, 'b.yaml', u'b: 1\nb: 2\n')
|
||||
assert lint.main([str(tmp_path)]) == 1
|
||||
out = capsys.readouterr().out
|
||||
assert 'FAILED' in out
|
||||
assert bad in out
|
||||
assert 'Duplicate key' in out
|
||||
# the sentence that says what a dropped layer looks like from the outside
|
||||
assert 'agrees with the defaults' in out
|
||||
|
||||
|
||||
def test_main_with_nothing_to_check_is_not_a_pass(tmp_path, capsys):
|
||||
"""`no files found` printed with exit 0 is a gate that has stopped
|
||||
checking and says it is fine -- which is the failure mode, not a state to
|
||||
report cheerfully."""
|
||||
assert lint.main([str(tmp_path)]) == 1
|
||||
assert 'no knowledge layer found' in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_json_output_carries_every_result(tmp_path, capsys):
|
||||
import json
|
||||
_write(tmp_path, 'a.yaml', u'a: 1\n')
|
||||
_write(tmp_path, 'b.yaml', u'b: 1\nb: 2\n')
|
||||
assert lint.main([str(tmp_path), '--json']) == 1
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload['checked'] == 2 and payload['failed'] == 1
|
||||
assert [r['ok'] for r in payload['results']] == [True, False]
|
||||
|
||||
|
||||
def test_list_prints_the_targets_and_checks_nothing(tmp_path, capsys):
|
||||
bad = _write(tmp_path, 'b.yaml', u'b: 1\nb: 2\n')
|
||||
assert lint.main([str(tmp_path), '--list']) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert bad in out
|
||||
assert 'FAILED' not in out
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- targets
|
||||
|
||||
def test_a_folder_argument_is_walked_and_bak_snapshots_are_left_alone(
|
||||
tmp_path):
|
||||
_write(tmp_path, 'a.yaml', u'a: 1\n')
|
||||
_write(tmp_path, 'k.bak-2026-01-01.yaml', u'x: 1\nx: 2\n')
|
||||
nested = tmp_path / 'deeper'
|
||||
nested.mkdir()
|
||||
_write(nested, 'b.yml', u'b: 1\n')
|
||||
found = [os.path.basename(p) for p in lint.walk_yaml(str(tmp_path))]
|
||||
assert found == ['a.yaml', 'b.yml']
|
||||
|
||||
|
||||
def test_default_targets_include_the_catalogue_that_ships_with_the_tool():
|
||||
"""It is the one layer in the repository, so it is the one target that is
|
||||
there on every machine -- including a colleague's clone with no plant or
|
||||
job folder and no environment set."""
|
||||
assert lint.CATALOGUE in lint.default_targets()
|
||||
|
||||
|
||||
def test_default_targets_are_files_that_exist_and_are_not_repeated():
|
||||
targets = lint.default_targets()
|
||||
assert all(os.path.isfile(p) for p in targets)
|
||||
assert len(set(os.path.realpath(p) for p in targets)) == len(targets)
|
||||
|
||||
|
||||
def test_the_plant_root_is_read_the_way_plantpath_reads_it(monkeypatch,
|
||||
tmp_path):
|
||||
"""Mirrors `vibe.plantpath.plant_root`, which cannot be imported here.
|
||||
Set to a FOLDER that folder is the root; set to a FILE its directory is,
|
||||
because the machine-wide file lives IN the root."""
|
||||
monkeypatch.setenv(lint.PLANT_ENV, str(tmp_path))
|
||||
assert lint.plant_root() == str(tmp_path)
|
||||
monkeypatch.setenv(lint.PLANT_ENV, str(tmp_path / 'knowledge.yaml'))
|
||||
assert lint.plant_root() == str(tmp_path)
|
||||
|
||||
|
||||
def test_the_plant_and_job_layers_of_this_machine_are_targets(monkeypatch,
|
||||
tmp_path):
|
||||
plants = tmp_path / 'plants'
|
||||
(plants / 'northgate').mkdir(parents=True)
|
||||
_write(plants / 'northgate', 'knowledge.yaml', u'a: 1\n')
|
||||
_write(plants, 'knowledge.yaml', u'b: 1\n')
|
||||
jobs = tmp_path / 'jobs'
|
||||
(jobs / 'sample_job').mkdir(parents=True)
|
||||
_write(jobs / 'sample_job', 'knowledge.yaml', u'c: 1\n')
|
||||
monkeypatch.setenv(lint.PLANT_ENV, str(plants))
|
||||
monkeypatch.setenv(lint.JOBS_ENV, str(jobs))
|
||||
|
||||
targets = [os.path.realpath(p) for p in lint.default_targets()]
|
||||
for expected in (plants / 'knowledge.yaml',
|
||||
plants / 'northgate' / 'knowledge.yaml',
|
||||
jobs / 'sample_job' / 'knowledge.yaml'):
|
||||
assert os.path.realpath(str(expected)) in targets
|
||||
|
||||
|
||||
def test_a_project_config_pointer_is_followed_but_a_dead_one_is_not_a_verdict(
|
||||
monkeypatch, tmp_path):
|
||||
"""A pointer to a file that is gone is `knowledge._config_missing` -- the
|
||||
buttons already refuse on it, and the file may simply live on a share this
|
||||
machine is not on today. Not a parse failure, so not this tool's verdict."""
|
||||
jobs = tmp_path / 'jobs'
|
||||
(jobs / 'sample_job').mkdir(parents=True)
|
||||
_write(jobs / 'sample_job', 'knowledge.yaml', u'c: 1\n')
|
||||
config = _write(tmp_path, 'active-config.yaml', u'knowledge:\n a: 1\n')
|
||||
monkeypatch.setenv(lint.JOBS_ENV, str(jobs))
|
||||
|
||||
(jobs / 'sample_job' / 'active-config.txt').write_text(
|
||||
config, encoding='utf-8')
|
||||
assert os.path.realpath(config) in [os.path.realpath(p)
|
||||
for p in lint.default_targets()]
|
||||
|
||||
(jobs / 'sample_job' / 'active-config.txt').write_text(
|
||||
str(tmp_path / 'gone.yaml'), encoding='utf-8')
|
||||
assert os.path.realpath(str(tmp_path / 'gone.yaml')) not in \
|
||||
[os.path.realpath(p) for p in lint.default_targets()]
|
||||
|
||||
|
||||
# The obvious next test -- "every real layer on this machine parses" -- is
|
||||
# deliberately NOT here. It is the gate's whole job, but the gate is
|
||||
# `python tools/knowledgelint.py`, run by whoever edited a layer; as a pytest
|
||||
# case it would turn a broken gitignored file that no code change touched
|
||||
# into a red suite, and this repository already has one env-dependent test
|
||||
# teaching people that a failure is not necessarily theirs. Run the tool.
|
||||
Reference in New Issue
Block a user