Skip to content

[codex] add python test infrastructure#42

Merged
hartsock merged 3 commits into
mainfrom
codex/issue-20-python-test-infra
Jun 7, 2026
Merged

[codex] add python test infrastructure#42
hartsock merged 3 commits into
mainfrom
codex/issue-20-python-test-infra

Conversation

@hartsock

@hartsock hartsock commented Jun 6, 2026

Copy link
Copy Markdown
Owner

What changed

  • Added pytest, pytest-cov, and pytest-mock as the dev optional dependency group.
  • Added pytest and coverage configuration for the Python package.
  • Added a mock_gitxtend fixture for Python unit tests to patch the compiled extension surface, including planned write-side exports.
  • Marked the existing E2E suite as an integration test and moved CI/pre-push Python validation to pytest coverage.
  • Kept the M2 coverage gate intentionally broad: all pytest tests under python/tests/, including integration tests, contribute to the current 80% coverage gate.
  • Ignored generated coverage and Windows debug symbol artifacts.

Why

This creates the Python test infrastructure needed by the remaining M2 Python issues and gives CI an 80% Python coverage gate.

Closes #20

Validation

  • cargo fmt --all --check
  • git diff --check
  • .venv\Scripts\maturin develop --release --extras dev
  • .venv\Scripts\python -m pytest python\tests\ --cov=gitxtend --cov-fail-under=80 (20 passed, 100% coverage)
  • cargo clippy --no-default-features --all-targets -- -D warnings
  • cargo test --no-default-features (47 passed, 0 failed)
  • cargo clippy --features extension-module -- -D warnings
  • cargo build --features extension-module

Refs #20

Co-authored-by: Codex <codex@openai.com>

@hartsock hartsock left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: Drake (drake-interactive, Claude Opus 4.7 / 1M context), on behalf of @hartsock.

Solid parts

  • pyproject.toml config (pytest options, coverage source/branch/fail_under) matches issue #20 exactly.
  • The mock_gitxtend fixture correctly patches two locations (gitxtend._gitxtend.X AND gitxtend.X). That second patch is necessary because python/gitxtend/__init__.py does from ._gitxtend import (...), so the names are bound into the gitxtend namespace at import time — patching only the source module wouldn't reach call sites that look up gitxtend.is_git_repo. Good catch.
  • pytestmark = pytest.mark.integration on test_e2e.py plus --strict-markers is the right shape.
  • .githooks/pre-push adds a Windows code path via .venv/Scripts/maturin.exe.

Issues to address before un-drafting

1. test_mock_gitxtend_fixture_includes_write_side_exports is tautological

MagicMock returns True for hasattr(m, 'anything') regardless — the test would pass even if the fixture loop never ran. Replace with a check that actually inspects what the fixture did:

def test_mock_gitxtend_fixture_includes_write_side_exports(mock_gitxtend):
    import gitxtend
    import gitxtend._gitxtend as extension
    for name in (
        "pull", "push", "add", "commit",
        "stash_push", "stash_pop", "create_branch",
        "reset_hard", "rebase", "stash_rebase",
    ):
        attr = getattr(mock_gitxtend, name)
        assert getattr(extension, name) is attr   # same mock landed on the source
        sentinel = object()
        attr.return_value = sentinel
        assert getattr(gitxtend, name)() is sentinel  # patched through to gitxtend.X

2. The integration marker is declared but not used for filtering

Issue #20 says: "test_e2e.py is kept as an integration test (marked --integration) but does not block the coverage gate."

The PR runs pytest python/tests/ (collects everything, integration included), so integration tests contribute to the coverage measurement AND can fail the gate. Either:

  • Split into two invocations: pytest -m "not integration" --cov=gitxtend --cov-fail-under=80 (unit, with the gate) followed by pytest -m integration (integration, no gate).
  • Or amend #20's intent so "everything contributes" is fine.

3. Wasted wheel build in CI

pip install -q ".[dev]" maturin builds + installs gitxtend's wheel via maturin's PEP 517 backend, then maturin develop --release immediately overwrites it. Cheaper:

.venv/bin/pip install -q maturin pytest pytest-cov pytest-mock

Not a blocker — CI works as written — but a few minutes per run adds up over a daily cadence.

4. PUBLIC_EXTENSION_NAMES lists write-side names that don't exist yet

pull, push, add, commit, stash_push, etc. are not in python/gitxtend/__init__.py until a future PyO3-binding PR lands. The fixture handles this gracefully (raising=False), but the list will be a maintenance burden until the bindings land — and a typo here could go undetected for a while. Consider importing the list from a single source-of-truth tuple in python/gitxtend/__init__.py once both PRs settle, so a future addition only needs to be made in one place.

Recommended pre-merge checklist

  • Rewrite test_mock_gitxtend_fixture_includes_write_side_exports with a non-tautological assertion (snippet above).
  • Decide on integration-vs-unit split for the coverage gate (or update #20 if "everything contributes" is intended).
  • Trim the CI install command.

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
@hartsock
hartsock marked this pull request as ready for review June 7, 2026 00:28
@hartsock
hartsock merged commit 6d4fb65 into main Jun 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M2-T1: Python test infrastructure — pytest + coverage ≥80%

1 participant