From c166a67822e618d30e6191914bdaf9e47f16d514 Mon Sep 17 00:00:00 2001 From: Xavier Karma Date: Fri, 6 Nov 2026 21:25:42 +0530 Subject: [PATCH] test: fence upstream references on the fork surfaces The naming sweep forbids upstream hosts, handles and URLs on the fork surfaces except each file's recorded credit line, matched exactly. Packaging tests read the surfaces that still exist: the Wayland hotkey section and the Snap recipe. --- tests/test_native_packaging.py | 6 +- tests/test_rename_sweep.py | 127 +++++++++++++++++++++++++++++---- tests/test_snap_packaging.py | 16 ++--- 3 files changed, 125 insertions(+), 24 deletions(-) diff --git a/tests/test_native_packaging.py b/tests/test_native_packaging.py index 89430f4..6eeb9bc 100644 --- a/tests/test_native_packaging.py +++ b/tests/test_native_packaging.py @@ -322,10 +322,12 @@ def test_wayland_input_group_is_documented() -> None: already listed in /etc/group: a fresh Wayland install leaves the default hotkey dead unless the docs name the step.""" install_md = (REPO_ROOT / "docs" / "INSTALL.md").read_text(encoding="utf-8") - section = install_md.split("## Distro packages", 1)[1].split("\n## ", 1)[0] + section = install_md.split("### Wayland hotkeys need the input group", 1)[1].split("\n## ", 1)[ + 0 + ] assert ( "usermod -aG input" in section - ), "INSTALL.md's .deb/.rpm section must document the Wayland input-group step" + ), "INSTALL.md's Wayland hotkey section must document the input-group step" release = RELEASE_YML.read_text(encoding="utf-8") assert ( "usermod -aG input" in release diff --git a/tests/test_rename_sweep.py b/tests/test_rename_sweep.py index 91ba498..48d557a 100644 --- a/tests/test_rename_sweep.py +++ b/tests/test_rename_sweep.py @@ -20,6 +20,12 @@ scoped to what the surface is, not to a word that happens to co-occur: ``CHANGELOG.md``) and the git worktree pointer file ``.git``; - web upstream surfaces (ticket #24): the nix flake snippets on the nixos page. +The Void release cleanup adds the fork surfaces (``FORK_SURFACES``): README, +the support and security docs, the install docs, the About page and the Void +install message. On those, upstream references are not allowlisted by pattern +at all. An upstream URL, handle or host is a regression unless the line is +exactly the one credit line recorded for that file. + ``tests/test_rename_sweep.py`` also checks the allowlist itself, so a loosened rule fails here before it lets an old name onto a user-visible surface. """ @@ -83,6 +89,44 @@ RETAINED_MODULE_HEADS = frozenset( ] ) - {"__init__"} +#: The one upstream credit per fork surface, matched as whole stripped lines. +CREDIT_LINE = "Voculinux is a hard fork of Vocalinux by VocaHQ: https://github.com/VocaHQ/vocalinux" +FORK_SURFACE_CREDITS: dict[str, frozenset[str]] = { + "README.md": frozenset({CREDIT_LINE}), + "CONTRIBUTING.md": frozenset({CREDIT_LINE}), + "src/vocalinux/ui/settings_about.py": frozenset( + { + 'UPSTREAM_REPO_URL = "https://github.com/VocaHQ/vocalinux"', + 'UPSTREAM_CREDIT_LINE = f"Voculinux is a hard fork of Vocalinux by VocaHQ: {UPSTREAM_REPO_URL}"', + } + ), +} +FORK_SURFACES = frozenset( + { + "README.md", + "SECURITY.md", + "SUPPORT.md", + "CONTRIBUTING.md", + "docs/UPDATE.md", + "docs/INSTALL.md", + "docs/TROUBLESHOOTING.md", + "docs/DISTRO_COMPATIBILITY.md", + "src/vocalinux/ui/settings_about.py", + "src/vocalinux/ui/settings_dialog.py", + "packaging/void/srcpkgs/voculinux/INSTALL.msg", + } +) +#: Upstream hosts, handles and URL forms. Plain words such as "Flathub" are not +#: listed, so a sentence that says a channel is not published stays legal. +UPSTREAM_REFERENCE = re.compile( + r"VocaHQ|vocahq|jatinkrmalik|raw\.githubusercontent|discord\.gg|(?\.|apps\.\.|nixosModules\.|homeManagerModules\.|programs\." - r"|\bflake\b|\binputs?\b|outputs = \{|nixpkgs, |pkgs\.vocalinux|nix run|nix profile" - r"|github:VocaHQ", - re.IGNORECASE, + + +WEB_REBRAND_TEST = "web/src/lib/__tests__/voculinux-rebrand.test.ts" +WEB_REBRAND_SCANNER_LINES = frozenset( + { + "const OLD_NAME = /Vocalinux/;", + 'expect(home).not.toContain("Then launch vocalinux");', + } ) +def _fork_surface_allowed(path: str, line: str) -> bool: + """Fork surfaces allow the exact credit, retained module references and legacy prose.""" + if line.strip() in FORK_SURFACE_CREDITS.get(path, frozenset()): + return True + if UPSTREAM_REFERENCE.search(line): + return False + return bool( + _MODULE_PATH.search(line) + or _DOTTED_MODULE.search(line) + or _IMPORT_FORM.search(line) + or _CAMEL_CLASS.search(line) + or _RETAINED_PROSE.search(line) + or _LEGACY_PROSE.search(line) + ) + + def _allowed(path: str, line: str) -> bool: """Return True when an old-name match is an allowlisted retained surface.""" if path == "tests/test_rename_sweep.py": # This file defines the allowlist and quotes the old name to do so. return True + if path in FORK_SURFACES: + return _fork_surface_allowed(path, line) + if path == WEB_REBRAND_TEST and line.strip() in WEB_REBRAND_SCANNER_LINES: + # The web rebrand test names the old word only to scan for it. + return True if _UPSTREAM_SLUG.search(line) or _UPSTREAM_LABEL.search(line): return True if _MODULE_PATH.search(line) or _DOTTED_MODULE.search(line): @@ -169,12 +237,6 @@ def _allowed(path: str, line: str) -> bool: return True if _LEGACY_PROSE.search(line): return True - if path == "web/src/app/install/nixos/page.tsx": - # The Nix flake is upstream's: flake URL, input and output names and - # programs.* options spell the retained upstream API. The page carries - # a fork-status note saying those snippets install Vocalinux, not - # Voculinux, so only flake-shaped lines are allowed here. - return bool(_NIX_FLAKE_IDENTIFIER.search(line)) if path in _LEGACY_SOURCE_FILES and _LEGACY_LINE.search(line): return True if path.startswith(_TOOLING_PREFIXES) and _PACKAGE_SEGMENT.search(line): @@ -221,6 +283,28 @@ def test_naming_sweep_no_old_name_outside_allowlist() -> None: ), f"{len(offenders)} old-name string(s) outside the allowlist:\n" + "\n".join(offenders[:60]) +def test_fork_surfaces_name_no_upstream_but_the_one_credit() -> None: + """A fork surface may name upstream only on its recorded credit line.""" + offenders: list[str] = [] + for rel in sorted(FORK_SURFACES): + with open(os.path.join(REPO_ROOT, rel), encoding="utf-8") as handle: + text = handle.read() + credits = FORK_SURFACE_CREDITS.get(rel, frozenset()) + for number, line in enumerate(text.splitlines(), 1): + if UPSTREAM_REFERENCE.search(line) and line.strip() not in credits: + offenders.append(f"{rel}:{number}:{line.strip()[:160]}") + assert offenders == [], "upstream reference outside the credit:\n" + "\n".join(offenders) + + +def test_fork_surfaces_carry_their_credit_exactly_once() -> None: + """The credit is present, once, on every file that records one.""" + for rel, credits in sorted(FORK_SURFACE_CREDITS.items()): + with open(os.path.join(REPO_ROOT, rel), encoding="utf-8") as handle: + lines = [line.strip() for line in handle.read().splitlines()] + for credit in credits: + assert lines.count(credit) == 1, f"{rel}: credit line must appear exactly once" + + def test_owned_web_files_carry_new_name() -> None: """The two web rename surfaces name the fork (URLs aside).""" for rel in sorted(WEB_OWNED): @@ -232,6 +316,20 @@ def test_owned_web_files_carry_new_name() -> None: def test_allowlist_denies_user_facing_old_names() -> None: """A user-facing old name is never allowlisted, wherever it sits.""" denied = [ + ("README.md", "Upstream: https://github.com/VocaHQ/vocalinux/issues"), + ("README.md", "Join us on [Discord](https://discord.gg/t6muquAJbm)."), + ("docs/INSTALL.md", "flatpak install vocahq com.voculinux.Voculinux"), + ("SUPPORT.md", "| [Discussions](https://github.com/VocaHQ/vocalinux/discussions) |"), + ("SECURITY.md", "Email hello@vocahq.com for security reports."), + ("src/vocalinux/ui/settings_about.py", 'VOCAHQ_X_URL = "https://x.com/vocahq"'), + ( + "src/vocalinux/ui/settings_dialog.py", + 'MODEL_SELECTION_DOC_URL = "https://github.com/VocaHQ/vocalinux/x"', + ), + ( + "packaging/void/srcpkgs/voculinux/INSTALL.msg", + "Report bugs at github.com/VocaHQ/vocalinux", + ), ("web/src/app/page.tsx", "

