integration

package
v0.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: UNKNOWN not legal advice Imports: 0 Imported by: 0

Documentation

Index

Constants

View Source
const (
	NodeConfigPrefix    = "config."
	GenesisParamsPrefix = "genesis."
)

The two generic key families, one per typed target they are applied to.

View Source
const HarnessAssignedReason = "the harness assigns each node its listen ports and its peers"

Variables

View Source
var HarnessAssignedConfigKeys = []string{"rpc.laddr", "p2p.laddr", "p2p.persistent_peers"}

HarnessAssignedConfigKeys are the node config paths a scenario cannot set, and HarnessAssignedReason says why.

The peer list is here for the same reason as the listen addresses, and it hides better: the harness writes each validator a list of its own, and a scenario's overrides are applied afterwards, once, identically to every node. A cluster whose validators were all handed the same peers reaches no quorum and commits no block.

View Source
var NamedClusterKeys = []string{"validators", "code-submission-policy", "pkg-approver", "block-max-gas"}

NamedClusterKeys are the keys with a name of their own, as opposed to the two prefixed families. The switch in parseClusterSection is what accepts them; this is what the listing reads, so the two cannot drift apart without the test that compares them going red.

Functions

func EventuallyCmd

func EventuallyCmd(cmds map[string]func(*testscript.TestScript, bool, []string)) func(*testscript.TestScript, bool, []string)

EventuallyCmd returns a testscript command that reruns a subcommand until it succeeds or the timeout passes.

timeout bounds when a new attempt starts, not the wall-clock time of the command itself: the deadline is only checked between attempts, so a slow or hanging sub-command can overrun it. A hard per-attempt cutoff would need to abandon a still-running attempt from another goroutine, leaving ts in a half-mutated state, which is worse than overrunning. The outer test run timeout is the real backstop for a hung command.

It dispatches only to the custom command map, not to testscript builtins, because the builtin table is not reachable from a user command.

There is no chain event announcing that a package went live, so every assertion about the oracle is a poll. This is what keeps those polls from being fixed sleeps.

Every attempt starts with the builtin output buffers emptied, so a following "stdout", "! stdout", "stderr", "! stderr" or "cp stdout" sees the attempt that succeeded and nothing else. That is what lets a read needing a negation or a capture still be retried instead of running once and flaking on a transient RPC blip.

It does not poll a following assertion: without a gate it returns as soon as the sub-command exits 0, so a "stdout <pattern>" line after it runs exactly once, against whatever that first success produced. That is unsound for a query answering an empty result rather than an error -- vm/qinertpaths lists nothing and exits 0 -- because the wait ends before the value arrives.

"-stdout <regex>" is the answer to that: the pattern is checked inside the attempt, so an exit 0 whose output does not match is not yet an answer and the wait runs the command again. HTTPGetCmd's second argument gates its own body the same way (httpget.go:86).

func GnokeyTSCmd

func GnokeyTSCmd() func(ts *testscript.TestScript, neg bool, args []string)

GnokeyTSCmd returns a testscript command handler for gnokey. It auto-injects -home, -insecure-password-stdin and -remote.

func GpaoTSCmd

func GpaoTSCmd(cfg GpaoConfig) func(*testscript.TestScript, bool, []string)

GpaoTSCmd returns the "gpao" testscript command, which accepts start, stop and restart.

The oracle's lifetime is owned here rather than in the script so that a script that fails part way still leaves no daemon behind, and so that readiness is a probe rather than a sleep.

func HTTPGetCmd

func HTTPGetCmd() func(ts *testscript.TestScript, neg bool, args []string)

HTTPGetCmd returns a testscript command that fetches a URL and writes the response body where the stdout matcher can see it.

This is how a scenario reads the oracle's own verdict. The status board is the only machine-readable place the oracle distinguishes rejected, pending, gave_up and blocked; everywhere else those live in operator output.

An optional second argument gates on the body: the status board answers HTTP 200 for every verdict it knows, including "unknown", so without a pattern "eventually ... http_get" would return on the first response no matter what it says. The gate has to live in here rather than as a following "stdout <pattern>": that check runs only once eventually has already returned, so it would report whatever verdict the poll stopped on instead of waiting for the one the scenario is after.

func RepeatCmd

