From 90c9ffe839ab3a4b8583b7a4cf4432a8e9c1a084 Mon Sep 17 00:00:00 2001 From: orchestrator-dev Date: Thu, 4 Jun 2026 00:43:04 +0300 Subject: [PATCH] fix(qg): run pytest directly instead of make in check_tests_local --- src/qg/checks.py | 8 +++++++- tests/test_qg.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/qg/checks.py b/src/qg/checks.py index 01665b1..769c94d 100644 --- a/src/qg/checks.py +++ b/src/qg/checks.py @@ -252,6 +252,11 @@ def check_tests_local(repo: str, branch: str) -> tuple[bool, str]: S-1 fix: run the project test suite locally and judge by exit code, instead of depending on Gitea CI (which is not configured -> always false). + БАГ 5 fix: invoke pytest directly instead of make test. make is not installed + in the orchestrator container, so the previous ["make", "test"] call raised + FileNotFoundError. This reproduces the Makefile test target 1:1 + (cd src/api && python -m pytest ../../tests/ -v). + ORCH-2 / S-4: tests run inside the per-branch worktree (ensure_worktree), so this is safe for concurrent active tasks — no shared /repos checkout race. """ @@ -259,7 +264,8 @@ def check_tests_local(repo: str, branch: str) -> tuple[bool, str]: try: repo_path = ensure_worktree(repo, branch) r = subprocess.run( - ["make", "test"], cwd=repo_path, + ["python", "-m", "pytest", "../../tests/", "-v"], + cwd=os.path.join(repo_path, "src", "api"), capture_output=True, text=True, timeout=600, ) if r.returncode == 0: diff --git a/tests/test_qg.py b/tests/test_qg.py index fbaf52c..ed4adaf 100644 --- a/tests/test_qg.py +++ b/tests/test_qg.py @@ -17,6 +17,7 @@ from src.qg.checks import ( check_ci_green, check_review_approved, check_tests_passed, + check_tests_local, ) @@ -186,3 +187,41 @@ class TestCheckTestsPassed: passed, reason = check_tests_passed("enduro-trails", "ET-001") assert passed is False assert "not found" in reason.lower() + + +class TestCheckTestsLocal: + """BUG 5: check_tests_local must run pytest directly (not make, which is + not installed in the orchestrator container).""" + + @patch("src.qg.checks.ensure_worktree") + @patch("subprocess.run") + def test_passes_on_returncode_zero(self, mock_run, mock_wt, tmp_path): + mock_wt.return_value = str(tmp_path) + mock_run.return_value = MagicMock(returncode=0, stdout="ok", stderr="") + passed, reason = check_tests_local("enduro-trails", "feature/ET-001-x") + assert passed is True + assert reason == "Local tests passed" + + @patch("src.qg.checks.ensure_worktree") + @patch("subprocess.run") + def test_fails_on_nonzero_returncode(self, mock_run, mock_wt, tmp_path): + mock_wt.return_value = str(tmp_path) + mock_run.return_value = MagicMock(returncode=1, stdout="boom", stderr="trace") + passed, reason = check_tests_local("enduro-trails", "feature/ET-001-x") + assert passed is False + assert "Local tests failed" in reason + + @patch("src.qg.checks.ensure_worktree") + @patch("subprocess.run") + def test_invokes_pytest_not_make(self, mock_run, mock_wt, tmp_path): + """The subprocess call must be pytest, from src/api, against ../../tests/.""" + mock_wt.return_value = str(tmp_path) + mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") + check_tests_local("enduro-trails", "feature/ET-001-x") + args, kwargs = mock_run.call_args + cmd = args[0] + assert "make" not in cmd + assert cmd[:3] == ["python", "-m", "pytest"] + assert "../../tests/" in cmd + assert kwargs["cwd"] == os.path.join(str(tmp_path), "src", "api") + -- 2.49.1