grove tend grove tend
grove-tend is a scenario-based testing framework designed for end-to-end validation of command-line tools and Terminal User Interfaces (TUIs). It provides a structured environment for defining test cases in Go, managing isolated filesystems, and interacting with application subprocesses. Its core components are Scenarios, Steps, the Harness, and the Context.
Tests are defined as Scenarios composed of sequential Steps. The test harness executes these scenarios, managing state, sandboxed filesystems, mock dependencies, and automatic cleanup.
When executed, the tend CLI acts as a proxy. It discovers the project under test, builds a project-specific test binary containing that project’s compiled Scenario definitions, and then executes that binary with the specified arguments.
Key capabilities include:
- Hermetic test execution via temporary, sandboxed filesystems and home directories.
- Mocking of command-line dependencies (e.g.,
git,docker,kubectl). - Programmatic control and state assertion for TUIs via managed tmux sessions.
- Helpers for manipulating Git repositories, running commands, and managing Docker containers.
- Interactive debugging modes for step-through execution and live TUI exploration.
1. Scenarios and Steps
The fundamental building blocks of a grove-tend test suite are Scenarios and Steps.
- A
Scenariois the top-level container for a single test case, representing a complete user workflow or feature validation. It groups a series of actions and assertions. Scenarios are defined using theharness.NewScenarioconstructor. - A
Stepis a single, named function within aScenario. Each step performs a discrete action, such as setting up a file, running a command, or verifying output. It receives aContextobject, which provides the API for interacting with the test environment.
// A minimal scenario with a single step
var MyFirstScenario = harness.NewScenario(
"my-first-scenario",
"Verifies the basic functionality of a command.",
[]string{"smoke"}, // Tags for filtering
[]harness.Step{
harness.NewStep("Run command and check output", func(ctx *harness.Context) error {
// Test logic goes here
result := ctx.Command("echo", "hello").Run()
return result.AssertStdoutContains("hello")
}),
},
)2. Scenario Configuration
The harness.Scenario struct provides several fields to control test execution and organization.
NameandDescription: Strings used for identifying the test case in logs and reports.Tags: A slice of strings for categorizing scenarios. Tests can be filtered using the--tagsflag (e.g.,tend run --tags=smoke).Setup: A slice ofSteps that run once before the main test steps. This phase is used for prerequisite tasks like setting up mocks or preparing the test filesystem.Steps: The primary sequence ofSteps that define the core logic of the test case.Teardown: A slice ofSteps that run after the main steps have completed, even if a failure occurred. This is used for cleanup tasks that must be performed regardless of the test outcome.LocalOnly: A boolean that, whentrue, causes the scenario to be skipped in CI environments. This is for tests that depend on a local developer machine setup. The--include-localflag can override this behavior.ExplicitOnly: A boolean that, whentrue, prevents the scenario from running as part of a general test run (e.g.,tend run). It must be invoked explicitly by name (tend run <scenario-name>) or with the--explicitflag. This is for long-running, resource-intensive, or destructive tests.
3. The Harness
The Harness is the engine that executes scenarios. It is an internal component of the tend CLI that manages the entire test lifecycle.
Its primary responsibilities include:
- Isolation: Creating a new, temporary root directory for each scenario run to ensure tests are hermetic.
- Context Management: Instantiating the
Contextobject passed to each step, populating it with paths to the isolated directories and other test state. - Lifecycle Execution: Executing the
Setup,Steps, andTeardownphases in the correct order. - Cleanup: Automatically removing all temporary directories and resources created during a test run, unless disabled with the
--no-cleanupflag for debugging.
4. The Context (harness.Context)
The harness.Context object is the primary API for test authors. It is passed to every Step function and provides methods for interacting with the sandboxed test environment.
Key functionalities include:
- Filesystem Management: Create and retrieve paths to sandboxed directories.
NewDir,Dir: Manage named subdirectories within the test’s root.HomeDir,ConfigDir,DataDir,CacheDir: Access paths to a sandboxed user home directory structure ($HOME,$XDG_CONFIG_HOME, etc.).
- State Management: Share data between steps within the same scenario.
Set(key, value): Store a value.Get(key),GetString(key),GetInt(key), etc.: Retrieve stored values with type safety.HasKey(key),Keys(): Introspect the stored state.
- Command Execution: Run external commands within the sandboxed environment.
Command(name, args...): Creates a command that automatically runs with aPATHthat prioritizes mock binaries set up for the test.Bin(args...): A convenience wrapper for running the main binary of the project under test.
- Assertions: Perform validations.
Check(description, err): A “fail-fast” or hard assertion. If the error is notnil, the step fails immediately.Verify(func(v *verify.Collector)): A “collecting” or soft assertion. It gathers multiple failures within its function block and reports them all at once without stopping on the first failure.
- TUI Control: Launch and interact with TUI applications.
StartTUI(...): Starts a TUI application in an isolatedtmuxsession, returning aSessionhandle for programmatic interaction (sending keys, capturing screen content).StartHeadless(...): Runs abubbleteamodel in a test mode without a real terminal, allowing for direct inspection of its state and view output.