Skip to content

docs: fork-changes entries for the CLI wave (836d1a1, 2848e96, ff9ee2… #807

docs: fork-changes entries for the CLI wave (836d1a1, 2848e96, ff9ee2…

docs: fork-changes entries for the CLI wave (836d1a1, 2848e96, ff9ee2… #807

Workflow file for this run

name: Tests
# Path-filter: skip the full workflow when only docs change (markdown
# under any path + the docs/ tree). Mixed PRs (docs + code) still run.
# workflow_dispatch lets us manually re-run the full matrix when needed.
on:
push:
branches: [main, develop]
paths-ignore:
- '**/*.md'
- 'docs/**'
pull_request:
branches: [main, develop]
paths-ignore:
- '**/*.md'
- 'docs/**'
workflow_dispatch:
jobs:
test-linux:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.13"]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- run: pip install -e ".[dev]"
- run: python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace --cov-report=xml --cov-report=term-missing --cov-fail-under=0 --durations=10
- uses: actions/upload-artifact@v4
with:
name: coverage-linux-${{ matrix.python-version }}
path: .coverage
include-hidden-files: true
if-no-files-found: error
# Windows + macOS run only on manual dispatch (Actions budget — Windows
# is 2x Linux billing, macOS is 10x). Trigger via Actions tab → "Tests"
# → Run workflow when a release or platform-sensitive change needs the
# cross-platform pass.
test-windows:
if: github.event_name == 'workflow_dispatch'
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: 'pip'
- run: pip install -e ".[dev]"
# ChromaDB's rust HNSW core intermittently fails compaction or reader
# initialization on Windows ("Failed to apply logs to the hnsw segment
# writer" / "Error creating hnsw segment reader: Nothing found on disk")
# regardless of our code — long-standing, non-reproducible-on-Linux/macOS
# flakes. Retry ONLY those specific transient errors (via --only-rerun) so real,
# deterministic failures still fail on the first run. Linux/macOS jobs
# deliberately run with no reruns so genuine regressions surface there.
- run: >-
python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace
--cov-report=term-missing --cov-fail-under=0 --durations=10 --reruns 2
--reruns-delay 5 --only-rerun
"Failed to apply logs to the hnsw segment writer|Error creating hnsw segment reader: Nothing found on disk"
test-macos:
if: github.event_name == 'workflow_dispatch'
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: 'pip'
- run: pip install -e ".[dev]"
- run: python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace --cov-report=term-missing --durations=10
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.11"
cache: 'pip'
# Keep this pin identical to the `ruff==` pin in pyproject.toml
# ([project.optional-dependencies].dev and [dependency-groups].dev)
# so CI and `pip install -e ".[dev]"` format/lint identically.
# This drifted once (CI on 0.15.14 against pyproject's 0.15.20) and the
# drift was invisible: the lint job never installs from pyproject, so a
# dependabot bump to the pin there passes CI without the new version
# ever running. test_ruff_pins_match asserts the two stay equal.
- run: pip install "ruff==0.16.1"
- run: ruff check .
- run: ruff format --check .
# Postgres-backed tests run against `apache/age:release_PG16_1.6.0`, the
# same image production (`mempalace-db` on familiar) is built on. The AGE
# image is just `postgres:16` with the AGE extension compiled in, so we
# apt-install `postgresql-16-pgvector` into the running service container
# at job start. That matches the substrate documented in
# `tests/test_backends_postgres.py`. The integration suite
# `tests/test_knowledge_graph_age.py` was previously silently skipping
# on the `pgvector/pgvector:pg16` image (no AGE) because every test in
# that file gates on `TEST_POSTGRES_DSN`; promoting the image to AGE
# lets the KG-AGE integration tests actually exercise.
#
# `tests/test_palace_graph.py::TestPostgresFastPath` (#164) carries a
# `@pgmark`-gated test that exercises the postgres-direct aggregate
# path against a live AGE+pgvector instance. The rest of the file is
# mock-based and already runs in `test-linux`; invoking the whole
# file here is the simplest way to unlock the gated case, since
# pytest skips it silently in test-linux otherwise. The unit suite
# `tests/test_age_kg_units.py` and other mock-based KG/queue/worker
# suites (`test_backfill_kg_triples.py`, `test_kg_extraction_queue.py`,
# `test_kg_triple_worker.py`, `test_palace_graph_tunnels.py`,
# `test_searcher_stopwords.py`) are DSN-free and already covered by
# `test-linux`, so we don't repeat them here.
test-postgres:
runs-on: ubuntu-latest
services:
postgres:
image: apache/age:release_PG16_1.6.0
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: mempalace_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: 'pip'
- run: pip install -e ".[dev,postgres]"
- name: Install pgvector into AGE service container
# The apache/age image ships AGE but not pgvector. The production
# substrate apt-installs `postgresql-16-pgvector` on top; mirror
# that here by exec-ing into the running service container.
env:
PG_CONTAINER: ${{ job.services.postgres.id }}
run: |
docker exec "$PG_CONTAINER" bash -c '
set -e
apt-get update -qq
apt-get install -y --no-install-recommends postgresql-16-pgvector
'
- name: Create vector + age extensions
env:
PGURL: postgresql://postgres:postgres@localhost:5432/mempalace_test
run: |
python - <<'PY'
import os, psycopg
conn = psycopg.connect(os.environ["PGURL"])
conn.autocommit = True
cur = conn.cursor()
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
cur.execute("CREATE EXTENSION IF NOT EXISTS age")
print("vector + age extensions ready")
PY
- name: Run postgres-backed tests (backends + AGE knowledge graph)
env:
TEST_POSTGRES_DSN: postgresql://postgres:postgres@localhost:5432/mempalace_test
run: |
python -m pytest \
tests/test_backends_postgres.py \
tests/test_knowledge_graph_age.py \
tests/test_palace_graph.py \
-v --cov=mempalace --cov-report=xml --cov-report=term-missing --cov-fail-under=0
- uses: actions/upload-artifact@v4
with:
name: coverage-postgres
path: .coverage
include-hidden-files: true
if-no-files-found: error
# Combined coverage gate. Each test job runs pytest with --cov but no
# --cov-fail-under; this job downloads every coverage artifact, merges
# them with `coverage combine`, and enforces the 80% threshold once.
# That keeps the bar meaningful even when new code lives in a backend
# only one job exercises (e.g. postgres-only paths covered by
# test-postgres but not by test-linux). See #99.
check-coverage:
runs-on: ubuntu-latest
needs: [test-linux, test-postgres]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: 'pip'
- run: pip install -e ".[dev]"
- uses: actions/download-artifact@v4
with:
pattern: coverage-*
path: coverage-artifacts
- name: Combine coverage and enforce threshold
run: |
# Each artifact directory contains a single .coverage file.
# Rename them to .coverage.<artifact-name> so `coverage combine`
# picks them all up rather than overwriting on collision.
mkdir -p combined
for d in coverage-artifacts/*/; do
name=$(basename "$d")
cp "$d/.coverage" "combined/.coverage.$name"
done
cd combined
coverage combine
# 78% reflects the current combined floor. Low-coverage modules:
# searcher.py (63%), palace_graph_age.py (30%), mcp_server.py (64%).
# Raise back to 80% as test coverage improves for those paths.
coverage report --fail-under=78