Documentation
¶
Overview ¶
Package runner provides the core replay logic including state management.
Index ¶
- Constants
- func BuildChildEnv(interceptDir, sessionID, scenarioPath string) []string
- func CleanExpiredSessions(cliReplayDir string, ttl time.Duration, warnWriter io.Writer) (int, error)
- func DeleteState(path string) error
- func ExitCodeFromError(err error) int
- func FindGroupContaining(ranges []scenario.GroupRange, flatIdx int) int
- func FormatDryRunReport(report *DryRunReport, w io.Writer) error
- func FormatMismatchError(err *MismatchError) string
- func FormatStdinMismatchError(err *StdinMismatchError) string
- func InterceptDirPath(scenarioPath string) (string, error)
- func IsTraceEnabled(envValue string) bool
- func ReplayResponse(step *scenario.Step, _ string, stdout, stderr io.Writer) int
- func ReplayResponseWithFile(step *scenario.Step, scenarioPath string, stdout, stderr io.Writer) int
- func ReplayResponseWithTemplate(step *scenario.Step, scn *scenario.Scenario, scenarioPath string, ...) int
- func StateFilePath(scenarioPath string) string
- func StateFilePathWithSession(scenarioPath, session string) string
- func WriteDeniedEnvTrace(w io.Writer, varName string)
- func WriteState(path string, state *State) error
- func WriteTraceOutput(w io.Writer, stepIndex int, argv []string, exitCode int)
- type DryRunReport
- type DryRunStep
- type GroupMismatchError
- type MismatchError
- type ReplayResult
- type State
- func (s *State) Advance()
- func (s *State) AdvanceStep(idx int)
- func (s *State) AllStepsConsumed() bool
- func (s *State) AllStepsMetMin(steps []scenario.Step) bool
- func (s *State) CurrentGroupRange(ranges []scenario.GroupRange) *scenario.GroupRange
- func (s *State) EnterGroup(groupIdx int)
- func (s *State) ExitGroup()
- func (s *State) GroupAllMaxesHit(gr scenario.GroupRange, steps []scenario.Step) bool
- func (s *State) GroupAllMinsMet(gr scenario.GroupRange, steps []scenario.Step) bool
- func (s *State) IncrementStep(idx int)
- func (s *State) IsComplete() bool
- func (s *State) IsInGroup() bool
- func (s *State) IsStepConsumed(idx int) bool
- func (s *State) RemainingSteps() int
- func (s *State) StepBudgetRemaining(idx, maxCalls int) int
- type StdinMismatchError
Constants ¶
const TraceEnvVar = "CLI_REPLAY_TRACE"
TraceEnvVar is the environment variable name for enabling trace mode.
Variables ¶
This section is empty.
Functions ¶
func BuildChildEnv ¶
BuildChildEnv returns a copy of the current process's environment with:
- PATH prepended with interceptDir
- CLI_REPLAY_SESSION set to sessionID
- CLI_REPLAY_SCENARIO set to scenarioPath
The returned slice is suitable for use as exec.Cmd.Env.
func CleanExpiredSessions ¶
func CleanExpiredSessions(cliReplayDir string, ttl time.Duration, warnWriter io.Writer) (int, error)
CleanExpiredSessions scans a .cli-replay/ directory for state files older than the given TTL. For each expired state file, it removes the associated intercept directory (if any) and the state file itself. Returns the number of cleaned sessions and any error. Files with last_updated in the future are treated as active (with a warning written to stderr if warnWriter is non-nil). Permission errors on individual files are logged and skipped.
func DeleteState ¶
DeleteState removes the state file at the given path. Does not return an error if the file doesn't exist.
func ExitCodeFromError ¶
ExitCodeFromError extracts the exit code from an exec.Cmd.Wait() error.
Returns:
- 0 if err is nil (child exited successfully)
- The child's exit code if it exited normally with non-zero status
- 128+signum if the child was killed by a signal (POSIX convention)
- 1 for any other error (e.g., command not found wraps a non-ExitError)
func FindGroupContaining ¶
func FindGroupContaining(ranges []scenario.GroupRange, flatIdx int) int
FindGroupContaining returns the index into groupRanges that contains the given flat step index, or -1 if the index is not inside any group.
func FormatDryRunReport ¶
func FormatDryRunReport(report *DryRunReport, w io.Writer) error
FormatDryRunReport writes a human-readable dry-run report to the writer.
func FormatMismatchError ¶
func FormatMismatchError(err *MismatchError) string
FormatMismatchError formats a MismatchError for user-friendly output. Uses ElementMatchDetail for per-element diff, shows template patterns, and handles length mismatches with detailed position info.
func FormatStdinMismatchError ¶
func FormatStdinMismatchError(err *StdinMismatchError) string
FormatStdinMismatchError formats a StdinMismatchError for user-friendly output.
func InterceptDirPath ¶
InterceptDirPath creates an intercept directory inside .cli-replay/ next to the scenario file. Returns the path to the created directory.
func IsTraceEnabled ¶
IsTraceEnabled returns true if trace mode should be enabled.
func ReplayResponse ¶
ReplayResponse writes the step's response to stdout/stderr and returns the exit code. This variant handles inline stdout/stderr only (no templates).
func ReplayResponseWithFile ¶
ReplayResponseWithFile writes the step's response to stdout/stderr and returns the exit code. This variant handles both inline and file-based stdout/stderr (no templates).
func ReplayResponseWithTemplate ¶
func ReplayResponseWithTemplate(step *scenario.Step, scn *scenario.Scenario, scenarioPath string, captures map[string]string, stdout, stderr io.Writer) int
ReplayResponseWithTemplate writes the step's response with template rendering. Templates in stdout/stderr are rendered with vars from scenario meta + environment, and captures from prior steps via the "capture" template namespace. If deny_env_vars is configured, denied env vars are suppressed and traced.
func StateFilePath ¶
StateFilePath returns the path to the state file for a given scenario path. The state file is stored in .cli-replay/ next to the scenario file, with a hash of the scenario path to ensure uniqueness. If CLI_REPLAY_SESSION is set, it is included in the hash to allow parallel sessions.
func StateFilePathWithSession ¶
StateFilePathWithSession returns the state file path for a given scenario and session ID. When session is non-empty, each session gets its own state file, enabling parallel test execution against the same scenario.
func WriteDeniedEnvTrace ¶
WriteDeniedEnvTrace writes a trace line for a denied environment variable substitution. Called when CLI_REPLAY_TRACE is enabled and an env var override is suppressed by a deny pattern.
func WriteState ¶
WriteState persists the state to the given file path. Uses atomic write (write to temp file, then rename) to prevent corruption.
Types ¶
type DryRunReport ¶
type DryRunReport struct {
ScenarioName string
Description string
TotalSteps int
Steps []DryRunStep
Groups []scenario.GroupRange
Commands []string
Allowlist []string
AllowlistIssues []string
TemplateVars []string
SessionTTL string
}
DryRunReport contains the information needed to display a dry-run preview of a scenario. Not persisted — used only for formatting output.
func BuildDryRunReport ¶
func BuildDryRunReport(scn *scenario.Scenario) *DryRunReport
BuildDryRunReport constructs a DryRunReport from a loaded scenario.
type DryRunStep ¶
type DryRunStep struct {
Index int
MatchArgv string
Exit int
StdoutPreview string
CallsMin int
CallsMax int
GroupName string
GroupMode string
Captures []string
}
DryRunStep contains per-step information for dry-run display.
type GroupMismatchError ¶
type GroupMismatchError struct {
Scenario string
GroupName string
GroupIndex int // index into GroupRanges()
Candidates []int // flat indices of unconsumed group steps
CandidateArgv [][]string // argv of each candidate step
Received []string // the received argv that didn't match
}
GroupMismatchError is returned when a command does not match any step within an unordered group and the group's minimum counts are not yet met.
func (*GroupMismatchError) Error ¶
func (e *GroupMismatchError) Error() string
type MismatchError ¶
type MismatchError struct {
Scenario string
StepIndex int
Expected []string
Received []string
SoftAdvanced bool // true if we tried soft-advancing past a satisfied step
NextStepIndex int // index of the next step tried (when SoftAdvanced)
NextExpected []string // argv of the next step tried (when SoftAdvanced)
}
MismatchError represents an argv mismatch during replay.
func (*MismatchError) Error ¶
func (e *MismatchError) Error() string
type ReplayResult ¶
ReplayResult contains the outcome of a replay operation.
func ExecuteReplay ¶
func ExecuteReplay(scenarioPath string, argv []string, stdout, stderr io.Writer) (*ReplayResult, error)
ExecuteReplay runs the replay logic for a given scenario and argv. It loads the scenario, checks/creates state, delegates matching to pkg/replay.Engine, writes response output, and persists state.
type State ¶
type State struct {
ScenarioPath string `json:"scenario_path"`
ScenarioHash string `json:"scenario_hash"`
CurrentStep int `json:"current_step"`
TotalSteps int `json:"total_steps"`
StepCounts []int `json:"step_counts,omitempty"`
ConsumedSteps []bool `json:"consumed_steps,omitempty"` // deprecated: read-only migration
ActiveGroup *int `json:"active_group,omitempty"`
InterceptDir string `json:"intercept_dir,omitempty"`
LastUpdated time.Time `json:"last_updated"`
Captures map[string]string `json:"captures,omitempty"`
}
State tracks scenario progress across CLI invocations.
func ReadState ¶
ReadState loads the state from the given file path. Returns os.ErrNotExist if the file doesn't exist. Migrates legacy ConsumedSteps []bool to StepCounts []int if needed.
func (*State) Advance ¶
func (s *State) Advance()
Advance increments the current step counter and marks the step as consumed.
func (*State) AdvanceStep ¶
AdvanceStep marks a specific step index as consumed (for out-of-order consumption).
func (*State) AllStepsConsumed ¶
AllStepsConsumed returns true if every step has been invoked at least once.
func (*State) AllStepsMetMin ¶
AllStepsMetMin returns true if every step has been invoked at least its minimum required number of times. Steps without explicit CallBounds default to min=1 via EffectiveCalls().
func (*State) CurrentGroupRange ¶
func (s *State) CurrentGroupRange(ranges []scenario.GroupRange) *scenario.GroupRange
CurrentGroupRange returns the GroupRange for the currently active group. Returns nil if the state is not inside a group.
func (*State) EnterGroup ¶
EnterGroup sets the active group index.
func (*State) ExitGroup ¶
func (s *State) ExitGroup()
ExitGroup clears the active group, indicating the state has left a group.
func (*State) GroupAllMaxesHit ¶
GroupAllMaxesHit returns true if every step in the given group range has reached its maximum call count.
func (*State) GroupAllMinsMet ¶
GroupAllMinsmet returns true if every step in the given group range has met its minimum call count.
func (*State) IncrementStep ¶
IncrementStep increments the invocation count for a specific step index without advancing CurrentStep. Used by the call-count-bounds loop when re-matching the same step.
func (*State) IsComplete ¶
IsComplete returns true if all steps have been consumed.
func (*State) IsStepConsumed ¶
IsStepConsumed returns true if the step at the given index has been invoked at least once.
func (*State) RemainingSteps ¶
RemainingSteps returns the number of steps not yet consumed.
func (*State) StepBudgetRemaining ¶
StepBudgetRemaining returns how many more calls step[idx] can accept given a maximum call count. Returns 0 when the budget is exhausted.
type StdinMismatchError ¶
StdinMismatchError represents a stdin content mismatch during replay.
func (*StdinMismatchError) Error ¶
func (e *StdinMismatchError) Error() string