Help us shape the future of Go! Take the 2026 Go Developer Survey

e2e

package
v1.127.1 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: Apache-2.0 Imports: 37 Imported by: 0

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

View Source
const HarnessClaudeCode = "Claude Code"

HarnessClaudeCode is the column name of Claude Code in the feature table.

Variables

This section is empty.

Functions

func Markdown

func Markdown(res *Result) string

Markdown renders the result matrix as a markdown table: one row per feature and one column per harness and model (R-010).

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).

func Compare

func Compare(prev, cur *Result) Changes

Compare compares the current result with the previous one. prev is nil when there is no previous result. A cell that failed in the previous run too is not reported again (R-007, D-005).

func (Changes) Empty

func (c Changes) Empty() bool

Empty reports whether there is nothing to report.

type Check

type Check struct {
	Scenario string
	Verify   func(s *Session) error
}

Check verifies one feature on the result of one scenario session.

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) Column

func (c *Claude) Column() string

Column implements Harness.

func (*Claude) DisableSessionStart

func (c *Claude) DisableSessionStart(repo string) error

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) HookConfig

func (c *Claude) HookConfig() HookConfig

HookConfig implements Harness.

func (*Claude) Run

func (c *Claude) Run(ctx context.Context, req RunRequest) (*RunOutput, error)

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.

func (*Claude) SkillPath

func (c *Claude) SkillPath(name string) string

SkillPath implements Harness.

func (*Claude) TraceFlag

func (c *Claude) TraceFlag() string

TraceFlag implements Harness.

func (*Claude) Version

func (c *Claude) Version(ctx context.Context) (string, error)

Version implements Harness. `claude --version` prints "2.1.0 (Claude Code)".

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) Column added in v1.127.0

func (c *Codex) Column() string

Column implements Harness.

func (*Codex) DisableSessionStart added in v1.127.0

func (c *Codex) DisableSessionStart(repo string) error

DisableSessionStart removes only the session-start hook for recovery tests.

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

func (c *Codex) Run(ctx context.Context, req RunRequest) (*RunOutput, error)

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.

Hook messages are not extracted from the headless event stream yet.

func (*Codex) SkillPath added in v1.127.0

func (c *Codex) SkillPath(name string) string

SkillPath implements Harness. Codex finds project skills in .agents/skills.

func (*Codex) TraceFlag added in v1.127.0

func (c *Codex) TraceFlag() string

TraceFlag implements Harness.

func (*Codex) Version added in v1.127.0

func (c *Codex) Version(ctx context.Context) (string, error)

Version implements Harness.

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

func LoadConfig(data []byte) (*Config, error)

LoadConfig reads the suite configuration. A nil data reads the embedded config.yaml.

type Cursor added in v1.127.1

type Cursor struct {
	// Bin is the cursor-agent executable. Empty means "cursor-agent" from
	// PATH.
	Bin string
}

Cursor is the harness adapter of the Cursor CLI (cursor-agent).

func (*Cursor) Column added in v1.127.1

func (c *Cursor) Column() string

Column implements Harness.

func (*Cursor) DisableSessionStart added in v1.127.1

func (c *Cursor) DisableSessionStart(repo string) error

DisableSessionStart implements Harness. It deletes the sessionStart entry from the hooks of the project file and keeps the other entries as they are.

func (*Cursor) HookConfig added in v1.127.1

func (c *Cursor) HookConfig() HookConfig

HookConfig implements Harness. The hooks of the user and the Chainloop hooks share one file.

func (*Cursor) Run added in v1.127.1

func (c *Cursor) Run(ctx context.Context, req RunRequest) (*RunOutput, error)

Run implements Harness. It runs `cursor-agent -p` with the stream-json output.

HOME is a folder next to the workspace, so the configuration, the user hooks (~/.cursor/hooks.json) and the transcripts of the user who runs the suite do not load or mix with the sessions of the suite. The Trace hooks run in the same environment, so the provider reads the transcripts from that folder too. The CLI then has no stored login: it reads its key from CURSOR_API_KEY. --force approves the tool calls and --trust trusts the workspace, which a headless run cannot answer.

Cursor gives the session-start context to the model only, and the stream-json output does not show it, so the hook messages are not reported.

func (*Cursor) SkillPath added in v1.127.1

func (c *Cursor) SkillPath(name string) string

SkillPath implements Harness. Cursor finds project skills in .cursor/skills.

func (*Cursor) TraceFlag added in v1.127.1

func (c *Cursor) TraceFlag() string

TraceFlag implements Harness.

func (*Cursor) Version added in v1.127.1

func (c *Cursor) Version(ctx context.Context) (string, error)

Version implements Harness.

type Duration

type Duration time.Duration

