orchestrata_tools.test_generator.testgen.structural
Reference › orchestrata_tools › test_generator › testgen › structural
This module is the mechanical Layer 2 generator. It writes high-confidence tests from purely structural facts: dataclass field metadata, enum members, abstract method lists and protocol contracts. Each function returns a rendered pytest class or function block ready to be spliced into a TestFile from writer. It is imported by generator and the package, and covered by tests/test_orchestrata_tools_testgen_structural_coverage.py.
Every function takes a class name, an "extra" dictionary of structural facts built by adapter, an import path, an optional TestFile and a config: gen_dataclass_tests, gen_enum_tests, gen_abstract_tests and gen_protocol_tests. gen_entry_point_tests takes the top N ranked entries from ranking and writes a prioritised stub class. Two template fixes are recorded: an unresolved type emits cast(Any, None) and registers the typing import in the same file, and a deliberately invalid constructor call inside pytest.raises(TypeError) carries a pyright ignore comment.
At a glance
| Source | src/orchestrata_tools/test_generator/testgen/structural.py |
| Lines of code | 774 |
| Classes | 0 |
| Functions | 9 |
| Imports (internal) | orchestrata_tools.test_generator.testgen.config, orchestrata_tools.test_generator.testgen.ranking, orchestrata_tools.test_generator.testgen.types, orchestrata_tools.test_generator.testgen.writer |
| Imported by | orchestrata_tools.test_generator.testgen, orchestrata_tools.test_generator.testgen.generator |
| Tests that import it | tests/test_orchestrata_tools_testgen_structural_coverage.py |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
gen_dataclass_tests |
gen_dataclass_tests(class_name: str, extra: dict[str, Any], import_path: str, tf: TestFile | None = None, config: ProjectConfig | None = None) -> str |
Generate a pytest class with tests for a @dataclass. |
gen_enum_tests |
gen_enum_tests(class_name: str, extra: dict[str, Any], import_path: str, tf: TestFile | None = None, config: ProjectConfig | None = None) -> str |
Generate a pytest class with tests for an Enum subclass. |
gen_abstract_tests |
gen_abstract_tests(class_name: str, extra: dict[str, Any], import_path: str, tf: TestFile | None = None, config: ProjectConfig | None = None) -> str |
Generate a pytest class with tests for an ABC. |
_method_stub |
_method_stub(m: dict) -> str |
Return a sensible stub return value for a method. |
gen_protocol_tests |
gen_protocol_tests(class_name: str, extra: dict[str, Any], import_path: str, tf: TestFile | None = None, config: ProjectConfig | None = None) -> str |
Generate: 1. |
gen_entry_point_tests |
gen_entry_point_tests(ranked_entries: list[RankedEntry], top_n: int = 10) -> str |
For the top-N ranked functions, generate a prioritised TODO stub class with a comment showing priority/score and a basic call stub. |
_field_sentinel |
_field_sentinel(field_info: dict, tf: TestFile | None = None, config: ProjectConfig | None = None) -> str |
Return a sentinel value for a dataclass field, tracking imports. |
_sentinel_for_annotation |
_sentinel_for_annotation(annotation: str | None, tf: TestFile | None = None, config: ProjectConfig | None = None) -> str |
Return a sentinel value for a type annotation, tracking imports. |
_sig_from_params |
_sig_from_params(params: list[Any]) -> str |
Build a call-site argument string from a list of ParamInfo-like objects (orchestrata_ast.models.ParamInfo: .name, .type_annotation, .kind). |
Source
"""structural.py — Layer 2 mechanical test generation.
Generates high-confidence, zero-hallucination tests from purely structural
information: dataclass field metadata, enum members, abstract method lists,
and protocol member contracts.
Each generator returns a rendered string (a pytest class or standalone
function block) ready to be spliced into a TestFile.
Restored 2026-08-19 from ``scripts/ast_serializer/testgen`` (deleted
2026-07-09, commit 6a186c9d). This module was already self-contained (only
``.config``/``.types``/``.writer``, no dependency on the deleted parser's
``StatementRecord`` tree — every public function here takes a plain
``dict[str, Any]`` "extra" bag, not a parser type) — the caller
(``orchestrata_tools.test_generator.testgen.adapter``) builds that dict
from the current AST layer (``orchestrata_ast.models``) plus a direct,
narrow walk of the already-parsed ``ast.ClassDef`` node for the handful of
facts (dataclass field defaults, enum member values) the current AST layer
doesn't carry. Only the imports moved to absolute paths.
Template-defect fixes applied here:
(b) sentinel() may now emit ``cast(Any, None)`` for an unresolved type;
every call site below registers the needed typing import on the
TestFile in the same place it emits the cast.
(d) a deliberately-invalid constructor call inside a
``pytest.raises(TypeError)`` block (missing required dataclass
field / abstract class direct instantiation) carries
``# pyright: ignore[reportCallIssue]`` — these calls are invalid by
construction, that's the point of the test, and pyright's static
check has no way to know that.
Also drops the "mocked variant" half of ``gen_entry_point_tests``: this
project's hard rule is "no mocking, ever" (the only sanctioned test double
is the LLM boundary), so ``@mock.patch``/``MagicMock`` scaffolding had no
legitimate use here. The basic TODO stub half is kept, gated by
``ProjectConfig.enable_entry_point_stubs`` (renamed from the old
``enable_mock_tests`` gate — same default, off, for ORCHESTRATA_ULTIMATE).
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from orchestrata_tools.test_generator.testgen.types import (
sentinel,
sentinel_imports,
sentinel_needs_cast,
)
from orchestrata_tools.test_generator.testgen.writer import CodeBuilder, TestFile
if TYPE_CHECKING:
from orchestrata_tools.test_generator.testgen.config import ProjectConfig
from orchestrata_tools.test_generator.testgen.ranking import RankedEntry
# ---------------------------------------------------------------------------
# Dataclass tests
# ---------------------------------------------------------------------------
def gen_dataclass_tests(
class_name: str,
extra: dict[str, Any],
import_path: str,
tf: TestFile | None = None,
config: ProjectConfig | None = None,
) -> str:
"""
Generate a pytest class with tests for a @dataclass.
Covers:
- Required fields enforced (TypeError when missing)
- Default values are correct
- All fields assignable / readable
- frozen=True → FrozenInstanceError on assignment
- slots=True → no __dict__
- eq=True → equality by value (default)
"""
fields: list[dict] = extra.get("dataclass_fields", [])
params: dict = extra.get("dataclass_params", {})
frozen = params.get("frozen") == "True"
slots = params.get("slots") == "True"
eq = params.get("eq", "True") != "False" # default True
has_post_init = extra.get("has_post_init", False)
required = [f for f in fields if f["required"]]
optional = [f for f in fields if not f["required"]]
cb = CodeBuilder()
cb.cls(f"Test{class_name}Dataclass")
cb.docstring(
f"Structural tests for @dataclass {class_name}.\n"
f"Fields: {[f['name'] for f in fields]}"
)
cb.blank()
# ---- minimal valid instance helper ----
all_args = ", ".join(
f"{f['name']}={_field_sentinel(f, tf, config)}" for f in required
)
cb.comment("Helper: minimal valid instance")
cb.func("_make")
cb.line(f"return {class_name}({all_args})")
cb.end()
cb.blank()
# ---- required fields ----
if required:
cb.func("test_required_fields_enforced")
cb.docstring("Omitting any required field must raise TypeError.")
cb.pytest_raises("TypeError")
cb.line(
f"{class_name}() # missing: {', '.join(f['name'] for f in required)}"
" # pyright: ignore[reportCallIssue]"
)
cb.end() # with
cb.end() # def
cb.blank()
for f in required:
# Drop one required field at a time
others = [r for r in required if r["name"] != f["name"]]
args = ", ".join(
f"{r['name']}={_field_sentinel(r, tf, config)}" for r in others
)
cb.func(f"test_missing_{f['name']}_raises")
cb.pytest_raises("TypeError")
call = f"{class_name}({args})" if args else f"{class_name}()"
cb.line(
f"{call} # missing: {f['name']} # pyright: ignore[reportCallIssue]"
)
cb.end()
cb.end()
cb.blank()
# ---- defaults ----
# Skip exact default assertions when __post_init__ may mutate fields
if optional and not has_post_init:
cb.func("test_default_values")
cb.docstring("Default values must match the class definition.")
if all_args:
cb.line(f"obj = {class_name}({all_args})")
else:
cb.line(f"obj = {class_name}()")
for f in optional:
if f["default"] is not None:
cb.line(f"assert obj.{f['name']} == {f['default']}")
elif f["default_factory"] is not None:
# Default factories may reference modules not imported in the
# test file; assert non-None and correct type instead.
cb.line(f"assert obj.{f['name']} is not None")
cb.end()
cb.blank()
# ---- full construction / field readback ----
all_fields_args = ", ".join(
f"{f['name']}={_field_sentinel(f, tf, config)}" for f in fields
)
cb.func("test_field_assignment_and_readback")
cb.line(f"obj = {class_name}({all_fields_args})")
for f in fields:
sval = _field_sentinel(f, tf, config)
# For callable sentinels, use callable() check instead of equality
if "lambda" in sval:
cb.line(f"assert callable(obj.{f['name']})")
else:
cb.line(f"assert obj.{f['name']} == {sval}")
cb.end()
cb.blank()
# ---- frozen ----
if frozen:
cb.func("test_frozen_immutability")
cb.docstring(
"frozen=True: attribute assignment must raise FrozenInstanceError."
)
cb.line("from dataclasses import FrozenInstanceError")
if all_args:
cb.line(f"obj = {class_name}({all_args})")
else:
cb.line(f"obj = {class_name}()")
if fields:
first = fields[0]
cb.pytest_raises("FrozenInstanceError")
cb.line(f"obj.{first['name']} = {_field_sentinel(first, tf, config)}")
cb.end()
cb.end()
cb.blank()
# ---- slots ----
if slots:
cb.func("test_slots_no_dict")
cb.docstring("slots=True: instances must not have a __dict__.")
cb.line("obj = self._make()")
cb.line("assert not hasattr(obj, '__dict__')")
cb.end()
cb.blank()
# ---- equality (eq=True by default) ----
if eq and fields:
cb.func("test_equality_by_value")
cb.line(f"a = {class_name}({all_fields_args})")
cb.line(f"b = {class_name}({all_fields_args})")
cb.line("assert a == b")
cb.line("assert a is not b")
cb.end()
cb.blank()
# ---- field() metadata ----
for f in fields:
if f.get("is_field_call") and f.get("metadata") and f["metadata"] != "None":
cb.func(f"test_{f['name']}_field_metadata")
cb.docstring(f"field() metadata for {f['name']}: {f['metadata']}")
cb.line("import dataclasses")
cb.line(f"field_info = dataclasses.fields({class_name})")
cb.line("meta = {f.name: f.metadata for f in field_info}")
cb.line(f"assert '{f['name']}' in meta")
cb.end()
cb.blank()
cb.end() # class
return cb.render()
# ---------------------------------------------------------------------------
# Enum tests
# ---------------------------------------------------------------------------
def gen_enum_tests(
class_name: str,
extra: dict[str, Any],
import_path: str,
tf: TestFile | None = None,
config: ProjectConfig | None = None,
) -> str:
"""
Generate a pytest class with tests for an Enum subclass.
Covers:
- All members exist by name
- Known values are correct (skips auto() members)
- Lookup by value works
- Invalid value raises ValueError
- Member count matches definition
- Iteration yields all members
- String representation is stable
"""
members: list[dict] = extra.get("enum_members", [])
base = extra.get("enum_base", "Enum")
cb = CodeBuilder()
cb.cls(f"Test{class_name}Enum")
cb.docstring(
f"Structural tests for {base} subclass {class_name}.\n"
f"Members: {[m['name'] for m in members]}"
)
cb.blank()
# ---- member existence ----
cb.func("test_all_members_exist")
cb.docstring("Every declared member must be accessible by name.")
for m in members:
cb.line(f"assert hasattr({class_name}, '{m['name']}')")
cb.line(f"assert {class_name}.{m['name']} is not None")
cb.end()
cb.blank()
# ---- known values (skip auto) ----
known = [m for m in members if not m["is_auto"]]
if known:
cb.func("test_member_values")
cb.docstring("Declared values must match the definition exactly.")
for m in known:
cb.line(f"assert {class_name}.{m['name']}.value == {m['value_repr']}")
cb.end()
cb.blank()
# ---- lookup by value ----
if known:
cb.func("test_lookup_by_value")
for m in known[:3]: # first 3 to keep tests concise
cb.line(
f"assert {class_name}({m['value_repr']}) is {class_name}.{m['name']}"
)
cb.end()
cb.blank()
# ---- invalid value ----
cb.func("test_invalid_value_raises")
cb.pytest_raises("ValueError")
if base == "IntEnum":
cb.line(f"{class_name}(999999)")
elif base == "StrEnum":
cb.line(f'{class_name}("__invalid__")')
else:
cb.line(f"{class_name}(object()) # no member matches")
cb.end()
cb.end()
cb.blank()
# ---- member count ----
cb.func("test_member_count")
cb.line(f"assert len({class_name}) == {len(members)}")
cb.end()
cb.blank()
# ---- iteration ----
cb.func("test_iteration_yields_all_members")
cb.line(f"all_members = list({class_name})")
cb.line(f"assert len(all_members) == {len(members)}")
for m in members:
cb.line(f"assert {class_name}.{m['name']} in all_members")
cb.end()
cb.blank()
# ---- containment / membership ----
if members:
first = members[0]
cb.func("test_membership")
cb.line(f"assert {class_name}.{first['name']} in {class_name}")
cb.end()
cb.blank()
# ---- string repr stability ----
cb.func("test_repr_is_stable")
cb.docstring("repr() should not raise and should include the class name.")
if members:
m = members[0]
cb.line(f"r = repr({class_name}.{m['name']})")
cb.line(f"assert '{class_name}' in r")
cb.end()
cb.blank()
# ---- name attribute ----
if members:
m = members[0]
cb.func("test_name_attribute")
cb.line(f"assert {class_name}.{m['name']}.name == '{m['name']}'")
cb.end()
cb.blank()
# ---- IntEnum arithmetic (if applicable) ----
if base == "IntEnum" and known:
cb.func("test_int_coercion")
cb.docstring("IntEnum members must behave as integers.")
m = known[0]
cb.line(f"val = int({class_name}.{m['name']})")
cb.line(f"assert val == {m['value_repr']}")
cb.end()
cb.blank()
cb.end() # class
return cb.render()
# ---------------------------------------------------------------------------
# Abstract class tests
# ---------------------------------------------------------------------------
def gen_abstract_tests(
class_name: str,
extra: dict[str, Any],
import_path: str,
tf: TestFile | None = None,
config: ProjectConfig | None = None,
) -> str:
"""
Generate a pytest class with tests for an ABC.
Covers:
- Direct instantiation raises TypeError
- Partial implementation raises TypeError
- Full concrete subclass can be instantiated
- Each abstract method is callable on the concrete subclass
"""
abstract_methods: list[dict] = extra.get("abstract_methods", [])
abc_base = extra.get("abc_base", "ABC")
cb = CodeBuilder()
cb.cls(f"Test{class_name}Abstract")
cb.docstring(
f"Structural tests for abstract class {class_name} ({abc_base}).\n"
f"Abstract methods: {[m['name'] for m in abstract_methods]}"
)
cb.blank()
# ---- cannot instantiate directly ----
cb.func("test_cannot_instantiate_directly")
cb.docstring(
f"{class_name} is abstract and must raise TypeError on direct instantiation."
)
cb.pytest_raises("TypeError")
cb.line(f"{class_name}() # pyright: ignore[reportCallIssue]")
cb.end()
cb.end()
cb.blank()
# ---- partial implementation fails ----
if len(abstract_methods) > 1:
for missing in abstract_methods[:2]: # first 2 to avoid bloat
others = [m for m in abstract_methods if m["name"] != missing["name"]]
cb.func(f"test_missing_{missing['name']}_raises")
cb.docstring(
f"Omitting {missing['name']} from the subclass must still raise TypeError."
)
cb.cls("_Partial", [class_name])
for m in others:
body = _method_stub(m)
cb.func(m["name"], "self, *args, **kwargs")
cb.line(body)
cb.end()
cb.end() # _Partial class
cb.pytest_raises("TypeError")
cb.line("_Partial() # pyright: ignore[reportCallIssue]")
cb.end()
cb.end()
cb.blank()
# ---- full concrete subclass ----
cb.func("test_concrete_subclass_instantiates")
cb.docstring(
"A class implementing all abstract methods must instantiate without error."
)
cb.cls("_Concrete", [class_name])
for m in abstract_methods:
body = _method_stub(m)
if m.get("is_property"):
cb.line("@property")
cb.func(m["name"], "self")
else:
cb.func(m["name"], "self, *args, **kwargs")
cb.line(body)
cb.end()
cb.end() # _Concrete class
cb.line("obj = _Concrete()")
cb.line("assert obj is not None")
cb.end()
cb.blank()
# ---- each abstract method is callable ----
if abstract_methods:
cb.func("test_abstract_methods_callable_on_concrete")
cb.cls("_Concrete", [class_name])
for m in abstract_methods:
body = _method_stub(m)
if m.get("is_property"):
cb.line("@property")
cb.func(m["name"], "self")
else:
cb.func(m["name"], "self, *args, **kwargs")
cb.line(body)
cb.end()
cb.end() # _Concrete
cb.line("obj = _Concrete()")
for m in abstract_methods:
if m.get("is_property"):
cb.line(f"_ = obj.{m['name']}")
cb.line(f"assert True # property accessible: {m['name']}")
else:
cb.line(f"result = obj.{m['name']}()")
cb.line(f"assert True # method callable: {m['name']}")
cb.end()
cb.blank()
cb.end() # class
return cb.render()
def _method_stub(m: dict) -> str:
"""Return a sensible stub return value for a method."""
ret = m.get("signature") or ""
if "-> None" in ret or not ret:
return "pass"
if "-> bool" in ret:
return "return False"
if "-> int" in ret:
return "return 0"
if "-> float" in ret:
return "return 0.0"
if "-> str" in ret:
return 'return ""'
if "-> list" in ret.lower():
return "return []"
if "-> dict" in ret.lower():
return "return {}"
return "return None # TODO"
# ---------------------------------------------------------------------------
# Protocol tests
# ---------------------------------------------------------------------------
def gen_protocol_tests(
class_name: str,
extra: dict[str, Any],
import_path: str,
tf: TestFile | None = None,
config: ProjectConfig | None = None,
) -> str:
"""
Generate:
1. A standalone conformance-checker function (usable in other tests).
2. A pytest class testing the protocol contract.
Covers:
- @runtime_checkable isinstance check (if applicable)
- Non-conforming object fails isinstance
- Minimal conforming class passes the checker
- Each required attribute/method is validated
"""
members: list[dict] = extra.get("protocol_members", [])
checkable: bool = extra.get("is_runtime_checkable", False)
attrs = [m for m in members if m["kind"] == "attribute"]
methods = [m for m in members if m["kind"] in ("method", "async_method")]
cb = CodeBuilder()
# ---- standalone conformance checker ----
cb.comment("Protocol conformance checker — import and use in integration tests")
cb.func(f"assert_{class_name.lower()}_conformance", args="obj: object")
cb.docstring(
f"Assert that obj satisfies the {class_name} protocol.\n\n"
f"Required attributes: {[a['name'] for a in attrs]}\n"
f"Required methods: {[m['name'] for m in methods]}"
)
for a in attrs:
ann = f" ({a['type']})" if a.get("type") else ""
cb.line(
f'assert hasattr(obj, "{a["name"]}"), '
f'"Missing {class_name} attribute: {a["name"]}{ann}"'
)
for m in methods:
if m.get("is_property"):
cb.line(
f'assert hasattr(obj, "{m["name"]}"), '
f'"Missing {class_name} property: {m["name"]}"'
)
else:
cb.line(
f'assert callable(getattr(obj, "{m["name"]}", None)), '
f'"Missing {class_name} method: {m["name"]}"'
)
cb.end()
cb.blank()
cb.blank()
# ---- pytest class ----
cb.cls(f"Test{class_name}Protocol")
cb.docstring(
f"Structural tests for Protocol {class_name}.\n"
+ ("@runtime_checkable" if checkable else "Not @runtime_checkable")
)
cb.blank()
# build a minimal conforming class inline
def _add_conforming_class() -> None:
cb.comment("Minimal conforming implementation")
cb.cls("_Conforming")
if not attrs and not methods:
cb.line("pass")
for a in attrs:
cb.line(
f"{a['name']}: {a['type'] or 'object'} = "
f"{_sentinel_for_annotation(a.get('type'), tf, config)}"
)
for m in methods:
if m.get("is_property"):
cb.line("@property")
cb.func(m["name"], "self")
cb.line(
f"return {_sentinel_for_annotation(m.get('returns'), tf, config)}"
)
cb.end()
elif m.get("is_classmethod"):
cb.line("@classmethod")
cb.func(m["name"], "cls, *args, **kwargs")
cb.line("return cls()")
cb.end()
elif m.get("is_staticmethod"):
cb.line("@staticmethod")
cb.func(m["name"], "*args, **kwargs")
cb.line("return None")
cb.end()
else:
cb.func(m["name"], "self, *args, **kwargs")
cb.line(
f"return {_sentinel_for_annotation(m.get('returns'), tf, config)}"
)
cb.end()
cb.end() # _Conforming
# ---- runtime_checkable isinstance ----
if checkable:
cb.func("test_conforming_isinstance")
cb.docstring(
f"@runtime_checkable: isinstance(obj, {class_name}) must return True "
f"for a conforming object."
)
_add_conforming_class()
cb.line("obj = _Conforming()")
cb.line(f"assert isinstance(obj, {class_name})")
cb.end()
cb.blank()
cb.func("test_non_conforming_isinstance")
cb.docstring("A plain object() must not satisfy the protocol.")
cb.line(f"assert not isinstance(object(), {class_name})")
cb.end()
cb.blank()
# ---- conformance checker ----
cb.func("test_conformance_checker_passes")
cb.docstring("The standalone conformance checker must not raise for a valid impl.")
_add_conforming_class()
cb.line("obj = _Conforming()")
cb.line(f"assert_{class_name.lower()}_conformance(obj) # must not raise")
cb.end()
cb.blank()
cb.func("test_conformance_checker_catches_missing_members")
cb.docstring(
"The conformance checker must raise AssertionError for an empty class."
)
if attrs or methods:
cb.pytest_raises("AssertionError")
cb.line(f"assert_{class_name.lower()}_conformance(object())")
cb.end()
else:
cb.line("pass # no members to check")
cb.end()
cb.blank()
# ---- per-attribute existence ----
if attrs:
cb.func("test_required_attributes_present")
_add_conforming_class()
cb.line("obj = _Conforming()")
for a in attrs:
cb.line(
f'assert hasattr(obj, "{a["name"]}"), '
f'"expected attribute {a["name"]}"'
)
cb.end()
cb.blank()
# ---- per-method callability ----
if methods:
cb.func("test_required_methods_callable")
_add_conforming_class()
cb.line("obj = _Conforming()")
for m in [x for x in methods if not x.get("is_property")]:
cb.line(f'assert callable(getattr(obj, "{m["name"]}", None))')
cb.end()
cb.blank()
cb.end() # class
return cb.render()
# ---------------------------------------------------------------------------
# Entry-point ranked stubs (Layer 1)
# ---------------------------------------------------------------------------
def gen_entry_point_tests(ranked_entries: list[RankedEntry], top_n: int = 10) -> str:
"""
For the top-N ranked functions, generate a prioritised TODO stub class
with a comment showing priority/score and a basic call stub.
No mocked-dependency variant: this project's hard rule is "no
mocking, ever" — a real integration test belongs in a hand-written
suite, not a generated one that can't know which collaborators are
safe to exercise for real.
"""
cb = CodeBuilder()
cb.comment("=" * 70)
cb.comment("Entry point tests — ranked by fan_in + reachable scope")
cb.comment("=" * 70)
cb.blank()
for entry in ranked_entries[:top_n]:
short = entry.symbol.name
sig_args = _sig_from_params(entry.symbol.parameters)
call_str = (
f"{short}({sig_args})"
if not entry.is_method
else f"obj.{short}({sig_args})"
)
cls_name = (
f"Test{entry.class_name}{short.title().replace('_', '')}"
if entry.is_method
else f"Test{short.title().replace('_', '')}"
)
cb.cls(cls_name)
cb.docstring(
f"Tests for: {entry.qualified_name}\n"
f"Priority score : {entry.score:.1f}\n"
f"Fan-in : {entry.fan_in} callers\n"
f"Reachable : {entry.reachable} downstream scopes"
)
cb.blank()
cb.func("test_basic_call")
cb.docstring(
f"Smoke test: {entry.qualified_name} must not raise with minimal valid inputs."
)
if entry.is_method:
cb.todo(f"instantiate {entry.class_name} and call .{short}()")
else:
cb.todo(f"arrange inputs for {entry.qualified_name}")
cb.line(f"# result = {call_str}")
cb.line("# assert result is not None")
cb.line("pass # TODO: fill in")
cb.end()
cb.blank()
cb.end() # class
cb.blank()
return cb.render()
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
def _field_sentinel(
field_info: dict, tf: TestFile | None = None, config: ProjectConfig | None = None
) -> str:
"""Return a sentinel value for a dataclass field, tracking imports."""
ann = field_info.get("type")
val = sentinel(ann, config)
if tf is not None:
for mod in sentinel_imports(ann):
tf.add_import(mod)
if sentinel_needs_cast(ann, config):
tf.add_typing_import("Any")
tf.add_typing_import("cast")
return val
def _sentinel_for_annotation(
annotation: str | None,
tf: TestFile | None = None,
config: ProjectConfig | None = None,
) -> str:
"""Return a sentinel value for a type annotation, tracking imports."""
val = sentinel(annotation, config)
if tf is not None:
for mod in sentinel_imports(annotation):
tf.add_import(mod)
if sentinel_needs_cast(annotation, config):
tf.add_typing_import("Any")
tf.add_typing_import("cast")
return val
def _sig_from_params(params: list[Any]) -> str:
"""Build a call-site argument string from a list of ParamInfo-like
objects (``orchestrata_ast.models.ParamInfo``: ``.name``,
``.type_annotation``, ``.kind``)."""
parts: list[str] = []
for p in params:
if p.name in ("self", "cls") or p.kind in ("varargs", "kwargs"):
continue
parts.append(f"{p.name}={sentinel(p.type_annotation)}")
return ", ".join(parts)