mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:07:04 +01:00
vscode_optimizer: move under python_pkg and cover 0% -> 100%
meta/scripts/optimize_vscode.py was 498 lines against a 250-line cap and completely untested, and at that path the repo-wide gate (--cov=python_pkg, fail_under = 100) could not see it. Moved verbatim to python_pkg/vscode_optimizer/_optimize.py - git records a pure rename, and the content is byte-identical to HEAD. The move and the tests are one commit by necessity: any intermediate state with the module under python_pkg/ but uncovered turns every pytest run in the repo red, including ci-mirror on pre-push for unrelated work. Running it by path no longer works after the move (sys.path[0] would be the module's own directory), so __main__.py replaces it, following the existing focus_policy convention rather than a sys.path insert. Nothing invoked the old path: grep over .sh/.desktop/.service/.yml/.py found zero callers. 98 tests reach 100% line and branch coverage. Patching is confined to real boundaries - subprocess.run, the module-level Path, builtins.input, sys.argv - so the forthcoming split has few monkeypatch targets to re-point. Writing the tests surfaced a pre-existing bug, left unfixed here and pinned by a strict xfail: _VENDOR_KW's "ati" key is a substring of "VGA compatible controller", which appears in every lspci display line, so it matches before "intel" is ever tried and Intel iGPUs are misreported as AMD. Verified: python3 -m python_pkg.vscode_optimizer --dry-run reproduces the pre-move 18-change plan and writes nothing; the real settings.json is untouched; repo-wide pytest is 1846 passed at 100%; over-cap files 37 -> 36. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
57b9005a6b
commit
9d12c03ccc
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"title": "Move optimize_vscode.py under python_pkg/ and cover it to 100%",
|
||||||
|
"objective": "meta/scripts/optimize_vscode.py was 498 lines against a 250-line cap and completely untested, and at that path the repo-wide gate (--cov=python_pkg, fail_under = 100) could not see it. This commit moves it verbatim to python_pkg/vscode_optimizer/_optimize.py and lands a full test suite, so the module arrives already measured by the standing gate. The move and the tests are one commit by necessity: any intermediate state with the module under python_pkg/ but uncovered turns every pytest run in the repo red, including ci-mirror on pre-push for unrelated work. No production logic changes here; the split follows in later commits.",
|
||||||
|
"acceptance_criteria": [
|
||||||
|
"_optimize.py reaches 100% line and branch coverage, from 0%",
|
||||||
|
"_optimize.py is byte-identical to the file at HEAD - git records it as a pure rename",
|
||||||
|
"python3 -m python_pkg.vscode_optimizer --dry-run reproduces the pre-move plan (18 changes) and writes nothing",
|
||||||
|
"No suppression of any kind: no noqa, no type: ignore, no lowered threshold",
|
||||||
|
"Every new test file is under the 250-line cap, so the over-cap total falls to 36 rather than rising"
|
||||||
|
],
|
||||||
|
"out_of_scope": [
|
||||||
|
"The 3+ way split of _optimize.py itself, which is the next commit",
|
||||||
|
"Fixing the _VENDOR_KW 'ati' substring bug found while writing these tests - a behaviour change that does not belong in a move commit; it is pinned by a strict xfail and reported to the user",
|
||||||
|
"The three transcribe_* files and the 29 over-cap shell scripts"
|
||||||
|
],
|
||||||
|
"verifier": "pytest python_pkg/vscode_optimizer/ --cov=python_pkg/vscode_optimizer --cov-branch --cov-report=term-missing for the coverage claim; git diff --cached -M --stat to confirm the rename is content-identical; python3 -m python_pkg.vscode_optimizer --dry-run for the behaviour claim; pre-commit run --files for lint; bash ~/utils/scripts/check_file_length.sh --all for the cap."
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"intent": "Move the untested 498-line optimize_vscode.py under python_pkg/ so the repo-wide 100% coverage gate measures it, and land a full test suite in the same commit, as the groundwork for splitting it under the 250-line cap.",
|
||||||
|
"scope": [
|
||||||
|
"meta/scripts/optimize_vscode.py -> python_pkg/vscode_optimizer/_optimize.py (pure rename)",
|
||||||
|
"New: python_pkg/vscode_optimizer/{__init__,__main__}.py and seven test modules",
|
||||||
|
"Non-goal: the split itself, which follows in its own commits",
|
||||||
|
"Non-goal: fixing the _VENDOR_KW bug these tests uncovered - pinned by a strict xfail, reported to the user"
|
||||||
|
],
|
||||||
|
"changes": [
|
||||||
|
"git mv of the module with zero content change, verified by diffing against HEAD.",
|
||||||
|
"Added __main__.py so the script is run as `python3 -m python_pkg.vscode_optimizer`. Running it by path no longer works after the move, because sys.path[0] would be the module's own directory rather than the repo root; this follows the existing focus_policy convention rather than a sys.path insert.",
|
||||||
|
"Added 98 tests across five test modules plus a conftest for the two shared subprocess doubles, reaching 100% line and branch coverage.",
|
||||||
|
"Patched only at real boundaries - subprocess.run, the module-level Path name, builtins.input and sys.argv - so the forthcoming split has as few monkeypatch targets to re-point as possible. Settings and flags I/O uses real files under tmp_path.",
|
||||||
|
"Split the hardware tests into test_hw.py (runner, CPU, RAM) and test_gpu_disk.py (GPU, disk, aggregate) because the single module landed at 362 lines, over the cap it exists to help clear."
|
||||||
|
],
|
||||||
|
"verification": [
|
||||||
|
{
|
||||||
|
"command": "git show HEAD:meta/scripts/optimize_vscode.py | diff - python_pkg/vscode_optimizer/_optimize.py",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "No output: the moved file is byte-identical to HEAD. git diff --cached -M records it as a rename."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "pytest python_pkg/vscode_optimizer/ --cov=python_pkg/vscode_optimizer --cov-branch --cov-report=term-missing",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "98 passed, 1 xfailed. _optimize.py: 295 statements, 0 missing, 120 branches, 0 partial, 100%. __main__.py and __init__.py both 100%. Coverage was driven from 0%; the last three gaps closed were __main__'s import, _detect_gpu's no-display-device loop exit, and _apply_variant's flags-only path."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "python3 -m python_pkg.vscode_optimizer --dry-run",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "Detects Ryzen 9 7900X3D / 30 GB / RTX 3090 / NVME and proposes the same 18 changes as the pre-move run of the old path, ending '(dry-run) No files modified.' Confirms the import graph resolves through the new entry point - the unit tests stub subprocess, so only a real run proves this."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "ls -l ~/.config/VSCodium/User/settings.json",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "No such file or directory, both before and after the dry run - the real user settings were never created or modified. A dedicated test also asserts that with Path.home redirected, every discovered variant path stays inside tmp_path."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "pytest python_pkg/vscode_optimizer/ (test count across the test-file split)",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "98 passed both before and after splitting test_hw.py, which is the discriminating check for a test-file split: a file outside the runner's glob would be silently uncollected."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "bash ~/utils/scripts/check_file_length.sh --all",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "36 over-cap files, down from 37 at the moment test_hw.py stood at 362 lines. Every new test file is under 250: 235/133/227/239/210/111. _optimize.py is still 498 and is the next commit's target."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "pre-commit run --files <11 staged files>",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "All hooks pass, including ruff, ruff format, mypy, pylint, bandit, name-tests-test, check-python-location and the pytest coverage gate."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"risks": [
|
||||||
|
"The module is no longer runnable as a path script. Mitigated: grep over .sh/.desktop/.service/.yml/.py found zero callers, so the only invocation was by hand, and __main__.py replaces it.",
|
||||||
|
"Running `python3 -m python_pkg.vscode_optimizer` from outside the repo root fails with ModuleNotFoundError. This is true of every python_pkg module here (focus_policy included) and is the established convention, not a regression from this move.",
|
||||||
|
"The strict xfail pinning the _VENDOR_KW bug will fail the suite if someone fixes the bug without removing the marker. That is intended: it forces the fix and its test to land together."
|
||||||
|
],
|
||||||
|
"rollback": [
|
||||||
|
"git revert the commit; the module returns to meta/scripts/optimize_vscode.py and the tests disappear with it.",
|
||||||
|
"After rollback, confirm `python3 meta/scripts/optimize_vscode.py --dry-run` prints the 18-change plan again, and that the repo-wide `pytest` still passes with python_pkg back at its previous coverage."
|
||||||
|
]
|
||||||
|
}
|
||||||
3
python_pkg/vscode_optimizer/__init__.py
Normal file
3
python_pkg/vscode_optimizer/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""Auto-optimize VS Code settings based on detected hardware."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
15
python_pkg/vscode_optimizer/__main__.py
Normal file
15
python_pkg/vscode_optimizer/__main__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""Run the VS Code optimizer as ``python3 -m python_pkg.vscode_optimizer``.
|
||||||
|
|
||||||
|
The optimizer used to live at ``meta/scripts/optimize_vscode.py`` and was run
|
||||||
|
by path. It moved under ``python_pkg/`` so the repo-wide coverage gate
|
||||||
|
(``--cov=python_pkg``, ``fail_under = 100``) measures it; running it by path
|
||||||
|
no longer works, because ``sys.path[0]`` would be the script's own directory
|
||||||
|
rather than the repo root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer._optimize import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
3
python_pkg/vscode_optimizer/tests/__init__.py
Normal file
3
python_pkg/vscode_optimizer/tests/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""Tests for the VS Code settings optimizer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
33
python_pkg/vscode_optimizer/tests/conftest.py
Normal file
33
python_pkg/vscode_optimizer/tests/conftest.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""Shared doubles for the hardware-probe tests.
|
||||||
|
|
||||||
|
``name-tests-test`` requires every module under a tests directory to be named
|
||||||
|
``test_*.py``, so helpers shared between test modules live here rather than in
|
||||||
|
a helper module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
|
class FakeProc:
|
||||||
|
"""Stand-in for the CompletedProcess ``_run`` inspects."""
|
||||||
|
|
||||||
|
def __init__(self, stdout: str) -> None:
|
||||||
|
self.stdout = stdout
|
||||||
|
|
||||||
|
|
||||||
|
def fake_run(mapping: dict[str, str]) -> Callable[..., FakeProc]:
|
||||||
|
"""Return a ``subprocess.run`` double keyed on the executable name.
|
||||||
|
|
||||||
|
Commands absent from *mapping* return empty stdout, which is how the
|
||||||
|
probes see a tool that is installed but reports nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _run(args: list[str], **_kw: object) -> FakeProc:
|
||||||
|
return FakeProc(mapping.get(args[0], ""))
|
||||||
|
|
||||||
|
return _run
|
||||||
235
python_pkg/vscode_optimizer/tests/test_gpu_disk.py
Normal file
235
python_pkg/vscode_optimizer/tests/test_gpu_disk.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
"""Tests for GPU and root-disk detection, and the aggregate probe.
|
||||||
|
|
||||||
|
Both probes shell out, so ``subprocess.run`` is patched at the boundary; the
|
||||||
|
sysfs reads in ``_detect_disk`` are redirected by patching the module-level
|
||||||
|
``Path`` name. Nothing here touches the machine's real hardware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
from python_pkg.vscode_optimizer.tests.conftest import fake_run as _fake_run
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _detect_gpu
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
_NVIDIA_LINE = (
|
||||||
|
"01:00.0 VGA compatible controller: NVIDIA Corporation GA102 [GeForce RTX 3090]"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_gpu_reads_nvidia_vram(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""An NVIDIA card triggers a second probe for its VRAM."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
subprocess,
|
||||||
|
"run",
|
||||||
|
_fake_run({"lspci": _NVIDIA_LINE, "nvidia-smi": "24576\n"}),
|
||||||
|
)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
|
assert hw.gpu_vendor == "NVIDIA"
|
||||||
|
assert hw.gpu_model == "NVIDIA Corporation GA102 [GeForce RTX 3090]"
|
||||||
|
assert hw.gpu_vram_mb == 24576
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_gpu_tolerates_nvidia_smi_being_absent(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Without nvidia-smi the vendor is still known; VRAM stays unset."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": _NVIDIA_LINE}))
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
|
assert hw.gpu_vendor == "NVIDIA"
|
||||||
|
assert hw.gpu_vram_mb == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("line", "vendor"),
|
||||||
|
[
|
||||||
|
("03:00.0 VGA compatible controller: AMD Radeon RX 7900", "AMD"),
|
||||||
|
("03:00.0 VGA compatible controller: ATI Technologies Inc", "AMD"),
|
||||||
|
# Characterisation, not endorsement: see the xfail below. Every real
|
||||||
|
# lspci display line contains "VGA compATIble", so "ati" matches first
|
||||||
|
# and both of these are reported as AMD.
|
||||||
|
("00:02.0 VGA compatible controller: Intel UHD Graphics 630", "AMD"),
|
||||||
|
("04:00.0 VGA compatible controller: Some Unlisted Vendor", "AMD"),
|
||||||
|
# A 3D controller line has no "compatible" in it, so it escapes the trap.
|
||||||
|
("04:00.0 3D controller: Some Unlisted Vendor", "Unknown"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_detect_gpu_maps_each_vendor_keyword(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, line: str, vendor: str
|
||||||
|
) -> None:
|
||||||
|
"""Vendor keywords map to display names; 3D controllers count as GPUs."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": line}))
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
|
assert hw.gpu_vendor == vendor
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
reason=(
|
||||||
|
"Pre-existing bug carried verbatim through the move: _VENDOR_KW's 'ati' "
|
||||||
|
"key is a substring of 'VGA compatible controller', which appears in "
|
||||||
|
"every lspci display line, so it matches before 'intel' is ever tried. "
|
||||||
|
"Intel iGPUs are misreported as AMD, which wrongly enables the "
|
||||||
|
"GPU-accelerated terminal and AMD-only Electron flags. Fixing it is a "
|
||||||
|
"behaviour change and does not belong in a move commit."
|
||||||
|
),
|
||||||
|
strict=True,
|
||||||
|
)
|
||||||
|
def test_detect_gpu_should_recognise_an_intel_igpu(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""An Intel iGPU ought to be detected as Intel."""
|
||||||
|
line = "00:02.0 VGA compatible controller: Intel UHD Graphics 630"
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": line}))
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
|
assert hw.gpu_vendor == "Intel"
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_gpu_leaves_defaults_when_lspci_lists_no_display_device(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""A listing with no VGA/3D line exhausts the loop without a match."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
subprocess, "run", _fake_run({"lspci": "00:1f.3 Audio device: Intel\n"})
|
||||||
|
)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
|
assert hw.gpu_vendor == "Unknown"
|
||||||
|
assert hw.gpu_model == "Unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_gpu_skips_non_display_devices(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Only VGA/3D lines are considered; the first match wins."""
|
||||||
|
listing = "00:1f.3 Audio device: Intel\n" + _NVIDIA_LINE + "\n05:00.0 VGA: AMD\n"
|
||||||
|
monkeypatch.setattr(
|
||||||
|
subprocess, "run", _fake_run({"lspci": listing, "nvidia-smi": "24576"})
|
||||||
|
)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
|
assert hw.gpu_vendor == "NVIDIA"
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _detect_disk
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def _disk_paths(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, base: str, rotational: str | None
|
||||||
|
) -> None:
|
||||||
|
"""Redirect /sys/block lookups into tmp_path, keeping /dev names real."""
|
||||||
|
if rotational is not None:
|
||||||
|
queue = tmp_path / "sys" / "block" / base / "queue"
|
||||||
|
queue.mkdir(parents=True)
|
||||||
|
(queue / "rotational").write_text(rotational)
|
||||||
|
real_path = opt.Path
|
||||||
|
monkeypatch.setattr(
|
||||||
|
opt,
|
||||||
|
"Path",
|
||||||
|
lambda p: (
|
||||||
|
tmp_path / str(p).lstrip("/")
|
||||||
|
if str(p).startswith("/sys/")
|
||||||
|
else real_path(p)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device", "base", "rotational", "expected"),
|
||||||
|
[
|
||||||
|
("/dev/nvme0n1p2", "nvme0n1", "0", "nvme"),
|
||||||
|
("/dev/sda1", "sda", "0", "ssd"),
|
||||||
|
("/dev/sdb2", "sdb", "1", "hdd"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_detect_disk_classifies_the_root_device(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
device: str,
|
||||||
|
base: str,
|
||||||
|
rotational: str,
|
||||||
|
expected: str,
|
||||||
|
) -> None:
|
||||||
|
"""Partition suffixes are stripped, then rotational/nvme decide the type."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({"findmnt": device}))
|
||||||
|
_disk_paths(monkeypatch, tmp_path, base, rotational)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_disk(hw)
|
||||||
|
|
||||||
|
assert hw.disk_type == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_disk_gives_up_when_findmnt_says_nothing(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""No root device means the type stays unknown."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({}))
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_disk(hw)
|
||||||
|
|
||||||
|
assert hw.disk_type == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_disk_gives_up_without_a_rotational_flag(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""A device with no sysfs queue entry (e.g. dm-crypt) stays unknown."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({"findmnt": "/dev/mapper/root"}))
|
||||||
|
_disk_paths(monkeypatch, tmp_path, "mapper/root", None)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_disk(hw)
|
||||||
|
|
||||||
|
assert hw.disk_type == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _detect_hardware
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_detect_hardware_runs_every_probe(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""The aggregate calls all four detectors against one _Hw."""
|
||||||
|
called: list[str] = []
|
||||||
|
|
||||||
|
def _recorder(name: str) -> Callable[[opt._Hw], None]:
|
||||||
|
"""Build a probe double that records the name it stands in for."""
|
||||||
|
|
||||||
|
def _probe(_hw: opt._Hw) -> None:
|
||||||
|
called.append(name)
|
||||||
|
|
||||||
|
return _probe
|
||||||
|
|
||||||
|
for probe_name in ("_detect_cpu", "_detect_ram", "_detect_gpu", "_detect_disk"):
|
||||||
|
monkeypatch.setattr(opt, probe_name, _recorder(probe_name))
|
||||||
|
|
||||||
|
result = opt._detect_hardware()
|
||||||
|
|
||||||
|
assert isinstance(result, opt._Hw)
|
||||||
|
assert called == ["_detect_cpu", "_detect_ram", "_detect_gpu", "_detect_disk"]
|
||||||
132
python_pkg/vscode_optimizer/tests/test_hw.py
Normal file
132
python_pkg/vscode_optimizer/tests/test_hw.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
"""Tests for the command runner and for CPU and RAM detection.
|
||||||
|
|
||||||
|
``_run`` shells out, so ``subprocess.run`` is patched at the boundary, and
|
||||||
|
``_detect_ram``'s hardcoded ``/proc/meminfo`` is redirected by patching the
|
||||||
|
module-level ``Path`` name. Nothing here reads the machine's real hardware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
from python_pkg.vscode_optimizer.tests.conftest import FakeProc as _Proc
|
||||||
|
from python_pkg.vscode_optimizer.tests.conftest import fake_run as _fake_run
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_LSCPU_OUT = """\
|
||||||
|
Architecture: x86_64
|
||||||
|
Model name: AMD Ryzen 9 7900X3D 12-Core Processor
|
||||||
|
CPU(s): 24
|
||||||
|
Core(s) per socket: 12
|
||||||
|
CPU max MHz: 5662.5000
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _run
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_run_returns_stripped_stdout(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""A successful command hands back its stdout without surrounding space."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", lambda *_a, **_k: _Proc(" out \n"))
|
||||||
|
|
||||||
|
assert opt._run(["anything"]) == "out"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error",
|
||||||
|
[subprocess.TimeoutExpired(cmd="x", timeout=10), FileNotFoundError()],
|
||||||
|
)
|
||||||
|
def test_run_returns_empty_when_the_command_fails(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
error: Exception,
|
||||||
|
) -> None:
|
||||||
|
"""A missing binary or a timeout degrades to an empty string, not a crash."""
|
||||||
|
|
||||||
|
def _boom(*_a: object, **_k: object) -> None:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
monkeypatch.setattr(subprocess, "run", _boom)
|
||||||
|
|
||||||
|
assert opt._run(["anything"]) == ""
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _detect_cpu
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_detect_cpu_reads_model_cores_and_clock(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Each recognised lscpu key lands on its typed _Hw attribute."""
|
||||||
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lscpu": _LSCPU_OUT}))
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_cpu(hw)
|
||||||
|
|
||||||
|
assert hw.cpu_model == "AMD Ryzen 9 7900X3D 12-Core Processor"
|
||||||
|
assert hw.cpu_logical_cores == 24
|
||||||
|
assert hw.cpu_physical_cores == 12
|
||||||
|
assert hw.cpu_max_mhz == pytest.approx(5662.5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_cpu_ignores_unknown_keys(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""Lines outside the _LSCPU map leave the defaults alone."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
subprocess,
|
||||||
|
"run",
|
||||||
|
_fake_run({"lscpu": "Architecture: x86_64\nBogoMIPS: 9000\n"}),
|
||||||
|
)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_cpu(hw)
|
||||||
|
|
||||||
|
assert hw.cpu_model == "Unknown"
|
||||||
|
assert hw.cpu_physical_cores == 1
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _detect_ram
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_detect_ram_converts_kb_to_mb(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""MemTotal in kB becomes whole MB."""
|
||||||
|
meminfo = tmp_path / "meminfo"
|
||||||
|
meminfo.write_text("MemTotal: 32768000 kB\nMemFree: 100 kB\n")
|
||||||
|
monkeypatch.setattr(opt, "Path", lambda _p: meminfo)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_ram(hw)
|
||||||
|
|
||||||
|
assert hw.ram_total_mb == 32000
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_ram_leaves_zero_when_memtotal_is_absent(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""A procfs file without MemTotal is not an error, just no reading."""
|
||||||
|
meminfo = tmp_path / "meminfo"
|
||||||
|
meminfo.write_text("SwapTotal: 4096 kB\n")
|
||||||
|
monkeypatch.setattr(opt, "Path", lambda _p: meminfo)
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_ram(hw)
|
||||||
|
|
||||||
|
assert hw.ram_total_mb == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_ram_survives_an_unreadable_procfs(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""An OSError reading /proc/meminfo leaves the default in place."""
|
||||||
|
monkeypatch.setattr(opt, "Path", lambda _p: tmp_path / "does-not-exist")
|
||||||
|
hw = opt._Hw()
|
||||||
|
|
||||||
|
opt._detect_ram(hw)
|
||||||
|
|
||||||
|
assert hw.ram_total_mb == 0
|
||||||
230
python_pkg/vscode_optimizer/tests/test_io.py
Normal file
230
python_pkg/vscode_optimizer/tests/test_io.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
"""Tests for the filesystem layer: variant discovery, the JSONC parser,
|
||||||
|
backups, and the settings/flags readers and writers.
|
||||||
|
|
||||||
|
These use real files under ``tmp_path``. Nothing in this module may resolve a
|
||||||
|
path inside the running user's real ``~/.config``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _parse_jsonc
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_parse_jsonc_reads_plain_json() -> None:
|
||||||
|
"""The parser is a superset of JSON."""
|
||||||
|
assert opt._parse_jsonc('{"a": 1}') == {"a": 1}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_strips_line_and_block_comments() -> None:
|
||||||
|
"""Both VS Code comment styles are removed before parsing."""
|
||||||
|
text = """
|
||||||
|
{
|
||||||
|
// a line comment
|
||||||
|
"a": 1, /* an inline block */
|
||||||
|
/* a
|
||||||
|
multi-line block */
|
||||||
|
"b": 2
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert opt._parse_jsonc(text) == {"a": 1, "b": 2}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_keeps_comment_markers_inside_strings() -> None:
|
||||||
|
"""A // or /* inside a string literal is data, not a comment."""
|
||||||
|
text = '{"url": "https://example.com/*x*/", "path": "// not a comment"}'
|
||||||
|
|
||||||
|
assert opt._parse_jsonc(text) == {
|
||||||
|
"url": "https://example.com/*x*/",
|
||||||
|
"path": "// not a comment",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_handles_escaped_quotes_in_strings() -> None:
|
||||||
|
"""A backslash-escaped quote does not end the string."""
|
||||||
|
assert opt._parse_jsonc(r'{"a": "he said \"hi\""}') == {"a": 'he said "hi"'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_drops_trailing_commas() -> None:
|
||||||
|
"""Trailing commas are legal in JSONC, before both } and ]."""
|
||||||
|
assert opt._parse_jsonc('{"a": [1, 2,], "b": 3,}') == {"a": [1, 2], "b": 3}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_treats_an_empty_document_as_no_settings() -> None:
|
||||||
|
"""A blank or comment-only file parses to an empty mapping, not an error."""
|
||||||
|
assert opt._parse_jsonc("") == {}
|
||||||
|
assert opt._parse_jsonc(" \n\t ") == {}
|
||||||
|
assert opt._parse_jsonc("// nothing but a comment") == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_tolerates_a_comment_without_a_trailing_newline() -> None:
|
||||||
|
"""A line comment running to end-of-file terminates cleanly."""
|
||||||
|
assert opt._parse_jsonc('{"a": 1}\n// trailing') == {"a": 1}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_tolerates_an_unterminated_block_comment() -> None:
|
||||||
|
"""An unclosed /* swallows the rest of the file rather than looping."""
|
||||||
|
assert opt._parse_jsonc('{"a": 1}\n/* never closed') == {"a": 1}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_jsonc_tolerates_an_unterminated_string() -> None:
|
||||||
|
"""An unclosed quote consumes to end-of-input and then fails to parse."""
|
||||||
|
with pytest.raises(json.JSONDecodeError):
|
||||||
|
opt._parse_jsonc('{"a": "unterminated')
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _discover_variants
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_discover_variants_finds_an_installation_by_its_settings_file(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""A settings.json on disk is enough, even with no binary on PATH."""
|
||||||
|
settings = tmp_path / ".config" / "VSCodium" / "User" / "settings.json"
|
||||||
|
settings.parent.mkdir(parents=True)
|
||||||
|
settings.write_text("{}")
|
||||||
|
monkeypatch.setattr(opt.Path, "home", classmethod(lambda _cls: tmp_path))
|
||||||
|
monkeypatch.setattr(shutil, "which", lambda _b: None)
|
||||||
|
|
||||||
|
found = opt._discover_variants()
|
||||||
|
|
||||||
|
assert [v.name for v in found] == ["VSCodium"]
|
||||||
|
assert found[0].settings == settings
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_variants_finds_an_installation_by_its_binary(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""A binary on PATH counts even before the settings file exists."""
|
||||||
|
monkeypatch.setattr(opt.Path, "home", classmethod(lambda _cls: tmp_path))
|
||||||
|
monkeypatch.setattr(
|
||||||
|
shutil, "which", lambda b: "/usr/bin/code" if b == "code" else None
|
||||||
|
)
|
||||||
|
|
||||||
|
found = opt._discover_variants()
|
||||||
|
|
||||||
|
assert [v.name for v in found] == ["VS Code (stable)"]
|
||||||
|
assert found[0].flags == tmp_path / ".config" / "code-flags.conf"
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_variants_returns_nothing_when_no_editor_is_installed(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""A machine with no VS Code variant yields an empty list."""
|
||||||
|
monkeypatch.setattr(opt.Path, "home", classmethod(lambda _cls: tmp_path))
|
||||||
|
monkeypatch.setattr(shutil, "which", lambda _b: None)
|
||||||
|
|
||||||
|
assert opt._discover_variants() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_variants_finds_every_installed_variant(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""All three known variants can coexist."""
|
||||||
|
monkeypatch.setattr(opt.Path, "home", classmethod(lambda _cls: tmp_path))
|
||||||
|
monkeypatch.setattr(shutil, "which", lambda _b: "/usr/bin/x")
|
||||||
|
|
||||||
|
assert len(opt._discover_variants()) == 3
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _backup
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_backup_copies_the_file_and_returns_the_destination(tmp_path: Path) -> None:
|
||||||
|
"""The backup keeps the original content and leaves the source in place."""
|
||||||
|
src = tmp_path / "settings.json"
|
||||||
|
src.write_text('{"a": 1}')
|
||||||
|
|
||||||
|
dst = opt._backup(src)
|
||||||
|
|
||||||
|
assert dst is not None
|
||||||
|
assert dst.read_text() == '{"a": 1}'
|
||||||
|
assert src.exists()
|
||||||
|
assert dst.name.endswith(".bak")
|
||||||
|
|
||||||
|
|
||||||
|
def test_backup_of_a_missing_file_is_a_no_op(tmp_path: Path) -> None:
|
||||||
|
"""Nothing to back up is not an error."""
|
||||||
|
assert opt._backup(tmp_path / "absent.json") is None
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# settings and flags round-trips
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_read_settings_parses_jsonc_from_disk(tmp_path: Path) -> None:
|
||||||
|
"""Comments in a real settings.json are tolerated."""
|
||||||
|
path = tmp_path / "settings.json"
|
||||||
|
path.write_text('{\n // comment\n "editor.fontSize": 14\n}')
|
||||||
|
|
||||||
|
assert opt._read_settings(path) == {"editor.fontSize": 14}
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_settings_of_a_missing_file_is_empty(tmp_path: Path) -> None:
|
||||||
|
"""A variant with no settings yet reads as no settings."""
|
||||||
|
assert opt._read_settings(tmp_path / "absent.json") == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_settings_merges_the_plan_over_the_current_values(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Proposed values win; untouched user settings survive."""
|
||||||
|
path = tmp_path / "nested" / "settings.json"
|
||||||
|
current: dict[str, object] = {"editor.fontSize": 14, "a": 1}
|
||||||
|
opts = [
|
||||||
|
opt._Opt("a", 2, "reason"),
|
||||||
|
opt._Opt(key="b", value=True, reason="reason"),
|
||||||
|
]
|
||||||
|
|
||||||
|
opt._write_settings(path, current, opts)
|
||||||
|
|
||||||
|
assert json.loads(path.read_text()) == {"editor.fontSize": 14, "a": 2, "b": True}
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_settings_creates_the_parent_directory(tmp_path: Path) -> None:
|
||||||
|
"""A brand-new variant directory is created rather than failing."""
|
||||||
|
path = tmp_path / "deep" / "nested" / "settings.json"
|
||||||
|
|
||||||
|
opt._write_settings(path, {}, [])
|
||||||
|
|
||||||
|
assert path.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_flags_ignores_comments_and_blank_lines(tmp_path: Path) -> None:
|
||||||
|
"""The flags file is a plain list, with # comments."""
|
||||||
|
path = tmp_path / "code-flags.conf"
|
||||||
|
path.write_text(
|
||||||
|
"# a comment\n\n --enable-gpu-rasterization \n--enable-zero-copy\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert opt._read_flags(path) == [
|
||||||
|
"--enable-gpu-rasterization",
|
||||||
|
"--enable-zero-copy",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_flags_of_a_missing_file_is_empty(tmp_path: Path) -> None:
|
||||||
|
"""No flags file means no existing flags."""
|
||||||
|
assert opt._read_flags(tmp_path / "absent.conf") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_flags_round_trips_through_read_flags(tmp_path: Path) -> None:
|
||||||
|
"""What is written is what is read back."""
|
||||||
|
path = tmp_path / "code-flags.conf"
|
||||||
|
flags = ["--enable-gpu-rasterization", "--ignore-gpu-blocklist"]
|
||||||
|
|
||||||
|
opt._write_flags(path, flags)
|
||||||
|
|
||||||
|
assert opt._read_flags(path) == flags
|
||||||
|
assert path.read_text().endswith("\n")
|
||||||
239
python_pkg/vscode_optimizer/tests/test_main.py
Normal file
239
python_pkg/vscode_optimizer/tests/test_main.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
"""Tests for applying a plan to one variant, and for the CLI entry point.
|
||||||
|
|
||||||
|
Every variant used here points at ``tmp_path``. The suite must never be able
|
||||||
|
to resolve a real ``~/.config/*/User/settings.json``; the last test in this
|
||||||
|
module asserts that directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def _variant(tmp_path: Path) -> opt._Variant:
|
||||||
|
"""A VS Code variant rooted entirely inside tmp_path."""
|
||||||
|
return opt._Variant(
|
||||||
|
name="Test Code",
|
||||||
|
settings=tmp_path / "User" / "settings.json",
|
||||||
|
flags=tmp_path / "code-flags.conf",
|
||||||
|
binary="code",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_HW = opt._Hw(cpu_physical_cores=8, ram_total_mb=32000, gpu_vendor="NVIDIA")
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _apply_variant
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_apply_variant_writes_settings_and_flags(tmp_path: Path) -> None:
|
||||||
|
"""With --yes, both files are written and the plan is applied."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, ["--enable-zero-copy"], dry_run=False, auto_yes=True)
|
||||||
|
|
||||||
|
written = json.loads(v.settings.read_text())
|
||||||
|
assert written["search.maxThreads"] == 8
|
||||||
|
assert written["files.maxMemoryForLargeFilesMB"] == 4096
|
||||||
|
assert v.flags.read_text().strip() == "--enable-zero-copy"
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_variant_writes_nothing_in_dry_run(
|
||||||
|
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
"""--dry-run reports the plan and leaves the disk untouched."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, ["--enable-zero-copy"], dry_run=True, auto_yes=True)
|
||||||
|
|
||||||
|
assert not v.settings.exists()
|
||||||
|
assert not v.flags.exists()
|
||||||
|
assert "dry-run" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_variant_backs_up_an_existing_settings_file(tmp_path: Path) -> None:
|
||||||
|
"""An existing file is copied aside before being overwritten."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
v.settings.parent.mkdir(parents=True)
|
||||||
|
v.settings.write_text('{"editor.fontSize": 14}')
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, [], dry_run=False, auto_yes=True)
|
||||||
|
|
||||||
|
backups = list(v.settings.parent.glob("*.bak"))
|
||||||
|
assert len(backups) == 1
|
||||||
|
assert json.loads(backups[0].read_text()) == {"editor.fontSize": 14}
|
||||||
|
assert json.loads(v.settings.read_text())["editor.fontSize"] == 14
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_variant_backs_up_an_existing_flags_file(tmp_path: Path) -> None:
|
||||||
|
"""The flags file gets the same backup treatment as settings."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
v.flags.write_text("--existing-flag\n")
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, ["--enable-zero-copy"], dry_run=False, auto_yes=True)
|
||||||
|
|
||||||
|
assert list(tmp_path.glob("*.bak"))
|
||||||
|
assert "--existing-flag" in v.flags.read_text()
|
||||||
|
assert "--enable-zero-copy" in v.flags.read_text()
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_variant_does_nothing_when_already_optimized(
|
||||||
|
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
"""A variant needing no changes is reported and skipped."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
opt._apply_variant(v, _HW, [], dry_run=False, auto_yes=True)
|
||||||
|
before = v.settings.read_text()
|
||||||
|
capsys.readouterr()
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, [], dry_run=False, auto_yes=True)
|
||||||
|
|
||||||
|
assert v.settings.read_text() == before
|
||||||
|
assert "Nothing to do" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_variant_writes_only_flags_when_settings_are_current(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""A flags-only change leaves settings.json untouched, with no backup."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
opt._apply_variant(v, _HW, [], dry_run=False, auto_yes=True)
|
||||||
|
settings_before = v.settings.read_text()
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, ["--ignore-gpu-blocklist"], dry_run=False, auto_yes=True)
|
||||||
|
|
||||||
|
assert v.settings.read_text() == settings_before
|
||||||
|
assert not list(v.settings.parent.glob("*.bak"))
|
||||||
|
assert "--ignore-gpu-blocklist" in v.flags.read_text()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("answer", ["y", "yes", "Y", "YES"])
|
||||||
|
def test_apply_variant_applies_on_confirmation(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, answer: str
|
||||||
|
) -> None:
|
||||||
|
"""Any spelling of yes at the prompt applies the change."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
monkeypatch.setattr(builtins, "input", lambda _p: answer)
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, [], dry_run=False, auto_yes=False)
|
||||||
|
|
||||||
|
assert v.settings.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("answer", ["n", "", "no", "maybe"])
|
||||||
|
def test_apply_variant_skips_when_declined(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
answer: str,
|
||||||
|
) -> None:
|
||||||
|
"""Anything other than yes leaves the disk untouched."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
monkeypatch.setattr(builtins, "input", lambda _p: answer)
|
||||||
|
|
||||||
|
opt._apply_variant(v, _HW, [], dry_run=False, auto_yes=False)
|
||||||
|
|
||||||
|
assert not v.settings.exists()
|
||||||
|
assert "Skipped" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_variant_adds_flags_without_duplicating_existing_ones(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Merging flags preserves order and drops duplicates."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
v.flags.write_text("--enable-zero-copy\n--custom\n")
|
||||||
|
|
||||||
|
opt._apply_variant(
|
||||||
|
v,
|
||||||
|
_HW,
|
||||||
|
["--enable-zero-copy", "--ignore-gpu-blocklist"],
|
||||||
|
dry_run=False,
|
||||||
|
auto_yes=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert v.flags.read_text().split() == [
|
||||||
|
"--enable-zero-copy",
|
||||||
|
"--custom",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# main
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_main_optimizes_every_discovered_variant(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""The entry point detects hardware, then applies to each variant."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
monkeypatch.setattr(sys, "argv", ["optimize_vscode", "--yes"])
|
||||||
|
monkeypatch.setattr(opt, "_detect_hardware", lambda: _HW)
|
||||||
|
monkeypatch.setattr(opt, "_discover_variants", lambda: [v])
|
||||||
|
|
||||||
|
opt.main()
|
||||||
|
|
||||||
|
assert json.loads(v.settings.read_text())["search.maxThreads"] == 8
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_honours_dry_run(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||||
|
"""--dry-run reaches _apply_variant and prevents all writes."""
|
||||||
|
v = _variant(tmp_path)
|
||||||
|
monkeypatch.setattr(sys, "argv", ["optimize_vscode", "--dry-run"])
|
||||||
|
monkeypatch.setattr(opt, "_detect_hardware", lambda: _HW)
|
||||||
|
monkeypatch.setattr(opt, "_discover_variants", lambda: [v])
|
||||||
|
|
||||||
|
opt.main()
|
||||||
|
|
||||||
|
assert not v.settings.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_exits_nonzero_without_an_installation(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
"""No VS Code variant is a usage error, not a silent success."""
|
||||||
|
monkeypatch.setattr(sys, "argv", ["optimize_vscode", "--dry-run"])
|
||||||
|
monkeypatch.setattr(opt, "_detect_hardware", lambda: _HW)
|
||||||
|
monkeypatch.setattr(opt, "_discover_variants", list)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
|
opt.main()
|
||||||
|
|
||||||
|
assert excinfo.value.code == 1
|
||||||
|
assert "No VS Code installation found" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_module_entry_point_exposes_main() -> None:
|
||||||
|
"""`python3 -m python_pkg.vscode_optimizer` resolves to the same main().
|
||||||
|
|
||||||
|
The script used to be run by path from meta/scripts/; this import is what
|
||||||
|
proves the replacement entry point works after the move.
|
||||||
|
"""
|
||||||
|
from python_pkg.vscode_optimizer import __main__ as entry
|
||||||
|
|
||||||
|
assert entry.main is opt.main
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_suite_never_touches_the_real_user_settings(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""Guard rail: with Path.home redirected, discovery stays inside tmp_path.
|
||||||
|
|
||||||
|
A regression here would mean some test could rewrite the developer's own
|
||||||
|
VS Code configuration, which is exactly what --dry-run exists to prevent.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(opt.Path, "home", classmethod(lambda _cls: tmp_path))
|
||||||
|
|
||||||
|
for variant in opt._discover_variants():
|
||||||
|
assert tmp_path in variant.settings.parents
|
||||||
|
assert tmp_path in variant.flags.parents
|
||||||
210
python_pkg/vscode_optimizer/tests/test_plan.py
Normal file
210
python_pkg/vscode_optimizer/tests/test_plan.py
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
"""Tests for the pure planning layer: memory sizing, dict merges, the
|
||||||
|
settings plan, and the Electron GPU flags.
|
||||||
|
|
||||||
|
Nothing here touches the filesystem or a subprocess: every function under
|
||||||
|
test maps hardware plus current settings to a proposed change.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
|
||||||
|
|
||||||
|
def _opts_by_key(opts: list[opt._Opt]) -> dict[str, object]:
|
||||||
|
"""Index a plan by setting key, for assertions that ignore ordering."""
|
||||||
|
return {o.key: o.value for o in opts}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _ideal_mem
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("ram_mb", "expected"),
|
||||||
|
[
|
||||||
|
(32000, 4096),
|
||||||
|
(28000, 4096),
|
||||||
|
(27999, 2048),
|
||||||
|
(14000, 2048),
|
||||||
|
(13999, 1024),
|
||||||
|
(7000, 1024),
|
||||||
|
(6999, 512),
|
||||||
|
(0, 512),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_ideal_mem_picks_the_band_for_the_installed_ram(
|
||||||
|
ram_mb: int, expected: int
|
||||||
|
) -> None:
|
||||||
|
"""Each threshold is inclusive, and anything below the last one defaults."""
|
||||||
|
assert opt._ideal_mem(ram_mb) == expected
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _dict_merge_opt
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_dict_merge_opt_returns_none_when_every_key_is_present() -> None:
|
||||||
|
"""A superset of the ideal keys needs no change, whatever the values."""
|
||||||
|
current = {"search.exclude": {"**/node_modules": True, "**/extra": False}}
|
||||||
|
|
||||||
|
result = opt._dict_merge_opt(
|
||||||
|
current, "search.exclude", {"**/node_modules": True}, "reason"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_dict_merge_opt_preserves_user_entries_while_adding_missing_ones() -> None:
|
||||||
|
"""The merge is additive: existing keys survive alongside the ideal set."""
|
||||||
|
current = {"search.exclude": {"**/mine": True}}
|
||||||
|
|
||||||
|
result = opt._dict_merge_opt(
|
||||||
|
current, "search.exclude", {"**/node_modules": True}, "reason"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result.value == {"**/mine": True, "**/node_modules": True}
|
||||||
|
assert result.current == {"**/mine": True}
|
||||||
|
|
||||||
|
|
||||||
|
def test_dict_merge_opt_replaces_a_non_dict_current_value() -> None:
|
||||||
|
"""A malformed setting (not an object) is treated as empty, not merged."""
|
||||||
|
result = opt._dict_merge_opt(
|
||||||
|
{"search.exclude": "oops"}, "search.exclude", {"**/x": True}, "reason"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result.value == {"**/x": True}
|
||||||
|
assert result.current is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_dict_merge_opt_reports_no_current_value_when_the_key_is_absent() -> None:
|
||||||
|
"""A missing key yields a plain add with nothing to show as 'before'."""
|
||||||
|
result = opt._dict_merge_opt({}, "search.exclude", {"**/x": True}, "reason")
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result.current is None
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _compute_opts
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_compute_opts_scales_threads_and_memory_to_the_hardware() -> None:
|
||||||
|
"""Physical cores drive search threads; RAM drives the large-file budget."""
|
||||||
|
hw = opt._Hw(cpu_physical_cores=12, ram_total_mb=32000)
|
||||||
|
|
||||||
|
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
||||||
|
|
||||||
|
assert plan["search.maxThreads"] == 12
|
||||||
|
assert plan["files.maxMemoryForLargeFilesMB"] == 4096
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_opts_keeps_a_floor_under_search_threads() -> None:
|
||||||
|
"""A dual-core machine still gets the four-thread minimum."""
|
||||||
|
hw = opt._Hw(cpu_physical_cores=2)
|
||||||
|
|
||||||
|
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
||||||
|
|
||||||
|
assert plan["search.maxThreads"] == opt._MIN_THREADS
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_opts_enables_gpu_extras_for_a_dedicated_card() -> None:
|
||||||
|
"""A discrete GPU turns on terminal acceleration and smooth scrolling."""
|
||||||
|
hw = opt._Hw(gpu_vendor="NVIDIA")
|
||||||
|
|
||||||
|
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
||||||
|
|
||||||
|
assert plan["terminal.integrated.gpuAcceleration"] == "on"
|
||||||
|
assert plan["editor.smoothScrolling"] is True
|
||||||
|
assert plan["workbench.list.smoothScrolling"] is True
|
||||||
|
assert plan["terminal.integrated.smoothScrolling"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_opts_leaves_gpu_extras_alone_without_a_dedicated_card() -> None:
|
||||||
|
"""Integrated or unknown graphics get no GPU-dependent settings."""
|
||||||
|
plan = _opts_by_key(opt._compute_opts(opt._Hw(gpu_vendor="Intel"), {}))
|
||||||
|
|
||||||
|
assert "terminal.integrated.gpuAcceleration" not in plan
|
||||||
|
assert "editor.smoothScrolling" not in plan
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_opts_proposes_nothing_already_set() -> None:
|
||||||
|
"""Settings that already hold the ideal value are not re-proposed."""
|
||||||
|
hw = opt._Hw(cpu_physical_cores=8, ram_total_mb=32000)
|
||||||
|
current: dict[str, object] = {
|
||||||
|
"search.maxThreads": 8,
|
||||||
|
"files.maxMemoryForLargeFilesMB": 4096,
|
||||||
|
"search.followSymlinks": False,
|
||||||
|
"editor.guides.bracketPairs": "active",
|
||||||
|
"diffEditor.maxComputationTime": 0,
|
||||||
|
"editor.minimap.enabled": False,
|
||||||
|
"files.watcherExclude": dict(opt._WATCHER_EX),
|
||||||
|
"search.exclude": dict(opt._SEARCH_EX),
|
||||||
|
"git.detectSubmodulesLimit": opt._SUBMOD_LIMIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert opt._compute_opts(hw, current) == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("current_limit", "proposed"),
|
||||||
|
[
|
||||||
|
(None, True),
|
||||||
|
(10, True),
|
||||||
|
(opt._SUBMOD_LIMIT, False),
|
||||||
|
(99, False),
|
||||||
|
("many", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_compute_opts_raises_only_a_low_submodule_limit(
|
||||||
|
current_limit: object, *, proposed: bool
|
||||||
|
) -> None:
|
||||||
|
"""The limit is raised when unset or too low, and left alone otherwise."""
|
||||||
|
current: dict[str, object] = (
|
||||||
|
{} if current_limit is None else {"git.detectSubmodulesLimit": current_limit}
|
||||||
|
)
|
||||||
|
|
||||||
|
plan = _opts_by_key(opt._compute_opts(opt._Hw(), current))
|
||||||
|
|
||||||
|
assert ("git.detectSubmodulesLimit" in plan) is proposed
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_opts_records_the_previous_value_for_display() -> None:
|
||||||
|
"""Each proposed change carries the value it replaces, for the diff."""
|
||||||
|
current: dict[str, object] = {"editor.minimap.enabled": True}
|
||||||
|
|
||||||
|
minimap = next(
|
||||||
|
o
|
||||||
|
for o in opt._compute_opts(opt._Hw(), current)
|
||||||
|
if o.key == "editor.minimap.enabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert minimap.current is True
|
||||||
|
assert minimap.value is False
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# _gpu_flags
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def test_gpu_flags_adds_a_video_decode_flag_only_for_nvidia() -> None:
|
||||||
|
"""NVIDIA gets the VAAPI extras on top of the shared rasterization flags."""
|
||||||
|
nvidia = opt._gpu_flags(opt._Hw(gpu_vendor="NVIDIA"))
|
||||||
|
amd = opt._gpu_flags(opt._Hw(gpu_vendor="AMD"))
|
||||||
|
|
||||||
|
assert "--enable-gpu-rasterization" in nvidia
|
||||||
|
assert "--enable-zero-copy" in nvidia
|
||||||
|
assert any("VaapiVideoEncoder" in f for f in nvidia)
|
||||||
|
assert not any("VaapiVideoEncoder" in f for f in amd)
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_flags_gives_intel_a_reduced_set() -> None:
|
||||||
|
"""Intel gets rasterization and decode, but not zero-copy."""
|
||||||
|
flags = opt._gpu_flags(opt._Hw(gpu_vendor="Intel"))
|
||||||
|
|
||||||
|
assert "--enable-gpu-rasterization" in flags
|
||||||
|
assert "--enable-zero-copy" not in flags
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_flags_are_empty_for_an_unknown_gpu() -> None:
|
||||||
|
"""No flags are forced on hardware the script could not identify."""
|
||||||
|
assert opt._gpu_flags(opt._Hw()) == []
|
||||||
118
python_pkg/vscode_optimizer/tests/test_render.py
Normal file
118
python_pkg/vscode_optimizer/tests/test_render.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
"""Tests for the terminal output: the hardware summary and the plan listing.
|
||||||
|
|
||||||
|
These assert on the informational content of each line rather than its exact
|
||||||
|
decoration, so the ANSI escapes stay free to change.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_hw_reports_every_detected_component(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""CPU, RAM, GPU and disk each appear, with RAM converted to GB."""
|
||||||
|
hw = opt._Hw(
|
||||||
|
cpu_model="AMD Ryzen 9 7900X3D",
|
||||||
|
cpu_physical_cores=12,
|
||||||
|
cpu_logical_cores=24,
|
||||||
|
cpu_max_mhz=5662.6,
|
||||||
|
ram_total_mb=30000,
|
||||||
|
gpu_vendor="NVIDIA",
|
||||||
|
gpu_model="GeForce RTX 3090",
|
||||||
|
gpu_vram_mb=24576,
|
||||||
|
disk_type="nvme",
|
||||||
|
)
|
||||||
|
|
||||||
|
opt._show_hw(hw)
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "AMD Ryzen 9 7900X3D" in out
|
||||||
|
assert "12 cores / 24 threads" in out
|
||||||
|
assert "5663 MHz" in out
|
||||||
|
assert "29 GB" in out
|
||||||
|
assert "NVIDIA - GeForce RTX 3090" in out
|
||||||
|
assert "24576 MB VRAM" in out
|
||||||
|
assert "NVME" in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_hw_omits_vram_when_it_is_unknown(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""A card whose VRAM could not be read shows no empty parentheses."""
|
||||||
|
opt._show_hw(opt._Hw(gpu_vendor="AMD", gpu_model="Radeon"))
|
||||||
|
|
||||||
|
assert "VRAM" not in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_plan_lists_new_flags_and_settings(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""Each proposed change is numbered and shows before -> after."""
|
||||||
|
opts = [
|
||||||
|
opt._Opt(
|
||||||
|
key="editor.minimap.enabled",
|
||||||
|
value=False,
|
||||||
|
reason="Minimap costs GPU",
|
||||||
|
current=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
opt._show_plan(opts, ["--enable-zero-copy"], [])
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "--enable-zero-copy" in out
|
||||||
|
assert "1. editor.minimap.enabled" in out
|
||||||
|
assert "Minimap costs GPU" in out
|
||||||
|
assert "true" in out
|
||||||
|
assert "false" in out
|
||||||
|
assert "2 change(s) proposed" in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_plan_says_flags_are_already_present(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""Flags that are all already set are reported, not listed as additions."""
|
||||||
|
opt._show_plan([], ["--enable-zero-copy"], ["--enable-zero-copy"])
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "already present" in out
|
||||||
|
assert "+ --enable-zero-copy" not in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_plan_reports_a_fully_optimized_configuration(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""With nothing to change, the plan says so and proposes no count."""
|
||||||
|
opt._show_plan([], [], [])
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "All settings already optimized" in out
|
||||||
|
assert "change(s) proposed" not in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_plan_renders_an_addition_with_no_previous_value(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""A setting that was never set shows a dash as its 'before'."""
|
||||||
|
opt._show_plan([opt._Opt("search.maxThreads", 12, "cores")], [], [])
|
||||||
|
|
||||||
|
assert "-" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_plan_truncates_very_long_values(
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""A large exclude dict is clipped so one setting cannot flood the screen."""
|
||||||
|
big = {f"**/dir{i}": True for i in range(50)}
|
||||||
|
|
||||||
|
opt._show_plan([opt._Opt("files.watcherExclude", big, "exclude", big)], [], [])
|
||||||
|
|
||||||
|
for line in capsys.readouterr().out.splitlines():
|
||||||
|
assert len(line) < 200
|
||||||
Loading…
Reference in New Issue
Block a user