ci: execute tagged module pytest suites
All checks were successful
Security Audit / security-audit (push) Successful in 4m3s
Security Audit Toolbox Update / toolbox-update (push) Successful in 4s
Dependency Audit / dependency-audit (push) Successful in 1m51s

This commit is contained in:
2026-07-23 01:18:47 +02:00
parent 52bd3527cd
commit 8d292184d4
5 changed files with 68 additions and 24 deletions

View File

@@ -28,8 +28,7 @@ jobs:
run: | run: |
python -m venv .venv python -m venv .venv
.venv/bin/python tools/repo/sync-python-environment.py --requirements requirements-release.txt --python .venv/bin/python --upgrade-pip .venv/bin/python tools/repo/sync-python-environment.py --requirements requirements-release.txt --python .venv/bin/python --upgrade-pip
.venv/bin/python -m pip install '../govoplan-core[dev]' .venv/bin/python -m pip install -r requirements-release-tests.txt '../govoplan-core[dev]'
.venv/bin/python -m pip install -r requirements-release-tests.txt
- name: Install WebUI release dependencies - name: Install WebUI release dependencies
working-directory: govoplan working-directory: govoplan
run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui

View File

@@ -32,5 +32,6 @@ idna>=3.15
jsonschema>=4,<5 jsonschema>=4,<5
pip>=26.1.2 pip>=26.1.2
pip-audit>=2.9,<3 pip-audit>=2.9,<3
pytest>=8,<9
python-multipart>=0.0.31 python-multipart>=0.0.31
ruff>=0.14,<1 ruff>=0.14,<1

View File