func RepeatCmd(cmds map[string]func(*testscript.TestScript, bool, []string)) func(ts *testscript.TestScript, neg bool, args []string)

RepeatCmd returns a testscript command that runs a subcommand N times. It takes a reference to the full commands map for dispatching.

func ResolveScriptFiles

func ResolveScriptFiles(args []string) ([]string, error)

ResolveScriptFiles turns command arguments into the scripts to run. Accepts no arguments (the tour), directories, .txtar files, or a mix.

A directory contributes its own scripts in filename order and never its subdirectories, and a named file contributes itself, so the caller's order survives as the run order.

func Run

func Run(cfg RunConfig) error

Run sets up testscript params and runs the script against the configured node.

func RunT

func RunT(t testscript.T, cfg RunConfig)

RunT drives one script through testscript.T, which a real *testing.T satisfies: a go test driver can hand its own T here and get the script's transcript in the test output rather than through a logger of our own.

func SleepCmd

func SleepCmd() func(ts *testscript.TestScript, neg bool, args []string)

func TSValidateError

func TSValidateError(ts *testscript.TestScript, cmd string, neg bool, err error)

TSValidateError checks a command result against the negation flag. If err != nil and neg is false, it fatals. If err == nil and neg is true, it fatals.

func ValidatorTSCmd

func ValidatorTSCmd(cl *cluster.Cluster) func(ts *testscript.TestScript, neg bool, args []string)

ValidatorTSCmd returns a testscript command that stops and restarts individual validators, so a scenario can inject a node outage.

Lifecycle is driven from Go rather than through txtar's exec primitive: a script says which node should be down and for how long, and the harness owns signalling it, waiting for it to be gone, and bringing it back ready. A script that spawned processes itself would also have to clean them up, and would leak one on every failed assertion.

Indexing matches the RPC_ADDR_N the scripts already use.

Only restart is negatable: "! validator restart N" asserts that the node cannot come back, and the error, which carries the node's stderr tail, reaches the script's stderr so the scenario can name the reason it died. It asserts that narrowly: a node the cluster does not have, or one that was never stopped, fails the script in either mode. A stop that fails is a harness fault, not something a scenario observes.

Types

type ClusterOverrides

type ClusterOverrides struct {
	Validators           *int
	CodeSubmissionPolicy *string
	// PkgApprover is a bech32 address here, where a script names a role.
	// ClusterSpec.ApplyTo accepts either.
	PkgApprover *string
	BlockMaxGas *int64
}

ClusterOverrides are the cluster settings named on the command line. Each is nil unless it was actually given, which is what separates a flag left at its default from one set to that same value.

A named setting wins over what a script declares, and the caller owns the result: running a scenario that declared three validators against two is allowed, and that scenario may go red. That is the point of an override.

func (ClusterOverrides) Apply

Apply returns the cluster a script will actually run against, leaving the declaration itself untouched. A setting named on the command line wins over the one the script declared.

type ClusterSpec

type ClusterSpec struct {
	// Validators is the only setting with no usable zero value, so it is the
	// only one a scenario must state.
	Validators int
	// CodeSubmissionPolicy is empty for the chain default.
	CodeSubmissionPolicy string
	// PkgApprover names who may enable inert packages, as a role rather than
	// an address, because the addresses are derived from mnemonics the runner
	// owns. Empty means the oracle, which is provisioned for every run.
	// "user" means the test user, which is what leaves the oracle
	// unauthorized.
	PkgApprover string
	// BlockMaxGas is zero for the chain default.
	BlockMaxGas int64
	// NodeConfig holds the "config." keys, prefix stripped, in declaration
	// order. Each is a `gnoland config set` key set on every node.
	NodeConfig []cluster.Override
	// GenesisParams holds the "genesis." keys, prefix stripped, in declaration
	// order. Each is a `gnogenesis params set` key set on the genesis params.
	GenesisParams []cluster.Override
}

ClusterSpec is the cluster one scenario declares it needs.

It is the projection of cluster.ClusterConfig a scenario is allowed to choose, not a copy of it: the settings a script may state, in the vocabulary a script states them in, before the runner resolves them against the keys and binaries it owns.

func ParseClusterSpec

func ParseClusterSpec(script []byte) (ClusterSpec, error)

