Documentation
¶
Overview ¶
Package conformance is the BAP/1 conformance suite of plan 9.2: the library behind the dev binary cmd/brigade-conformance and behind the `go test` subtests of suite_test.go (TestConformanceFS/C-25), so that `go test ./...` stays a complete gate on its own. The cases themselves live in the sibling package cases (one file per plan case, C-01..C-47 plus C-03b, C-19b and C-29b); this package never imports it, and the case authors code against the T API of t.go and the WatchProc API of watch.go, nothing else.
What a run is ¶
Run resolves the adapter under test (a bare name is looked up on PATH, exactly as the harness does), creates one run directory with os.MkdirTemp("", "brigade-conformance-"), runs `describe` once against a scratch principal to learn the adapter's name, version, capabilities, limits, lease range and retention floors, selects the cases, runs them one after another in id order — they share a fixture and the adapter's rate budgets, so they are never parallel inside one run — and writes the report: the human table on stderr in every mode, and with --json the machine-readable document on stdout. stdout carries that document and nothing else. The run directory is removed at the end unless --keep-temp, in which case its path is printed on stderr.
--shuffle <seed> runs the same cases in the permutation that seed names instead of in id order (0, the default, is id order). The cases share a fixture, a store and the adapter's rate budgets, so id order is one order out of many and a case that quietly depends on running after another one passes in it forever: the P1-6 verifier found two such cases with a throwaway tool the binary did not have. A non-zero seed is printed on the human summary's first line and carried in the JSON report's `shuffle`, and `results` is in execution order, so a failure is replayed with the same seed. TestConformanceFSWholeRun runs shuffled by default, with a seed taken from the clock and logged.
A selection that matches NO case is a usage error (exit 2), not a pass: `--tags cap:nosuch` selecting nothing and reporting "0 passed, 0 failed, 0 skipped" with exit 0 reads exactly like a clean run. The refusal names the selectors responsible and happens before the adapter is launched. Note that a case reported SKIP is still selected — `--tags slow` without --slow selects C-14 and exits 0.
Every adapter process gets an environment built from scratch (never os.Environ wholesale): PATH and TMPDIR copied from the suite's own environment, HOME, BRIGADE_CONFIG_DIR and BRIGADE_STATE_DIR under the principal's directory, BRIGADE_PROFILE=default, BRIGADE_LOG_LEVEL=debug (so C-05's "no secret at debug" check is meaningful), the --env pairs and, with --shared-env NAME, NAME=<run>/shared. Nothing else: an adapter that needs another variable fails C-01 with a clear message, and that is a test (4.1, Environment). The launcher applies four global checks to every spawn and attributes a violation to the running case: the exit status is in 0..12 (B-11); stdout is exactly one JSON object followed by one newline and that object is a valid 4.3 envelope (4.1; a watch's stdout is NDJSON with an `event` on every line instead); no join secret the run knows, and no join-secret-shaped `brg1.<x>.<y>` token (the fixed protocol text `expected brg1.<team_ref>.<secret>` names the format, not a secret, and is not a hit) outside `team create`'s own stdout, appears on stdout or stderr (C-05); and at the end of the run the whole run directory — profile files, logs, the fs store — is scanned the same way, any hit being a C-05 failure.
The fixture ¶
Three principals: A and B in team T1, C in team T2, each with its own HOME, BRIGADE_CONFIG_DIR and BRIGADE_STATE_DIR under <run>/principals/<name>/, and one registered session each (fixture-a, fixture-b, fixture-c). It is built lazily on the first case that asks for it, so `--only C-36` builds it and a case on scratch principals does not. With --setup <cmd> the command runs once per principal in that principal's environment (argv split on whitespace, no shell) and must leave the three profiles joined; otherwise the suite provisions them through `team create` and `team join`, which requires those two capabilities. Cases that need more principals than the fixture offers (C-28, C-40) join their own with T.JoinPrincipal, because the frozen principal_send_rate is 60 per minute and the whole fs run takes seconds: a case that sent 60 messages from A would drain A's budget for every case after it.
Tags and selection ¶
Every case carries `core`; a case that needs a capability carries `cap:<capability>` and is reported SKIP ("capability not advertised") when describe does not list it; a case tagged `slow` runs only with --slow and is reported SKIP otherwise. --only and --skip take comma-separated case ids, case-insensitively, and an unknown id is a usage error (exit 2). --tags is any-of. The exit status is 0 when every selected case passed (skips allowed), 1 on any failure, 2 on a usage error and 3 on a launcher error: adapter not found, describe not ok or unparseable or a protocol major other than "1", --setup failing, the fixture failing to build.
The mutants ¶
The suite's own positive control is mutants_test.go: four deliberately broken builds of the fs adapter, each a build-tagged twin inside internal/adapters/fs, must fail EXACTLY the cases named here and no other, and the normal build must fail none.
mutant_noack the ack routine reports `acked` and moves nothing
(4.5.3) -> C-29b, C-30, C-36, C-41
mutant_teamleak `session list` walks every team, not the profile's
(4.5.6) -> C-12, C-26
mutant_trustsender the forbidden members of 4.4.6 are accepted and a
forged sender is trusted (4.5.5, 4.5.7) -> C-23, C-24
mutant_caporder the two unacknowledged caps of 4.5.12 are checked
in the wrong order — the recipient-wide one before
the per-pair one -> C-28
The fourth exists because that order was the decisive untested defect in both P1-5 and P1-6: every other property of the caps survives the swap, and only a case that puts BOTH caps at their limit at once can see it. An expected set is never widened to fit a case that turns out to depend on a mutation the table does not foresee; that is reported instead.
Deadlines ¶
Every wait has one. A request/response command gets --timeout (20 s by default); a watch runs until the case closes it or the runner kills it at the end of the case. T.PushDeadline is 5 s for an adapter advertising message.watch.push, and also 5 s for a polling adapter: 4.4.9 says "two poll intervals" for those, but BAP/1 carries no poll-interval member in describe, so the suite has nothing to read — the driver has recorded that as a BAP/1.x question and the suite does not invent a member. The exit deadlines are the spec's own: 5 s after stdin EOF, a close command or SIGTERM (C-38, C-41), 10 s for the C-37 error exit.
Index ¶
- Constants
- func Main(cases []Case)
- func Run(ctx context.Context, opts Options, cases []Case, environ []string, ...) int
- func TagCap(capability string) string
- func Usage(w io.Writer)
- type AdapterInfo
- type Case
- type CaseResult
- type Event
- type Options
- type Principal
- type Report
- type Result
- type Status
- type Summary
- type T
- func (t *T) A() *Principal
- func (t *T) Ack(p *Principal, session string, ids ...string) *protocol.AckResult
- func (t *T) B() *Principal
- func (t *T) C() *Principal
- func (t *T) Close(p *Principal, session string) *Result
- func (t *T) Describe() *protocol.DescribeResult
- func (t *T) DescribeTouched() []string
- func (t *T) DifferentBytes(what string, a, b *Result)
- func (t *T) Errorf(format string, args ...any)
- func (t *T) Exec(p *Principal, stdin []byte, args ...string) *Result
- func (t *T) ExecEnv(p *Principal, extra []string, stdin []byte, args ...string) *Result
- func (t *T) ExecStdinOpen(p *Principal, args ...string) *Result
- func (t *T) ExecStdinOpenEnv(p *Principal, extra []string, args ...string) *Result
- func (t *T) Fail(r *Result, code protocol.Code) *protocol.ErrorObject
- func (t *T) Fatalf(format string, args ...any)
- func (t *T) HasCap(name string) bool
- func (t *T) Heartbeat(p *Principal, session string, req *protocol.HeartbeatRequest) *protocol.HeartbeatResult
- func (t *T) JoinPrincipal(name string) *Principal
- func (t *T) JoinPrincipalLabelled(name, label string) *Principal
- func (t *T) JoinSecret() string
- func (t *T) List(p *Principal, self string, includeOffline bool) (sessions []protocol.SessionRecord, raw map[string]any)
- func (t *T) Logf(format string, args ...any)
- func (t *T) Note(format string, args ...any)
- func (t *T) Now() time.Time
- func (t *T) OK(r *Result, into protocol.Validator)
- func (t *T) OKRaw(r *Result) map[string]any
- func (t *T) PushDeadline() time.Duration
- func (t *T) Rebind(p *Principal, teamRef string)
- func (t *T) Receive(p *Principal, session string, limit int) []protocol.MessageEnvelope
- func (t *T) Register(p *Principal, name string, edit func(*protocol.SessionRegistration)) (record map[string]any, sessionID string)
- func (t *T) Restore(p *Principal)
- func (t *T) RunDir() string
- func (t *T) RunID() string
- func (t *T) SameBytes(what string, a, b *Result)
- func (t *T) Scratch(name string) *Principal
- func (t *T) Send(p *Principal, sender, recipient, body string, edit func(*protocol.SendRequest)) *protocol.SendResponse
- func (t *T) SendRaw(p *Principal, stdin []byte) *Result
- func (t *T) Session(p *Principal) string
- func (t *T) Setup(p *Principal) bool
- func (t *T) SharedDir() string
- func (t *T) Skip(reason string)
- func (t *T) Sleep(d time.Duration)
- func (t *T) Slow() bool
- func (t *T) T1Principals() []string
- func (t *T) Watch(p *Principal, session string) *WatchProc
- func (t *T) WatchArgs(p *Principal, args ...string) *WatchProc
- type WatchProc
- func (w *WatchProc) CloseStdin()
- func (w *WatchProc) Command(v any)
- func (w *WatchProc) Events() []Event
- func (w *WatchProc) Expect(kind string, deadline time.Duration) Event
- func (w *WatchProc) ExpectNone(quiet time.Duration)
- func (w *WatchProc) Kill()
- func (w *WatchProc) Next(deadline time.Duration) (Event, bool)
- func (w *WatchProc) Signal(sig os.Signal)
- func (w *WatchProc) Wait(deadline time.Duration) (exit int, ok bool)
- func (w *WatchProc) WriteStdin(b []byte)
Constants ¶
const ( TagCore = "core" TagSlow = "slow" )
Tags of plan 9.2. Every case carries TagCore; a case gated on a capability carries TagCap(name) and is skipped when describe does not advertise it; a case tagged TagSlow runs only with --slow.
const ( // ExitPass: every selected case passed (skips allowed). ExitPass = 0 // ExitFail: at least one case failed. ExitFail = 1 // ExitUsage: bad argv, or an unknown case id in --only/--skip. ExitUsage = 2 // ExitLauncher: adapter not found, describe not ok/unparseable/wrong // major, --setup failed, the fixture could not be built, or the run // outlived the fixture's lease (SuiteWallClockBudget). ExitLauncher = 3 )
Exit statuses of the binary and of Run (plan 9.2).
const DefaultTimeout = 20 * time.Second
DefaultTimeout is the per-command timeout when --timeout is not given (4.1 applies 20 s to every request/response command).
const Harness = "brigade-conformance"
Harness is the `harness` member every registration the suite sends carries; `harness_version` is buildinfo.String().
const SuiteWallClockBudget = 8 * time.Minute
SuiteWallClockBudget is the whole-run wall time the suite supports, and the reason the fixture does not take the protocol's default lease.
A, B and C's fixture sessions are registered once and NOTHING heartbeats them, while C-12 asserts A's session is present in a LIVE `session list` (`include_offline = false`). Under the 4.4.1 default of 90 s (protocol.LeaseDefaultSeconds) that made the whole suite depend on the case order and on the backend's wall time: measured against the hosted Supabase project on 2026-09-05, C-12 started at t = 10.9 s in id order and passed, and at t = 124.8 s under `--shuffle 5150907` and failed — the fixture rows were `closed=false` with an expired lease, exactly the state C-14 requires an adapter to report `offline`. The fs run's whole wall time is ~20 s, so it never reached its own default lease and the dependency stayed latent there.
The fixture therefore registers with the LONGEST lease the adapter advertises (`describe.lease.max_seconds`), and this budget is what the suite promises that lease has to cover: 8 minutes is 3x the slowest measured run (160 s for 45 cases with --slow, hosted; ~20 s on the fs adapter) and sits 2 minutes under protocol.LeaseMaxSeconds, the top of the 4.4.1 default range that both shipped adapters advertise and the Supabase schema pins with a check constraint — so the two numbers can drift apart before either becomes a lie. Three joins keep it honest: TestSuiteWallClockBudgetFitsTheDefaultLeaseRange (the constants), TestFixtureLeaseCoversTheSuiteBudget (what a real adapter grants) and fixture.overrun, which refuses to report a run that outlived the lease it was granted — the "longer suite" half, which no unit test can see.
Variables ¶
This section is empty.
Functions ¶
func Main ¶
func Main(cases []Case)
Main is the binary's entry point: it parses os.Args, runs the suite with the process's own streams and environment, and exits with the status Run returns. It is the only file in this package that may name os.Stdout and os.Exit (plan 7.3; the lint config exempts it).
func Run ¶
func Run(ctx context.Context, opts Options, cases []Case, environ []string, stdout, stderr io.Writer) int
Run executes the suite: it resolves the adapter, creates the run directory, runs describe, selects and runs the cases, writes the report (JSON on stdout with opts.JSON; the human table on stderr always) and returns the exit status. environ is the suite's own environment (only PATH and TMPDIR are read from it); nothing here reads os.Getenv or names os.Stdout.
Types ¶
type AdapterInfo ¶
type AdapterInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Command string `json:"command"`
}
AdapterInfo is the report's `adapter` object: name and version from describe, and the resolved command line that was launched.
type Case ¶
type Case struct {
// ID is the plan id: "C-01" … "C-47", "C-03b", "C-19b", "C-29b".
ID string
// Rule is the 9.2 rule column, e.g. "4.5.3 ack idempotent"; it is the
// text after the duration in the human table.
Rule string
// Title is one line saying what the case asserts.
Title string
// Tags are TagCore, TagSlow and TagCap values.
Tags []string
// Run is the case body. It records outcomes through its T and returns
// normally; T.Fatalf and T.Skip abort it through a panic the runner
// recovers, so deferred cleanup still runs.
Run func(*T)
}
A Case is one conformance case. The cases package builds one per plan row; Run selects and executes them in the order given.
func (Case) RequiredCapabilities ¶
RequiredCapabilities returns the capabilities named by the case's cap: tags, in tag order.
type CaseResult ¶
type CaseResult struct {
ID string `json:"id"`
Rule string `json:"rule"`
Status Status `json:"status"`
DurationMS int64 `json:"duration_ms"`
Reason string `json:"reason"`
Notes []string `json:"notes,omitzero"`
}
CaseResult is one entry of the report's `results` array.
type Event ¶
type Event struct {
Kind string
// Line is the raw stdout line as the adapter wrote it, with the
// terminator removed and nothing else changed. It is what the
// byte-identity rules of 4.5.6/4.5.7 are asserted on: two refusals that
// must be indistinguishable have to match here, not merely in the
// members a parse recovers (C-37).
Line []byte
Raw map[string]any
Ready *protocol.WatchReady
Message *protocol.WatchMessage
Status *protocol.WatchStatus
Acked *protocol.WatchAcked
HeartbeatOK *protocol.WatchHeartbeatOK
Error *protocol.WatchError
}
An Event is one NDJSON line of `message watch` stdout, parsed loosely into Raw and, for a known kind, decoded and validated into the typed member for that kind (the others stay nil).
type Options ¶
type Options struct {
// Adapter is the executable to test; a bare name is looked up on PATH.
Adapter string
// Env holds extra K=V pairs for every adapter process (--env).
Env []string
// principal (--shared-env; BRIGADE_FS_ROOT for the fs adapter).
SharedEnv string
// Setup is a command run once per fixture principal, in that
// principal's environment, before any protocol command (--setup).
Setup string
// Rebind is a command that rebinds a profile to the team_ref given on
// stdin as {"team_ref": "…"} (--rebind); empty means the default
// team.json rewrite.
Rebind string
// Tags, Only and Skip select cases (--tags, --only, --skip).
Tags []string
Only []string
Skip []string
// Slow includes the cases tagged slow (--slow).
Slow bool
// Shuffle is the seed of the permutation the selected cases run in
// (--shuffle); 0 runs them in id order. A non-zero seed is reported on
// the human summary's first line and in the JSON report, so a failure
// found in a shuffled run is reproducible with the same seed.
Shuffle int64
// Timeout bounds every request/response command (--timeout).
Timeout time.Duration
// KeepTemp keeps the run directory and prints its path (--keep-temp).
KeepTemp bool
// JSON writes the machine-readable report on stdout (--json).
JSON bool
// Verbose logs every spawn to stderr (-v).
Verbose bool
// FixedArgs is everything after `--`, prepended to every invocation.
FixedArgs []string
}
Options is the 9.2 command line, parsed. The zero value is not usable: Adapter is required and Run applies DefaultTimeout when Timeout is zero.
type Principal ¶
type Principal struct {
Name, Home, ConfigDir, StateDir string
PrincipalRef, TeamRef, TeamName string
// contains filtered or unexported fields
}
A Principal is one adapter installation: its own HOME, configuration and state directories under the run directory. The fixture fills in the identity members after provisioning; a scratch principal has them empty.
type Report ¶
type Report struct {
Adapter AdapterInfo `json:"adapter"`
ProtocolVersion string `json:"protocol_version"`
Capabilities []string `json:"capabilities"`
Results []CaseResult `json:"results"`
Summary Summary `json:"summary"`
DurationMS int64 `json:"duration_ms"`
// Shuffle is the --shuffle seed the cases ran in, omitted when they ran
// in id order. `results` is in execution order, so this is what makes a
// shuffled run reproducible.
Shuffle int64 `json:"shuffle,omitzero"`
}
Report is the --json document of 9.2:
{"adapter":{name,version,command},"protocol_version","capabilities",
"results":[{id,rule,status,duration_ms,reason,notes?}],"summary":{pass,fail,skip},"duration_ms"}
`rule`, `notes` and the top-level `duration_ms` are additions to the P1-1 stub's shape; every member the stub emitted is still emitted.
func (*Report) WriteHuman ¶
WriteHuman writes the human table: a header naming the adapter, one line per case with its notes indented beneath it, and the totals line.
type Result ¶
type Result struct {
// Args is the full argv that ran: the resolved adapter, the fixed
// arguments and the command.
Args []string
// Stdin is the document that was fed (nil when stdin was the null
// device or a held-open pipe).
Stdin []byte
// Stdout and Stderr are the captured streams.
Stdout, Stderr []byte
// Exit is the process exit status; -1 when the process was killed by a
// signal, including the launcher's own kill on timeout.
Exit int
// Duration is wall time from start to reap.
Duration time.Duration
// Envelope is stdout parsed and validated as one 4.3 envelope; nil when
// stdout is not exactly one valid envelope followed by one newline.
Envelope *protocol.Envelope
// Raw is the first JSON value on stdout parsed loosely; nil when stdout
// does not start with a JSON object. It exists for key-presence checks
// (B-2's `retryable`, C-17's unknown members, C-30's empty arrays).
Raw map[string]any
// contains filtered or unexported fields
}
A Result is one finished adapter process: what was sent, what came back, and the launcher's reading of stdout.
type T ¶
type T struct {
// contains filtered or unexported fields
}
T is the per-case context: the fixture, the spawning seam, the assertion helpers and the bookkeeping. One T lives for one case.
func (*T) Close ¶
Close runs `session close --session session`, asserts ok:true and exit 0, and returns the Result for the {session_id, state} check.
func (*T) Describe ¶
func (t *T) Describe() *protocol.DescribeResult
Describe returns the start-of-run describe result.
func (*T) DescribeTouched ¶
DescribeTouched reports what the start-of-run describe left behind on its fresh scratch principal — entries under its config or state directory, or the shared directory — as one line each; empty when it touched nothing (4.2). That spawn precedes every case, so C-01 can assert it in any case order.
func (*T) DifferentBytes ¶
DifferentBytes is the positive control for SameBytes: it records a failure when the two stdouts are identical.
func (*T) Exec ¶
Exec runs one adapter command for p with the document on stdin. A nil stdin is the null device (a command that takes no input); a non-nil stdin, even empty, is fed through a pipe and closed. The global checks of the launcher (exit range, stdout discipline, secrets) are recorded against the case; nothing else is asserted.
func (*T) ExecEnv ¶
ExecEnv is Exec with extra K=V pairs appended to the process environment (the launcher's BRIGADE_TEST_OFFLINE=1 for C-01 and C-07).
func (*T) ExecStdinOpen ¶
ExecStdinOpen is Exec with stdin the read end of a pipe that is never written and never closed until the process exits (B-1: a command that takes no input must not read stdin; one that does hangs until the timeout and fails).
func (*T) ExecStdinOpenEnv ¶
ExecStdinOpenEnv is ExecStdinOpen with extra K=V pairs appended to the process environment, so C-01's B-1 describe runs under the same BRIGADE_TEST_OFFLINE=1 as its first one.
func (*T) Fail ¶
Fail asserts ok:false, exit == code.Exit(), error.code == code, that the `retryable` member is present (B-2) and that it equals code.Retryable(). It returns the error object. Any failure aborts the case.
func (*T) Heartbeat ¶
func (t *T) Heartbeat(p *Principal, session string, req *protocol.HeartbeatRequest) *protocol.HeartbeatResult
Heartbeat runs `session heartbeat --session session` with req (nil: an empty document, a pure renewal) and asserts a HeartbeatResult.
func (*T) JoinPrincipal ¶
JoinPrincipal joins a fresh principal into T1 with the remembered join secret, labelled <name>@example.com. It needs team.join and a known secret; without them the case is skipped. No session is registered.
func (*T) JoinPrincipalLabelled ¶ added in v0.6.6
JoinPrincipalLabelled is JoinPrincipal with the membership label chosen by the caller. An empty label joins with none at all — the state every member of a team created before the label existed is in, and what C-45 needs to watch a registration fill.
func (*T) JoinSecret ¶
JoinSecret returns T1's join secret, or "" when the fixture was provisioned by --setup (cases that need it Skip).
func (*T) List ¶
func (t *T) List(p *Principal, self string, includeOffline bool) (sessions []protocol.SessionRecord, raw map[string]any)
List runs `session list [--session self] [--include-offline]` and returns the validated records plus the raw result for the composite members (team_ref, team_name, server_time, truncated).
func (*T) Note ¶
Note records an observation that is reported but not asserted (C-32's order, C-29b's window arm).
func (*T) OK ¶
OK asserts ok:true and exit 0, then decodes and validates the result into `into` (nil: the result is not decoded — use OKRaw for composite results). Any failure aborts the case.
func (*T) OKRaw ¶
OKRaw asserts ok:true and exit 0 and returns the result object parsed loosely, for the composite results (session register, session list, message receive, team members) and for key-presence checks.
func (*T) PushDeadline ¶
PushDeadline is how long a case waits for a `message` event after a send: 5 s with message.watch.push, and 5 s for a polling adapter too (see the package comment: BAP/1 publishes no poll interval to read).
func (*T) Rebind ¶
Rebind binds p's profile to teamRef: through the --rebind command with {"team_ref": …} on stdin when given, else by rewriting `team_ref` in <config>/teams/default/team.json, keeping the original bytes for Restore. Cases that call it ALWAYS `defer t.Restore(p)`.
func (*T) Receive ¶
Receive runs `message receive --session session [--limit limit]` (limit <= 0: no flag) and returns the validated envelopes. An absent `messages` array is a failure (JSON convention 3) and reads as empty.
func (*T) Register ¶
func (t *T) Register(p *Principal, name string, edit func(*protocol.SessionRegistration)) (record map[string]any, sessionID string)
Register registers a session for p named name: harness brigade-conformance, harness_version buildinfo.String(), activity busy, inbound accept, then edit (nil allowed). The raw result is returned for the composite members (resumed, lease_seconds, server_time) together with the session id; the record itself is validated as a SessionRecord.
func (*T) Restore ¶
Restore puts p's original binding back after Rebind: the original team.json bytes, or the --rebind command with the original team_ref. It is a no-op for a principal that was never rebound.
func (*T) RunID ¶
RunID returns the run's identifier (unique per run, used in every name the cases create).
func (*T) SameBytes ¶
SameBytes records a failure unless a.Stdout and b.Stdout are identical byte for byte (the no-oracle rule of 4.5.6, 4.5.7, 4.4.10). Always pair it with DifferentBytes in the same case.
func (*T) Scratch ¶
Scratch creates a fresh principal — the three directories, no setup, no team — for the cases that must not touch the fixture (C-01..C-08). The directory is <run>/principals/<case id>-<name>; name must be unique within the case.
func (*T) Send ¶
func (t *T) Send(p *Principal, sender, recipient, body string, edit func(*protocol.SendRequest)) *protocol.SendResponse
Send sends body from sender to recipient (both session ids) for p, after edit (nil allowed), and asserts an accepted SendResponse.
func (*T) SendRaw ¶
SendRaw runs `message send` with an arbitrary stdin document and asserts nothing: for the forbidden members of C-23 and malformed documents.
func (*T) Session ¶
Session returns the fixture session id of p (fixture-a, -b or -c); a principal without one aborts the case.
func (*T) Setup ¶
Setup runs the --setup command once against p with p's environment (the fixture's operator seam, for C-07's "without those caps" leg). It returns false when no --setup was given; a non-zero exit or a spawn failure aborts the case.
func (*T) SharedDir ¶
SharedDir returns <run>/shared, the directory --shared-env names, or "" without that flag. The suite never creates it (C-01 asserts describe does not either).
func (*T) Skip ¶
Skip marks the case SKIP with reason and aborts it. A case that already recorded a failure stays FAIL.
func (*T) Slow ¶
Slow reports whether the run was started with --slow, for the arms a non-slow case runs only then (C-19b's lease-expiry arm).
func (*T) T1Principals ¶
T1Principals returns the principal_refs the suite has put into team T1: A, B and every principal JoinPrincipal has joined so far, in that order. A case that asserts "only T1 sessions are listed" checks against this set rather than {A, B}, so it holds whatever cases ran before it (C-28 and C-40 join extra principals into T1; a reordered run put them before C-12 and failed it).
func (*T) Watch ¶
Watch spawns `message watch --session session` for p and returns the running process. The runner kills every watch still alive when the case ends.
func (*T) WatchArgs ¶
WatchArgs spawns `message watch` with args for p: the seam for a case whose subject is the arguments themselves. C-37 uses it for its positive control, a watch with no --session at all, whose `usage` error event must NOT be byte-identical to the not_found one (4.1: an unknown or missing flag on a core command is usage; 4.4.9: a watch says so in an `error` event, because its stdout is NDJSON and nothing else).
type WatchProc ¶
type WatchProc struct {
// contains filtered or unexported fields
}
A WatchProc is one running `message watch --session <id>`: stdin is a pipe the case writes NDJSON commands to, stdout is read line by line on a goroutine, stderr is captured. Every wait has a deadline and never blocks the case; the runner kills every WatchProc still alive when the case ends.
func (*WatchProc) CloseStdin ¶
func (w *WatchProc) CloseStdin()
CloseStdin closes the watch's stdin (the EOF of C-38).
func (*WatchProc) Expect ¶
Expect reads events until one of kind arrives and returns it; other kinds are logged and skipped. Not seeing it within deadline (or before EOF) aborts the case.
func (*WatchProc) ExpectNone ¶
ExpectNone records a failure if any event other than an informational `status` or an unknown kind arrives within quiet (C-33).
func (*WatchProc) Next ¶
Next returns the next event, or false when deadline passes or the process's stdout has ended. Every parsed line is delivered, unknown kinds included (Kind says which).
func (*WatchProc) Signal ¶
Signal sends sig to the watch. The launcher's exit-range check then no longer applies: the case asserts the exit itself through Wait.
func (*WatchProc) Wait ¶
Wait waits for the process to exit and returns its status (-1 when it died by a signal). On deadline it kills the process and returns ok false. The B-11 exit-range check runs here once, unless the case signalled the process.
func (*WatchProc) WriteStdin ¶
WriteStdin writes raw bytes to the watch's stdin (B-6's over-long line, B-5's unknown type).