Skip to content

feat(runner): add the test runner, plans and tmachine Session API, and run a plan on CI - #4773

Draft
matthewgrossman wants to merge 9 commits into
test/3954-testkit-ci-lane/matthewgrossmanfrom
test/3954-first-testkit-lane/matthewgrossman
Draft

matthewgrossman wants to merge 9 commits into
test/3954-testkit-ci-lane/matthewgrossmanfrom
test/3954-first-testkit-lane/matthewgrossman

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds the test runner, plans, and the tmachine Session API, and runs a plan on CI. A cpu-tests plan runs the conformance smoke suite on the Ubuntu, rootful Docker, Debian package environment through a new reusable e2e-suites.yml workflow. The plan is the only list of what runs where: nothing in a workflow or playbook repeats it.

Most of the code comes from @elezar's draft #4629: the Session API, testkit-spec, the runner, environments, and waivers, and the lockfile alignment check (a37ebe917). It is adapted to testkit finding the gateway the way the CLI does, so the runner sets OPENSHELL_GATEWAY and OPENSHELL_BIN instead of writing a target file.

Stack

Part of a stack of four; each PR targets the one below it. The tmachine move (#4726) has merged, so the stack uses its tests/tmachine/ paths.

  1. test(testkit): add the testkit library for end-to-end suites #4724: the testkit library.
  2. test(sandbox): run a testkit suite in Branch E2E #4822: the first testkit suite, run in Branch E2E against a Docker gateway. The simplest route to testkit tests in CI.
  3. feat(runner): add the test runner, plans and tmachine Session API, and run a plan on CI #4773: the test runner, plans, and tmachine Session API, with a plan running on CI.
  4. test(suites): port the old suites onto testkit and run them from the plan #4774: ports the old suites onto testkit and adds them to the plan.

Still to come, on top of #4774: moving the release lanes onto plans and deleting the old suites, once the plan has run the ported suites green.

Related Issue

Part of #3954

Changes

  • tests/runner (openshell-test): plan resolution, environments in tests/environments, waivers, JUnit result checking (a missing, skipped, or unselected test fails the lane), strict mode, and --emit-github-matrix. The catalog leaves #[ignore] tests out of what a lane expects.
  • tests/plans/cpu-tests.yaml: the plan. tests/environments also defines the k3s, Podman and arm64 environments, so adding one to a plan is one line once it has a runner path.
  • tmachine: the Session API; SSH, scp, and exec timeouts; down() never panics; QEMU is cleaned up on SIGTERM; the SSH port is TMACHINE_SSH_PORT. A test-runner flake package.
  • testkit: optional #[testkit::test(id = "t-…", requires = [...])], a testkit-spec crate shared by the macro and the runner, and capabilities through OPENSHELL_TEST_CAPABILITIES. Existing tests don't change.
  • tests/suites/conformance/sandbox: the smoke suite. It replaces test(sandbox): run a testkit suite in Branch E2E #4822's standalone tests/suites/sandbox, so there is one smoke test. mise run e2e:testkit and the testkit-e2e job run it on a Docker gateway.
  • .github/workflows/e2e-suites.yml (reusable) and a plan-cpu-tests job in Branch E2E (label test:e2e): one job per environment from the plan, running nix run .#test-runner -- test --plan ... --env <id> --strict, with JUnit and logs uploaded. It is not part of the Core E2E result yet.
  • The lockfile alignment check, enrolling the testkit and conformance workspaces. fmt, clippy, and nextest for the runner, tmachine, and suite in Branch Checks.

Testing

  • Checks appropriate to the affected code and behavior pass
    • On Branch E2E (workflow_dispatch), the plan-cpu-tests job passed: tmachine.ubuntu2404-amd64.docker-rootful.deb / conformance: PASS, 1 passed, with smoke: create_exec_delete selected from the plan. The testkit-e2e job also passed.
    • Locally: fmt, clippy, the runner (57), tmachine (8), and testkit (23) tests, the lockfile and alignment checks, the plan dry run, and the smoke test against a Docker gateway.
  • Unit tests added/updated
  • E2E tests added/updated

Not done yet

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (TESTING.md, AGENTS.md, CONTRIBUTING.md, tests/plans/README.md)

🤖 Generated with Claude Code

@copy-pr-bot

copy-pr-bot Bot commented Oct 20, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@matthewgrossman
matthewgrossman added this pull request to stack #4776 October 20, 2026 23:38
@github-actions

Copy link
Copy Markdown

@matthewgrossman
matthewgrossman force-pushed the test/3954-first-testkit-lane/matthewgrossman branch 3 times, most recently from e06a0eb to a23e140 Compare October 21, 2026 20:41
@matthewgrossman
matthewgrossman force-pushed the test/3954-first-testkit-lane/matthewgrossman branch 2 times, most recently from be4af48 to 63f4141 Compare October 22, 2026 04:51
@matthewgrossman
matthewgrossman removed this pull request from stack #4776 October 22, 2026 04:51
@matthewgrossman
matthewgrossman changed the base branch from test/3954-land-testkit/matthewgrossman to test/3954-testkit-ci-lane/matthewgrossman October 22, 2026 04:51
@matthewgrossman
matthewgrossman added this pull request to stack #4824 October 22, 2026 04:51
matthewgrossman and others added 9 commits October 21, 2026 21:59
…tkit-spec

`#[testkit::test]` takes two optional arguments. `id = "t-0123abcd"` gives a test
a stable identity that survives renames and moves. `requires = [gpu]` names the
capabilities the environment must provide, read from
OPENSHELL_TEST_CAPABILITIES. A test with requirements fails instead of passing
when the environment lacks them; a test without them never reads the variable.

The attribute's parsing moves into a new testkit-spec crate, which the test
runner's catalog also uses to list tests without compiling them. `#[should_panic]`
stays rejected; the requirement tests call `testkit::run` directly.

The testkit lockfile is also realigned with the root lockfile.

Co-authored-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Add a library API that boots an environment, runs commands in it with timeouts,
copies files in and out, collects diagnostics and tears it down, so a caller can
drive a machine without writing an Ansible playbook per suite. SSH and exec
timeouts and QEMU cleanup on SIGTERM keep a hung guest or a cancelled CI job from
leaving QEMU running.

Add the `test-runner` flake package, which runs tests/runner with the toolchains
and VM tools it needs.

The Session API and test-runner package are adapted from #4629.

Co-authored-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…like the root

`cargo --locked` only checks a lockfile against its own manifest. A workspace
outside the root that builds root crates through path dependencies, such as the
testkit suites building `openshell-sdk`, has its own Cargo.lock, so the two
lockfiles can resolve a shared crate to different versions without any check
failing. That is how aws-lc-sys 0.46 was picked up in #4629, which does not link
against the AWS-LC that Nix provides.

check_cargo_lockfile_alignment.py fails when an aligned workspace locks a
registry crate that the root lockfile also contains at a version the root
lockfile does not have. With --fix it re-locks the workspace starting from the
root lockfile. tests/testkit is the first aligned workspace.

The script and its tests come from #4629 (a37ebe9).

Co-authored-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Add the `sandbox` crate to the conformance workspace with one testkit test that
creates a sandbox, runs `echo` in it with exec, deletes it and checks it is gone.
It is deliberately small: it exists so a plan can run a real test through the
runner on CI before the existing suites move to testkit.

The workspace's other crate, the CLI conformance suite, keeps running as it does
today. The conformance workspace joins the lockfile alignment check, so its
lockfile is realigned with the root lockfile.

The test is adapted from the `sandbox-lifecycle` suite in #4629.

Co-authored-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
`openshell-test` expands a plan (tests/plans/*.yaml) into one job per
environment, builds the family's nextest archive for the guest, boots the
environment through the tmachine Session API, runs nextest in it and brings back
the JUnit report and logs. No per-suite Ansible playbook is involved.

The runner points the tests at the system under test with environment variables:
OPENSHELL_GATEWAY (the gateway the installer registered, from the environment's
`gateway` field), OPENSHELL_BIN (the installed CLI) and
OPENSHELL_TEST_CAPABILITIES (from the environment definition). Nextest runs with
`--test-threads 1` and a slow-timeout that terminates a hung test. The JUnit
report is checked against the catalog, so a missing or skipped expected test
fails the job; `#[ignore]` tests are not expected and nextest's skipped case for
them is accepted. `--strict` fails on an unsupported environment, an environment
with nothing left to run, or an empty selection.

`--emit-github-matrix` prints the GitHub Actions matrix for a plan, so the plan
is the only list of what runs where. Environments are defined for amd64 and
arm64 Ubuntu with Docker, k3s and Fedora Podman; tests/plans/cpu-tests.yaml
selects only the amd64 Ubuntu rootful Docker + Debian package environment, the
one CI can provision today.

Most of this comes from #4629, adapted to testkit finding the gateway the way the
CLI does instead of reading a target file.

Co-authored-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Run fmt, clippy and nextest for tests/tmachine and tests/runner, and fmt and
clippy for the new `sandbox` suite crate, wherever tests/testkit already runs
them: the mise tasks and Branch Checks. The suite's tests need a gateway, so they
run through `mise run e2e:testkit` against a local Docker gateway and, on CI,
through the test runner rather than the unit-test job.

The lockfile task and the Cargo lockfiles job also run the alignment check, and
its own tests run with `mise run test`.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Add a reusable workflow that takes a plan. A planning job runs
`openshell-test test --plan <plan> --emit-github-matrix`, and one job per
environment, on the runner label the matrix gives, runs
`nix run .#test-runner -- test --plan <plan> --env <id> --strict`. The jobs use the
same integration inputs as the existing tmachine lanes, cache the tmachine disks
keyed on the Ansible tree, upload the JUnit reports and logs whether or not the
run passes, and an aggregator job reports the overall result.

Branch E2E calls it for tests/plans/cpu-tests.yaml when the test:e2e label is
set. The job is not in the Core E2E result's needs yet, so it starts out
advisory. The existing conformance, feature and driver lanes are unchanged.

Modeled on the E2E Suites workflow in #4629.

Co-authored-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Describe how a plan runs on CI and locally in TESTING.md and a new
tests/plans/README.md, add a README for the sandbox suite, point the testkit
README at the runner, and add rows for tests/suites, tests/tmachine, tests/runner,
tests/plans and tests/environments to the project-structure tables in AGENTS.md
and CONTRIBUTING.md.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
The conformance sandbox suite replaces the standalone tests/suites/sandbox smoke
suite from the Docker lane, so there is one smoke test. `mise run e2e:testkit`
and the testkit-e2e job now run `cargo test -p sandbox` in
tests/suites/conformance.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman
matthewgrossman force-pushed the test/3954-first-testkit-lane/matthewgrossman branch from 63f4141 to df44e02 Compare October 22, 2026 05:03
@matthewgrossman matthewgrossman changed the title test(sandbox): add the first testkit suite and run it in Branch E2E feat(runner): add the test runner, plans and tmachine Session API, and run a plan on CI Oct 22, 2026
@matthewgrossman
matthewgrossman removed this pull request from stack #4824 October 22, 2026 05:29
@matthewgrossman
matthewgrossman added this pull request to stack #4825 October 22, 2026 05:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant