diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 231b874..ce68a02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,3 +9,7 @@ Work in a separate source checkout. Describe the problem, resulting behavior, af Official branches and release artifacts are maintainer-controlled. Changes enter them through review; official packages are built from identified source revisions and signed through the release process. A pull request does not itself become an official binary. See [OFFICIAL-RELEASES.md](OFFICIAL-RELEASES.md). Preserve the historical RC7 installer and source evidence. New executable or payload changes require a separately identifiable build. Do not present a modified artifact as the unchanged certified RC7 release. + +## Run source tests + +From a fresh clone, run `python3 tools/run_tests.py` as your normal Linux user. The runner creates the private `source/qa` fixture directory and a temporary test home, clears inherited FrameBridge profile variables, and tests the repository source. It requires the native tools used by the tests, including `tmux`, `openssl`, `dpkg` and `systemd` command-line utilities. Test fixtures are generated from tracked source; no previous build directory or installed profile is copied. diff --git a/tools/run_tests.py b/tools/run_tests.py new file mode 100755 index 0000000..aa5c5f6 --- /dev/null +++ b/tools/run_tests.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Run source tests with generated fixtures and an isolated application environment.""" +from pathlib import Path +import os +import stat +import subprocess +import sys +import tempfile + + +def main(): + root = Path(__file__).resolve().parents[1] + qa = root / "source/qa" + qa.mkdir(mode=0o700, exist_ok=True) + info = qa.lstat() + if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid() or info.st_mode & 0o077: + raise SystemExit("The generated source/qa directory must be private and owned by the test user.") + environment = {key: value for key, value in os.environ.items() + if not key.startswith(("FRAMEBRIDGE_", "FBTERM_", "FB_"))} + with tempfile.TemporaryDirectory(prefix="unit-test-home-", dir=qa) as home: + environment.update( + HOME=home, XDG_DATA_HOME=str(Path(home) / ".local/share"), + XDG_CONFIG_HOME=str(Path(home) / ".config"), + PYTHONPATH=str(root / "source/expanded"), PYTHONDONTWRITEBYTECODE="1") + result = subprocess.run([sys.executable, "-m", "unittest", "discover", "-s", + str(root / "source/tests")], cwd=root, env=environment) + return result.returncode + + +if __name__ == "__main__": + raise SystemExit(main())