Skip to content

Latest commit

 

History

History
113 lines (89 loc) · 5.15 KB

File metadata and controls

113 lines (89 loc) · 5.15 KB

Development

This repo is a Cargo workspace (core/ = the pgpq crate, py/ = the pgpq Python package, json/ = the arrow-json Python package) plus a uv workspace for the Python development environment.

Prerequisites

  • Rust — current stable. The rust-version in the root Cargo.toml ([workspace.package]) is the library floor; the dev-dependencies used by cargo test have a higher one (see the comment next to rust-version), so building the test suite needs a reasonably recent stable.
  • uv — manages the Python environment. Do not use pip directly.
  • PostgreSQL client/server binaries — needed only for the Python test suite (see below). The Rust integration tests download their own Postgres.
  • Optional: a nightly toolchain plus cargo-fuzz for the fuzz targets, and cargo-llvm-cov for local coverage.

Everyday commands

make init          # create the venv from the committed uv.lock + install pre-commit
make build-develop # maturin develop both extension modules into the venv
make test          # cargo test + pytest
make lint          # uv lock --check + dependency-group check + pre-commit (cargo fmt, clippy, ruff)

cargo test alone runs the entire Rust suite (unit tests, byte-exact snapshot tests, the typed roundtrip harness against an embedded Postgres, and a property-based suite — set PROPTEST_CASES=1000 for a longer soak).

Python dependency convention (please read before adding a dep)

The development environment is locked: the root pyproject.toml is a virtual uv workspace root (no [project] table — it is never published) whose [dependency-groups] mirror the union of the [test]/[bench] extras of py/pyproject.toml and json/pyproject.toml. CI installs only the root group (uv sync --locked --only-group test --no-install-workspace), so:

A test dependency must be added in BOTH places — the package extra in py/pyproject.toml (or json/pyproject.toml) and the root [dependency-groups] — then re-run uv lock and commit uv.lock.

Adding it only to the package extra installs nothing anywhere — neither the Makefile's uv sync nor maturin develop consults the extras — but it can appear to work locally when the package happens to be present transitively (or via the bench group, which local make init installs and CI does not). CI, which installs only the root test group, then fails with ModuleNotFoundError. uv lock --check catches a stale lock but not a missing mirror entry, so make lint (and the CI lint job) also runs scripts/check_dep_groups.py, which fails if a root group is no longer a superset of the corresponding extras.

The --no-sync flags on uv run in the Makefile are load-bearing: the maturin-built extension modules are not part of the lock, and an implicit sync would prune them from the venv.

PostgreSQL for the tests

Two different mechanisms, two different requirements:

  • Rust integration tests use postgresql_embedded, which downloads a Postgres build from GitHub releases into ~/.theseus on first run. The download is unauthenticated by default and can hit GitHub's rate limit — set GITHUB_TOKEN in your environment to avoid that (any token with public-repo read access works).

  • Python tests use testing.postgresql, which needs real initdb / postgres binaries on PATH. On macOS with Homebrew:

    brew install postgresql@17
    export PATH="$(brew --prefix postgresql@17)/bin:$PATH"
    export LC_ALL=en_US.UTF-8   # without a UTF-8 locale, initdb fails on macOS
                                # (or comes up SQL_ASCII and text columns decode as bytes)

    Alternatively, once the Rust tests have run, the embedded download works too: export PATH="$HOME/.theseus/postgresql/<version>/bin:$PATH".

Coverage

cargo llvm-cov --workspace --locked --lcov --output-path lcov.info  # Rust
uv run --no-sync pytest py --cov=pgpq --cov-report=term             # Python wrappers

The Python numbers cover only the pure-Python wrapper modules; the compiled extension shows up in the Rust coverage instead. CI uploads both to Codecov (informational only, never a required check).

Snapshots and test data

core/tests/testdata/*.arrow inputs are generated by core/tests/generate_test_data.py; core/tests/snapshots/*.bin are byte-exact encodings of them, and core/tests/snapshots_csv/*.csv are what real Postgres exports after a binary COPY load. If a snapshot is missing, the test writes it and fails — inspect the new file and commit it. A changed snapshot means the wire format changed and deserves scrutiny, not a reflexive regeneration.

Benchmarks and fuzzing

cargo bench -p pgpq --bench yellow_cab_dataset   # downloads a ~40MB parquet on first run
cargo +nightly fuzz run encode_record_batch      # from core/, needs cargo-fuzz

The fuzz crate (core/fuzz/) is its own workspace and is excluded from normal builds; stable cargo test/clippy never touch it.