Documentation
¶
Overview ¶
Package cli contains side-effect-free command orchestration for FlowBaton.
Index ¶
- Constants
- Variables
- func DefaultOutputDirectory(options TestOptions, home string, now time.Time) string
- func ExitCodeFor(err error) int
- type ArtifactSink
- type BugreportRunner
- type CheckSyntaxRunner
- type Checker
- type CryptoRandom
- type DeviceSession
- type DriverRecordingController
- type DriverSetupRunner
- type GenerateCompletionRunner
- type HierarchyRunner
- type ImageChecker
- type InputGenerator
- type ListDevicesRunner
- type MCPRunner
- type ParserChecker
- type Preflight
- type QueryRunner
- type RecordRunner
- type ResourceReader
- type SessionFactory
- type Shard
- type Source
- type StartDeviceRunner
- type TestOptions
- type TestRunner
- type TestSession
- type UnsupportedRecordingController
- type UsageError
Constants ¶
const ( ExitOK = 0 ExitInvalid = 2 CheckSyntaxUsage = "usage: flowbaton check-syntax FILE|-\n" )
const ExitFailure = 1
ExitFailure is the exit code for a run that was understood and did not pass.
const TestUsage = "usage: flowbaton test [options] FILE|DIR...\n"
TestUsage is the one-line usage for the subcommand.
Variables ¶
var TopLevelSubcommands = []string{
"check-syntax",
"test",
"list-devices",
"start-device",
"hierarchy",
"query",
"bugreport",
"driver-setup",
"mcp",
}
TopLevelSubcommands is the canonical list of subcommands flowbaton dispatches. generate-completion reads it so a new command shows up in completion the moment it is added here; a test pins the set so dispatch and completion do not silently drift apart.
Functions ¶
func DefaultOutputDirectory ¶
func DefaultOutputDirectory(options TestOptions, home string, now time.Time) string
DefaultOutputDirectory resolves where a run's artifacts go.
specs/03-cli-tooling.md section 1 gives the precedence: an explicit --test-output-dir wins, then --debug-output, then a timestamped directory under the user's home. --flatten-debug-output drops the timestamp segment, which is what a CI job wants when it collects a fixed path.
func ExitCodeFor ¶
ExitCodeFor classifies an error into the documented exit codes. Anything not explicitly a usage error is a failure: a misclassification that returned OK would report a green suite that never ran.
Types ¶
type ArtifactSink ¶
type ArtifactSink struct {
// contains filtered or unexported fields
}
ArtifactSink writes run artifacts, into one of two directories.
A name the flow author wrote resolves against the process working directory: a flow saying `takeScreenshot: settings` leaves ./settings.png where the operator ran from, whichever directory the flow itself lives in. Everything the author did not name — the automatic failure captures — is the run's own bookkeeping and stays in the run directory.
func NewArtifactSink ¶
func NewArtifactSink(directory, authoredDirectory string) *ArtifactSink
func (*ArtifactSink) Write ¶
func (sink *ArtifactSink) Write( _ context.Context, request engine.ArtifactWriteRequest, ) (engine.ArtifactWriteResult, error)
type BugreportRunner ¶
type BugreportRunner struct {
Collect func(ctx context.Context, serial, outputPath string) error
CollectIOS func(ctx context.Context, udid, outputPath string) error
ResolveSerial func() (string, error)
}
BugreportRunner holds the collection and serial resolution behind fields so a test can record the call without a device. The defaults reach adb / simctl.
type CheckSyntaxRunner ¶
CheckSyntaxRunner enforces the command's exact stdout/stderr/exit contract.
type Checker ¶
Checker owns parse plus recursive capability preflight for one selected root. A successful return is the sole authority for printing OK.
type CryptoRandom ¶
type CryptoRandom struct{}
CryptoRandom is the process random source, shared by the JS faker binding and the random-input commands so there is one notion of randomness.
func (CryptoRandom) Intn ¶
func (CryptoRandom) Intn(n int) int
type DeviceSession ¶
type DeviceSession struct {
Driver device.Driver
// OutputDirectory receives screenshots and other run artifacts.
OutputDirectory string
// BaseDirectory resolves flow resources such as runScript files.
BaseDirectory string
// Clock is optional; a nil clock uses the wall clock. Tests supply their
// own so a flow's waits do not take real time.
Clock engine.Clock
// ExecutionID is optional; a blank one is derived from the run's own start.
ExecutionID string
// Shard is which share of the run this session executes. It is what the
// reserved FLOWBATON_SHARD_* and FLOWBATON_DEVICE_UDID variables are built
// from, so a flow can report where it ran.
Shard Shard
}
DeviceSession runs a prepared program against one device.
It exists to hold the assembly of engine.Dependencies in one place. That assembly is the part where a missing service is invisible: the engine takes every boundary as an interface, so a nil one compiles and then fails inside a flow, where it reads as a flow failure rather than a wiring mistake.
func NewDeviceSession ¶
func NewDeviceSession(options TestOptions, shard Shard) (DeviceSession, error)
NewDeviceSession builds a session for one shard, or explains why it cannot.
func (DeviceSession) Execute ¶
func (session DeviceSession) Execute( ctx context.Context, program *engine.Program, options TestOptions, ) ([]engine.FlowResult, error)
Execute opens the device, runs every selected root, and closes the device even when a flow fails.
type DriverRecordingController ¶
type DriverRecordingController struct {
// contains filtered or unexported fields
}
DriverRecordingController holds the capture id between the two commands.
func NewDriverRecordingController ¶
func NewDriverRecordingController( driver screenRecordingDriver, directory string, ) *DriverRecordingController
func (*DriverRecordingController) Start ¶
func (controller *DriverRecordingController) Start( ctx context.Context, request engine.RecordingStartRequest, ) error
type DriverSetupRunner ¶
DriverSetupRunner builds the driver. The build is a field so a test can record the invocation without running xcodebuild.
type GenerateCompletionRunner ¶
type GenerateCompletionRunner struct{}
GenerateCompletionRunner emits a shell completion script. Pure: no device, no I/O beyond stdout.
type HierarchyRunner ¶
type HierarchyRunner struct {
Fetch func(ctx context.Context, platform, udid string, appIDs []string) (device.TreeNode, error)
}
HierarchyRunner holds the tree fetch behind a field so a test can stand in a known tree without a device. The default opens the real driver, snapshots, and closes it.
type ImageChecker ¶
type ImageChecker struct{}
ImageChecker forwards to the shared check rather than repeating it.
func (ImageChecker) Check ¶
func (ImageChecker) Check( _ context.Context, request engine.ImageCheckRequest, ) (imagecheck.Result, error)
type InputGenerator ¶
type InputGenerator struct {
// contains filtered or unexported fields
}
InputGenerator supplies values for the inputRandom* commands.
func NewInputGenerator ¶
func NewInputGenerator() *InputGenerator
NewInputGenerator builds a generator over the process random source.
func (*InputGenerator) Generate ¶
func (generator *InputGenerator) Generate( _ context.Context, request engine.InputRequest, ) (string, error)
type ListDevicesRunner ¶
type ListDevicesRunner struct {
IOS func(context.Context) ([]ios.Device, error)
Android func(context.Context) ([]android.Device, error)
}
ListDevicesRunner holds the two listing calls behind fields so a test can stand in a fake without a simulator or an attached phone. The defaults reach real tooling.
func (ListDevicesRunner) Run ¶
func (runner ListDevicesRunner) Run(ctx context.Context, args []string, stdout, stderr io.Writer) int
Run lists devices for the selected platform(s). With no -p it lists both, and a platform whose tooling is absent is a note rather than a failure — the command is most wanted exactly on a machine that has only one SDK. With an explicit -p, that platform's error is the failure the operator asked to hear.
type MCPRunner ¶
type MCPRunner struct {
Checker Checker
ListDevices ListDevicesRunner
Hierarchy HierarchyRunner
Query QueryRunner
}
MCPRunner builds and serves the flowbaton MCP server over stdio. Its dependencies are fields so a test can drive the same server through an in-memory transport.
type ParserChecker ¶
type ParserChecker struct {
Preflight Preflight
}
ParserChecker parses the selected root before invoking recursive preflight. A missing preflight fails closed and can never produce a successful check.
func NewParserChecker ¶
func NewParserChecker() ParserChecker
NewParserChecker returns the production syntax checker. Parsing is followed by recursive capability analysis before a syntax check can succeed.
type Preflight ¶
Preflight is the narrow integration boundary required from the recursive graph/capability package. It receives the parsed root and the cwd resolution context, and must remain side-effect-free.
type QueryRunner ¶
type QueryRunner struct {
Fetch func(ctx context.Context, platform, udid, appID, expression string) ([]device.TreeNode, error)
}
QueryRunner holds the query behind a field so a test can match without a device. The default opens the real driver, queries, and closes it.
type RecordRunner ¶
type RecordRunner struct {
NewSession func(Shard, TestOptions) (TestSession, error)
}
RecordRunner records one flow. The session hook is the same one TestRunner takes, so a test drives it without a device.
type ResourceReader ¶
type ResourceReader struct {
// contains filtered or unexported fields
}
ResourceReader reads flow resources confined to one base directory.
func NewResourceReader ¶
func NewResourceReader(baseDirectory string) *ResourceReader
func (*ResourceReader) Read ¶
func (reader *ResourceReader) Read( _ context.Context, request engine.ResourceReadRequest, ) (engine.ResourceReadResult, error)
type SessionFactory ¶
type SessionFactory func(Shard, TestOptions) (TestSession, error)
SessionFactory builds the session for one shard.
type Shard ¶
type Shard struct {
// Index is 0-based; the human-facing number is Index+1.
Index int
// Roots are the flow paths this shard runs, in the plan's own order.
Roots []string
// Device is the udid this shard runs on. It is blank for an unsharded run
// with no --device, so the driver keeps saying which flag is missing.
Device string
// OutputDirectory is where this shard's artifacts go. The runner fills it
// in after planning; planning itself has no opinion about the filesystem.
OutputDirectory string
// DriverPort is the loopback port this shard's driver talks to. Shard 1
// keeps the contract's port; later shards get one each, because two shards
// on one port drive the same runner.
DriverPort int
}
Shard is one device's share of a run.
func PlanShards ¶
func PlanShards(options TestOptions, plan workspace.Plan) ([]Shard, error)
PlanShards divides a discovered plan into per-device shards.
type Source ¶
Source is one selected syntax-check root. BaseDir is always the invocation working directory; this makes links from stdin resolve from cwd without changing their user-facing source name from "-".
func (Source) ResolveLink ¶
ResolveLink resolves a parser-produced link against the invocation working directory. Absolute links remain absolute.
type StartDeviceRunner ¶
type StartDeviceRunner struct {
// Boot boots an existing iOS simulator by udid (default: simctl boot).
Boot func(ctx context.Context, platform, udid string) error
// ListAVDs enumerates installed Android AVD names (default: emulator -list-avds).
ListAVDs func(ctx context.Context) ([]string, error)
// LaunchAVD launches an Android AVD by name (default: emulator -avd <name>).
LaunchAVD func(ctx context.Context, avd string) error
// CreateSim creates a new iOS simulator and returns its udid (default: simctl create).
CreateSim func(ctx context.Context, osVersion, deviceLocale string) (string, error)
// CreateAVD creates a new Android AVD and returns its name (default: avdmanager create).
CreateAVD func(ctx context.Context, osVersion, deviceLocale string) (string, error)
}
StartDeviceRunner holds each external operation behind a field so tests can record calls without a real device. The defaults reach simctl/emulator.
type TestOptions ¶
type TestOptions struct {
Roots []string
ConfigPath string
Env map[string]string
IncludeTags []string
ExcludeTags []string
Format string
TestSuiteName string
Output string
DebugOutput string
TestOutputDir string
FlattenDebugOutput bool
Continuous bool
Headless bool
Analyze bool
ReinstallDriver bool
ScreenSize string
APIURL string
APIKey string
AppleTeamID string
Platform string
Devices []string
ShardSplit int
ShardAll int
// RecordTo is where `record` wants the video. It is NOT parsed from a
// command line: the `test` command has no such flag. RecordRunner sets it.
RecordTo string
// SequencedRoots and ContinueOnFailure come from the workspace's
// executionOrder, not from argv. They decide whether a failed flow ends
// the suite; see engine.Dependencies for the rule.
SequencedRoots int
ContinueOnFailure bool
// contains filtered or unexported fields
}
TestOptions is one parsed `test` command line.
func ParseTestOptions ¶
func ParseTestOptions(args []string) (TestOptions, error)
ParseTestOptions reads a `test` command line.
Flags and positionals may interleave. Boolean flags never consume the following argument — doing so would silently drop a flow from the run, which is the worst way for a suite to pass.
type TestRunner ¶
type TestRunner struct {
// NewSession acquires one shard's device and executes its flows. It is
// injected because no production session can be built without a device; a
// nil factory falls back to the real one.
NewSession SessionFactory
Loader capability.FlowLoader
// Clock is optional; nil uses the wall clock. It fixes the run's own notion
// of now, which both the default output directory and the report timestamp
// read, so two identical runs render identical bytes.
Clock engine.Clock
// Environ is optional; nil reads the process environment. It supplies the
// shell's FLOWBATON_ variables, per specs/01-core-engine.md:101.
Environ func() []string
// AllocatePort is optional; nil asks the operating system for an ephemeral
// port. Injected so a test can pin the ports a sharded run picks.
AllocatePort func() (int, error)
// PollInterval is how often continuous mode re-stamps its watch set; zero
// uses defaultPollInterval. Injected so a test does not wait on it.
PollInterval time.Duration
// RecordTo is set by RecordRunner, never by argv. See TestOptions.RecordTo.
RecordTo string
}
TestRunner discovers flows, preflights them, splits them into shards, and runs each shard on its own device.
Discovery, preflight, and shard planning all run BEFORE anything touches a device. That order is the point: a config error, an unknown command, a tag filter that selects nothing, or a shard count with no devices behind it are all knowable without a simulator, and finding them after devices have been acquired wastes the slowest part of a run and reports a setup mistake as a flow failure.
func (TestRunner) Run ¶
func (runner TestRunner) Run( ctx context.Context, args []string, stdout io.Writer, stderr io.Writer, ) int
Run executes one `test` invocation and returns its process exit code.
func (TestRunner) RunOptions ¶
func (runner TestRunner) RunOptions( ctx context.Context, options TestOptions, stdout io.Writer, stderr io.Writer, ) int
RunOptions is Run past its argument parsing. `record` uses it: it has to read the positionals itself (a flow plus an optional output file), so it parses the same options and then hands them over rather than rebuilding a command line for this to parse a second time.
type TestSession ¶
type TestSession interface {
Execute(context.Context, *engine.Program, TestOptions) ([]engine.FlowResult, error)
}
TestSession is the boundary between the host pipeline and a device.
type UnsupportedRecordingController ¶
type UnsupportedRecordingController struct{}
UnsupportedRecordingController refuses both halves of the recording lifecycle.
It is what a driver without the stop half gets. Reporting success and producing no file would tell a run it has a recording it does not have.
func (UnsupportedRecordingController) Start ¶
func (UnsupportedRecordingController) Start(context.Context, engine.RecordingStartRequest) error
type UsageError ¶
type UsageError struct {
Message string
}
UsageError marks a command line that could not be understood. It is the only thing that exits 2; everything else exits 1.
func (*UsageError) Error ¶
func (err *UsageError) Error() string
Source Files
¶
- android_agent_apks.go
- android_sdk.go
- bugreport.go
- bugreport_simctl.go
- capability_preflight.go
- check_syntax.go
- continuous.go
- debug_artifacts.go
- device_selection.go
- driver_ports.go
- driver_setup.go
- generate_completion.go
- hierarchy.go
- ios_runner_bundle.go
- list_devices.go
- mcp.go
- mcp_viewer.go
- query.go
- query_match.go
- record.go
- recording.go
- reporting.go
- reserved_environment.go
- services.go
- session.go
- shard_devices.go
- sharding.go
- start_device.go
- test_options.go
- test_runner.go
- web_chrome_options.go