feat(changeset): persist changesets and diff artifacts
CI / lint (pull_request) Successful in 24s
CI / typecheck (pull_request) Successful in 59s
CI / security (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 36s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 27s
CI / integration_tests (pull_request) Successful in 5m30s
CI / benchmark-regression (pull_request) Successful in 25m38s
CI / unit_tests (pull_request) Successful in 36m51s
CI / docker (pull_request) Successful in 1m3s
CI / coverage (pull_request) Successful in 1h48m49s
CI / lint (push) Successful in 22s
CI / security (push) Successful in 58s
CI / typecheck (push) Successful in 1m3s
CI / quality (push) Successful in 46s
CI / build (push) Successful in 23s
CI / integration_tests (push) Successful in 5m23s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 15m5s
CI / unit_tests (push) Successful in 20m1s
CI / docker (push) Successful in 1m0s
CI / coverage (push) Successful in 1h45m36s
CI / lint (pull_request) Successful in 24s
CI / typecheck (pull_request) Successful in 59s
CI / security (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 36s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 27s
CI / integration_tests (pull_request) Successful in 5m30s
CI / benchmark-regression (pull_request) Successful in 25m38s
CI / unit_tests (pull_request) Successful in 36m51s
CI / docker (pull_request) Successful in 1m3s
CI / coverage (pull_request) Successful in 1h48m49s
CI / lint (push) Successful in 22s
CI / security (push) Successful in 58s
CI / typecheck (push) Successful in 1m3s
CI / quality (push) Successful in 46s
CI / build (push) Successful in 23s
CI / integration_tests (push) Successful in 5m23s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 15m5s
CI / unit_tests (push) Successful in 20m1s
CI / docker (push) Successful in 1m0s
CI / coverage (push) Successful in 1h45m36s
Add SQLite persistence for ChangeSet entries and ToolInvocation records via new changeset_repository module implementing the ChangeSetStore protocol. - Alembic migration d0_001 creates changeset_entries and tool_invocations tables - SqliteChangeSetStore, ChangeSetEntryRepository, ToolInvocationRepository - PlanApplyService.cleanup_changeset() for cancel/failure cleanup - Behave tests (17 scenarios), Robot tests (5 cases), ASV benchmarks - Reference documentation in docs/reference/changeset.md Closes #163
This commit was merged in pull request #429.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""ASV benchmarks for ChangeSet persistence overhead.
|
||||
|
||||
Measures the cost of persisting ChangeEntry records and retrieving
|
||||
them via the SqliteChangeSetStore, simulating typical plan execution
|
||||
workloads.
|
||||
"""
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from cleveragents.domain.models.core.change import (
|
||||
ChangeEntry,
|
||||
ChangeOperation,
|
||||
)
|
||||
from cleveragents.infrastructure.database.changeset_repository import (
|
||||
ChangeSetEntryRepository,
|
||||
SqliteChangeSetStore,
|
||||
)
|
||||
from cleveragents.infrastructure.database.models import Base
|
||||
|
||||
|
||||
def _make_factory() -> tuple[sessionmaker[Session], object]:
|
||||
"""Create an in-memory SQLite engine + session factory."""
|
||||
engine = create_engine(
|
||||
"sqlite:///:memory:",
|
||||
echo=False,
|
||||
connect_args={"check_same_thread": False},
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
factory: sessionmaker[Session] = sessionmaker(bind=engine)
|
||||
return factory, engine
|
||||
|
||||
|
||||
def _make_entry(plan_id: str, idx: int) -> ChangeEntry:
|
||||
return ChangeEntry(
|
||||
plan_id=plan_id,
|
||||
resource_id="res-001",
|
||||
tool_name="file-write",
|
||||
operation=ChangeOperation.CREATE,
|
||||
path=f"src/file_{idx}.py",
|
||||
after_hash=f"hash_{idx:06d}",
|
||||
)
|
||||
|
||||
|
||||
class TimeChangeSetPersistence:
|
||||
"""Benchmark suite for ChangeSet persistence operations."""
|
||||
|
||||
timeout = 60.0
|
||||
|
||||
def setup(self) -> None:
|
||||
self.factory, self.engine = _make_factory()
|
||||
self.store = SqliteChangeSetStore(self.factory)
|
||||
self.plan_id = "bench-plan-001"
|
||||
self.changeset_id = self.store.start(self.plan_id)
|
||||
# Pre-populate for retrieval benchmarks
|
||||
for i in range(50):
|
||||
entry = _make_entry(self.plan_id, i)
|
||||
self.store.record(self.changeset_id, entry)
|
||||
session = self.factory()
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
def time_record_single_entry(self) -> None:
|
||||
"""Time recording a single ChangeEntry."""
|
||||
entry = _make_entry(self.plan_id, 999)
|
||||
self.store.record(self.changeset_id, entry)
|
||||
|
||||
def time_get_changeset_50_entries(self) -> None:
|
||||
"""Time retrieving a changeset with 50 entries."""
|
||||
self.store.get(self.changeset_id)
|
||||
|
||||
def time_summarize_changeset(self) -> None:
|
||||
"""Time computing summary for a changeset."""
|
||||
self.store.summarize(self.changeset_id)
|
||||
|
||||
def time_start_changeset(self) -> None:
|
||||
"""Time starting a new empty changeset."""
|
||||
self.store.start("bench-plan-new")
|
||||
|
||||
|
||||
class TimeEntryRepositoryBulk:
|
||||
"""Benchmark bulk entry operations."""
|
||||
|
||||
timeout = 60.0
|
||||
|
||||
def setup(self) -> None:
|
||||
self.factory, self.engine = _make_factory()
|
||||
self.repo = ChangeSetEntryRepository(self.factory)
|
||||
|
||||
def time_save_100_entries(self) -> None:
|
||||
"""Time saving 100 entries sequentially."""
|
||||
for i in range(100):
|
||||
entry = _make_entry("bench-bulk", i)
|
||||
self.repo.save_entry(f"cs-bulk-{i % 5}", entry)
|
||||
|
||||
def time_retrieve_by_plan(self) -> None:
|
||||
"""Time retrieving all entries for a plan."""
|
||||
self.repo.get_entries_for_plan("bench-bulk")
|
||||
Reference in New Issue
Block a user