@@ -1,8 +1,10 @@
from __future__ import annotations from __future__ import annotations
import os
import tempfile import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import patch
from tools.checks.release_integration import ( from tools.checks.release_integration import (
SOURCE_COUPLED_CORE_TESTS, SOURCE_COUPLED_CORE_TESTS,
@@ -10,6 +12,7 @@ from tools.checks.release_integration import (
artifact_contract_issues, artifact_contract_issues,
filter_test_suite, filter_test_suite,
release_package_names, release_package_names,
run_module_release_tests,
) )
@@ -89,8 +92,43 @@ class ReleaseIntegrationTests(unittest.TestCase):
self.assertLess(artifact_check, core_tests) self.assertLess(artifact_check, core_tests)
self.assertLess(core_tests, module_tests) self.assertLess(core_tests, module_tests)
self.assertNotIn("unittest discover", script)
self.assertNotIn('"$PYTHON" -m unittest \\\n tests.test_module_system', script) self.assertNotIn('"$PYTHON" -m unittest \\\n tests.test_module_system', script)
def test_tagged_module_runner_executes_pytest_functions_from_module_root(self) -> None:
with tempfile.TemporaryDirectory(prefix="govoplan-release-module-tests-") as temp_dir:
module_root = Path(temp_dir)
tests_root = module_root / "tests"
tests_root.mkdir()
marker = module_root / "pytest-function-ran"
(tests_root / "test_probe.py").write_text(
"""from pathlib import Path
import os
def test_pytest_style_function():
expected_root = Path(os.environ[\"GOVOPLAN_TEST_MODULE_ROOT\"])
assert Path.cwd() == expected_root
Path(os.environ[\"GOVOPLAN_TEST_MARKER\"]).write_text(\"ran\", encoding=\"utf-8\")
""",
encoding="utf-8",
)
with patch.dict(
os.environ,
{
"GOVOPLAN_TEST_MODULE_ROOT": str(module_root),
"GOVOPLAN_TEST_MARKER": str(marker),
},
):
status = run_module_release_tests(module_root)
self.assertEqual(status, 0)
self.assertEqual(marker.read_text(encoding="utf-8"), "ran")
def test_tagged_module_runner_skips_missing_test_tree(self) -> None:
with tempfile.TemporaryDirectory(prefix="govoplan-release-module-tests-") as temp_dir:
self.assertEqual(run_module_release_tests(Path(temp_dir)), 0)
def test_module_matrix_uses_artifact_aware_core_suite(self) -> None: def test_module_matrix_uses_artifact_aware_core_suite(self) -> None:
meta_root = Path(__file__).resolve().parents[1] meta_root = Path(__file__).resolve().parents[1]
script = (meta_root / "tools" / "checks" / "check-module-matrix.sh").read_text(encoding="utf-8") script = (meta_root / "tools" / "checks" / "check-module-matrix.sh").read_text(encoding="utf-8")

View File

@@ -60,24 +60,6 @@ retry() {
done done
} }
run_discovered_tests() {
local suite_dir="$1"
local status
if ! find "$suite_dir" -type f -name 'test*.py' -print -quit | grep -q .; then
echo "No unittest test files found under $suite_dir; skipping."
return 0
fi
set +e
"$PYTHON" -m unittest discover -s "$suite_dir"
status=$?
set -e
if [[ "$status" == 5 ]]; then
echo "No unittest tests discovered under $suite_dir; skipping."
return 0
fi
return "$status"
}
install_cloned_webui_dependencies() { install_cloned_webui_dependencies() {
local webui_dir="$1" local webui_dir="$1"
if [[ ! -f "$webui_dir/package.json" ]]; then if [[ ! -f "$webui_dir/package.json" ]]; then
@@ -253,9 +235,10 @@ run_step "Run core backend release tests"
"$PYTHON" "$META_ROOT/tools/checks/check_dependency_boundaries.py" "$PYTHON" "$META_ROOT/tools/checks/check_dependency_boundaries.py"
run_step "Run cloned module backend tests" run_step "Run cloned module backend tests"
run_discovered_tests "$WORK_ROOT/govoplan-mail/tests" for repo in govoplan-mail govoplan-calendar govoplan-campaign; do
run_discovered_tests "$WORK_ROOT/govoplan-calendar/tests" "$PYTHON" "$META_ROOT/tools/checks/release_integration.py" module-tests \
run_discovered_tests "$WORK_ROOT/govoplan-campaign/tests" --module-root "$WORK_ROOT/$repo"
done
run_step "Run core WebUI release tests and build" run_step "Run core WebUI release tests and build"
cd "$ROOT/webui" cd "$ROOT/webui"

View File

@@ -5,6 +5,7 @@ import argparse
import importlib.metadata as metadata import importlib.metadata as metadata
import os import os
import re import re
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
@@ -354,6 +355,24 @@ def run_core_release_tests(core_root: Path) -> int:
return 0 if result.wasSuccessful() else 1 return 0 if result.wasSuccessful() else 1
def run_module_release_tests(module_root: Path) -> int:
resolved_root = module_root.resolve()
tests_root = resolved_root / "tests"
if not tests_root.is_dir() or not any(tests_root.rglob("test*.py")):
print(f"No test files found under {tests_root}; skipping.")
return 0
result = subprocess.run(
(sys.executable, "-m", "pytest", "-q", "tests"),
cwd=resolved_root,
check=False,
)
if result.returncode == 5:
print(f"No tests collected under {tests_root}; skipping.")
return 0
return result.returncode
def _parser() -> argparse.ArgumentParser: def _parser() -> argparse.ArgumentParser:
meta_root = Path(__file__).resolve().parents[2] meta_root = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser(description="Artifact-aware GovOPlaN release integration checks") parser = argparse.ArgumentParser(description="Artifact-aware GovOPlaN release integration checks")
@@ -366,6 +385,8 @@ def _parser() -> argparse.ArgumentParser:
) )
core_parser = subparsers.add_parser("core-tests", help="Run Core tests that are valid for tagged module artifacts") core_parser = subparsers.add_parser("core-tests", help="Run Core tests that are valid for tagged module artifacts")
core_parser.add_argument("--core-root", type=Path, required=True) core_parser.add_argument("--core-root", type=Path, required=True)
module_parser = subparsers.add_parser("module-tests", help="Run tests from one tagged module source checkout")
module_parser.add_argument("--module-root", type=Path, required=True)
return parser return parser
@@ -373,7 +394,9 @@ def main(argv: Sequence[str] | None = None) -> int:
args = _parser().parse_args(argv) args = _parser().parse_args(argv)
if args.command == "artifacts": if args.command == "artifacts":
return run_installed_artifact_checks(args.requirements) return run_installed_artifact_checks(args.requirements)
return run_core_release_tests(args.core_root) if args.command == "core-tests":
return run_core_release_tests(args.core_root)
return run_module_release_tests(args.module_root)
if __name__ == "__main__": if __name__ == "__main__":