Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 16 additions & 0 deletions .coveragerc.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Coverage config for CI only — used via COVERAGE_RCFILE=.coveragerc.ci.
#
# Why a separate file: the local .coveragerc sets
# `dynamic_context = test_function`, which coverage.py implements via
# sys.settrace. pytest-cov refuses to run that under pytest-xdist
# (DistCovError, pytest-cov#604) and it adds ~+13.6% runtime
# (AGENT_DIARY 2026-09-15). CI only needs the coverage % gate, not
# per-test contexts, so it uses this context-free config and runs in
# parallel (-n auto). Local/bootstrap runs keep the context-rich
# .coveragerc.
[run]
source = src
branch = true

[report]
show_missing = true
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@ jobs:
pip-audit -r requirements-lock.txt --no-deps --disable-pip

- name: Full test suite (no filters)
run: python -m pytest tests/ -q --tb=short -m 'not slow and not benchmark' --cov=src --cov-report=term-missing --cov-fail-under=38
# -n auto (pytest-xdist): run tests on all runner cores. Measured
# locally 197s -> 71s (x2.76, 12 cores); CI runners have 4 cores.
# COVERAGE_RCFILE=.coveragerc.ci: the local .coveragerc sets
# dynamic_context=test_function, which pytest-cov rejects under xdist
# (DistCovError, pytest-cov#604) and which costs ~+13.6% (sys.settrace).
# CI only needs the coverage % gate, so it uses the context-free config.
env:
COVERAGE_RCFILE: .coveragerc.ci
run: python -m pytest tests/ -q --tb=short -m 'not slow and not benchmark' -n auto --cov=src --cov-report=term-missing --cov-fail-under=38
timeout-minutes: 20

# Grader mutation probe (evalmut-перенос, 2026-08-14): мутационный аудит
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ desktop.ini
.coverage
.coverage*
.coveragerc
!.coveragerc.ci
htmlcov/
.tox/

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ dependencies = [
dev = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"pytest-xdist>=3.5.0", # CI parallel run (-n auto): 197s -> 71s locally
"pytest-cov>=7.1.0", # CI coverage reporting (проверено: 7.1.0 + pytest 9.0.3)
# Pinned: ruff 0.16+ ужесточил I001 (сортировка вложенных импорт-блоков) —
# CI падал на lint 2026-08-05 (10 ошибок в 8 файлах). Бамп — только после
Expand Down
25 changes: 19 additions & 6 deletions tests/test_temporal_facts_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import importlib.util
from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parent.parent

_spec = importlib.util.spec_from_file_location(
Expand All @@ -15,8 +17,19 @@
_spec.loader.exec_module(_mod) # noqa: E402


def test_generate_structure_and_kinds():
facts = _mod.generate(seed=7)
@pytest.fixture(scope="module")
def facts():
"""generate(seed=7) — один раз на модуль (было 5 вызовов, стало 3).

generate() делает десятки git-подпроцессов (~7с на вызов). Три теста
используют один и тот же детерминированный набор. test_deterministic_
generation намеренно вызывает generate дважды (проверка детерминизма),
поэтому его не кэшируем.
"""
return _mod.generate(seed=7)


def test_generate_structure_and_kinds(facts):
assert len(facts) >= 30
kinds = {f["kind"] for f in facts}
assert {"removed", "real", "absent"} <= kinds
Expand All @@ -27,9 +40,9 @@ def test_generate_structure_and_kinds():
assert f["id"].startswith("T")


def test_removed_ground_truth_validated_via_git():
def test_removed_ground_truth_validated_via_git(facts):
"""removed-факт: символ реально был на C~1 (родителе коммита удаления), false@HEAD."""
removed = [f for f in _mod.generate(seed=7) if f["kind"] == "removed"]
removed = [f for f in facts if f["kind"] == "removed"]
assert removed
r = removed[0]
path = r["support_patterns"][0][5:]
Expand All @@ -42,8 +55,8 @@ def test_removed_ground_truth_validated_via_git():
assert r["was_true"] is True and r["truth"] is False


def test_absent_names_are_grep_zero():
for f in _mod.generate(seed=7):
def test_absent_names_are_grep_zero(facts):
for f in facts:
if f["kind"] == "absent":
assert _mod._grep_zero(f["value"]), f"{f['value']} встречается в src/"

Expand Down
Loading