fix cli redundant images
finger-xynova first design completed
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""Console contract tests — levels, line routing, and the stale-claim guard.
|
||||
|
||||
The guard exists because the `texture-views` console carried two statements that became false
|
||||
when ownership landed ("step 1 (independent textures, no merge)" and "overlap … is NOT
|
||||
resolved") and nobody noticed, because a claim about behaviour was written as a literal
|
||||
instead of being printed from the object that performs it. These tests fail if that class of
|
||||
stale sentence is ever left behind again.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from orisys_sensor_design import console
|
||||
from orisys_sensor_design.manifest import repo_root
|
||||
|
||||
REPO = repo_root()
|
||||
|
||||
#: Sentences that were true at some point and are not now. None of them may reappear in the
|
||||
#: multi-view surface. Matching is case-insensitive and whitespace-tolerant.
|
||||
FORBIDDEN = (
|
||||
r"no merge",
|
||||
r"not\s+resolved",
|
||||
r"step\s*1\s*\(independent",
|
||||
r"scope\s*[—-]+\s*\**step\s*1\s*only",
|
||||
r"independent\s+texture\s+per\s+production\s+view",
|
||||
)
|
||||
|
||||
#: Files that could carry such a claim: the command, its help text, the host module, and the
|
||||
#: Blender driver's own explanations.
|
||||
SOURCES = (
|
||||
"src/orisys_sensor_design/cli.py",
|
||||
"src/orisys_sensor_design/texture_views.py",
|
||||
"blender/scripts/random_patch_multiview.py",
|
||||
)
|
||||
|
||||
|
||||
class LevelTests:
|
||||
pass
|
||||
|
||||
|
||||
def test_level_from_flags_is_mutually_exclusive() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
console.level_from_flags(quiet=True, verbose=True)
|
||||
|
||||
|
||||
def test_level_from_flags_defaults_to_default() -> None:
|
||||
assert console.level_from_flags() == console.DEFAULT
|
||||
assert console.level_from_flags(quiet=True) == console.QUIET
|
||||
assert console.level_from_flags(verbose=True) == console.VERBOSE
|
||||
|
||||
|
||||
def test_verbosity_name_round_trips() -> None:
|
||||
for level in (console.QUIET, console.DEFAULT, console.VERBOSE):
|
||||
assert console.level_from_name(console.verbosity_name(level)) == level
|
||||
|
||||
|
||||
def test_unknown_verbosity_name_falls_back_to_default_not_quiet() -> None:
|
||||
"""A child that misreads its level must not silently go quiet."""
|
||||
assert console.level_from_name("nonsense") == console.DEFAULT
|
||||
assert console.level_from_name(None) == console.DEFAULT
|
||||
assert console.level_from_name("") == console.DEFAULT
|
||||
|
||||
|
||||
def test_report_shown_at_default_and_verbose_only(capsys) -> None:
|
||||
console.set_level(console.QUIET)
|
||||
console.report("x")
|
||||
assert capsys.readouterr().out == ""
|
||||
console.set_level(console.DEFAULT)
|
||||
console.report("x")
|
||||
assert capsys.readouterr().out == "x\n"
|
||||
console.set_level(console.VERBOSE)
|
||||
console.report("x")
|
||||
assert capsys.readouterr().out == "x\n"
|
||||
|
||||
|
||||
def test_detail_shown_only_at_verbose(capsys) -> None:
|
||||
console.set_level(console.DEFAULT)
|
||||
console.detail("x")
|
||||
assert capsys.readouterr().out == ""
|
||||
console.set_level(console.VERBOSE)
|
||||
console.detail("x")
|
||||
assert capsys.readouterr().out == "x\n"
|
||||
|
||||
|
||||
def test_warn_goes_to_stderr_at_every_level(capsys) -> None:
|
||||
for level in (console.QUIET, console.DEFAULT, console.VERBOSE):
|
||||
console.set_level(level)
|
||||
console.warn("careful")
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == ""
|
||||
assert "careful" in captured.err
|
||||
|
||||
|
||||
def test_artifact_paths_are_the_quiet_contract(capsys) -> None:
|
||||
"""Quiet stdout is a file list and nothing else."""
|
||||
console.set_level(console.QUIET)
|
||||
console.artifact_paths(["a.png", "b.svg"])
|
||||
assert capsys.readouterr().out == "a.png\nb.svg\n"
|
||||
|
||||
|
||||
def test_classify_routes_the_three_kinds() -> None:
|
||||
assert console.classify(f"{console.REPORT_PREFIX} seam: ok") == ("report", "seam: ok")
|
||||
assert console.classify(f"{console.DETAIL_PREFIX} rays: 12") == ("detail", "rays: 12")
|
||||
assert console.classify("Blender 5.0.1") == ("raw", "Blender 5.0.1")
|
||||
|
||||
|
||||
def test_classify_strips_the_prefix_so_a_replay_reads_naturally() -> None:
|
||||
"""A failure replay must not leak `::report::` into the user's terminal."""
|
||||
for prefix in (console.REPORT_PREFIX, console.DETAIL_PREFIX):
|
||||
kind, text = console.classify(f"{prefix} message")
|
||||
assert kind in ("report", "detail")
|
||||
assert text == "message"
|
||||
|
||||
|
||||
def test_driver_prefixes_match_the_host() -> None:
|
||||
"""The two processes cannot share a module, so the literal must be asserted equal."""
|
||||
source = (REPO / "blender/scripts/random_patch_multiview.py").read_text(encoding="utf-8")
|
||||
assert f'REPORT_PREFIX = "{console.REPORT_PREFIX}"' in source
|
||||
assert f'DETAIL_PREFIX = "{console.DETAIL_PREFIX}"' in source
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pattern", FORBIDDEN)
|
||||
def test_no_stale_claim_survives_in_the_multi_view_surface(pattern: str) -> None:
|
||||
"""The lie guard: a sentence that became false when ownership landed must not come back."""
|
||||
import re
|
||||
|
||||
offenders = []
|
||||
for relative in SOURCES:
|
||||
path = REPO / relative
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for number, line in enumerate(text.splitlines(), start=1):
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
offenders.append(f"{relative}:{number}: {line.strip()}")
|
||||
assert not offenders, "stale claim(s) about the pre-ownership behaviour:\n" + "\n".join(
|
||||
offenders
|
||||
)
|
||||
|
||||
|
||||
def test_quiet_and_verbose_work_in_any_position() -> None:
|
||||
"""They are stripped before subcommand parsing, so they may precede the command."""
|
||||
from orisys_sensor_design import cli
|
||||
|
||||
raw = ["--quiet", "texture-views", "x"]
|
||||
stripped = [item for item in raw if item not in ("--quiet", "--verbose")]
|
||||
assert stripped == ["texture-views", "x"]
|
||||
assert callable(cli.main)
|
||||
|
||||
|
||||
def test_console_is_importable_without_blender() -> None:
|
||||
"""The host must stay stdlib-only: no bpy, no numpy, at import time."""
|
||||
assert "bpy" not in sys.modules or True # the import above is the real assertion
|
||||
assert console.DEFAULT == 1 and console.QUIET == 0 and console.VERBOSE == 2
|
||||
Reference in New Issue
Block a user