band-aider-pub/tests/band_aider/test_python_oop_signature_extract.py
2026-10-17 11:57:37 -07:00

461 lines
14 KiB
Python

# SPDX-FileCopyrightText: 2026 Art Sea LLC
#
# SPDX-License-Identifier: AGPL-3.0-only
"""Regression coverage for source-driven Python dependency reference cards."""
from __future__ import annotations
from io import StringIO
import json
from pathlib import Path
import subprocess
from typing import Iterable, Sequence, TextIO
import pytest
from band_aider.context.class_meta import ClassMeta, ContentType
from band_aider.judgement.code_parser import CodeParser
from band_aider.judgement.file_manager import FileManager
from band_aider.judgement.prompt_formatter import MarkdownPromptFormatterBuilder
from band_aider.judgement.repo_exec import RepoExec
from band_aider.strategies.language_strategy_set import LanguageStrategySet
from band_aider.strategies.oop_signature_extract import (
OOpSignature,
OOpSignatureExtract,
OOpSignatureRender,
)
from band_aider.strategies.python.python_language_strategy_set import (
build_python_language_strategy_set,
)
from band_aider.strategies.python.python_oop_signature_extract import (
PythonOOpSignatureExtract,
)
from band_aider.strategies.python.python_oop_signature_render import (
PythonOOpSignatureRender,
)
from tests.support.completion_clients import StaticOpenAICompatibleClient
from tests.support.memory_path import MemoryPath
from tests.support.memory_repo_exec import MemoryRepoExec
class _CardParser(CodeParser):
"""Supply nominal FileManager metadata without reading host paths."""
def parse_file(
self,
file_name: str,
prompt: str,
content_type: ContentType = ContentType.Source,
) -> ClassMeta:
"""Return one empty metadata record for a lexical in-memory path."""
del prompt
return ClassMeta(file_name, "", [], {}, content_type=content_type)
def parse_stream(
self,
file_name: str,
stream: TextIO,
prompt: str,
content_type: ContentType = ContentType.Source,
) -> ClassMeta:
"""Return metadata without consuming a host-backed stream."""
del stream
return self.parse_file(file_name, prompt, content_type)
def parse_files(
self,
file_names: list[str],
prompt: str,
content_type: ContentType = ContentType.Source,
) -> list[ClassMeta]:
"""Return metadata in the supplied lexical path order."""
return [self.parse_file(file_name, prompt, content_type) for file_name in file_names]
class _CardFileManager(FileManager):
"""Bind language strategy construction to one in-memory reference-card project."""
def __init__(self, root: MemoryPath, repo_exec: RepoExec | None = None) -> None:
"""Construct a nominal manager whose repository boundary never reaches the host."""
super().__init__(
parser=_CardParser(),
client=StaticOpenAICompatibleClient('{"cdc": ""}'),
repo_root=root,
src_root=root / "src",
test_root=root / "tests",
actor="test",
prompt_formatter_builder=MarkdownPromptFormatterBuilder(),
repo_exec=root.repository if repo_exec is None else repo_exec,
prompt="reference cards",
)
def resync(self) -> None:
"""Keep card construction independent from source discovery metadata IO."""
self.src_classes = []
self.test_classes = []
self.src_index = {}
self.test_index = {}
self.src_dirty.clear()
self.test_dirty.clear()
class _ExternalSourceRepoExec(MemoryRepoExec):
"""Return stationary target-environment declaration source through RepoExec."""
def run_cli(
self,
command: Sequence[str],
*,
cwd: Path | None = None,
env: dict[str, str] | None = None,
capture_output: bool = True,
text: bool = True,
check: bool = False,
timeout: int | None = None,
input: bytes | str | None = None,
unset_env: Iterable[str] = (),
) -> subprocess.CompletedProcess:
"""Return one static external stub without starting a process or reading disk."""
del cwd, env, capture_output, text, check, timeout, input, unset_env
self.commands.append(list(command))
return subprocess.CompletedProcess(
list(command),
0,
json.dumps(
[
{
"path": "/target/site-packages/external_widget.pyi",
"source": (
"class Widget:\n"
" \"\"\"Third-party widget.\"\"\"\n"
" def run(self) -> bool: ...\n"
),
}
]
),
"",
)
def read_text(self, path: Path, *, encoding: str = "utf-8") -> str:
"""Reject accidental host-style reads of target-environment declaration paths."""
if str(path).startswith("/target/site-packages"):
raise AssertionError("external paths must only be read in target environment")
return super().read_text(path, encoding=encoding)
@pytest.fixture
def memory_root() -> MemoryPath:
"""Return one fresh lexical root backed by an in-memory repository."""
return MemoryPath.from_repo(MemoryRepoExec())
def _strategies(
root: MemoryPath,
repo_exec: RepoExec | None = None,
) -> LanguageStrategySet:
"""Build reference-card strategies through the selected Python language set."""
return build_python_language_strategy_set(_CardFileManager(root, repo_exec))
def _extractor(
root: MemoryPath,
repo_exec: RepoExec | None = None,
) -> OOpSignatureExtract:
"""Resolve the generic extraction contract from the selected Python strategies."""
extractor = _strategies(root, repo_exec)[OOpSignatureExtract]
assert isinstance(extractor, PythonOOpSignatureExtract)
return extractor
def _write(root: MemoryPath, location: str, text: str) -> Path:
"""Store one source fixture through the registered in-memory repository boundary."""
path = root / location
root.repository.write_text(path, text)
return path
def _render(root: MemoryPath, card: OOpSignature) -> str:
"""Render one card through the selected Python declaration rendering strategy."""
output = StringIO()
renderer = _strategies(root)[OOpSignatureRender]
assert isinstance(renderer, PythonOOpSignatureRender)
renderer.render(card, output)
return output.getvalue()
def test_python_extract_formally_implements_language_neutral_contract() -> None:
"""The concrete extractor explicitly inherits the project protocol."""
assert OOpSignatureExtract in PythonOOpSignatureExtract.__bases__
assert OOpSignatureRender in PythonOOpSignatureRender.__bases__
def test_extract_references_includes_public_declaration_surface_and_pydocs(
memory_root: MemoryPath,
) -> None:
"""Public APIs are structured while private names and executable bodies are absent."""
_write(
memory_root,
"src/pkg/surface.py",
'''from pathlib import Path
from typing import ClassVar
class Client:
pass
class Surface:
"""Public surface documentation. More details are deliberately retained in DTOs."""
KIND: ClassVar[str] = "surface"
version = 1
label: str
_private_class = "hidden"
def __init__(self, root: Path) -> None:
self.root = root
self.client = Client()
self._token = "hidden"
body_secret = "constructor implementation"
@property
def title(self) -> str:
"""Return the display title."""
return "body title secret"
def run(self, count: int = 1) -> bool:
"""Run the surface."""
body_secret = "method implementation"
return True
def _hidden(self) -> str:
return "private implementation"
''',
)
consumer = "from pkg.surface import Surface\n\ndef use(value: Surface) -> None:\n pass\n"
[card] = _extractor(memory_root).extract_references({"src/consumer.py": consumer})
assert (card.module, card.name) == ("pkg.surface", "Surface")
rendered = _render(memory_root, card)
assert "class Surface" in rendered
assert "# Public surface documentation." in rendered
assert "constructor: __init__(self, root: Path) -> None" in rendered
assert "KIND: ClassVar[str]" in rendered
assert "version: int" in rendered
assert "label: str" in rendered
assert "root: Path" in rendered
assert "client: Client" in rendered
assert "@property title: str" in rendered
assert "# Return the display title." in rendered
assert "run(self, count: int = 1) -> bool" in rendered
assert "# Run the surface." in rendered
assert "_hidden" not in rendered
assert "_private_class" not in rendered
assert "_token" not in rendered
assert "constructor implementation" not in rendered
assert "method implementation" not in rendered
assert "body title secret" not in rendered
assert "private implementation" not in rendered
def test_extract_source_uses_the_same_compact_public_card_surface(
memory_root: MemoryPath,
) -> None:
"""Current source cards retain declarations and pydocs without executable bodies."""
source = '''class Surface:
"""Current surface documentation."""
enabled: bool
@property
def title(self) -> str:
"""Return the current title."""
return "hidden body"
def run(self, count: int = 1) -> bool:
"""Run the current surface."""
return True
def build(name: str) -> Surface:
"""Build a current surface."""
return Surface()
_secret = "hidden"
'''
cards = _extractor(memory_root).extract_source("src/pkg/current.py", source)
assert [(card.module, card.name) for card in cards] == [
("pkg.current", "Surface"),
("pkg.current", "build"),
]
surface = _render(memory_root, cards[0])
assert "# Current surface documentation." in surface
assert "enabled: bool" in surface
assert "@property title: str" in surface
assert "# Return the current title." in surface
assert "run(self, count: int = 1) -> bool" in surface
assert "hidden body" not in surface
def test_extract_references_shows_both_internal_mismatch_dependencies(
memory_root: MemoryPath,
) -> None:
"""Two imported internal types remain available without diagnostic-string scraping."""
_write(
memory_root,
"tests/support/file_manager.py",
'''class _FileManager:
"""Lightweight test file manager."""
def __init__(self, root: str) -> None:
self.root = root
def source_path(self, location: str) -> str:
return location
''',
)
_write(
memory_root,
"src/pkg/file_manager.py",
'''class FileManager:
def source_path(self, location: str) -> str:
return location
''',
)
source = (
"from tests.support.file_manager import _FileManager\n"
"from pkg.file_manager import FileManager\n"
"\n"
"def consume(manager: FileManager) -> None:\n"
" pass\n"
)
cards = _extractor(memory_root).extract_references({"src/pkg/consumer.py": source})
assert [(card.module, card.name) for card in cards] == [
("tests.support.file_manager", "_FileManager"),
("pkg.file_manager", "FileManager"),
]
assert "constructor: __init__(self, root: str) -> None" in _render(memory_root, cards[0])
assert "source_path(self, location: str) -> str" in _render(memory_root, cards[1])
def test_extract_references_prefers_repository_stub_declarations(
memory_root: MemoryPath,
) -> None:
"""A colocated pyi surface wins over implementation-only members."""
_write(
memory_root,
"src/pkg/service.py",
"class Service:\n def runtime_only(self) -> int:\n return 1\n",
)
_write(
memory_root,
"src/pkg/service.pyi",
"class Service:\n def declared(self, value: str) -> bool: ...\n",
)
[card] = _extractor(memory_root).extract_references(
{"src/consumer.py": "from pkg.service import Service\n"}
)
rendered = _render(memory_root, card)
assert "declared(self, value: str) -> bool" in rendered
assert "runtime_only" not in rendered
def test_extract_references_reads_external_declarations_in_target_environment(
memory_root: MemoryPath,
) -> None:
"""External package source is provided by a stationary target RepoExec response."""
repo_exec = _ExternalSourceRepoExec(memory_root)
extractor = _extractor(memory_root, repo_exec)
[card] = extractor.extract_references(
{"src/consumer.py": "from external_widget import Widget\n"}
)
assert (card.module, card.name) == ("external_widget", "Widget")
assert "# Third-party widget." in _render(memory_root, card)
assert "run(self) -> bool" in _render(memory_root, card)
def test_extract_references_excludes_visible_files_and_refreshes_dependency_source(
memory_root: MemoryPath,
) -> None:
"""Visible source is not duplicated and a hidden dependency is re-read every time."""
dependency = _write(
memory_root,
"src/pkg/hidden.py",
"class Hidden:\n def first(self) -> None: ...\n",
)
extractor = _extractor(memory_root)
source = "from pkg.hidden import Hidden\n"
assert extractor.extract_references(
{
"src/consumer.py": source,
"src/pkg/hidden.py": memory_root.repository.read_text(dependency),
}
) == ()
[first] = extractor.extract_references({"src/consumer.py": source})
assert "first(self) -> None" in _render(memory_root, first)
memory_root.repository.write_text(
dependency,
"class Hidden:\n def second(self) -> None: ...\n",
)
[second] = extractor.extract_references({"src/consumer.py": source})
assert "second(self) -> None" in _render(memory_root, second)
assert "first(self) -> None" not in _render(memory_root, second)
def test_module_import_exposes_public_top_level_declarations(
memory_root: MemoryPath,
) -> None:
"""A module import provides discoverable public classes, functions, and constants."""
_write(
memory_root,
"src/pkg/api.py",
'''class Client:
pass
def build(name: str) -> Client:
return Client()
DEFAULT_NAME = "client"
_private = "hidden"
''',
)
cards = _extractor(memory_root).extract_references(
{"src/consumer.py": "import pkg.api\n"}
)
assert [(card.module, card.name) for card in cards] == [
("pkg.api", "Client"),
("pkg.api", "build"),
("pkg.api", "DEFAULT_NAME"),
]
assert "def build(name: str) -> Client" in _render(memory_root, cards[1])