ParseClusterSpec reads the cluster a script declares. Unknown keys are errors rather than ignored, so a typo fails at parse time instead of silently booting the wrong chain.

func (ClusterSpec) ApplyTo

func (s ClusterSpec) ApplyTo(cfg *cluster.ClusterConfig, user, oracle crypto.Address) error

ApplyTo settles the spec onto a cluster config, leaving every option the scenario did not state at the chain default.

The approver is resolved here rather than in the section, because a script names a role while genesis needs an address, and the addresses come from mnemonics the runner owns.

type GpaoConfig

type GpaoConfig struct {
	// Binary yields the oracle binary, building it if the run has not needed
	// one yet. A provider rather than a path, because a run whose scripts
	// never start the oracle must not pay to build it.
	Binary   func() (string, error)
	Mnemonic string
	ChainID  string
	Remote   string // -remote used when the script names no node
	GnoRoot  string
}

GpaoConfig carries what stays fixed for a whole run. Per-invocation flags come from the script.

type RunConfig

type RunConfig struct {
	// ScriptPath is the single script this run drives. One script per run,
	// because the cluster it names in RPCAddr was booted for it alone.
	ScriptPath string
	RPCAddr    string
	// RPCAddrs holds every validator's RPC address in index order. Scripts see
	// them as RPC_ADDR_0 upward. RPCAddr stays the first validator.
	RPCAddrs []string
	ChainID  string
	GnoHome  string
	UserAddr string
	KeyName  string
	Verbose  bool
	Logger   *slog.Logger
	Gpao     GpaoConfig
	// GpaoKeyName is the keybase name under which the oracle's key was
	// imported, exported to scripts as GPAO_KEY_NAME.
	GpaoKeyName string
	// Cluster is the local cluster the scripts run against, so a scenario can
	// stop and restart individual validators. Nil only when no local cluster
	// was started for the script.
	Cluster *cluster.Cluster
	// GpaoAddr is the oracle's approver address, exported to scripts as
	// GPAO_ADDR. GpaoKeyName names the keybase entry; a scenario querying the
	// approver's own balance needs the address itself.
	GpaoAddr string
}

RunConfig holds resolved values for running one txtar script.

type Scenario

type Scenario struct {
	Spec ClusterSpec
	Path string
}

Scenario is one script and the cluster it runs against.

func ResolveScenarios

func ResolveScenarios(paths []string, overrides ClusterOverrides) ([]Scenario, error)

ResolveScenarios reads each script's declared cluster and settles the command line's overrides over it.

One scenario per script, so every one of them gets a chain built from genesis and torn down after. A chain carries deployed packages, account balances and its height, and none of that is reset while it runs, so two scenarios sharing one would make the second's result depend on the first: on what it deployed, on what it spent, and on the name that decided which ran first. That is a suite where adding a file changes the verdict of a file nobody touched.

Scenarios come back in the caller's order.

type TestscriptT

type TestscriptT struct {
	Logger  *slog.Logger
	Failed  bool
	Skipped bool
	// contains filtered or unexported fields
}

TestscriptT adapts slog.Logger to satisfy testscript.T. It uses runtime.Goexit() for FailNow/Fatal/Skip to match the semantics that testscript expects (same as testing.T).

func NewTestscriptT

func NewTestscriptT(logger *slog.Logger, verbose bool) *TestscriptT

NewTestscriptT creates a TestscriptT with the given logger and verbosity.

func (*TestscriptT) FailNow

func (t *TestscriptT) FailNow()

func (*TestscriptT) Fatal

func (t *TestscriptT) Fatal(args ...any)

func (*TestscriptT) Log

func (t *TestscriptT) Log(args ...any)

func (*TestscriptT) Parallel

func (t *TestscriptT) Parallel()

func (*TestscriptT) Run

func (t *TestscriptT) Run(name string, f func(testscript.T))

Run executes f in an isolated goroutine so that runtime.Goexit() within f only terminates that goroutine, not the caller. This matches how testing.T.Run works internally.

func (*TestscriptT) Skip

func (t *TestscriptT) Skip(args ...any)

func (*TestscriptT) Verbose

func (t *TestscriptT) Verbose() bool

Jump to

Keyboard shortcuts

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