Competing installers and checkout-local venv assumptions bypassed PM selection, install consent, and generation lifetimes. Route consumers through PM and installation-bound launchers. Refresh source launchers before obsolete Python entries can be collected. Remove Node, browser, and CUA acquisition engines, obsolete venv-holder handling, detached sync, and unused PM APIs. Keep historical updater exports inert and preserve external tool ownership and native integration. Share product freshness and prepared inputs across builders. Align plugin admission, Docker provisioning, setup instructions, and behavioral tests. Verified targeted Python and JavaScript tests, desktop and web typechecks, scoped lint, real product builds, and the Docker frontend smoke test. The missed post-setup test cleanup is included and verified. Native Windows/macOS execution, full Rust compilation, and the complete repository suite remain unverified. Historical compatibility requirements were preserved and extended, not fully rescanned.
80 lines
3.4 KiB
Python
80 lines
3.4 KiB
Python
"""Exercise the frontend COPY closure before an image/network build.
|
|
|
|
PM environment construction is the only substituted boundary. The copied icon
|
|
provider must load, ask for its real dependency group, and invoke the copied
|
|
generator. Image-level runtime/permission coverage stays in tests/docker.
|
|
"""
|
|
from pathlib import Path
|
|
import runpy
|
|
import shlex
|
|
import shutil
|
|
from types import SimpleNamespace
|
|
from types import ModuleType
|
|
|
|
from pathspec import PathSpec
|
|
|
|
import pm
|
|
|
|
|
|
def test_frontend_copy_closure_reaches_the_icon_provider(tmp_path, monkeypatch):
|
|
repo = Path(__file__).resolve().parents[2]
|
|
# Apply the frontend's local COPY declarations, not a duplicated filename
|
|
# allowlist. Runtime-base supplies PM itself and is separately image-tested.
|
|
frontend = False
|
|
ignored = PathSpec.from_lines("gitignore", (repo / ".dockerignore").read_text().splitlines())
|
|
|
|
def excluded(directory, names):
|
|
return [name for name in names if ignored.match_file(
|
|
(Path(directory) / name).relative_to(repo).as_posix() +
|
|
("/" if (Path(directory) / name).is_dir() else ""))]
|
|
for line in (repo / "Dockerfile").read_text().splitlines():
|
|
if not line.startswith(("FROM ", "COPY ")):
|
|
continue
|
|
words = shlex.split(line.rstrip("\\"), comments=True)
|
|
if not words:
|
|
continue
|
|
if words[0] == "FROM":
|
|
frontend = words[-1] == "frontend_build"
|
|
if not frontend or words[0] != "COPY" or any(word.startswith("--") for word in words[1:]):
|
|
continue
|
|
sources, destination = words[1:-1], tmp_path / words[-1]
|
|
for pattern in sources:
|
|
for source in repo.glob(pattern):
|
|
target = destination / source.name if words[-1].endswith("/") else destination
|
|
if source.is_dir():
|
|
shutil.copytree(source, destination, dirs_exist_ok=True,
|
|
ignore=excluded)
|
|
else:
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(source, target)
|
|
acquired = []
|
|
|
|
def environment(**kwargs):
|
|
acquired.append(kwargs)
|
|
return Path("/prepared/icon-python")
|
|
|
|
launched = []
|
|
monkeypatch.setattr(pm, "build_environment", environment)
|
|
provider = runpy.run_path(str(tmp_path / "scripts/build/icon_environment.py"))
|
|
|
|
def run(argv, *, cwd):
|
|
assert Path(argv[2]).is_file(), "generator must also be in the stage"
|
|
assert cwd == tmp_path
|
|
launched.append(argv)
|
|
return SimpleNamespace(returncode=0)
|
|
|
|
monkeypatch.setattr(provider["subprocess"], "run", run)
|
|
assert provider["main"](["--source", str(tmp_path), "--out", str(tmp_path / "icons")]) == 0
|
|
assert acquired[0]["source"] == tmp_path
|
|
assert acquired[0]["groups"] == ["icon-build"]
|
|
assert acquired[0]["only_groups"] is True
|
|
assert launched[0][:2] == ["/prepared/icon-python", "-I"]
|
|
|
|
# Exercise the generator's own source loader. Rasterization is not needed
|
|
# to prove that every composed SVG's artwork made it into the build context.
|
|
monkeypatch.setitem(__import__("sys").modules, "resvg_py", ModuleType("resvg_py"))
|
|
generator = runpy.run_path(str(tmp_path / "scripts/generate_icons.py"))
|
|
monkeypatch.setitem(generator["IconArt"].__init__.__globals__, "girl_bbox", lambda *args: (0, 0, 512, 512))
|
|
art = generator["IconArt"](tmp_path)
|
|
assert art.master_mac and art.master_mac_dark
|