Files
willtools-demo/WillTools.extension/tests/test_setup_machine.py
T
will 8e6ee31596
ci / test (push) Successful in 6m18s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
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.
2026-09-09 08:32:15 -05:00

512 lines
20 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for tools/setup_machine.py's `.setup-opts` opt-file.
Loaded the way `test_libindex.py` and `test_snapshot_cycle.py` load their
`tools/` modules: put `tools/` on `sys.path` and import it directly. Lives
here because this is where every other `tools/*.py` test already lives.
WHAT THIS FILE DOES NOT COVER
------------------------------
Nothing here shells out to real `pip`, PowerShell, or the real `panellift`
CLI -- those are exercised by hand (see the module's own docstring and
machine-setup.md) because they mutate machine-wide state (env vars, the
installed package set) that a test suite must not touch. What IS covered is
everything `.setup-opts` changes about the DECISIONS this script makes:
whether a line is read at all, whether an unrecognised or malformed line is
refused, whether the panellift step is skipped instead of run, and how the
final VERDICT line counts a skip. `_run` is monkeypatched wherever a real
subprocess would otherwise be launched, and `S.OPTS_FILE` is monkeypatched to
a path under `tmp_path` so nothing here reads or writes the real repo root's
`.setup-opts`.
"""
import os
import subprocess
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)
S = pytest.importorskip('setup_machine')
def _step():
"""A Step that records every line to a list instead of printing it."""
out = []
step = S.Step(out.append)
step.lines = out
return step
def _write_opts(path, text):
with open(str(path), 'w', encoding='utf-8') as fh:
fh.write(text)
def _git(repo, *args):
"""Run a real `git` command against a real temp repo. Used instead of a
mocked porcelain string for the completeness-guard tests below -- Gitea
issue #1's diagnosis turned on exactly which two-column codes a real
`git status --porcelain` prints for real filesystem states (an untracked
folder vs. a tracked file actually missing from disk), and a hand-typed
porcelain string is precisely the kind of guess that could quietly
encode the same wrong assumption the bug had."""
result = subprocess.run(['git', '-C', str(repo)] + list(args),
capture_output=True, text=True, encoding='utf-8')
assert result.returncode == 0, (
'git {0} failed: {1}'.format(' '.join(args), result.stderr))
return result
def _init_repo(repo):
repo.mkdir()
_git(repo, 'init', '-q', '.')
_git(repo, 'config', 'user.email', 'scrub-test' + '@' + 'example.invalid')
_git(repo, 'config', 'user.name', 'test')
return repo
# --------------------------------------------------------------- load_opts()
def test_absent_file_returns_no_opts_and_no_failures(tmp_path, monkeypatch):
"""No `.setup-opts` at all -- today's default on every machine that has
never declared one. Must behave exactly as if the feature did not
exist: an empty dict, nothing on `step.failures`."""
monkeypatch.setattr(S, 'OPTS_FILE', str(tmp_path / '.setup-opts'))
step = _step()
opts = S.load_opts(step)
assert opts == {}
assert step.failures == []
assert step.lines == []
def test_dev_mode_declared_is_read(tmp_path, monkeypatch):
opts_file = tmp_path / '.setup-opts'
_write_opts(opts_file, 'panellift: dev-mode\n')
monkeypatch.setattr(S, 'OPTS_FILE', str(opts_file))
step = _step()
opts = S.load_opts(step)
assert opts == {'panellift': 'dev-mode'}
assert step.failures == []
def test_comments_and_blank_lines_are_ignored(tmp_path, monkeypatch):
opts_file = tmp_path / '.setup-opts'
_write_opts(opts_file,
'# a comment\n\n \npanellift: dev-mode # trailing note\n')
monkeypatch.setattr(S, 'OPTS_FILE', str(opts_file))
step = _step()
opts = S.load_opts(step)
assert opts == {'panellift': 'dev-mode'}
assert step.failures == []
def test_stale_entry_is_flagged_not_silently_dropped(tmp_path, monkeypatch):
"""A step name this script does not know -- the blindfold-left-behind
case. Must be refused loudly (a FAIL step naming it 'stale'), not merged
into an empty-looking opts dict that quietly ignores it."""
opts_file = tmp_path / '.setup-opts'
_write_opts(opts_file, 'snapshot_cycle: dev-mode\n')
monkeypatch.setattr(S, 'OPTS_FILE', str(opts_file))
step = _step()
opts = S.load_opts(step)
assert opts == {}
assert len(step.failures) == 1
assert 'stale' in ' '.join(step.lines).lower()
assert 'snapshot_cycle' in ' '.join(step.lines)
def test_unknown_mode_for_a_known_step_is_flagged(tmp_path, monkeypatch):
opts_file = tmp_path / '.setup-opts'
_write_opts(opts_file, 'panellift: some-other-mode\n')
monkeypatch.setattr(S, 'OPTS_FILE', str(opts_file))
step = _step()
opts = S.load_opts(step)
assert opts == {}
assert step.failures
assert 'some-other-mode' in ' '.join(step.lines)
def test_empty_value_is_refused_like_a_bare_empty_key(tmp_path, monkeypatch):
"""`panellift:` with nothing after the colon is ambiguous -- it cannot be
told apart from a value that was meant to be written and was not -- so it
is refused, the same way a bare empty knowledge key is, rather than
treated as an opt-out."""
opts_file = tmp_path / '.setup-opts'
_write_opts(opts_file, 'panellift:\n')
monkeypatch.setattr(S, 'OPTS_FILE', str(opts_file))
step = _step()
opts = S.load_opts(step)
assert opts == {}
assert step.failures
assert 'no value' in ' '.join(step.lines).lower()
def test_unreadable_line_is_refused(tmp_path, monkeypatch):
opts_file = tmp_path / '.setup-opts'
_write_opts(opts_file, 'this is not a key value line\n')
monkeypatch.setattr(S, 'OPTS_FILE', str(opts_file))
step = _step()
opts = S.load_opts(step)
assert opts == {}
assert step.failures
# ----------------------------------------------------------- verify_panellift
def test_verify_panellift_skips_in_dev_mode_and_names_the_file(tmp_path, monkeypatch):
monkeypatch.setattr(S, 'OPTS_FILE', str(tmp_path / '.setup-opts'))
step = _step()
ok = S.verify_panellift(step, str(tmp_path), {'panellift': 'dev-mode'})
assert ok is True
assert step.skipped == ['panellift CLI runs installed (no PYTHONPATH)']
assert step.failures == []
assert step.passed == []
joined = ' '.join(step.lines)
assert 'SKIP' in joined
assert '.setup-opts' in joined
assert 'dev mode' in joined.lower()
def test_verify_panellift_with_no_opts_runs_the_real_check_unchanged(monkeypatch, tmp_path):
"""opts={} (equivalently opts=None, i.e. no `.setup-opts` at all) must
take the ORIGINAL code path -- the installed-CLI subprocess is actually
invoked, and a failure there is reported as FAIL under the same step
name as before this feature existed, never as SKIP."""
calls = []
def fake_run(cmd, **kw):
calls.append(cmd)
class R(object):
returncode = 1
stdout = ''
stderr = "No module named panellift"
return R()
monkeypatch.setattr(S, '_run', fake_run)
step = _step()
ok = S.verify_panellift(step, str(tmp_path), {})
assert ok is False
assert step.skipped == []
assert step.failures == ['panellift CLI runs installed (no PYTHONPATH)']
assert len(calls) == 1 # the real CLI invocation was attempted
def test_verify_panellift_default_opts_argument_behaves_like_absent_file(monkeypatch, tmp_path):
"""Calling verify_panellift with no `opts` argument at all (the old
call shape, before this feature existed) must still work -- a caller
that never learned about `.setup-opts` is not broken by it."""
def fake_run(cmd, **kw):
class R(object):
returncode = 1
stdout = ''
stderr = "No module named panellift"
return R()
monkeypatch.setattr(S, '_run', fake_run)
step = _step()
ok = S.verify_panellift(step, str(tmp_path))
assert ok is False
assert step.skipped == []
assert step.failures == ['panellift CLI runs installed (no PYTHONPATH)']
# --------------------------------------------------------- install_dependencies
def test_install_dependencies_dev_mode_skips_pip_install_e_and_verifies_pythonpath(
monkeypatch, tmp_path):
"""With `panellift: dev-mode`, the full (non---check) run must not call
`pip install -e panellift`, and must instead run the PYTHONPATH route and
say which route it verified."""
calls = []
def fake_run(cmd, **kw):
calls.append((cmd, kw))
class R(object):
returncode = 0
stdout = ''
stderr = ''
return R()
# write_requirements() only writes if requirements.txt is missing; the
# real one exists in this checkout, so no filesystem write happens here.
monkeypatch.setattr(S, '_run', fake_run)
step = _step()
ok = S.install_dependencies(step, {'panellift': 'dev-mode'})
assert ok is True
assert step.failures == []
# Exactly two subprocess calls: `pip install -r requirements.txt`, then
# the PYTHONPATH=src probe. `pip install -e panellift` never happens.
assert len(calls) == 2
first_cmd, _ = calls[0]
assert 'requirements.txt' in ' '.join(first_cmd)
second_cmd, second_kw = calls[1]
assert '-e' not in second_cmd
assert 'panellift' in second_cmd
assert '--help' in second_cmd
env = second_kw.get('env') or {}
assert 'PYTHONPATH' in env
assert env['PYTHONPATH'].endswith(os.path.join('panellift', 'src'))
joined = ' '.join(step.lines)
assert 'PYTHONPATH=src' in joined or 'PYTHONPATH=' in joined
assert 'dev mode' in joined.lower()
def test_install_dependencies_no_opts_still_installs_panellift_editable(
monkeypatch, tmp_path):
"""opts={} must reach the original `pip install -e panellift` call --
unchanged behaviour when nothing has been declared."""
calls = []
def fake_run(cmd, **kw):
calls.append(cmd)
class R(object):
returncode = 0
stdout = ''
stderr = ''
return R()
monkeypatch.setattr(S, '_run', fake_run)
step = _step()
ok = S.install_dependencies(step, {})
assert ok is True
assert len(calls) == 2
assert '-e' in calls[1]
# ------------------------------------------------------------------ _verdict
def test_verdict_pass_with_no_skips_has_old_unadorned_shape():
step = _step()
step.passed = ['a', 'b', 'c']
code, lines = S._verdict(step)
assert code == 0
assert len(lines) == 1
assert lines[0].startswith('VERDICT: PASS -- 3 passed --')
assert 'skipped' not in lines[0]
def test_verdict_pass_with_skips_counts_them_in_the_summary():
step = _step()
step.passed = ['a', 'b', 'c', 'd', 'e']
step.skipped = ['panellift CLI runs installed (no PYTHONPATH)']
code, lines = S._verdict(step)
assert code == 0
assert lines[0] == ('VERDICT: PASS -- 5 passed, 1 skipped (see SKIP '
'lines above for why each one) -- everything above '
'is a real, checked answer, not just an exit code.')
def test_verdict_fail_still_fails_even_with_a_skip_present():
"""A skip is never allowed to launder a real failure into a pass -- the
whole point of SKIP as a third state is that it is honest about doing
nothing, not that it forgives something else that broke."""
step = _step()
step.passed = ['a']
step.skipped = ['panellift CLI runs installed (no PYTHONPATH)']
step.failures = ['knowledgelint.py']
code, lines = S._verdict(step)
assert code == 1
assert lines[0].startswith('VERDICT: FAIL')
assert 'knowledgelint.py' in lines[0]
# the passed/skipped counts are still visible even though it failed
assert any('1 skipped' in line for line in lines)
def test_verdict_fail_with_no_skips_has_old_shape():
step = _step()
step.passed = ['a']
step.failures = ['knowledgelint.py']
code, lines = S._verdict(step)
assert code == 1
assert not any('skipped' in line for line in lines)
# ------------------------------------------- _parse_porcelain_status (Gitea #1)
#
# Gitea issue #1: `resolve_private_repo`'s completeness guard failed on ANY
# non-empty `git status --porcelain` output, but the truncation it exists to
# catch (the long-path `sessions/` failure, machine-setup.md sec.2) only ever
# shows up as a TRACKED file missing from the worktree -- porcelain worktree
# status `D`. An untracked addition (`??`) or a locally modified tracked file
# (`M`) cannot mean that, and used to false-FAIL anyway, with a
# `restore --source=HEAD` hint that is a no-op for untracked paths.
def test_parse_porcelain_classifies_untracked_as_untracked_not_missing():
missing, untracked, other = S._parse_porcelain_status('?? sample_job/\n')
assert missing == []
assert untracked == ['sample_job/']
assert other == []
def test_parse_porcelain_treats_worktree_D_as_missing_tracked():
"""The truncation signature itself: index still names the file (X is
one of ' ', M, A, R, C -- whatever staged state), but Y='D' means it is
not actually on disk."""
for code in (' D', 'MD', 'AD', 'RD', 'CD'):
missing, untracked, other = S._parse_porcelain_status(
'{0} plant/foo.yaml\n'.format(code))
assert missing == ['plant/foo.yaml'], 'code {0!r} should be missing-tracked'.format(code)
assert untracked == []
assert other == []
def test_parse_porcelain_staged_deletion_is_not_missing_tracked():
"""'D ' (X='D', Y=' ') is a DELIBERATE staged deletion (`git rm`, or
`git add` after deleting by hand) -- index and worktree already agree
the file is gone on purpose. This must not be confused with 'D' in the
worktree column, which means the opposite: an accident."""
missing, untracked, other = S._parse_porcelain_status('D plant/foo.yaml\n')
assert missing == []
assert other == ['plant/foo.yaml']
def test_parse_porcelain_ordinary_modifications_are_other_not_missing():
for code in ('M ', ' M', 'MM', 'A ', 'R ', 'C '):
missing, untracked, other = S._parse_porcelain_status(
'{0} plant/foo.yaml\n'.format(code))
assert missing == [], 'code {0!r} must not be treated as truncation'.format(code)
def test_parse_porcelain_rename_reports_destination_path():
missing, untracked, other = S._parse_porcelain_status(
'R old/name.yaml -> new/name.yaml\n')
assert other == ['new/name.yaml']
missing, untracked, other = S._parse_porcelain_status(
'RD old/name.yaml -> new/name.yaml\n')
assert missing == ['new/name.yaml']
def test_parse_porcelain_conflict_codes_are_not_guessed_at_as_missing():
"""Merge-conflict markers can't arise on the plain clone/status path
this guard runs on (no merge in progress); they are filed as 'other'
rather than treated as truncation evidence."""
for code in ('UU', 'AA', 'DD', 'AU', 'UD', 'UA', 'DU'):
missing, untracked, other = S._parse_porcelain_status(
'{0} plant/foo.yaml\n'.format(code))
assert missing == [], 'conflict code {0!r} must not fail the guard'.format(code)
def test_parse_porcelain_empty_output_is_all_empty():
missing, untracked, other = S._parse_porcelain_status('')
assert (missing, untracked, other) == ([], [], [])
# --------------------------------------------- resolve_private_repo (Gitea #1)
#
# These use real temp git repos (not mocked porcelain strings) so the
# completeness guard is exercised against what `git status --porcelain`
# actually prints for real filesystem states, per the issue's own
# instruction not to trust a hand-typed porcelain string here.
def test_untracked_job_folder_checkout_passes(tmp_path):
"""The issue's exact false-FAIL scenario: a complete checkout with one
untracked job folder (named `sample_job`, never a real client name --
see CLAUDE.md's scrub rule) must PASS, not FAIL."""
repo = _init_repo(tmp_path / 'private')
(repo / 'plant').mkdir()
(repo / 'plant' / 'knowledge.yaml').write_text('a: 1\n', encoding='utf-8')
_git(repo, 'add', '.')
_git(repo, 'commit', '-q', '-m', 'init')
(repo / 'sample_job').mkdir()
(repo / 'sample_job' / 'notes.txt').write_text('scratch work\n', encoding='utf-8')
step = _step()
result = S.resolve_private_repo(step, str(repo))
assert result == str(repo)
assert step.failures == []
joined = ' '.join(step.lines)
assert '1 untracked' in joined
assert "installer's business" in joined
def test_missing_tracked_file_checkout_fails_with_restore_hint(tmp_path):
"""The real truncation signature: a committed file that never made it
onto disk (simulating the long-path checkout failure). Must FAIL, and
the fix hint's `restore --source=HEAD` genuinely applies here."""
repo = _init_repo(tmp_path / 'private')
(repo / 'plant').mkdir()
(repo / 'plant' / 'knowledge.yaml').write_text('a: 1\n', encoding='utf-8')
_git(repo, 'add', '.')
_git(repo, 'commit', '-q', '-m', 'init')
os.remove(str(repo / 'plant' / 'knowledge.yaml'))
step = _step()
result = S.resolve_private_repo(step, str(repo))
assert result is None
assert step.failures == ['private repo checkout is complete']
joined = ' '.join(step.lines)
assert 'tracked path(s) missing from the working tree' in joined
assert 'restore --source=HEAD :/' in joined
def test_modified_only_checkout_passes_with_informational_note(tmp_path):
"""A locally modified tracked file is ordinary uncommitted work -- it
cannot indicate a truncated checkout (the file IS present, just
edited), and must PASS, with the modification only noted, not acted on."""
repo = _init_repo(tmp_path / 'private')
(repo / 'plant').mkdir()
(repo / 'plant' / 'knowledge.yaml').write_text('a: 1\n', encoding='utf-8')
_git(repo, 'add', '.')
_git(repo, 'commit', '-q', '-m', 'init')
(repo / 'plant' / 'knowledge.yaml').write_text('a: 2\n', encoding='utf-8')
step = _step()
result = S.resolve_private_repo(step, str(repo))
assert result == str(repo)
assert step.failures == []
joined = ' '.join(step.lines)
assert '1 modified' in joined
assert "installer's business" in joined
assert 'restore --source=HEAD' not in joined
@pytest.mark.skipif(sys.platform != 'win32',
reason='wire_links shells out to real PowerShell to '
'create NTFS junctions, which do not exist off '
'Windows')
def test_cascade_untracked_job_folder_passes_wiring_end_to_end(tmp_path, monkeypatch):
"""The issue's reported cascade: a false FAIL on the completeness guard
used to skip wiring entirely (plant layer stays empty, knowledgelint
reports 1 layer). With the guard fixed, resolving a checkout that has
only an untracked job folder must let `wire_links` actually run and
succeed -- proving the fix, not just the guard's own return value."""
private = _init_repo(tmp_path / 'KNOWLEDGE')
for name in ('plant', 'job', 'library', 'qna'):
d = private / name
d.mkdir()
(d / 'README.md').write_text('placeholder for ' + name, encoding='utf-8')
_git(private, 'add', '.')
_git(private, 'commit', '-q', '-m', 'init')
(private / 'sample_job').mkdir()
(private / 'sample_job' / 'notes.txt').write_text('scratch\n', encoding='utf-8')
step = _step()
resolved = S.resolve_private_repo(step, str(private))
assert resolved == str(private)
assert step.failures == []
fake_root = tmp_path / 'checkout'
(fake_root / 'knowledge').mkdir(parents=True)
monkeypatch.setattr(S, 'ROOT', str(fake_root))
wire_step = _step()
S.wire_links(wire_step, resolved)
assert wire_step.failures == []
plant_local = fake_root / 'knowledge' / 'plant'
assert plant_local.is_dir()
assert (plant_local / 'README.md').read_text(encoding='utf-8') == 'placeholder for plant'
job_local = fake_root / 'knowledge' / 'job'
assert (job_local / 'README.md').read_text(encoding='utf-8') == 'placeholder for job'