conformance

package
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 25 Imported by: 0

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

View Source
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.

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

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

View Source
const Harness = "brigade-conformance"

Harness is the `harness` member every registration the suite sends carries; `harness_version` is buildinfo.String().

View Source
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.

func TagCap

func TagCap(capability string) string

TagCap returns the tag that gates a case on a 4.7 capability, e.g. TagCap("team.create") == "cap:team.create".

func Usage

func Usage(w io.Writer)

Usage writes the 9.2 command line to w.

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

func (c Case) HasTag(tag string) bool

HasTag reports whether the case carries tag.

func (Case) IsSlow

func (c Case) IsSlow() bool

IsSlow reports whether the case carries TagSlow.

func (Case) RequiredCapabilities

func (c Case) RequiredCapabilities() []string

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
	// SharedEnv names a variable exported as <run>/shared to every
	// 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.

func ParseArgs

func ParseArgs(args []string) (Options, error)

ParseArgs parses the 9.2 command line (os.Args[1:]). It returns flag.ErrHelp for -h/--help; any other error is a usage error (exit 2) whose text is safe to print. Whether the ids in --only/--skip exist is checked by Run, which has the case list.

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

func (r *Report) WriteHuman(w io.Writer)

WriteHuman writes the human table: a header naming the adapter, one line per case with its notes indented beneath it, and the totals line.

func (*Report) WriteJSON

func (r *Report) WriteJSON(w io.Writer) error

WriteJSON writes the report as one indented JSON document followed by a newline. Required arrays are emitted as [] when empty (JSON convention 3), so a consumer never sees null.

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 Status

type Status string

Status is a case outcome in the report.

const (
	StatusPass Status = "pass"
	StatusFail Status = "fail"
	StatusSkip Status = "skip"
)

The three outcomes of plan 9.2.

type Summary

type Summary struct {
	Pass int `json:"pass"`
	Fail int `json:"fail"`
	Skip int `json:"skip"`
}

Summary counts the outcomes.

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

func (t *T) A() *Principal

A returns fixture principal A (team T1), building the fixture on first use.

func (*T) Ack

func (t *T) Ack(p *Principal, session string, ids ...string) *protocol.AckResult

Ack runs `message ack --session session` with ids and asserts an AckResult.

func (*T) B

func (t *T) B() *Principal

B returns fixture principal B (team T1).

func (*T) C

func (t *T) C() *Principal

C returns fixture principal C (team T2).

func (*T) Close

func (t *T) Close(p *Principal, session string) *Result

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

func (t *T) DescribeTouched() []string

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

func (t *T) DifferentBytes(what string, a, b *Result)

DifferentBytes is the positive control for SameBytes: it records a failure when the two stdouts are identical.

func (*T) Errorf

func (t *T) Errorf(format string, args ...any)

Errorf records a failure and continues.

func (*T) Exec

func (t *T) Exec(p *Principal, stdin []byte, args ...string) *Result

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

func (t *T) ExecEnv(p *Principal, extra []string, stdin []byte, args ...string) *Result

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

func (t *T) ExecStdinOpen(p *Principal, args ...string) *Result

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

func (t *T) ExecStdinOpenEnv(p *Principal, extra []string, args ...string) *Result

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

func (t *T) Fail(r *Result, code protocol.Code) *protocol.ErrorObject

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

func (t *T) Fatalf(format string, args ...any)

Fatalf records a failure and aborts the case (deferred functions run).

func (*T) HasCap

func (t *T) HasCap(name string) bool

HasCap reports whether describe advertises the capability.

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

func (t *T) JoinPrincipal(name string) *Principal

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

func (t *T) JoinPrincipalLabelled(name, label string) *Principal

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

func (t *T) JoinSecret() string

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

func (t *T) Logf(format string, args ...any)

Logf writes to the -v log only.

func (*T) Note

func (t *T) Note(format string, args ...any)

Note records an observation that is reported but not asserted (C-32's order, C-29b's window arm).

func (*T) Now

func (t *T) Now() time.Time

Now returns the suite's wall clock.

func (*T) OK

func (t *T) OK(r *Result, into protocol.Validator)

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

func (t *T) OKRaw(r *Result) map[string]any

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

func (t *T) PushDeadline() time.Duration

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

func (t *T) Rebind(p *Principal, teamRef string)

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

func (t *T) Receive(p *Principal, session string, limit int) []protocol.MessageEnvelope

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

func (t *T) Restore(p *Principal)

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

func (t *T) RunDir() string

RunDir returns the run directory.

func (*T) RunID

func (t *T) RunID() string

RunID returns the run's identifier (unique per run, used in every name the cases create).

func (*T) SameBytes

func (t *T) SameBytes(what string, a, b *Result)

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

func (t *T) Scratch(name string) *Principal

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

func (t *T) SendRaw(p *Principal, stdin []byte) *Result

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

func (t *T) Session(p *Principal) string

Session returns the fixture session id of p (fixture-a, -b or -c); a principal without one aborts the case.

func (*T) Setup

func (t *T) Setup(p *Principal) bool

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

func (t *T) SharedDir() string

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

func (t *T) Skip(reason string)

Skip marks the case SKIP with reason and aborts it. A case that already recorded a failure stays FAIL.

func (*T) Sleep

func (t *T) Sleep(d time.Duration)

Sleep pauses for d, or until the run is cancelled.

func (*T) Slow

func (t *T) Slow() bool

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

func (t *T) T1Principals() []string

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

func (t *T) Watch(p *Principal, session string) *WatchProc

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

func (t *T) WatchArgs(p *Principal, args ...string) *WatchProc

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

func (w *WatchProc) Command(v any)

Command writes v as one NDJSON line to the watch's stdin.

func (*WatchProc) Events

func (w *WatchProc) Events() []Event

Events returns everything seen so far, in arrival order.

func (*WatchProc) Expect

func (w *WatchProc) Expect(kind string, deadline time.Duration) Event

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

func (w *WatchProc) ExpectNone(quiet time.Duration)

ExpectNone records a failure if any event other than an informational `status` or an unknown kind arrives within quiet (C-33).

func (*WatchProc) Kill

func (w *WatchProc) Kill()

Kill sends SIGKILL.

func (*WatchProc) Next

func (w *WatchProc) Next(deadline time.Duration) (Event, bool)

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

func (w *WatchProc) Signal(sig os.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

func (w *WatchProc) Wait(deadline time.Duration) (exit int, ok bool)

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

func (w *WatchProc) WriteStdin(b []byte)

WriteStdin writes raw bytes to the watch's stdin (B-6's over-long line, B-5's unknown type).

Directories

Path Synopsis
Package cases holds the conformance cases of plan 9.2, one file per case, coded against the T API of internal/conformance.
Package cases holds the conformance cases of plan 9.2, one file per case, coded against the T API of internal/conformance.

Jump to

Keyboard shortcuts

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