Documentation
¶
Overview ¶
Package e2e is the end-to-end test suite of Chainloop Trace (spec issue-3635). It runs real coding agents in headless mode, in throwaway repositories with Trace in local storage mode, and builds a result matrix of harness, model and feature. The expected matrix is the feature table of the Trace README.
Index ¶
- Constants
- func Markdown(res *Result) string
- type Cell
- type Change
- type Changes
- type Check
- type Claude
- func (c *Claude) Column() string
- func (c *Claude) DisableSessionStart(repo string) error
- func (c *Claude) HookConfig() HookConfig
- func (c *Claude) Run(ctx context.Context, req RunRequest) (*RunOutput, error)
- func (c *Claude) SkillPath(name string) string
- func (c *Claude) TraceFlag() string
- func (c *Claude) Version(ctx context.Context) (string, error)
- type Codex
- func (c *Codex) Column() string
- func (c *Codex) DisableSessionStart(string) error
- func (c *Codex) HookConfig() HookConfig
- func (c *Codex) Run(ctx context.Context, req RunRequest) (*RunOutput, error)
- func (c *Codex) SkillPath(name string) string
- func (c *Codex) TraceFlag() string
- func (c *Codex) Version(ctx context.Context) (string, error)
- type Config
- type Duration
- type Expected
- type Feature
- type Filter
- type Harness
- type HarnessConfig
- type HookConfig
- type HookMessage
- type OpenCode
- func (o *OpenCode) Column() string
- func (o *OpenCode) DisableSessionStart(repo string) error
- func (o *OpenCode) HookConfig() HookConfig
- func (o *OpenCode) Run(ctx context.Context, req RunRequest) (*RunOutput, error)
- func (o *OpenCode) SkillPath(name string) string
- func (o *OpenCode) TraceFlag() string
- func (o *OpenCode) Version(ctx context.Context) (string, error)
- type Pi
- func (p *Pi) Column() string
- func (p *Pi) DisableSessionStart(string) error
- func (p *Pi) HookConfig() HookConfig
- func (p *Pi) Run(ctx context.Context, req RunRequest) (*RunOutput, error)
- func (p *Pi) SkillPath(name string) string
- func (p *Pi) TraceFlag() string
- func (p *Pi) Version(ctx context.Context) (string, error)
- type Repeat
- type Result
- type ResultStore
- type RunOutput
- type RunRequest
- type Runner
- type Scenario
- type Session
- type Status
- type StoredSession
- type Support
- type Workspace
Constants ¶
const HarnessClaudeCode = "Claude Code"
HarnessClaudeCode is the column name of Claude Code in the feature table.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Cell ¶
type Cell struct {
Feature string `json:"feature"`
Harness string `json:"harness"`
HarnessVersion string `json:"harness_version,omitempty"`
Model string `json:"model"`
Expected Support `json:"expected"`
Status Status `json:"status"`
// Passes and Runs give the pass rate of the cell (R-006).
Passes int `json:"passes"`
Runs int `json:"runs"`
// Reason tells why the cell did not pass, or why it was not tested.
Reason string `json:"reason,omitempty"`
}
Cell is one cell of the result matrix: a feature for a harness and a model.
type Change ¶
type Change struct {
Cell Cell
// Previous is the status in the previous run, empty when the cell is new.
Previous Status
}
Change is a cell whose state changed from the previous run.
type Changes ¶
type Changes struct {
// Regressions are cells that passed in the previous run and fail now.
Regressions []Change
// Improvements are cells marked "Unknown" that pass now and did not pass
// in the previous run, so the team can update the table.
Improvements []Change
}
Changes are the cells to report after a run (R-007).
type Claude ¶
type Claude struct {
// Bin is the claude executable. Empty means "claude" from PATH.
Bin string
}
Claude is the harness adapter of Claude Code.
func (*Claude) DisableSessionStart ¶
DisableSessionStart implements Harness. It deletes the SessionStart entry from the hooks of the project settings and keeps the other entries as they are.
func (*Claude) Run ¶
Run implements Harness. It runs `claude -p` with the stream-json output and the hook events, so the output shows the hook messages that the user sees.
Only the project and local settings are loaded. The settings and the plugins of the user who runs the suite would add their own hooks and change the session.
type Codex ¶ added in v1.127.0
type Codex struct {
// Bin is the codex executable. Empty means "codex" from PATH.
Bin string
}
Codex is the harness adapter of the OpenAI Codex CLI.
func (*Codex) DisableSessionStart ¶ added in v1.127.0
DisableSessionStart implements Harness. Trace installs no Codex hooks yet, so there is no hook to remove.
func (*Codex) HookConfig ¶ added in v1.127.0
func (c *Codex) HookConfig() HookConfig
HookConfig implements Harness. The hooks of the user and the Chainloop hooks share one file.
func (*Codex) Run ¶ added in v1.127.0
Run implements Harness. It runs `codex exec` with the JSON output.
The configuration folder of Codex (CODEX_HOME) is next to the workspace, so the configuration, the hooks, the skills and the sessions of the user who runs the suite do not load or mix with the sessions of the suite. The sandbox and the approvals are off, because the workspace is a throwaway repository. The project hooks run with no persisted trust. --ignore-user-config is not set, because it also turns off the project hooks.
Trace has no Codex provider yet, so no hook message is reported.
func (*Codex) SkillPath ¶ added in v1.127.0
SkillPath implements Harness. Codex finds project skills in .agents/skills.
type Config ¶
type Config struct {
// Harnesses are keyed by the column name in the feature table.
Harnesses map[string]HarnessConfig `json:"harnesses"`
// ModelDriven is the default repeat of a model-driven feature (R-006).
ModelDriven Repeat `json:"modelDriven"`
// Features override the repeat of a feature, keyed by feature name.
Features map[string]Repeat `json:"features"`
ScenarioTimeout Duration `json:"scenarioTimeout"`
Parallel int `json:"parallel"`
}
Config is the configuration of the suite.
func LoadConfig ¶
LoadConfig reads the suite configuration. A nil data reads the embedded config.yaml.
type Duration ¶
Duration is a time.Duration written as a Go duration string in YAML.
func (*Duration) UnmarshalJSON ¶
UnmarshalJSON reads a duration string such as "5m".
type Expected ¶
type Expected struct {
// Features are the feature names in the order of the README.
Features []string
// Harnesses are the harness column names in the order of the README.
Harnesses []string
// contains filtered or unexported fields
}
Expected is the expected matrix read from the feature table.
func ParseExpected ¶
ParseExpected reads the feature tables of the Trace README. All feature tables must have the same harness columns.
type Feature ¶
type Feature struct {
// ControlPlane is true for a feature that needs a control plane. The suite
// runs in local storage mode only, so its cells are "not covered" (D-007).
ControlPlane bool
// NotCovered is the reason why the suite cannot see the feature at all,
// for example because neither the user nor the evidence can observe it.
// Its cells are "not covered".
NotCovered string
// ModelDriven is true for a feature that depends on the model following an
// instruction. Its cells run more than once (R-006).
ModelDriven bool
// Checks must all pass for a run of the feature to pass.
Checks []Check
}
Feature is the test definition of one feature row of the table. The key of the features map is the feature name in the table (R-002).
type Filter ¶
Filter limits a manual run to some harnesses, models or features (R-009). An empty list matches all.
type Harness ¶
type Harness interface {
// Column is the column name of the harness in the feature table.
Column() string
// TraceFlag is the flag of `chainloop trace init` that selects the
// harness, for example "--claude".
TraceFlag() string
// Version returns the installed version of the harness.
Version(ctx context.Context) (string, error)
// Run runs one headless session and returns its output. An error means
// that the session could not run, not that a feature failed.
Run(ctx context.Context, req RunRequest) (*RunOutput, error)
// HookConfig describes the hook configuration file of the harness.
HookConfig() HookConfig
// DisableSessionStart removes the session-start hook that `trace init`
// installed in the repository, so that a session starts with no
// session-start hook.
DisableSessionStart(repo string) error
// SkillPath returns the file of a project skill, relative to the
// repository root.
SkillPath(name string) string
}
Harness adapts one coding agent. It is the only part of the suite that knows the agent (R-011).
type HarnessConfig ¶
type HarnessConfig struct {
// Version is the pinned harness version (D-013).
Version string `json:"version"`
// Models are the models that the suite runs the harness with (D-009).
Models []string `json:"models"`
}
HarnessConfig pins a harness column and lists its models.
type HookConfig ¶
type HookConfig struct {
// Path is the file of the Chainloop hooks, relative to the repository
// root.
Path string
// ChainloopMarker is a text that only a Chainloop hook in the file holds.
ChainloopMarker string
// UserPath is the file of the hook of the user, relative to the
// repository root. Empty means Path: the hooks share one file.
UserPath string
// UserConfig is the content of the user file with one hook of the user
// and no Chainloop hook.
UserConfig string
// UserMarker is a text that only the hook of the user holds.
UserMarker string
}
HookConfig describes the files where the harness reads its hooks.
func (HookConfig) UserFile ¶
func (c HookConfig) UserFile() string
UserFile returns the file of the hook of the user.
type HookMessage ¶
type HookMessage struct {
// Event is the hook event, for example SessionStart.
Event string
// UserMessage is the text that the harness shows to the user.
UserMessage string
// ModelContext is the text that the harness gives to the model.
ModelContext string
}
HookMessage is a message that a Trace hook returned to the harness.
type OpenCode ¶
type OpenCode struct {
// Major is the major version of OpenCode that the column covers.
Major string
// Bin is the opencode executable. Empty means "opencode" from PATH.
Bin string
}
OpenCode is the harness adapter of OpenCode. One adapter serves one major version, which is one column of the feature table.
func (*OpenCode) DisableSessionStart ¶
DisableSessionStart implements Harness. It changes the session.created handler of the plugin, so that the session-start hook never runs.
func (*OpenCode) HookConfig ¶
func (o *OpenCode) HookConfig() HookConfig
HookConfig implements Harness. The Chainloop plugin is a file of its own, so the plugin of the user is in another file.
func (*OpenCode) Run ¶
Run implements Harness. It runs `opencode run` with the JSON output. Each session gets a private server (--standalone): the shared background server keeps the environment and the plugins of the directory that started it. --auto approves the tool calls, which a headless run cannot answer.
The plugin posts the instruction and the reminder of Trace to the session as synthetic messages, which the run output does not show. Run exports the session after the run and reads them from the export.
type Pi ¶
type Pi struct {
// Bin is the pi executable. Empty means "pi" from PATH.
Bin string
}
Pi is the harness adapter of the Pi coding agent.
func (*Pi) DisableSessionStart ¶
DisableSessionStart implements Harness. The Chainloop extension of Pi does not exist yet, so there is no handler to change.
func (*Pi) HookConfig ¶
func (p *Pi) HookConfig() HookConfig
HookConfig implements Harness. The Chainloop extension is a file of its own, so the extension of the user is in another file.
func (*Pi) Run ¶
Run implements Harness. It runs `pi -p` with the JSON output.
The configuration folder of Pi and its session folder are next to the workspace, so the extensions, the skills and the settings of the user who runs the suite do not load, and the sessions of the suite do not mix with the sessions of the user. --approve trusts the project files of the workspace, which hold the Chainloop extension.
The Chainloop extension gives its instruction and reminder to the model as hidden context, which the run output does not show. Until the suite reads them from the session file, the hook messages are not reported.
type Result ¶
type Result struct {
SchemaVersion int `json:"schema_version"`
StartedAt time.Time `json:"started_at"`
CLIVersion string `json:"cli_version"`
// Full is true for a run with no filter. Only a full run becomes the
// previous result of the next comparison (R-009).
Full bool `json:"full"`
Cells []Cell `json:"cells"`
}
Result is the result matrix of one run.
type ResultStore ¶
type ResultStore struct {
Dir string
}
ResultStore keeps the result matrices of the runs in a directory.
func (ResultStore) Previous ¶
func (s ResultStore) Previous() (*Result, error)
Previous returns the result of the last full run, or nil when there is none.
type RunOutput ¶
type RunOutput struct {
// Raw is the full output of the harness process.
Raw []byte
// Hooks are the hook messages that the harness reported, in order. A
// harness that does not report them leaves it empty.
Hooks []HookMessage
// HooksReported is true when the harness reports hook messages in its
// headless output. When it is false, a check on a hook message is "not
// covered" (R-004).
HooksReported bool
}
RunOutput is what a harness showed during a session.
type RunRequest ¶
type RunRequest struct {
// Dir is the repository the session runs in.
Dir string
// Model is the model of the session, as the harness names it.
Model string
// Prompt is the only user prompt of the session.
Prompt string
// Env is the environment of the harness process.
Env []string
// Wrapper is a command that runs the harness, for example
// `chainloop trace run --local --claude --`. Empty runs the harness
// directly.
Wrapper []string
// Images are PNG images that the user pastes with the prompt.
Images [][]byte
}
RunRequest is one headless session of a harness.
type Runner ¶
type Runner struct {
Expected *Expected
Config *Config
Harnesses []Harness
Filter Filter
// ChainloopBin is the chainloop CLI under test.
ChainloopBin string
CLIVersion string
// WorkDir holds the workspaces and the harness output of each session.
WorkDir string
Logf func(format string, args ...any)
}
Runner expands the expected matrix into scenario sessions, runs them, and builds the result matrix.
type Scenario ¶
type Scenario struct {
Name string
// Seed are the files of the repository before the session, by path.
Seed map[string]string
// Prompt is the only user prompt of the session.
Prompt string
// Checkout is the checkout where the agent commits and pushes, relative
// to the workspace base. Empty means the repository of the workspace.
Checkout string
// Images are PNG images that the user pastes with the prompt.
Images [][]byte
// Skills are the project skills of the repository, by name. The harness
// gives the path of each skill file.
Skills map[string]string
// NoAgent runs no harness session. The checks run commands in the
// workspace themselves.
NoAgent bool
// NoInit skips `chainloop trace init` in the workspace.
NoInit bool
// WrapTraceRun runs the harness with `chainloop trace run --local`.
WrapTraceRun bool
// SeedUserHooks writes a hook configuration of the harness with one hook
// of the user before `chainloop trace init`.
SeedUserHooks bool
// DisableSessionStart removes the session-start hook after
// `chainloop trace init`.
DisableSessionStart bool
}
Scenario is a scripted task in a throwaway repository. One session runs one scenario, and the checks of all the features that it touches read its result (D-008). Scenarios are shared by all harnesses (R-011).
type Session ¶
type Session struct {
Scenario string
Model string
Harness Harness
Workspace *Workspace
// Checkout is the checkout where the agent committed and pushed.
Checkout string
Output *RunOutput
// Stored is the session that the push stored in local storage mode.
Stored *StoredSession
// HeadMessage is the message of the commit that the agent made.
HeadMessage string
}
Session is the result of one scenario session.
type Status ¶
type Status string
Status is the outcome of one cell of the result matrix.
const ( StatusPass Status = "pass" StatusFail Status = "fail" // StatusSkipped is a cell that the feature table marks "No" or "Not // applicable" (R-002). StatusSkipped Status = "skipped" // StatusNotCovered is a cell that the suite cannot test: the feature needs // a control plane (D-007), or the headless mode of the harness cannot show // it (R-004). StatusNotCovered Status = "not_covered" // StatusNoCheck is a feature row with no registered check. It is a suite // problem (R-002, R-008). StatusNoCheck Status = "no_check" // StatusError is a cell whose scenario could not run, for example because // the harness did not start. It is a suite problem (R-008). StatusError Status = "error" )
type StoredSession ¶
type StoredSession struct {
// Dir is the directory of the stored session.
Dir string
// Evidence is the AI coding session evidence.
Evidence aicodingsession.Evidence
}
StoredSession is one session that the pre-push hook stored in local storage mode.
func StoredSessions ¶
func StoredSessions(checkout string) ([]StoredSession, error)
StoredSessions reads the sessions that local storage mode stored in a checkout. Each session directory holds the AI coding session evidence among its materials.
func (*StoredSession) HasStatement ¶
func (s *StoredSession) HasStatement() bool
HasStatement reports whether the stored session has its statement index.
type Workspace ¶
type Workspace struct {
// Dir is the repository.
Dir string
// Env is the environment of every command in the workspace, the harness
// included: the chainloop binary under test first in PATH, and an
// isolated git configuration.
Env []string
// Chainloop is the chainloop CLI under test.
Chainloop string
}
Workspace is a throwaway git repository with Trace in local storage mode, and a local bare remote to push to.
func NewWorkspace ¶
func NewWorkspace(ctx context.Context, base, chainloopBin string, h Harness, sc Scenario) (*Workspace, error)
NewWorkspace creates a workspace under base for a scenario. It writes the seed files, runs `chainloop trace init --local` for the harness unless the scenario says no, commits the result and pushes it, so the session starts from a clean, pushed tree.