Merge branch 'main' into codex/fix-bossbot-assets-publish

This commit is contained in:
Paul Hernandez
2026-06-09 19:23:27 -05:00
committed by GitHub
5 changed files with 471 additions and 40 deletions
+6 -8
View File
@@ -13,9 +13,10 @@ env:
jobs:
docker:
runs-on: ubuntu-latest
runs-on: depot-ubuntu-24.04
permissions:
contents: read
id-token: write
packages: write
steps:
@@ -24,10 +25,8 @@ jobs:
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
platforms: linux/amd64,linux/arm64
- name: Set up Depot
uses: depot/setup-action@v1
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
@@ -49,13 +48,12 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: depot/build-push-action@v1
with:
project: ${{ vars.DEPOT_BASIC_MEMORY_PROJECT_ID || vars.DEPOT_PROJECT_ID }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+84 -9
View File
@@ -13,12 +13,15 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
# Required CI records pytest-testmon data but still runs the full selected suite.
# The selective mode stays on explicit developer flows such as `just testmon`.
BASIC_MEMORY_TESTMON_FLAGS: "--testmon-noselect"
jobs:
static-checks:
name: Static Checks (Python 3.12)
timeout-minutes: 20
runs-on: ubuntu-latest
runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v6
@@ -60,10 +63,12 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: depot-ubuntu-24.04
python-version: "3.12"
- os: ubuntu-latest
- os: depot-ubuntu-24.04
python-version: "3.13"
# Python 3.14 unit tests are the longest full-suite slice; keep this
# one on GitHub-hosted runners after Depot terminated it mid-suite.
- os: ubuntu-latest
python-version: "3.14"
- os: windows-latest
@@ -87,6 +92,19 @@ jobs:
- uses: extractions/setup-just@v4
- name: Cache pytest-testmon results
uses: actions/cache@v4
with:
path: |
.testmondata
.testmondata-shm
.testmondata-wal
key: ${{ runner.os }}-testmon-sqlite-unit-py${{ matrix.python-version }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-testmon-sqlite-unit-py${{ matrix.python-version }}-${{ github.ref_name }}-
${{ runner.os }}-testmon-sqlite-unit-py${{ matrix.python-version }}-main-
${{ runner.os }}-testmon-sqlite-unit-py${{ matrix.python-version }}-
- name: Create virtual env
run: |
uv venv
@@ -106,11 +124,11 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: depot-ubuntu-24.04
python-version: "3.12"
- os: ubuntu-latest
- os: depot-ubuntu-24.04
python-version: "3.13"
- os: ubuntu-latest
- os: depot-ubuntu-24.04
python-version: "3.14"
- os: windows-latest
python-version: "3.12"
@@ -133,6 +151,19 @@ jobs:
- uses: extractions/setup-just@v4
- name: Cache pytest-testmon results
uses: actions/cache@v4
with:
path: |
.testmondata
.testmondata-shm
.testmondata-wal
key: ${{ runner.os }}-testmon-sqlite-integration-py${{ matrix.python-version }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-testmon-sqlite-integration-py${{ matrix.python-version }}-${{ github.ref_name }}-
${{ runner.os }}-testmon-sqlite-integration-py${{ matrix.python-version }}-main-
${{ runner.os }}-testmon-sqlite-integration-py${{ matrix.python-version }}-
- name: Create virtual env
run: |
uv venv
@@ -153,9 +184,14 @@ jobs:
matrix:
include:
- python-version: "3.12"
os: depot-ubuntu-24.04
- python-version: "3.13"
os: depot-ubuntu-24.04
# Match the SQLite unit slice: this full Python 3.14 path outlived
# the Depot runner and was terminated mid-suite.
- python-version: "3.14"
runs-on: ubuntu-latest
os: ubuntu-latest
runs-on: ${{ matrix.os }}
services:
postgres:
image: pgvector/pgvector:pg16
@@ -190,6 +226,19 @@ jobs:
- uses: extractions/setup-just@v4
- name: Cache pytest-testmon results
uses: actions/cache@v4
with:
path: |
.testmondata
.testmondata-shm
.testmondata-wal
key: ${{ runner.os }}-testmon-postgres-unit-py${{ matrix.python-version }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-testmon-postgres-unit-py${{ matrix.python-version }}-${{ github.ref_name }}-
${{ runner.os }}-testmon-postgres-unit-py${{ matrix.python-version }}-main-
${{ runner.os }}-testmon-postgres-unit-py${{ matrix.python-version }}-
- name: Create virtual env
run: |
uv venv
@@ -212,7 +261,7 @@ jobs:
- python-version: "3.12"
- python-version: "3.13"
- python-version: "3.14"
runs-on: ubuntu-latest
runs-on: depot-ubuntu-24.04
services:
postgres:
image: pgvector/pgvector:pg16
@@ -247,6 +296,19 @@ jobs:
- uses: extractions/setup-just@v4
- name: Cache pytest-testmon results
uses: actions/cache@v4
with:
path: |
.testmondata
.testmondata-shm
.testmondata-wal
key: ${{ runner.os }}-testmon-postgres-integration-py${{ matrix.python-version }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-testmon-postgres-integration-py${{ matrix.python-version }}-${{ github.ref_name }}-
${{ runner.os }}-testmon-postgres-integration-py${{ matrix.python-version }}-main-
${{ runner.os }}-testmon-postgres-integration-py${{ matrix.python-version }}-
- name: Create virtual env
run: |
uv venv
@@ -262,7 +324,7 @@ jobs:
test-semantic:
name: Test Semantic (Python 3.12)
timeout-minutes: 45
runs-on: ubuntu-latest
runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v6
@@ -281,6 +343,19 @@ jobs:
- uses: extractions/setup-just@v4
- name: Cache pytest-testmon results
uses: actions/cache@v4
with:
path: |
.testmondata
.testmondata-shm
.testmondata-wal
key: ${{ runner.os }}-testmon-semantic-py3.12-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-testmon-semantic-py3.12-${{ github.ref_name }}-
${{ runner.os }}-testmon-semantic-py3.12-main-
${{ runner.os }}-testmon-semantic-py3.12-
- name: Create virtual env
run: |
uv venv
+42 -23
View File
@@ -1,5 +1,9 @@
# Basic Memory - Modern Command Runner
TESTMON_FLAGS := env_var_or_default("BASIC_MEMORY_TESTMON_FLAGS", "--testmon-noselect")
TESTMON_SELECT_FLAGS := env_var_or_default("BASIC_MEMORY_TESTMON_SELECT_FLAGS", "--testmon --testmon-forceselect")
TESTMON_REFRESH_FLAGS := env_var_or_default("BASIC_MEMORY_TESTMON_REFRESH_FLAGS", "--testmon-noselect")
# Install dependencies
install:
uv sync
@@ -35,40 +39,55 @@ test-sqlite: test-unit-sqlite test-int-sqlite
test-postgres: test-unit-postgres test-int-postgres
# Run unit tests against SQLite
test-unit-sqlite:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov tests
test-unit-sqlite: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=unit-sqlite tests
# Run unit tests against Postgres
test-unit-postgres:
BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov tests
test-unit-postgres: testmon-seed
BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=unit-postgres tests
# Run integration tests against SQLite (excludes semantic benchmarks — use just test-semantic)
test-int-sqlite:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov -m "not semantic" test-int
test-int-sqlite: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=int-sqlite -m "not semantic" test-int
# Run integration tests against Postgres
# Note: Uses timeout due to FastMCP Client + asyncpg cleanup hang (tests pass, process hangs on exit)
# See: https://github.com/jlowin/fastmcp/issues/1311
test-int-postgres:
test-int-postgres: testmon-seed
#!/usr/bin/env bash
set -euo pipefail
# Use gtimeout (macOS/Homebrew) or timeout (Linux)
TIMEOUT_CMD=$(command -v gtimeout || command -v timeout || echo "")
if [[ -n "$TIMEOUT_CMD" ]]; then
$TIMEOUT_CMD --signal=KILL 600 bash -c 'BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov -m "not semantic" test-int' || test $? -eq 137
$TIMEOUT_CMD --signal=KILL 600 bash -c 'BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=int-postgres -m "not semantic" test-int' || test $? -eq 137
else
echo "⚠️ No timeout command found, running without timeout..."
BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov -m "not semantic" test-int
BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=int-postgres -m "not semantic" test-int
fi
# Run tests impacted by recent changes (requires pytest-testmon)
# Pass paths or node ids after `just testmon` to limit the candidate set further.
testmon *args:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov --testmon {{args}}
testmon *args: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_SELECT_FLAGS}} --testmon-env=local {{args}}
# Seed pytest-testmon data into this worktree from the shared Git cache.
testmon-seed:
uv run python scripts/testmon_cache.py seed
# Refresh the shared pytest-testmon cache from a full backend test run.
testmon-refresh:
#!/usr/bin/env bash
set -euo pipefail
BASIC_MEMORY_TESTMON_FLAGS="{{TESTMON_REFRESH_FLAGS}}" just test
uv run python scripts/testmon_cache.py refresh
# Show local and shared pytest-testmon cache locations.
testmon-status:
uv run python scripts/testmon_cache.py status
# Run MCP smoke test (fast end-to-end loop)
test-smoke:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov -m smoke test-int/mcp/test_smoke_integration.py
test-smoke: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=smoke -m smoke test-int/mcp/test_smoke_integration.py
# Fast local loop: lint, format, typecheck, impacted tests via pytest-testmon
fast-check:
@@ -97,18 +116,18 @@ postgres-migrate:
# Run Windows-specific tests only (only works on Windows platform)
# These tests verify Windows-specific database optimizations (locking mode, NullPool)
# Will be skipped automatically on non-Windows platforms
test-windows:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov -m windows tests test-int
test-windows: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=windows -m windows tests test-int
# Run benchmark tests only (performance testing)
# These are slow tests that measure sync performance with various file counts
# Excluded from default test runs to keep CI fast
test-benchmark:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov -m benchmark tests test-int
test-benchmark: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=benchmark -m benchmark tests test-int
# Run semantic search quality benchmarks (all combos)
test-semantic:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov -m semantic test-int/semantic/
test-semantic: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=semantic -m semantic test-int/semantic/
# Run semantic benchmarks with JSON artifact output, then show report
test-semantic-report:
@@ -120,8 +139,8 @@ test-litellm-live *args:
BASIC_MEMORY_ENV=test BASIC_MEMORY_RUN_LITELLM_INTEGRATION=1 PYTHONPATH=test-int:src uv run python -m semantic.litellm_live_harness {{args}}
# Run semantic benchmarks (Postgres combos only)
test-semantic-postgres:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov -m semantic -k postgres test-int/semantic/
test-semantic-postgres: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=semantic-postgres -m semantic -k postgres test-int/semantic/
# View semantic benchmark results (rich formatted table)
# Usage: just semantic-report [--filter-combo sqlite] [--filter-suite paraphrase] [--sort-by avg_latency_ms]
@@ -137,8 +156,8 @@ benchmark-compare baseline candidate *args:
# Run all tests including Windows, Postgres, and Benchmarks (for CI/comprehensive testing)
# Use this before releasing to ensure everything works across all backends and platforms
test-all:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov tests test-int
test-all: testmon-seed
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{TESTMON_FLAGS}} --testmon-env=all tests test-int
# Generate HTML coverage report
coverage:
+225
View File
@@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""Seed and refresh shared pytest-testmon data for Git worktrees."""
from __future__ import annotations
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import NamedTuple
TESTMON_FILENAMES = (".testmondata", ".testmondata-shm", ".testmondata-wal")
TESTMON_CACHE_ENV = "BM_TESTMON_CACHE_DIR"
class TestmonCacheResult(NamedTuple):
status: str
source_dir: Path
destination_dir: Path
copied: tuple[Path, ...]
def _run_git(args: list[str], cwd: Path) -> str:
return subprocess.check_output(["git", *args], cwd=cwd, text=True).strip()
def resolve_repo_root(repo_root: Path | None = None) -> Path:
if repo_root is not None:
return repo_root.expanduser().resolve()
return Path(_run_git(["rev-parse", "--show-toplevel"], Path.cwd())).resolve()
def resolve_cache_dir(repo_root: Path, cache_dir: Path | None = None) -> Path:
if cache_dir is not None:
return cache_dir.expanduser().resolve()
if env_cache_dir := os.environ.get(TESTMON_CACHE_ENV):
return Path(env_cache_dir).expanduser().resolve()
git_common_dir = Path(_run_git(["rev-parse", "--git-common-dir"], repo_root))
if not git_common_dir.is_absolute():
git_common_dir = repo_root / git_common_dir
return git_common_dir.resolve() / "testmon-cache" / "main"
def _testmon_datafile(directory: Path) -> Path:
return directory / ".testmondata"
def _testmon_files(directory: Path) -> list[Path]:
return [
directory / filename for filename in TESTMON_FILENAMES if (directory / filename).is_file()
]
def _remove_path(path: Path) -> None:
if path.is_dir():
shutil.rmtree(path)
elif path.exists():
path.unlink()
def _copy_testmon_files(source_dir: Path, destination_dir: Path) -> tuple[Path, ...]:
destination_dir.mkdir(parents=True, exist_ok=True)
copied: list[Path] = []
for source in _testmon_files(source_dir):
destination = destination_dir / source.name
shutil.copy2(source, destination)
copied.append(destination)
return tuple(copied)
def seed_testmon_data(repo_root: Path, cache_dir: Path) -> TestmonCacheResult:
local_datafile = _testmon_datafile(repo_root)
shared_datafile = _testmon_datafile(cache_dir)
if local_datafile.exists():
return TestmonCacheResult(
status="exists",
source_dir=cache_dir,
destination_dir=repo_root,
copied=(),
)
if not shared_datafile.exists():
return TestmonCacheResult(
status="missing",
source_dir=cache_dir,
destination_dir=repo_root,
copied=(),
)
# A worktree with sidecars but no main database is stale; replace the set
# together so SQLite never sees a mixed local/cache snapshot.
for filename in TESTMON_FILENAMES:
_remove_path(repo_root / filename)
copied = _copy_testmon_files(cache_dir, repo_root)
return TestmonCacheResult(
status="seeded",
source_dir=cache_dir,
destination_dir=repo_root,
copied=copied,
)
def refresh_testmon_data(repo_root: Path, cache_dir: Path) -> TestmonCacheResult:
local_datafile = _testmon_datafile(repo_root)
if not local_datafile.exists():
raise FileNotFoundError(
f"No local pytest-testmon data at {local_datafile}; run tests first."
)
cache_parent = cache_dir.parent
cache_parent.mkdir(parents=True, exist_ok=True)
temp_dir = Path(tempfile.mkdtemp(prefix=f".{cache_dir.name}.", dir=cache_parent))
backup_dir = cache_parent / f".{cache_dir.name}.previous-{os.getpid()}"
copied: tuple[Path, ...] = ()
try:
copied = _copy_testmon_files(repo_root, temp_dir)
_remove_path(backup_dir)
if cache_dir.exists():
cache_dir.rename(backup_dir)
try:
temp_dir.rename(cache_dir)
except Exception:
if backup_dir.exists() and not cache_dir.exists():
backup_dir.rename(cache_dir)
raise
finally:
_remove_path(temp_dir)
_remove_path(backup_dir)
return TestmonCacheResult(
status="refreshed",
source_dir=repo_root,
destination_dir=cache_dir,
copied=tuple(cache_dir / path.name for path in copied),
)
def _print_seed_result(result: TestmonCacheResult) -> None:
if result.status == "seeded":
print(f"Seeded pytest-testmon data from {result.source_dir} into {result.destination_dir}")
elif result.status == "exists":
print(f"Local pytest-testmon data already exists at {result.destination_dir}")
elif result.status == "missing":
print(
f"No shared pytest-testmon baseline at {result.source_dir}; "
"run `just testmon-refresh` after a full backend test run to create one."
)
else:
raise ValueError(f"Unexpected seed result: {result.status}")
def _print_refresh_result(result: TestmonCacheResult) -> None:
print(f"Published pytest-testmon data from {result.source_dir} to {result.destination_dir}")
def _print_status(repo_root: Path, cache_dir: Path) -> None:
print(f"Repo root: {repo_root}")
print(f"Worktree data: {_testmon_datafile(repo_root)}")
print(f"Shared cache: {_testmon_datafile(cache_dir)}")
print(f"Worktree ready: {_testmon_datafile(repo_root).exists()}")
print(f"Cache ready: {_testmon_datafile(cache_dir).exists()}")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--repo-root",
type=Path,
help="Repository root to operate on (default: git rev-parse --show-toplevel)",
)
parser.add_argument(
"--cache-dir",
type=Path,
help=(
"Shared testmon cache directory "
f"(default: ${TESTMON_CACHE_ENV} or <git-common-dir>/testmon-cache/main)"
),
)
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser("seed", help="Copy shared testmon data into this worktree if missing")
subparsers.add_parser("refresh", help="Publish this worktree's testmon data to the cache")
subparsers.add_parser("status", help="Show local and shared testmon data paths")
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
repo_root = resolve_repo_root(args.repo_root)
cache_dir = resolve_cache_dir(repo_root, args.cache_dir)
if args.command == "seed":
_print_seed_result(seed_testmon_data(repo_root=repo_root, cache_dir=cache_dir))
return 0
if args.command == "refresh":
_print_refresh_result(refresh_testmon_data(repo_root=repo_root, cache_dir=cache_dir))
return 0
if args.command == "status":
_print_status(repo_root=repo_root, cache_dir=cache_dir)
return 0
parser.error(f"Unknown command: {args.command}")
return 2
if __name__ == "__main__":
sys.exit(main())
+114
View File
@@ -0,0 +1,114 @@
from pathlib import Path
import pytest
from scripts import testmon_cache
def _write_testmon_file(directory: Path, filename: str, content: str) -> Path:
directory.mkdir(parents=True, exist_ok=True)
path = directory / filename
path.write_text(content, encoding="utf-8")
return path
def test_seed_testmon_data_reports_missing_shared_cache(tmp_path: Path) -> None:
repo_root = tmp_path / "repo"
cache_dir = tmp_path / "cache"
repo_root.mkdir()
result = testmon_cache.seed_testmon_data(repo_root=repo_root, cache_dir=cache_dir)
assert result.status == "missing"
assert result.copied == ()
assert not (repo_root / ".testmondata").exists()
def test_seed_testmon_data_keeps_existing_local_data(tmp_path: Path) -> None:
repo_root = tmp_path / "repo"
cache_dir = tmp_path / "cache"
local_datafile = _write_testmon_file(repo_root, ".testmondata", "local")
_write_testmon_file(cache_dir, ".testmondata", "shared")
result = testmon_cache.seed_testmon_data(repo_root=repo_root, cache_dir=cache_dir)
assert result.status == "exists"
assert result.copied == ()
assert local_datafile.read_text(encoding="utf-8") == "local"
def test_seed_testmon_data_replaces_stale_sidecars(tmp_path: Path) -> None:
repo_root = tmp_path / "repo"
cache_dir = tmp_path / "cache"
_write_testmon_file(repo_root, ".testmondata-shm", "stale sidecar")
_write_testmon_file(cache_dir, ".testmondata", "shared main")
_write_testmon_file(cache_dir, ".testmondata-wal", "shared wal")
result = testmon_cache.seed_testmon_data(repo_root=repo_root, cache_dir=cache_dir)
assert result.status == "seeded"
assert {path.name for path in result.copied} == {".testmondata", ".testmondata-wal"}
assert (repo_root / ".testmondata").read_text(encoding="utf-8") == "shared main"
assert (repo_root / ".testmondata-wal").read_text(encoding="utf-8") == "shared wal"
assert not (repo_root / ".testmondata-shm").exists()
def test_refresh_testmon_data_requires_local_data(tmp_path: Path) -> None:
repo_root = tmp_path / "repo"
cache_dir = tmp_path / "cache"
repo_root.mkdir()
with pytest.raises(FileNotFoundError):
testmon_cache.refresh_testmon_data(repo_root=repo_root, cache_dir=cache_dir)
def test_refresh_testmon_data_replaces_shared_cache(tmp_path: Path) -> None:
repo_root = tmp_path / "repo"
cache_dir = tmp_path / "cache"
_write_testmon_file(repo_root, ".testmondata", "local main")
_write_testmon_file(repo_root, ".testmondata-shm", "local shm")
_write_testmon_file(cache_dir, ".testmondata", "old main")
_write_testmon_file(cache_dir, ".testmondata-wal", "old wal")
result = testmon_cache.refresh_testmon_data(repo_root=repo_root, cache_dir=cache_dir)
assert result.status == "refreshed"
assert {path.name for path in result.copied} == {".testmondata", ".testmondata-shm"}
assert (cache_dir / ".testmondata").read_text(encoding="utf-8") == "local main"
assert (cache_dir / ".testmondata-shm").read_text(encoding="utf-8") == "local shm"
assert not (cache_dir / ".testmondata-wal").exists()
def test_resolve_cache_dir_prefers_explicit_path_over_env(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
repo_root = tmp_path / "repo"
env_cache_dir = tmp_path / "env-cache"
explicit_cache_dir = tmp_path / "explicit-cache"
repo_root.mkdir()
monkeypatch.setenv(testmon_cache.TESTMON_CACHE_ENV, str(env_cache_dir))
assert testmon_cache.resolve_cache_dir(repo_root) == env_cache_dir.resolve()
assert (
testmon_cache.resolve_cache_dir(repo_root, explicit_cache_dir)
== explicit_cache_dir.resolve()
)
def test_status_command_prints_local_and_shared_paths(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
repo_root = tmp_path / "repo"
cache_dir = tmp_path / "cache"
repo_root.mkdir()
_write_testmon_file(repo_root, ".testmondata", "local main")
exit_code = testmon_cache.main(
["--repo-root", str(repo_root), "--cache-dir", str(cache_dir), "status"]
)
assert exit_code == 0
output = capsys.readouterr().out
assert f"Repo root: {repo_root.resolve()}" in output
assert "Worktree ready: True" in output
assert "Cache ready: False" in output