5935940276
CI / lint (pull_request) Successful in 16s
CI / typecheck (pull_request) Successful in 31s
CI / security (pull_request) Successful in 29s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m21s
CI / quality (pull_request) Successful in 2m33s
CI / unit_tests (pull_request) Successful in 3m10s
CI / integration_tests (pull_request) Successful in 5m17s
CI / coverage (pull_request) Successful in 4m25s
CI / docker (pull_request) Successful in 40s
CI / lint (push) Successful in 12s
CI / typecheck (push) Successful in 31s
CI / quality (push) Successful in 15s
CI / security (push) Successful in 32s
CI / build (push) Successful in 21s
CI / integration_tests (push) Successful in 3m6s
CI / unit_tests (push) Successful in 3m30s
CI / benchmark-regression (push) Has been skipped
CI / docker (push) Successful in 1m43s
CI / coverage (push) Successful in 5m21s
CI / benchmark-regression (pull_request) Successful in 31m11s
CI / benchmark-publish (push) Successful in 17m24s
Implement sandbox_boundary(r) function that walks up containment edges in the resource DAG to the nearest sandboxable ancestor, enabling resources sharing a boundary to share one sandbox instance. Changes: - Add boundary.py: is_sandbox_boundary(), sandbox_boundary(), compute_sandbox_domains(), BoundaryCache (thread-safe, per-execution) - Update SandboxManager: resolve_sandbox_key() and get_or_create_sandbox_for_resource() key by (plan_id, boundary_id) instead of (plan_id, resource_id); boundary cache lifecycle methods - Define "sandboxable" via ResourceCapabilities.sandboxable + non-none sandbox_strategy as per specification section 24659-24674 - Export new symbols from sandbox __init__.py - Add vulture whitelist entries for new public API Tests: - 26 Behave BDD scenarios (features/sandbox_boundary_algebra.feature) - 5 Robot Framework integration tests (robot/sandbox_boundary_algebra.robot) - ASV benchmarks for boundary walk, domain grouping, and cache performance ISSUES CLOSED: #548
197 lines
6.2 KiB
Python
197 lines
6.2 KiB
Python
"""ASV benchmarks for sandbox boundary algebra.
|
|
|
|
Measures the performance of:
|
|
- ``is_sandbox_boundary()`` predicate
|
|
- ``sandbox_boundary()`` walk for shallow and deep DAGs
|
|
- ``compute_sandbox_domains()`` grouping
|
|
- ``BoundaryCache`` lookup hit and miss
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Ensure the local *source* tree is importable even when ASV has an
|
|
# older build of the package installed.
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
# Force-reload so ASV picks up the source tree version.
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
from cleveragents.domain.models.core.resource import ( # noqa: E402
|
|
PhysVirt,
|
|
Resource,
|
|
ResourceCapabilities,
|
|
SandboxStrategy,
|
|
)
|
|
from cleveragents.infrastructure.sandbox.boundary import ( # noqa: E402
|
|
BoundaryCache,
|
|
compute_sandbox_domains,
|
|
is_sandbox_boundary,
|
|
sandbox_boundary,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test data builders
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COUNTER = 0
|
|
|
|
|
|
def _ulid(name: str) -> str:
|
|
global _COUNTER
|
|
_COUNTER += 1
|
|
base = abs(hash(name)) % (10**20)
|
|
raw = f"{_COUNTER:06d}{base:020d}"
|
|
charset = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
|
result = ""
|
|
for ch in raw:
|
|
result += charset[int(ch)]
|
|
return (result + "0" * 26)[:26]
|
|
|
|
|
|
def _resource(
|
|
name: str,
|
|
sandboxable: bool = False,
|
|
strategy: str = "none",
|
|
parents: list[str] | None = None,
|
|
) -> Resource:
|
|
return Resource(
|
|
resource_id=_ulid(name),
|
|
name=name,
|
|
resource_type_name="git-checkout" if sandboxable else "fs-file",
|
|
classification=PhysVirt.PHYSICAL,
|
|
sandbox_strategy=SandboxStrategy(strategy),
|
|
capabilities=ResourceCapabilities(
|
|
readable=True,
|
|
writable=True,
|
|
sandboxable=sandboxable,
|
|
checkpointable=False,
|
|
),
|
|
parents=parents or [],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pre-built test fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _build_shallow_dag() -> tuple[Resource, dict[str, Resource]]:
|
|
"""Build a shallow DAG: boundary -> 10 files."""
|
|
boundary = _resource("bench-co", sandboxable=True, strategy="git_worktree")
|
|
registry = {boundary.resource_id: boundary}
|
|
leaf = boundary
|
|
for i in range(10):
|
|
child = _resource(f"bench-f{i}", parents=[boundary.resource_id])
|
|
registry[child.resource_id] = child
|
|
leaf = child
|
|
return leaf, registry
|
|
|
|
|
|
def _build_deep_dag(depth: int = 50) -> tuple[Resource, dict[str, Resource]]:
|
|
"""Build a deep linear DAG: boundary -> dir1 -> dir2 -> ... -> file."""
|
|
boundary = _resource("bench-deep-co", sandboxable=True, strategy="git_worktree")
|
|
registry = {boundary.resource_id: boundary}
|
|
current = boundary
|
|
for i in range(depth):
|
|
child = _resource(f"bench-d{i}", parents=[current.resource_id])
|
|
registry[child.resource_id] = child
|
|
current = child
|
|
return current, registry
|
|
|
|
|
|
_SHALLOW_LEAF, _SHALLOW_REGISTRY = _build_shallow_dag()
|
|
_DEEP_LEAF, _DEEP_REGISTRY = _build_deep_dag()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# is_sandbox_boundary benchmarks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TimeSandboxBoundaryCheck:
|
|
"""Benchmark is_sandbox_boundary() predicate."""
|
|
|
|
def setup(self) -> None:
|
|
self.boundary = _resource("bm-co", sandboxable=True, strategy="git_worktree")
|
|
self.non_boundary = _resource("bm-f", sandboxable=False)
|
|
|
|
def time_is_boundary_true(self) -> None:
|
|
is_sandbox_boundary(self.boundary)
|
|
|
|
def time_is_boundary_false(self) -> None:
|
|
is_sandbox_boundary(self.non_boundary)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# sandbox_boundary() walk benchmarks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TimeSandboxBoundaryWalk:
|
|
"""Benchmark sandbox_boundary() for various DAG shapes."""
|
|
|
|
def time_self_boundary(self) -> None:
|
|
"""Walk when resource is itself the boundary (O(1))."""
|
|
boundary = _SHALLOW_REGISTRY[_SHALLOW_LEAF.parents[0]]
|
|
sandbox_boundary(boundary, _SHALLOW_REGISTRY)
|
|
|
|
def time_shallow_walk(self) -> None:
|
|
"""Walk one hop from leaf to boundary."""
|
|
sandbox_boundary(_SHALLOW_LEAF, _SHALLOW_REGISTRY)
|
|
|
|
def time_deep_walk(self) -> None:
|
|
"""Walk 50 hops from leaf to boundary."""
|
|
sandbox_boundary(_DEEP_LEAF, _DEEP_REGISTRY)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# compute_sandbox_domains benchmarks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TimeSandboxDomains:
|
|
"""Benchmark compute_sandbox_domains() grouping."""
|
|
|
|
def setup(self) -> None:
|
|
self.boundary = _resource("dom-co", sandboxable=True, strategy="git_worktree")
|
|
self.resources = []
|
|
self.registry = {self.boundary.resource_id: self.boundary}
|
|
for i in range(100):
|
|
r = _resource(f"dom-f{i}", parents=[self.boundary.resource_id])
|
|
self.resources.append(r)
|
|
self.registry[r.resource_id] = r
|
|
|
|
def time_domains_100_resources(self) -> None:
|
|
compute_sandbox_domains(self.resources, self.registry)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# BoundaryCache benchmarks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TimeBoundaryCache:
|
|
"""Benchmark BoundaryCache hit and miss paths."""
|
|
|
|
def setup(self) -> None:
|
|
self.cache = BoundaryCache()
|
|
self.leaf = _SHALLOW_LEAF
|
|
self.registry = _SHALLOW_REGISTRY
|
|
|
|
def time_cache_miss(self) -> None:
|
|
"""First lookup (cache miss)."""
|
|
self.cache.clear()
|
|
self.cache.get_boundary(self.leaf, self.registry)
|
|
|
|
def time_cache_hit(self) -> None:
|
|
"""Second lookup (cache hit)."""
|
|
self.cache.get_boundary(self.leaf, self.registry)
|