From 7ec05e42f2f82a89732621b24a89f1c8d25a168f Mon Sep 17 00:00:00 2001 From: Cipher208 <269750686+Cipher208@users.noreply.github.com> Date: Wed, 4 Nov 2026 19:51:10 +0100 Subject: [PATCH 1/2] ci: parallel pytest (xdist) + uv installs for the heavy jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four heavy jobs spent 83% of their wall in a sequential pytest run (283s coverage, ~224s per test-matrix leg) while pip re-resolved deps. - make collection deterministic: the source-time parametrize evaluated time.time(), giving every xdist worker its own collection - isolate migrated_cm onto tmp_path (restored on teardown) and ensure_zero_result before the recall_zero_results DELETE — both fed on the shared global base and floated once tests stopped interleaving - add -n auto to the test matrix and coverage jobs (2868 passed in ~75s locally, 4x the sequential wall) - typecheck/test/coverage/docs now use uv sync --locked + uv run; the aiosqlite pin is redundant (uv.lock holds 0.22.1, uv lock --check runs in lint) --- .github/workflows/ci.yml | 24 ++++++++++++++---------- tests/test_features/test_source_time.py | 2 +- tests/test_s17_stage1_tail.py | 13 +++++++++---- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7a7c08c..fc1a3d89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,8 +41,9 @@ jobs: with: python-version: "3.12" cache: "pip" - - run: pip install -e ".[dev,binary]" - - run: mypy --config-file pyproject.toml features/ shared/ mcp_server/ rag/ hooks/ wiki/ lifecycle/ graph/ core/ + - uses: astral-sh/setup-uv@v5 + - run: uv sync --locked --extra dev --extra binary --python 3.12 + - run: uv run mypy --config-file pyproject.toml features/ shared/ mcp_server/ rag/ hooks/ wiki/ lifecycle/ graph/ core/ quality: runs-on: ubuntu-latest @@ -81,9 +82,10 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: "pip" - - run: pip install -e ".[dev,binary]" "aiosqlite>=0.21.0,!=0.22.0" - - run: python scripts/install_ner_models.py - - run: pytest tests/ -v --tb=long --timeout=30 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --locked --extra dev --extra binary --python ${{ matrix.python-version }} + - run: uv run python scripts/install_ner_models.py + - run: uv run pytest tests/ -v --tb=long --timeout=30 -n auto coverage: runs-on: ubuntu-latest @@ -93,9 +95,10 @@ jobs: with: python-version: "3.12" cache: "pip" - - run: pip install -e ".[dev,binary]" "aiosqlite>=0.21.0,!=0.22.0" - - run: python scripts/install_ner_models.py - - run: pytest tests/ --cov=features --cov=shared --cov=core --cov=rag --cov=wiki --cov=graph --cov=lifecycle --cov=hooks --cov=mcp_server --cov-report=xml --cov-report=term-missing --cov-fail-under=60 --timeout=60 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --locked --extra dev --extra binary --python 3.12 + - run: uv run python scripts/install_ner_models.py + - run: uv run pytest tests/ -n auto --cov=features --cov=shared --cov=core --cov=rag --cov=wiki --cov=graph --cov=lifecycle --cov=hooks --cov=mcp_server --cov-report=xml --cov-report=term-missing --cov-fail-under=60 --timeout=60 - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -136,6 +139,7 @@ jobs: with: python-version: "3.12" cache: "pip" - - run: pip install -e ".[docs]" + - uses: astral-sh/setup-uv@v5 + - run: uv sync --locked --extra docs --python 3.12 - name: Build docs (strict — fails on broken refs) - run: mkdocs build --strict + run: uv run mkdocs build --strict diff --git a/tests/test_features/test_source_time.py b/tests/test_features/test_source_time.py index 2e943f35..b4fa30cc 100644 --- a/tests/test_features/test_source_time.py +++ b/tests/test_features/test_source_time.py @@ -241,7 +241,7 @@ async def get_layer(self, *a: Any, **k: Any) -> Any: @pytest.mark.asyncio @pytest.mark.parametrize( ("payload_ts", "expected"), - [(time.time() - 5 * 86400, "forwarded"), ("2026-09-18T07:03:00", "refused"), (None, "refused")], + [(1700000000.0, "forwarded"), ("2026-09-18T07:03:00", "refused"), (None, "refused")], ) async def test_new_message_forwards_a_numeric_ts_and_refuses_anything_else(cm: Any, payload_ts: Any, expected: str, monkeypatch) -> None: """Hop 4: the daemon's `ts` reaches capture — if, and only if, it is a number. diff --git a/tests/test_s17_stage1_tail.py b/tests/test_s17_stage1_tail.py index 4b74f6d8..1fb1e209 100644 --- a/tests/test_s17_stage1_tail.py +++ b/tests/test_s17_stage1_tail.py @@ -174,13 +174,16 @@ async def test_distiller_similar_to_on_conflict() -> None: @pytest.fixture -async def migrated_cm(): +async def migrated_cm(tmp_path, monkeypatch): """Hermetic global DB + полная схема (l0_journal и co-таблицы живут миграциями).""" from shared.connection import connection_manager from shared.migrations import MigrationManager + monkeypatch.setattr(connection_manager, "base_dir", tmp_path) # патчим base_dir, не подменяем объект + connection_manager._conns.clear() # cached conns pin the old tmp dir await MigrationManager(cm=connection_manager).migrate() - return connection_manager + yield connection_manager + connection_manager._conns.clear() @pytest.mark.asyncio @@ -231,11 +234,12 @@ async def test_zero_result_journal_and_miner(migrated_cm) -> None: @pytest.mark.asyncio async def test_zero_result_single_hit_not_surfaced(migrated_cm) -> None: - from lifecycle.graph_miners import log_zero_result, miner_zero_results + from lifecycle.graph_miners import ensure_zero_result, log_zero_result, miner_zero_results from shared.constants import DB_NAME cm = migrated_cm conn = await cm.get(DB_NAME) + await ensure_zero_result(cm) # lazy-ensure таблица: без неё DELETE падает в герметичной БД await conn.execute("DELETE FROM recall_zero_results") # сессионная БД: изолируем журнал await conn.commit() await log_zero_result(cm, "user", "s17zr2", "единичный провал запроса XYZ") @@ -253,11 +257,12 @@ async def test_zero_result_miner_lets_a_failure_escape(migrated_cm, monkeypatch) ноль — это работа, которая не нашлась, а сбой пусть ловит `graph_enrich`. """ from graph.epistemic import EpistemicGraph - from lifecycle.graph_miners import log_zero_result, miner_zero_results + from lifecycle.graph_miners import ensure_zero_result, log_zero_result, miner_zero_results from shared.constants import DB_NAME cm = migrated_cm conn = await cm.get(DB_NAME) + await ensure_zero_result(cm) # lazy-ensure таблица: без неё DELETE падает в герметичной БД await conn.execute("DELETE FROM recall_zero_results") await conn.commit() for _ in range(2): From 5ebb1e4b764eca18e8fc803f04cc9bd6a4a04c33 Mon Sep 17 00:00:00 2001 From: Cipher208 <269750686+Cipher208@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:18:24 +0100 Subject: [PATCH 2/2] ci: seed pip into the venv for the NER-model installer install_ner_models.py shells out to 'sys.executable -m pip install '; a uv-created venv has no pip, so all four converted jobs failed at that step. 'uv venv --seed' before 'uv sync' gives the venv pip (verified locally: sync keeps the seeded pip), and setup-python's pip cache keeps the model wheels warm between runs. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc1a3d89..e984a5dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,11 @@ jobs: python-version: ${{ matrix.python-version }} cache: "pip" - uses: astral-sh/setup-uv@v5 + # Seed pip into the venv: scripts/install_ner_models.py shells out to + # `sys.executable -m pip install `, and a uv-created venv + # has no pip otherwise. `uv sync` keeps the seeded pip, and the pip + # cache (setup-python `cache: pip`) keeps the model wheels warm. + - run: uv venv --seed --python ${{ matrix.python-version }} - run: uv sync --locked --extra dev --extra binary --python ${{ matrix.python-version }} - run: uv run python scripts/install_ner_models.py - run: uv run pytest tests/ -v --tb=long --timeout=30 -n auto @@ -96,6 +101,7 @@ jobs: python-version: "3.12" cache: "pip" - uses: astral-sh/setup-uv@v5 + - run: uv venv --seed --python 3.12 # NER installer needs pip in the venv - run: uv sync --locked --extra dev --extra binary --python 3.12 - run: uv run python scripts/install_ner_models.py - run: uv run pytest tests/ -n auto --cov=features --cov=shared --cov=core --cov=rag --cov=wiki --cov=graph --cov=lifecycle --cov=hooks --cov=mcp_server --cov-report=xml --cov-report=term-missing --cov-fail-under=60 --timeout=60