Documentation
¶
Overview ¶
Package agenttest supplies offline executables for runner and caller contract tests: agents, the container engine, the Docker Sandboxes CLI and a host MCP server.
Index ¶
- Constants
- func CodexAppServer(t *testing.T, script CodexAppServerScript) (bin, record string)
- func Docker(t *testing.T, image, sessionVariable string) string
- func MCPServer(t *testing.T, sessionVariable string) string
- func RunCodexAppServer() int
- func Sbx(t *testing.T, sessionVariable string) string
- func Script(t *testing.T, name, body string) string
- type CodexAppServerAction
- type CodexAppServerInterrupt
- type CodexAppServerMessage
- type CodexAppServerPause
- type CodexAppServerRecord
- type CodexAppServerScript
- type CodexRPCError
Constants ¶
const CodexAppServerFakeEnv = "FAKE_CODEX_APP_SERVER"
CodexAppServerFakeEnv set to "1" makes the running test binary a fake `codex app-server` (RunCodexAppServer), the same way FAKE_CLAUDE makes it a fake `claude` in internal/scheduler/scheduler_test.go: the name does not start with BEES_, because the runner strips every inherited BEES_* variable from a session, so a flag in that namespace would never reach the fake. A test binary that wants to offer this fake, to its own tests or to a session a production Runner starts, checks this variable first in its TestMain and calls RunCodexAppServer when it is set, as this package's own tests do.
const OpenCodeNoCustomTools = `{"probe":"bees-opencode-custom-tools/1","roots":["/.opencode"],"tools":[],"errors":[]}`
OpenCodeNoCustomTools is what a fake opencode prints for the search for custom tools a held opencode turn runs first, the command it is started with BUN_BE_BUN=1 for: the search ran and found nothing to load.
Variables ¶
This section is empty.
Functions ¶
func CodexAppServer ¶
func CodexAppServer(t *testing.T, script CodexAppServerScript) (bin, record string)
CodexAppServer arranges for the running test binary, re-executed, to be a fake `codex app-server` scripted by script, the way FAKE_CLAUDE and Runner.ClaudeBin = os.Args[0] do for claude in busybees' internal/scheduler/scheduler_test.go (see CONTRIBUTING.md): it sets CodexAppServerFakeEnv and the script's location for the rest of the calling test (t.Setenv, undone when it ends) and returns the test binary's own path, which the caller hands a Runner as CodexBin, or runs directly. Invoked with "app-server" as its first argument, the fake speaks the app-server JSON-RPC conversation over stdin and stdout, one message per line, and exits when stdin closes. Invoked with "mcp" as its first argument instead — a restricted turn's inventory, run apart from the app-server process — it prints script's MCPServers (or MCPServersRaw) and exits at once, reading nothing. Every line the app-server conversation receives, and every invocation's own argv, go to the record file whose path CodexAppServer also returns, which ReadCodexAppServerRecord reads back; it also counts how many times the fake started.
A test binary that wants to offer this fake must check CodexAppServerFakeEnv in its own TestMain and call RunCodexAppServer when it is set, as this package's own tests do; CodexAppServer only arranges the environment, since it has no way to install a TestMain in the caller's package.
func Docker ¶
Docker writes a fake container engine. It answers `network`, `rm`, `image` and `build`, and runs what `run` is given on this machine, recording its arguments and environment in the directory sessionVariable names. A `run` with --entrypoint is the look an agent.NewContainer session takes into its image before any turn: its arguments are recorded in docker-probe.txt beside the script, and it prints image-vcs.txt from there, the VCS executables the image is to have ("f <path>" or "d <path>" a line; none without the file), or fails when a file named fail-probe is there.
Every subcommand but `run` and the probe is answered without consulting a real Docker: `network` always reports the one fixed gateway address, and `rm` always succeeds, whatever arguments they were given. A test that needs those to fail, or that needs the gateway address to vary, must use its own script instead. `run` itself is the one call this fake does not fake: it execs the command the caller gave it for real, after recording the arguments and environment it was started with.
func MCPServer ¶
MCPServer writes a fake host MCP server: it records its environment, PID and arguments beside sessionVariable names, prints one fixed listening address and then idles. It always starts and never reports a port already in use or any other startup failure; a test of that behaviour must use a script of its own.
func RunCodexAppServer ¶
func RunCodexAppServer() int
RunCodexAppServer runs a fake `codex app-server` on stdin and stdout, scripted by the CodexAppServerScript that CodexAppServer wrote under the directory named by codexAppServerDirEnv, appending its own argv and every line it receives there (record.jsonl). It returns the process's exit code: always 0, short of being unable to read back its own script or open its record file.
func Sbx ¶
Sbx writes a fake Docker Sandboxes CLI. `create` records its arguments in sbx-create.txt beside the script and fails when a file named fail-create is there, fails as sbx's start does after it has made the sandbox (a bare HTTP 500) when one named fail-start is, and fails the way sbx does when its primary workspace, the first one, is read-only, is not a directory, or has a parent at or inside a read-only workspace, where sbx's start writes the agent's instructions; `exec --interactive`, the session's command, records its arguments and the client's environment in the directory sessionVariable names (sbx-exec-args.txt, sbx-exec-env.txt) and runs the command after the sandbox name on this machine; `exec --workdir`, a probe the runner runs before the session's command, appends its arguments to sbx-probe.txt beside the script and runs its command the same way; `exec <name> dagger version` appends its arguments to sbx-dagger-probe.txt beside the script, one call per line, and prints the release in a file named dagger-version there the way `dagger version` does, or fails as a missing CLI when there is none; any other `exec`, a setup command, is appended to sbx-setup.txt beside the script, one argument per line, runs nothing and fails when a file named fail-setup is there; `policy` appends its arguments to sbx-policy.txt beside the script, one call per line, and fails when a file named fail-policy is there, `policy rm` also when one named fail-policy-rm is; `rm` records its arguments in sbx-rm.txt beside the script; `version` prints one, and fails when a file named fail-version is there. Every call but `exec` and `version` also appends its first two arguments to sbx-calls.txt beside the script, in the order they ran.
`rm` and `policy` beside `policy rm` always succeed unless their marker file is there: the fake does not check that a name passed to `rm` or a rule passed to `policy` matches a sandbox or rule that actually exists. A test of that check must read the recorded arguments itself.
Types ¶
type CodexAppServerAction ¶
type CodexAppServerAction struct {
Notify *CodexAppServerMessage `json:"notify,omitempty"`
Request *CodexAppServerMessage `json:"request,omitempty"`
}
CodexAppServerAction is one step a fake `codex app-server` performs once it has answered `turn/start`, run in order. Exactly one of Notify and Request is set: Notify is sent as a notification of the fake's own; Request is sent as a request of the fake's own (any method, known to the real protocol or not), with an id the fake assigns, and the fake waits for the client's answer to it, which is recorded like any other line the fake receives, before moving on to the next action.
type CodexAppServerInterrupt ¶
type CodexAppServerInterrupt struct {
Status string `json:"status"`
}
CodexAppServerInterrupt scripts how a fake `codex app-server` answers `turn/interrupt`. A script with no CodexAppServerInterrupt ignores every `turn/interrupt` it is sent: it never answers it. Given one, every `turn/interrupt` is answered with an empty result, followed by a `turn/completed` notification carrying Status.
type CodexAppServerMessage ¶
type CodexAppServerMessage struct {
Method string `json:"method"`
Params any `json:"params,omitempty"`
}
CodexAppServerMessage is a notification's or a request's method and params, as CodexAppServerScript schedules the fake to send one.
type CodexAppServerPause ¶
type CodexAppServerPause struct {
ReadyFile, ReleaseFile string
}
CodexAppServerPause scripts a pause once turn/start is answered; see CodexAppServerScript.Pause.
type CodexAppServerRecord ¶
CodexAppServerRecord is what a fake `codex app-server` wrote to its record file (see CodexAppServer): how many times it started, each start's argv and environment in the order the starts happened (Env[i] is Argv[i]'s invocation), and every line it received on stdin across every start, in the order it arrived.
func ReadCodexAppServerRecord ¶
func ReadCodexAppServerRecord(t *testing.T, path string) CodexAppServerRecord
ReadCodexAppServerRecord reads back the record file at path, the second value CodexAppServer returned.
type CodexAppServerScript ¶
type CodexAppServerScript struct {
// ExitBefore names the step the fake exits at without answering: one
// of "initialize", "config/read", "thread/start", "thread/resume" or
// "turn/start" (whichever of thread/start and thread/resume the
// client actually sends). Left empty, the fake answers every step it
// reaches.
ExitBefore string `json:"exitBefore,omitempty"`
// InitializeResult is initialize's answer. Left nil, it answers with
// an empty result.
InitializeResult any `json:"initializeResult,omitempty"`
// ConfigResult and ConfigError script config/read's answer; at most
// one should be set. Neither set answers with an empty result.
ConfigResult any `json:"configResult,omitempty"`
ConfigError *CodexRPCError `json:"configError,omitempty"`
// ThreadID and ThreadError script thread/start's answer, as
// {"thread":{"id":ThreadID}} or the error, and thread/resume's too,
// unless ResumeID or ResumeError is set.
ThreadID string `json:"threadID,omitempty"`
ThreadError *CodexRPCError `json:"threadError,omitempty"`
// ResumeID and ResumeError, whichever is set, override ThreadID and
// ThreadError for thread/resume only.
ResumeID string `json:"resumeID,omitempty"`
ResumeError *CodexRPCError `json:"resumeError,omitempty"`
// TurnID is turn/start's answer, as {"turn":{"id":TurnID}}.
TurnID string `json:"turnID,omitempty"`
// Actions runs in order once turn/start is answered.
Actions []CodexAppServerAction `json:"actions,omitempty"`
// Interrupt scripts turn/interrupt; see CodexAppServerInterrupt.
Interrupt *CodexAppServerInterrupt `json:"interrupt,omitempty"`
// Pause, when set, makes the fake wait once turn/start is answered,
// before running Actions: it creates PauseReady (so a test waiting on
// it knows the turn is in progress and the command line it launched
// with is already on disk) and then polls for PauseRelease before
// going on. It exists for a test that needs to act while the turn is
// still running — reading through a restricted turn's read server
// before its turn ends and the server is closed.
Pause *CodexAppServerPause `json:"pause,omitempty"`
// MCPServers is the fake's answer to `codex mcp list --json`, the
// inventory a restricted turn's command line runs (codexMCPInventory)
// as a process of its own, apart from the app-server conversation
// above: the configured servers' names, printed as the array `codex
// mcp list` writes. Left nil, the fake prints an empty array.
// MCPServersRaw, when set, is printed in its place, so a script can
// answer with output the inventory cannot decode.
MCPServers []string `json:"mcpServers,omitempty"`
MCPServersRaw string `json:"mcpServersRaw,omitempty"`
}
CodexAppServerScript scripts a fake `codex app-server`'s conversation, as CodexAppServer hands it to the fake: see that function.
type CodexRPCError ¶
CodexRPCError is a JSON-RPC error a scripted fake `codex app-server` answers a request with.
func ValidateCodexTurnInput ¶
func ValidateCodexTurnInput(raw json.RawMessage) *CodexRPCError
ValidateCodexTurnInput checks raw, turn/start's "input" parameter, against the shape codexRPCRun (agent/codex_rpc.go) always sends: a non-empty JSON array of items, each an object with a "type" of "text" and a string "text". It returns nil when raw matches that shape, or a *CodexRPCError with code -32600 when it does not — in particular when raw is a bare string, the shape this validates against regressing to.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package enforcertest is an agent.Enforcer that starts no process: a caller tests what it does around Prepare, Run and Release, and what its grants let a turn do, with a Go function playing the agent.
|
Package enforcertest is an agent.Enforcer that starts no process: a caller tests what it does around Prepare, Run and Release, and what its grants let a turn do, with a Go function playing the agent. |