runner

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 3, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package runner provides the core replay logic including state management.

Index

Constants

View Source
const TraceEnvVar = "CLI_REPLAY_TRACE"

TraceEnvVar is the environment variable name for enabling trace mode.

Variables

This section is empty.

Functions

func BuildChildEnv

func BuildChildEnv(interceptDir, sessionID, scenarioPath string) []string

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

func DeleteState(path string) error

DeleteState removes the state file at the given path. Does not return an error if the file doesn't exist.

func ExitCodeFromError

func ExitCodeFromError(err error) int

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

func InterceptDirPath(scenarioPath string) (string, error)

InterceptDirPath creates an intercept directory inside .cli-replay/ next to the scenario file. Returns the path to the created directory.

func IsTraceEnabled

func IsTraceEnabled(envValue string) bool

IsTraceEnabled returns true if trace mode should be enabled.

func ReplayResponse

func ReplayResponse(step *scenario.Step, _ string, stdout, stderr io.Writer) int

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

func ReplayResponseWithFile(step *scenario.Step, scenarioPath string, stdout, stderr io.Writer) int

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

func StateFilePath(scenarioPath string) string

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

func StateFilePathWithSession(scenarioPath, session string) string

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

func WriteDeniedEnvTrace(w io.Writer, varName string)

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

func WriteState(path string, state *State) error

WriteState persists the state to the given file path. Uses atomic write (write to temp file, then rename) to prevent corruption.

func WriteTraceOutput

func WriteTraceOutput(w io.Writer, stepIndex int, argv []string, exitCode int)

WriteTraceOutput writes trace information to the given writer.

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

type ReplayResult struct {
	ExitCode     int
	Matched      bool
	StepIndex    int
	ScenarioName string
}

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 NewState

func NewState(scenarioPath, scenarioHash string, totalSteps int) *State

NewState creates a new state for the given scenario.

func ReadState

func ReadState(path string) (*State, error)

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

func (s *State) AdvanceStep(idx int)

AdvanceStep marks a specific step index as consumed (for out-of-order consumption).

func (*State) AllStepsConsumed

func (s *State) AllStepsConsumed() bool

AllStepsConsumed returns true if every step has been invoked at least once.

func (*State) AllStepsMetMin

func (s *State) AllStepsMetMin(steps []scenario.Step) bool

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

func (s *State) EnterGroup(groupIdx int)

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

func (s *State) GroupAllMaxesHit(gr scenario.GroupRange, steps []scenario.Step) bool

GroupAllMaxesHit returns true if every step in the given group range has reached its maximum call count.

func (*State) GroupAllMinsMet

func (s *State) GroupAllMinsMet(gr scenario.GroupRange, steps []scenario.Step) bool

GroupAllMinsmet returns true if every step in the given group range has met its minimum call count.

func (*State) IncrementStep

func (s *State) IncrementStep(idx int)

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

func (s *State) IsComplete() bool

IsComplete returns true if all steps have been consumed.

func (*State) IsInGroup

func (s *State) IsInGroup() bool

IsInGroup returns true if the state is currently inside a step group.

func (*State) IsStepConsumed

func (s *State) IsStepConsumed(idx int) bool

IsStepConsumed returns true if the step at the given index has been invoked at least once.

func (*State) RemainingSteps

func (s *State) RemainingSteps() int

RemainingSteps returns the number of steps not yet consumed.

func (*State) StepBudgetRemaining

func (s *State) StepBudgetRemaining(idx, maxCalls int) int

StepBudgetRemaining returns how many more calls step[idx] can accept given a maximum call count. Returns 0 when the budget is exhausted.

type StdinMismatchError

type StdinMismatchError struct {
	Scenario  string
	StepIndex int
	Expected  string
	Received  string
}

StdinMismatchError represents a stdin content mismatch during replay.

func (*StdinMismatchError) Error

func (e *StdinMismatchError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL