GitHub - jc-lab/test-foundry

5 min read Original article ↗

test-foundry is a QEMU-based Windows guest automation test tool.
It covers VM setup, snapshot creation, test execution, file upload/download, screenshot capture, and QMP-event-based waits such as wait-panic and wait-reset.

Features

  • QEMU VM setup and snapshot-based test execution
  • Split execution and file-transfer methods for Windows guests (SSH / WinRM)
  • GitHub workflow-like step-based test definition (wait-boot, exec, file-upload, file-download, screenshot, shutdown, and more)
  • preboot.steps support for offline disk patching before boot (efi-add-file, etc.)
  • expression support in test step params
    • ${{ test.dir }}
    • ${{ output.dir }}
    • ${{ env.<name> }}
    • ${{ vmconfig.<json-key> }}

Requirements

  • QEMU
  • OVMF / UEFI firmware
  • qemu-img
  • swtpm (optional. linux only)

The example flow is primarily prepared for running Windows guests on Linux hosts.

Build

go build ./cmd/test-foundry

Or:

go run ./cmd/test-foundry --help

Running the root command shows output like this:

$ go run ./cmd/test-foundry/
test-foundry automates testing of Windows drivers and UEFI applications
using QEMU virtual machines. It provides VM lifecycle management,
snapshot-based test execution, and step-by-step test automation.

Usage:
  test-foundry [command]

Available Commands:
  action      Execute individual actions against a running VM
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  test        Run tests against a VM snapshot
  vm-destroy  Destroy VM context directory
  vm-setup    Create VM context and prepare a snapshot

Flags:
      --headless         Headless mode (VNC only, no display)
  -h, --help             help for test-foundry
      --qemu string      QEMU binary path (default "qemu-system-x86_64")
      --verbose          Verbose logging
      --vm-name string   VM context name (required)
      --workdir string   VM context directory root (default ".testfoundry")

Use "test-foundry [command] --help" for more information about a command.

The action subcommand help looks like this:

$ go run ./cmd/test-foundry/ action --help
Execute individual actions against a running VM session via the IPC daemon.
Each action communicates with the daemon over HTTP to perform operations
on the guest OS or QEMU instance.

Usage:
  test-foundry action [command]

Available Commands:
  dump          Dump guest memory via QMP
  exec          Execute a command on the guest via SSH
  file-download Download a file from the guest via SFTP
  file-upload   Upload a file to the guest via SFTP
  poweroff      Forcefully power off the VM
  reboot        Reboot the guest
  resume        Resume a paused VM
  screenshot    Capture a screenshot via VNC
  shutdown      Gracefully shut down the guest
  sleep         Wait for a specified duration
  wait-boot     Wait until the guest OS is reachable via SSH
  wait-oobe     Wait until Windows OOBE is completed
  wait-panic    Wait for a pvpanic event from the guest
  wait-reset    Wait for a reset event from the guest

Flags:
  -h, --help   help for action

Global Flags:
      --headless         Headless mode (VNC only, no display)
      --qemu string      QEMU binary path (default "qemu-system-x86_64")
      --verbose          Verbose logging
      --vm-name string   VM context name (required)
      --workdir string   VM context directory root (default ".testfoundry")

Use "test-foundry action [command] --help" for more information about a command.

Basic Workflow

  1. Prepare a base image.
  2. Run vm-setup to create a VM context and snapshot.
  3. Run test to restore the snapshot and execute test steps.

The default workdir is .testfoundry, and --vm-name is used to distinguish VM contexts.

Example: Windows 11 Test

1. Extract a qcow2 image

./scripts/vagrant-extract-qcow2.sh gusztavvargadr/windows-11 2601.0.0 ./images

This prepares the qcow2 image under ./images for the sample image definition at examples/images/windows-11.yaml.

2. Run VM setup

test-foundry --vm-name="win11" vm-setup --image ./examples/images/windows-11.yaml

This step performs:

  • VM context creation
  • QEMU boot
  • waiting for OOBE completion
  • WinRM / SSH preparation
  • setup step execution
  • shutdown and snapshot creation

3. Run the test

test-foundry --vm-name="win11" test --output ./temp --test ./examples/tests/01-hello-world/test.yaml

In this command:

  • --output ./temp is the location for test-result.json
  • test-result.json is updated after each completed step, so you can inspect in-flight progress
  • the sample test itself writes output files to ./output/01/... as defined in examples/tests/01-hello-world/test.yaml

So after the run, artifacts are split across two locations:

  • ./temp/test-result.json
  • ./output/01/...

4. Verify generated files

Expected output:

output/01/hello-output.txt
output/01/screenshot.png

Video

video_test02_bsod.mp4

Example Test Structure

The sample examples/tests/01-hello-world/test.yaml runs this sequence:

  1. wait-boot
  2. file-upload
  3. exec
  4. file-download
  5. screenshot
  6. shutdown

When pvpanic is detected during a test run, test-foundry pauses the VM first. You can then use action resume to continue it, or poweroff in panic.steps if you want the panic workflow to terminate the VM explicitly after collecting artifacts.

Test parameters can also use expressions:

params:
  src: "${{ test.dir }}/hello.bat"
  name: "${{ vmconfig.machine_name }}"
  ssh_port: "${{ vmconfig.ssh_host_port }}"

Preboot Steps

preboot.steps is used to modify the qcow2 disk offline before QEMU boots. The current built-in preboot action is efi-add-file, which writes files into the EFI System Partition FAT32 filesystem.

  • preboot.steps in image YAML
  • preboot.steps in test YAML

Both are supported, so you can patch EFI content either while preparing the base VM or right before an individual test run.

Example:

preboot:
  steps:
    - action: efi-add-file
      params:
        src: "${{ test.dir }}/bootx64.efi"
        dst: /EFI/Boot/bootx64.efi

For a full example, see examples/tests/03-patch-efi/test.yaml and examples/tests/03-patch-efi/shellx64.efi. That sample patches EFI boot paths, captures a screenshot, and then powers the VM off.

Expressions

Expression format:

Currently supported:

  • ${{ test.dir }}
    • the directory containing the currently running test YAML
  • ${{ env.<name> }}
    • access to the current process environment variables
  • ${{ vmconfig.<key> }}
    • access to runtime MachineConfig JSON fields

Examples:

  • ${{ vmconfig.machine_name }}
  • ${{ vmconfig.qmp_socket_path }}
  • ${{ vmconfig.ssh_host_port }}
  • ${{ env.HOME }}

TODO

  • Linux guest support

Documentations

License

GPL-2.0-only