Documentation
¶
Overview ¶
Package bench runs one command on a Linux bench against a copy of a local tree: `nova-ci bench run`.
THE HURT. Every card, every read and the coordinator ran Go on a bench with a recipe typed into each brief: ssh <bench> 'mkdir -p <d>' && rsync -a --delete <repo>/ <bench>:<d>/repo/ && ssh <bench> 'cd <d>/repo && export GOCACHE=... GOFLAGS=-mod=readonly NOVA_TEST_NO_HOST=1 && nice -n 19 go ...', a second bench by hand when the first did not answer, then ssh <bench> 'rm -rf <d>'. Each copy of the recipe was a place to get a path, a variable or the cleanup wrong (the owner, 2026-10-05: "We need to get away from these one shot shell scripts."). This package is the recipe, once, behind a Transport the tests fake.
THE RUN, per host, in order:
- make: one ssh that makes the root and a fresh run directory under it with mktemp -d, so two runs never share a directory and the run knows exactly the one it made. A host that does not answer (ssh's own exit 255, or ssh not starting) is passed over for the fallback; a host that answers and refuses is the end of the run, never a reason to try another.
- copy: the tree into <run>/repo, .git left out unless WithGit.
- exec: cd <run>/repo, then the command under nice -n 19 with GOCACHE, GOFLAGS=-mod=readonly and NOVA_TEST_NO_HOST=1, its output streamed to the caller as it arrives. Its exit status is the run's.
- remove: rm -rf of the run directory step 1 printed and nothing else, whatever happened in 2 and 3, under a context the caller's cancellation does not end, so an interrupted run still cleans up after itself.
THE MODEL. tla/BenchRun.tla is the run as a state machine: TLC holds OnlyTheMadeDirIsRemoved, AtMostOneHostAnswers, FallbackOnlyOnNoAnswer, ExitIsTheCommands and NothingLeftBehind on two hosts (MCBenchRun.cfg), and its reversed witness MCBenchRunBrokenNoRemove.cfg, a failed copy that skips the deferred remove, must break NothingLeftBehind.
Index ¶
- Constants
- Variables
- func CheckHost(h string) error
- func CheckPath(flag, p string) error
- func CopyLine(dst string) string
- func ExecLine(dir, cache string, argv []string) string
- func MakeLine(root string) string
- func Quote(s string) string
- func RemoveLine(dir string) string
- func WriteTree(w io.Writer, src string, withGit bool) error
- type Exec
- type Options
- type Result
- type Transport
Constants ¶
const ( // DefaultRoot is where the run directories are made. DefaultRoot = "nova-bench/runs" // DefaultCache is the bench's Go build cache, shared by every run on it. DefaultCache = "nova-bench/cache/go-build" // Nice is the niceness of the command: the bench is shared with the work // of every other card on it. Nice = 19 // NoAnswer is ssh's own exit status: it could not reach or log in to the // host, so the remote command never ran. NoAnswer = 255 // StepBudget bounds the make and remove steps, which are one short remote // command each; the copy and the command itself are bounded only by the // caller's context. StepBudget = 2 * time.Minute )
Defaults of the paths on the bench, relative to the login's home.
Variables ¶
var Env = []string{"GOFLAGS=-mod=readonly", "NOVA_TEST_NO_HOST=1"}
Env is the environment the command runs under, after GOCACHE: the same on every bench, so a run is a run wherever it lands.
var ErrNoBench = errors.New("no bench answered")
ErrNoBench is a run in which no host answered.
var SSHOptions = []string{"-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "ForwardAgent=no"}
SSHOptions are on every ssh this package starts: BatchMode so a missing key is a refusal rather than a prompt nobody answers, ConnectTimeout so a sleeping bench costs seconds and the fallback is tried, and no agent forwarded to a bench.
Functions ¶
func CheckHost ¶
CheckHost refuses a bench name ssh could read as an option or as more than one host.
func CheckPath ¶
CheckPath refuses a bench path that is not plain: one the remote shell would read differently from how it is written (~, a blank, a quote), one that climbs (..), or the home or the filesystem root itself.
func RemoveLine ¶
RemoveLine is the remote line of the remove step: the one directory made.
func WriteTree ¶
WriteTree writes the tree under src to w as a tar stream, paths relative to src: directories, regular files and symlinks, each with its mode. The .git at the top of the tree is left out unless withGit; anything else that is not one of those three (a socket, a device) is refused, never skipped.
Types ¶
type Exec ¶
type Exec struct{}
Exec is the production transport: the system ssh, and nothing else. The copy is a tar stream this process writes onto ssh's stdin, so no rsync or local tar is needed here and the bench needs only tar.
type Options ¶
type Options struct {
// Hosts are the benches in the order they are tried: the host, then the
// fallback. Only a host that does not answer moves the run to the next.
Hosts []string
// Dir is the local tree to copy.
Dir string
// Root is the directory on the bench the run directory is made in, and
// Cache is GOCACHE there; each is relative to the login's home or
// absolute. Empty is the default.
Root, Cache string
// WithGit copies the tree's .git as well.
WithGit bool
// Argv is the command, run in the copy.
Argv []string
// Stdout and Stderr receive the command's output as it arrives; Notes
// receives the run's own lines (a host passed over).
Stdout, Stderr, Notes io.Writer
}
Options is one run.
type Result ¶
type Result struct {
// Host is the bench that ran the command; RunDir the directory made there.
Host, RunDir string
// Code is the command's exit status.
Code int
// Removed is whether the run directory was removed; RemoveErr says why not.
Removed bool
RemoveErr error
}
Result is what a run did.
func Run ¶
Run is one run: the first host that answers makes a run directory, takes the copy, runs the command and has the directory removed. The error is a run that could not reach the command (no host answered, the copy failed, a step was refused); a command that ran and failed is Result.Code, never an error.
type Transport ¶
type Transport interface {
// Shell runs one line under the login shell on host, streaming its output.
// The int is the remote status (NoAnswer when ssh itself failed); the error
// is only for a transport that could not start at all.
Shell(ctx context.Context, host, line string, stdout, stderr io.Writer) (int, error)
// Copy copies the contents of the local directory src into dst on host
// (dst is created; its parent exists), leaving .git out unless withGit.
Copy(ctx context.Context, host, src, dst string, withGit bool, stderr io.Writer) error
}
Transport is what a run asks of the wire. The production transport is ssh alone (Exec), the copy a tar stream on its stdin; the tests pass a fake that records each call.