Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ is adding a file.
| `test_app_wrapper.sh` | Launching through either `app.sh` execution path does not leak an unrelated non-boolean `DEBUG` value into app2unit or xdg-terminal-exec |
| `test_binds.sh` | Loads the Lua keybinds against a stubbed Hyprland API: no two binds share a combination once modifiers are folded to what Hyprland matches on, no keysym sits in a modifier position, no bind uses the `code:NN` form the Lua parser rejects, every bind has a description, every `hyde-shell` command it runs exists. Touchpad gestures are checked in the same pass: a valid finger count, a direction and action Hyprland accepts, and no two gestures on the same finger count and direction |
| `test_git.sh` | The tree holds no gitlink without a matching `.gitmodules` entry, which would break `git submodule` and anything walking submodules |
| `test_dots.sh` | Every installer metafile under `Scripts/dots` parses, declares the keys the installer needs, uses a known action, and points at a source directory and source paths that exist. It also keeps Grimblast on the fixed official source |
| `test_dots.sh` | Every installer metafile under `Scripts/dots` parses, declares the keys the installer needs, uses a known action, and points at a source directory and non-glob source paths that exist, all of them inside the checkout. Entries whose dot declares a remote `source` are exempt from the existence checks, since those files come from an archive the installer downloads. It also keeps Grimblast on the fixed official source |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
| `test_lua_syntax.sh` | Every shipped Lua file parses |
| `test_schema.sh` | Generated schema artifacts use the current Lua battery notification daemon rather than the removed shell implementation |
| `test_screenshot_wrapper.sh` | Satty receives a compatible default GTK renderer while preserving explicit renderer overrides |
Expand Down
57 changes: 42 additions & 15 deletions tests/python/check_dots.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,52 @@


def declared_paths(table: dict) -> list:
"""`paths` is a single string or a list of them."""
"""`paths` is a single string or a list of them.

Anything else is wrapped rather than iterated, so a malformed value is
reported as a wrong type instead of crashing the checker.
"""
paths = table.get("paths", [])
return [paths] if isinstance(paths, str) else list(paths)
if isinstance(paths, str):
return [paths]
if isinstance(paths, list):
return list(paths)
return [paths]


def has_glob(relative: str) -> bool:
"""A pattern is resolved at deploy time, so it cannot be checked here."""
return any(character in relative for character in "*?[")


def inside_repo(relative: str) -> bool:
"""A source path has to stay in the checkout, absolute or `..` included."""
root = REPO_ROOT.resolve()
def inside(root: pathlib.Path, relative: str) -> bool:
"""A declared path has to stay under `root`, absolute or `..` included."""
try:
return (root / relative).resolve().is_relative_to(root)
except (OSError, ValueError):
return False
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def entries(document: dict) -> list[tuple[str, dict]]:
"""Yields (component, table) for every files table in a metafile."""
def inside_repo(relative: str) -> bool:
"""A source root has to stay in the checkout."""
return inside(REPO_ROOT.resolve(), relative)


def entries(document: dict) -> list[tuple[str, dict, bool]]:
"""Yields (component, table, remote) for every files table in a metafile.

`remote` is true when the component or the entry declares a `source`. Those
files come from an archive the installer downloads, so their paths resolve
against the extracted tree rather than this checkout and cannot be checked
for existence here.
"""
found = []
for component, body in document.items():
if isinstance(body, dict):
component_remote = "source" in body
for table in body.get("files", []) or []:
found.append((component, table))
remote = component_remote or "source" in table
found.append((component, table, remote))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
return found


Expand Down Expand Up @@ -80,7 +100,7 @@ def fail(message: str) -> None:
fail(f"{name} is not valid TOML: {error}")
continue

for component, table in entries(document):
for component, table, remote in entries(document):
where = f"{name} [{component}.files]"

if "paths" not in table:
Expand All @@ -92,19 +112,26 @@ def fail(message: str) -> None:
if action is not None and action not in ACTIONS:
fail(f"{where} has an unknown action {action!r}")

for relative in declared_paths(table):
if not isinstance(relative, str):
fail(f"{where} declares a path as {type(relative).__name__}, expected a string")

source_root = table.get("source_root")
if source_root is not None:
if not isinstance(source_root, str):
fail(f"{where} declares source_root as {type(source_root).__name__}, expected a string")
elif not inside_repo(source_root):
if source_root is not None and not isinstance(source_root, str):
fail(f"{where} declares source_root as {type(source_root).__name__}, expected a string")
elif isinstance(source_root, str) and not remote:
if not inside_repo(source_root):
fail(f"{where} points outside the repository with source_root {source_root!r}")
elif not (REPO_ROOT / source_root).is_dir():
fail(f"{where} points at a missing source_root {source_root!r}")
else:
root = (REPO_ROOT / source_root).resolve()
for relative in declared_paths(table):
if not isinstance(relative, str):
fail(f"{where} declares a path as {type(relative).__name__}, expected a string")
elif not has_glob(relative) and not (REPO_ROOT / source_root / relative).exists():
continue
if not inside(root, relative):
fail(f"{where} points outside its source_root with path {relative!r}")
elif not has_glob(relative) and not (root / relative).exists():
fail(f"{where} points at a missing path {relative!r}")

for component, table in dependencies(document):
Expand Down
Loading