Install vocalinux today

"), ("README.md", "Launch vocalinux from your app menu."), ("docs/UPDATE.md", "Your settings live in ~/.local/share/vocalinux."), @@ -247,7 +345,12 @@ def test_allowlist_denies_user_facing_old_names() -> None: def test_allowlist_keeps_the_retained_surfaces() -> None: """Upstream provenance, the retained package and the legacy import stay allowed.""" kept = [ - ("README.md", "Upstream: https://github.com/VocaHQ/vocalinux/issues"), + ("README.md", CREDIT_LINE), + ("CONTRIBUTING.md", CREDIT_LINE), + ( + "src/vocalinux/ui/settings_about.py", + 'UPSTREAM_REPO_URL = "https://github.com/VocaHQ/vocalinux"', + ), ("web/src/app/page.tsx", "Voculinux is a hard fork of Vocalinux for Void Linux."), ("web/src/app/install/[distro]/page.tsx", "installs Vocalinux (upstream), not Voculinux."), ("src/vocalinux/ui/tray_indicator.py", "from vocalinux.utils import paths"), diff --git a/tests/test_snap_packaging.py b/tests/test_snap_packaging.py index 451f200..2b0e44b 100644 --- a/tests/test_snap_packaging.py +++ b/tests/test_snap_packaging.py @@ -59,24 +59,20 @@ def test_snap_puts_gnome_platform_first_on_ld_library_path() -> None: def test_snap_docs_tell_users_to_connect_hardware_observe() -> None: """Hotkeys need hardware-observe for /proc/bus/input/devices (#857).""" - readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") - install = (REPO_ROOT / "docs" / "INSTALL.md").read_text(encoding="utf-8") + # Only the recipe still names the Snap plugs; the fork docs carry no Snap install text. snapcraft = SNAPCRAFT_YAML.read_text(encoding="utf-8") - for text in (readme, install, snapcraft): + for text in (snapcraft,): assert "sudo snap connect voculinux:raw-input" in text assert "sudo snap connect voculinux:hardware-observe" in text def test_snap_docs_explain_typing_without_uinput_plug() -> None: """The Store denied the uinput grant; docs must point at the portal, not a connect.""" - readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") - install = (REPO_ROOT / "docs" / "INSTALL.md").read_text(encoding="utf-8") - update = (REPO_ROOT / "docs" / "UPDATE.md").read_text(encoding="utf-8") - changelog = (REPO_ROOT / "web" / "src" / "app" / "changelog" / "page.tsx").read_text( - encoding="utf-8" - ) + # The Snap channel is not published for Voculinux, so the recipe is the one + # surface that still describes the plug set. The fork docs no longer carry + # Snap install text. snapcraft = SNAPCRAFT_YAML.read_text(encoding="utf-8") - for text in (readme, install, update, changelog, snapcraft): + for text in (snapcraft,): uinput_lines = "\n".join(line for line in text.splitlines() if "uinput" in line.lower()) lowered = uinput_lines.lower() assert "uinput" in lowered