from __future__ import annotations from collections.abc import Callable, Sequence from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Annotated, Literal, Protocol, TypeAlias from pydantic import BaseModel, ConfigDict, StringConstraints from .models import CaseDisposition, Coverage, HarnessCase, HarnessRun, SdkFunction, Surface from .rendering import StrategyRenderer if TYPE_CHECKING: from click import Command UpdateCallback: TypeAlias = Callable[[HarnessRun], None] NonBlankString: TypeAlias = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] class SuiteCaseSpec(BaseModel): model_config = ConfigDict(frozen=True, extra="forbid") disposition: Literal[CaseDisposition.RUNNABLE] = CaseDisposition.RUNNABLE coverage: Coverage suite: NonBlankString note: str = "" class ModuleCaseSpec(BaseModel): model_config = ConfigDict(frozen=True, extra="forbid") disposition: Literal[CaseDisposition.RUNNABLE] = CaseDisposition.RUNNABLE coverage: Coverage module: NonBlankString note: str = "" class NotImplementedCaseSpec(BaseModel): model_config = ConfigDict(frozen=True, extra="forbid") disposition: Literal[CaseDisposition.NOT_IMPLEMENTED] = CaseDisposition.NOT_IMPLEMENTED coverage: None = None reason: NonBlankString class SkippedCaseSpec(BaseModel): model_config = ConfigDict(frozen=True, extra="forbid") disposition: Literal[CaseDisposition.SKIPPED] = CaseDisposition.SKIPPED coverage: None = None reason: NonBlankString RunnableCaseSpec: TypeAlias = SuiteCaseSpec | ModuleCaseSpec UnavailableCaseSpec: TypeAlias = NotImplementedCaseSpec | SkippedCaseSpec CaseSpec: TypeAlias = RunnableCaseSpec | UnavailableCaseSpec @dataclass(frozen=True, slots=True) class CaseDefinition: sdk_function: SdkFunction spec: CaseSpec surface: Surface | None = None @dataclass(frozen=True, slots=True) class RunnerArgumentDefinition: option: str help: str metavar: str = "ARG" class StrategyRunner(Protocol): def __call__( self, cases: Sequence[HarnessCase], repo_root: Path, on_update: UpdateCallback, runner_args: Sequence[str] = (), ) -> tuple[int, HarnessRun]: ... @dataclass(frozen=True, slots=True) class StrategyDefinition: id: str order: int label: str description: str directory: Path runnable_spec: type[SuiteCaseSpec] | type[ModuleCaseSpec] cases: tuple[CaseDefinition, ...] run: StrategyRunner render: StrategyRenderer surfaces: tuple[Surface, ...] = () runner_argument: RunnerArgumentDefinition | None = None @dataclass(frozen=True, slots=True) class RepositoryStrategyDefinition: id: str order: int label: str description: str directory: Path command: Callable[[Path], Command]