Documentation
¶
Overview ¶
Package output centralizes stdout/stderr formatting for testfleet.
All commands produce one of two output modes: human (default) or JSON (when --json is set). JSON output uses a stable envelope shape with a schema_version field so external tooling can pin a format and detect breaking changes.
Hard contract for --json mode:
- stdout is pure JSON (a single envelope value, optionally trailing newline)
- all logs go to stderr (use slog)
- errors are still JSON envelopes (WriteError) but emitted to stderr
Index ¶
- Constants
- func AssertGoldenJSON(t *testing.T, name string, payload any)
- func AssertStdoutIsPureJSON(t *testing.T, stdout []byte) map[string]any
- func WriteError(w io.Writer, code ErrorCode, message string, details any) error
- func WriteHumanKV(w io.Writer, pairs ...string) error
- func WriteHumanTable(w io.Writer, headers []string, rows [][]string) error
- func WriteJSON(w io.Writer, payload any) error
- type Envelope
- type ErrorCode
- type ErrorPayload
- type OutputMode
Constants ¶
const SchemaVersion = "v1"
SchemaVersion is the current envelope schema version. Bump whenever the envelope shape changes incompatibly; golden-file tests will fail until regenerated, forcing the bump to be intentional.
Variables ¶
This section is empty.
Functions ¶
func AssertGoldenJSON ¶
AssertGoldenJSON normalizes payload, then either compares it to or writes it to testdata/golden/<name>.json depending on -update.
Normalization rules:
- keys recognized in placeholderFields have their values replaced
- URL substrings in any string value become <URL>
- JSON keys are emitted in sorted order (deterministic diffs)
payload may be []byte (treated as raw JSON), a string (same), or any value (marshalled first). Bytes/string inputs are required to be valid JSON.
func AssertStdoutIsPureJSON ¶
AssertStdoutIsPureJSON parses stdout as a single JSON value and asserts that the envelope-mandatory fields (schema_version, status) are present.
Use this in every CLI test that exercises --json to catch accidental stdout pollution from cobra warnings, slog misconfiguration, or stray prints. The test runner's "got: <bytes>" message echoes the raw stdout to ease debugging.
func WriteError ¶
WriteError emits an error envelope to w (typically stderr in --json mode).
The envelope mirrors WriteJSON's shape (schema_version + status) but with status="error" and a populated error field. details is optional and may be nil; when non-nil, it's marshalled inline so callers can attach structured context (machine name, run-id, underlying error string, etc).
func WriteHumanKV ¶
WriteHumanKV renders ordered key/value pairs as "key: value" lines.
Pairs is a flat list ["k1", "v1", "k2", "v2", ...]. An odd-length slice is treated as if a trailing empty value were present, to keep callers tolerant of partial data without panicking.
func WriteHumanTable ¶
WriteHumanTable renders rows as a left-aligned tab-padded table to w.
headers names the column headers; each row must have len(headers) cells. Cells with fewer/more elements are truncated/padded to keep the table rectangular. Empty rows produce a header-only output.
func WriteJSON ¶
WriteJSON marshals payload as a JSON Envelope and writes it to w.
payload may be:
- an Envelope: written as-is (SchemaVersion auto-filled if empty)
- any other value: wrapped in an Envelope with Status="ok" and Data=payload
The output is a single line of JSON followed by a newline so that callers can stream multiple envelopes if ever needed.
Types ¶
type Envelope ¶
type Envelope struct {
SchemaVersion string `json:"schema_version"`
Status string `json:"status"`
Data any `json:"data,omitempty"`
Error *ErrorPayload `json:"error,omitempty"`
}
Envelope is the wrapper around every JSON-mode response.
Status is one of: "ok", "error". Data carries the per-command payload. Error carries a structured ErrorPayload when Status == "error".
type ErrorCode ¶
type ErrorCode string
ErrorCode is the stable machine-readable error identifier emitted in --json mode.
These codes form a public contract: external tooling (skills, CI scripts) branches on them. Renaming or removing a code is a breaking change and requires bumping SchemaVersion.
const ( ErrMissingEntrypoint ErrorCode = "missing_entrypoint" ErrMachineBusy ErrorCode = "machine_busy" ErrTransferTimeout ErrorCode = "transfer_timeout" ErrRunNotFound ErrorCode = "run_not_found" ErrTailscaleAuthFailed ErrorCode = "tailscale_auth_failed" ErrGhNotAuthenticated ErrorCode = "gh_not_authenticated" ErrGhNotInstalled ErrorCode = "gh_not_installed" ErrIssueCreateFailed ErrorCode = "issue_create_failed" ErrInterrupted ErrorCode = "interrupted" ErrSSHFailed ErrorCode = "ssh_failed" ErrMachineNotFound ErrorCode = "machine_not_found" // Batch Part 1 (issue #8). ErrInvalidPlan ErrorCode = "invalid_plan" // plan.yaml malformed / structurally invalid ErrStagingRequired ErrorCode = "staging_required" // batch run missing required --staging ErrEmptyPlan ErrorCode = "empty_plan" // plan resolved to zero entries ErrBatchNotFound ErrorCode = "batch_not_found" // batch ID not in store ErrNoMachineAvailable ErrorCode = "no_machine_available" // selector matched no non-offline machine // ErrStoreFailed is a LOCAL state-store / config / IO failure: loading // config, resolving the state dir, opening a store, a store read/write // (List/Get/Create/Update other than not-found), or copying artifacts into // a local evidence dir. It is deliberately NOT a network code — the failure // is on this machine, not on the wire. Distinct from tailscale_auth_failed // (a real Tailscale API call), ssh_failed (transport), and dispatch_failed. ErrStoreFailed ErrorCode = "store_failed" // ErrDispatchFailed is an unexpected failure while dispatching a run that did // NOT arrive as a typed runner.DispatchError (those carry their own precise // code, e.g. ssh_failed / missing_entrypoint / machine_busy). It is the // honest fallback for "dispatch broke for a reason we can't classify" and // must not masquerade as ssh_failed when the cause was never SSH. ErrDispatchFailed ErrorCode = "dispatch_failed" )
type ErrorPayload ¶
type ErrorPayload struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
}
ErrorPayload is the structured error body embedded in the envelope.
type OutputMode ¶
type OutputMode int
OutputMode selects which formatter a command should use.
const ( ModeHuman OutputMode = iota ModeJSON )
func Mode ¶
func Mode(cmd *cobra.Command) OutputMode
Mode inspects the cobra command for the persistent --json flag. Defaults to ModeHuman if the flag is not registered or not set.