Duration is a time.Duration written as a Go duration string in YAML.

func (*Duration) UnmarshalJSON

func (d *Duration) UnmarshalJSON(b []byte) error

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

func ParseExpected(r io.Reader) (*Expected, error)

ParseExpected reads the feature tables of the Trace README. All feature tables must have the same harness columns.

func (*Expected) Support

func (e *Expected) Support(feature, harness string) (Support, bool)

Support returns the value of the cell of a feature and a harness column. The second result is false when the table has no such cell.

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

type Filter struct {
	Harnesses []string
	Models    []string
	Features  []string
}

Filter limits a manual run to some harnesses, models or features (R-009). An empty list matches all.

func (Filter) Empty

func (f Filter) Empty() bool

Empty reports whether the filter matches all cells. Only a run with an empty filter is a full run.

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) Column

func (o *OpenCode) Column() string

Column implements Harness.

func (*OpenCode) DisableSessionStart

func (o *OpenCode) DisableSessionStart(repo string) error

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

func (o *OpenCode) Run(ctx context.Context, req RunRequest) (*RunOutput, error)

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.

func (*OpenCode) SkillPath

func (o *OpenCode) SkillPath(name string) string

SkillPath implements Harness.

func (*OpenCode) TraceFlag

func (o *OpenCode) TraceFlag() string

TraceFlag implements Harness.

func (*OpenCode) Version

func (o *OpenCode) Version(ctx context.Context) (string, error)

Version implements Harness. `opencode --version` prints "opencode v2.0.22". An installed major version that is not the one of the column is an error, because the cells would test the wrong column.

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) Column

func (p *Pi) Column() string

Column implements Harness.

func (*Pi) DisableSessionStart

func (p *Pi) DisableSessionStart(string) error

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

func (p *Pi) Run(ctx context.Context, req RunRequest) (*RunOutput, error)

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.

func (*Pi) SkillPath

func (p *Pi) SkillPath(name string) string

SkillPath implements Harness. Pi finds project skills in .agents/skills.

func (*Pi) TraceFlag

func (p *Pi) TraceFlag() string

TraceFlag implements Harness.

func (*Pi) Version

func (p *Pi) Version(ctx context.Context) (string, error)

Version implements Harness. `pi --version` prints "0.79.6".

type Repeat

type Repeat struct {
	Runs      int `json:"runs"`
	Threshold int `json:"threshold"`
}

Repeat sets how many times a cell runs and how many runs must pass.

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.

func (*Result) Problems

func (r *Result) Problems() []Cell

Problems returns the cells that show a problem of the suite, not of Trace: a feature with no check, or a scenario that could not run (R-008).

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.

func (ResultStore) Save

func (s ResultStore) Save(res *Result) (string, error)

Save writes the result to the history and, for a full run, makes it the previous result of the next run. It returns the path of the history file.

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.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context) (*Result, error)

Run runs the suite and returns 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
	// Branch is a branch that the session works on, created from main and
	// pushed before the session. Empty means main. Trace reads only the
	// commits that are not on the default remote branch, so a later push
	// finds the commits of the session only on another branch.
	Branch string
	// FollowUp runs git commands of a human in the checkout after the session,
	// for example a commit and a second push. The checks read the session
	// that the last push stored.
	FollowUp func(ctx context.Context, w *Workspace, checkout string) error
}

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 last commit of the checkout: the
	// commit that the agent made, or the commit of the follow-up.
	HeadMessage string
	// HeadSHA is the last commit of the checkout.
	HeadSHA 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.

func (*StoredSession) Material

func (s *StoredSession) Material(digest string) ([]byte, error)

Material returns the stored content of a material, by its digest.

type Support

type Support string

Support is the value of one cell of the feature table.

const (
	SupportYes           Support = "Yes"
	SupportPartial       Support = "Partial"
	SupportNo            Support = "No"
	SupportUnknown       Support = "Unknown"
	SupportNotApplicable Support = "Not applicable"
)

func (Support) Tested

func (s Support) Tested() bool

Tested reports whether the suite tests a cell with this value. "No" and "Not applicable" cells are skipped (R-002).

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.

func (*Workspace) Git

func (w *Workspace) Git(ctx context.Context, args ...string) (string, error)

Git runs a git command in the repository and returns its output.

func (*Workspace) TraceInit

func (w *Workspace) TraceInit(ctx context.Context, h Harness) (string, error)

TraceInit runs `chainloop trace init --local` for the harness in the repository.

func (*Workspace) TraceUninstall

func (w *Workspace) TraceUninstall(ctx context.Context) (string, error)

TraceUninstall runs `chainloop trace uninstall` in the repository.

Jump to

Keyboard shortcuts

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