Skip to main content
This guide covers building, testing, and contributing to the BoxLite CLI (boxlite). The CLI is implemented in the boxlite-cli crate and follows the same Rust Style Guide as the rest of the project.

Building the CLI

From the repository root:
make cli
This builds the debug runtime first (if needed) via make runtime-debug, then runs cargo build -p boxlite-cli. The binary is produced at:
./target/debug/boxlite
Run it with ./target/debug/boxlite --help. For a release build, use:
cargo build -p boxlite-cli --release
The release binary is at ./target/release/boxlite.

Testing

make test vs make test:cli

  • make test runs Rust library tests, Python SDK tests, and Node.js SDK tests. It does not run CLI tests.
  • make test:cli runs the CLI integration tests. It depends on runtime-debug and then:
    cargo test -p boxlite-cli --tests --no-fail-fast -- --test-threads=1
    
When working on the CLI, run both as needed:
make test
make test:cli
CLI tests are integration tests: they pull images, create boxes, and run real commands. They require a working VM environment (KVM on Linux or Hypervisor.framework on macOS). Tests use a shared test home (/tmp/bl), a global lock to avoid concurrent use, and pre-pulled images (alpine:latest, python:alpine) to reduce rate limits.

CLI test layout

  • Entry points: boxlite-cli/tests/*.rs — one file per command (e.g. run.rs, create.rs, exec.rs, list.rs).
  • Shared setup: boxlite-cli/tests/common/mod.rs provides boxlite() returning a TestContext that:
    • Uses CARGO_BIN_EXE_boxlite and --home pointing at a shared directory (e.g. /tmp/bl).
    • Uses a global lock so tests don’t run concurrently against the same home.
    • Pre-pulls images, sets a timeout (e.g. 60s), and exposes cleanup_box / cleanup_boxes.
Tests use assert_cmd::Command and predicates to assert exit codes and stdout/stderr. New tests should use common::boxlite() and clean up any boxes they create (e.g. with --rm or ctx.cleanup_box(...)). Example pattern:
#[test]
fn test_run_exit_code_success() {
    let mut ctx = common::boxlite();
    ctx.cmd
        .args(["run", "--rm", "alpine:latest", "sh", "-c", "exit 0"]);
    ctx.cmd.assert().success();
}

Code structure

  • Entry: boxlite-cli/src/main.rs — parses the CLI and dispatches to commands::*.
  • Subcommands and flags: src/cli.rs — clap definitions: Cli, Commands, GlobalFlags, ProcessFlags, ResourceFlags, ManagementFlags.
  • Command implementations: src/commands/*.rs — each command has an execute(args, global); they share global.create_runtime() and similar helpers.

Adding a new subcommand

1

Define the command

Add a new variant to Commands in src/cli.rs and the corresponding Args type (or reuse existing flags).
2

Export the module

In src/commands/mod.rs, add the new module and export the execute function.
3

Wire up dispatch

In src/main.rs, add a branch in run_cli that calls the new command’s execute.
4

Add tests

Add tests in boxlite-cli/tests/<command>.rs and run make test:cli.

Command reference

CommandDescription
make cliBuild the CLI (after building the debug runtime).
make test:cliRun CLI integration tests (single-threaded).
make testRun Rust, Python, and Node unit tests (no CLI tests).
make fmtFormat all Rust code.
cargo clippy -p boxlite-cliLint the CLI crate.

See also