{"id":16836243,"url":"https://github.com/hugelgupf/vmtest","last_synced_at":"2025-07-09T16:37:20.146Z","repository":{"id":65071777,"uuid":"579813774","full_name":"hugelgupf/vmtest","owner":"hugelgupf","description":"Go API to start QEMU VMs + run tests in those VM guests","archived":false,"fork":false,"pushed_at":"2024-06-14T06:08:42.000Z","size":339,"stargazers_count":34,"open_issues_count":8,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-18T08:11:19.958Z","etag":null,"topics":["go","golang","qemu"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hugelgupf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-19T01:41:37.000Z","updated_at":"2025-03-09T04:44:30.000Z","dependencies_parsed_at":"2023-01-09T13:01:06.778Z","dependency_job_id":"fb74271c-2ad9-4e92-8c0a-5090ce52fa2a","html_url":"https://github.com/hugelgupf/vmtest","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugelgupf%2Fvmtest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugelgupf%2Fvmtest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugelgupf%2Fvmtest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugelgupf%2Fvmtest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hugelgupf","download_url":"https://codeload.github.com/hugelgupf/vmtest/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244907420,"owners_count":20529850,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","qemu"],"created_at":"2024-10-13T12:12:47.140Z","updated_at":"2025-03-22T04:30:53.250Z","avatar_url":"https://github.com/hugelgupf.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## vmtest\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/hugelgupf/vmtest)](https://goreportcard.com/report/github.com/hugelgupf/vmtest)\n[![GoDoc](https://godoc.org/github.com/hugelgupf/vmtest?status.svg)](https://godoc.org/github.com/hugelgupf/vmtest)\n\nvmtest is a Go API for launching QEMU VMs.\n\n* [The `qemu` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu)\n  contains APIs for\n\n    * launching QEMU processes\n    * configuring QEMU devices (such as a shared 9P directory, networking,\n      serial logging, etc)\n    * running tasks (goroutines) bound to the VM process lifetime, and\n    * using expect-scripting to check for outputs.\n\n    * [`quimage`](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu/quimage)\n      can be used to configure a Go-based u-root initramfs to be used as the\n      root file system, including any arbitrary Go commands.\n    * [`qnetwork`](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu/qnetwork)\n      (WIP) is an API to QEMU network devices.\n    * [`qevent`](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu/qevent)\n      provides a JSON-over-virtio-serial channel from guest to host.\n    * [`qcoverage`](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu/qcoverage)\n      adds utilities to collect kernel \u0026 Go\n      [`GOCOVERDIR`-based](https://go.dev/doc/build-cover) integration test\n      coverage.\n\n* [The `govmtest` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/govmtest)\n  (WIP) contains an API for running Go unit tests in the guest and collecting\n  their test coverage as well as results.\n\n* [The `scriptvm` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/scriptvm)\n  (WIP) contains an API for running shell scripts in the guest.\n\n## Running Tests\n\nThe `qemu` API picks up the following values from env vars by default:\n\n* `VMTEST_QEMU`: QEMU binary + arguments (e.g.\n  `VMTEST_QEMU=\"qemu-system-x86_64 -enable-kvm\"`)\n* `VMTEST_KERNEL`: Kernel to boot.\n* `VMTEST_ARCH`: Guest architecture (same as GOARCH values). Must match the QEMU\n  binary supplied. If not supplied, defaults to `runtime.GOARCH`, i.e. it\n  matches the host's GOARCH.\n* `VMTEST_KERNEL_APPEND`: is added to kernel command-line arguments\n* `VMTEST_QEMU_APPEND`: is added to QEMU command-line arguments.\n* `VMTEST_TIMEOUT`: Timeout value (e.g. `1m20s` -- parsed by Go's\n  `time.ParseDuration`).\n* `VMTEST_INITRAMFS`: Initramfs to boot.\n\nMost of these values can be overriden in the Go API, but typically only\n`VMTEST_INITRAMFS` and `VMTEST_TIMEOUT` are. `VMTEST_KERNEL_APPEND` and\n`VMTEST_QEMU_APPEND` are always additive.\n\nThe `runvmtest` tool automatically downloads `VMTEST_QEMU` and\n`VMTEST_KERNEL` for use with tests based on a provided `VMTEST_ARCH`. E.g.\n\n```sh\ngo install github.com/hugelgupf/vmtest/tools/runvmtest@latest\n\n# See how it works:\nrunvmtest -- bash -c \"echo \\$VMTEST_KERNEL -- \\$VMTEST_QEMU\"\n\n# Intended usage:\nrunvmtest -- go test -v ./tests/gohello\n\n# Or run an Arm64 guest:\nVMTEST_ARCH=arm64 runvmtest -- go test -v ./tests/gohello\n```\n\nYou can also override one or both, which will just be passed through:\n\n```sh\n# Will only download VMTEST_KERNEL.\nVMTEST_ARCH=arm64 VMTEST_QEMU=\"qemu-system-aarch64 -enable-kvm\" runvmtest -- go test -v ./tests/gohello\n```\n\nTo keep the artifacts around locally to reproduce the same test:\n\n```s\nrunvmtest --keep-artifacts -- go test -v ./tests/gohello\n```\n\nThe default kernel and QEMU supplied by `runvmtest` may of course not work well\nfor your tests. You can configure `runvmtest` to supply your own `VMTEST_KERNEL`\nand `VMTEST_QEMU` -- but also any additional environment variables. See\n[`runvmtest` configuration](#custom-runvmtest-configuration).\n\nTo build your own kernel or QEMU, check out\n[images/kernel-arm64](./images/kernel-arm64) for building a kernel-image-only\nDocker image, and [images/qemu](./images/qemu/Dockerfile) for how we build a\nDocker image with just QEMU binaries and their dependencies.\n\n## Writing Tests\n\n### Architecture-independent test with shared directory\n\nThe recommendation for architecture-independent tests is to allow\narchitecture-dependent values like `VMTEST_QEMU` and `VMTEST_KERNEL` be provided\nby `runvmtest` based on `VMTEST_ARCH`, while configuring everything else with\nthe Go API.\n\nSee e.g. [examples/shareddir](./examples/shareddir/vm_test.go)\n\nRun it with:\n\n```sh\n$ cd examples/shareddir\n$ VMTEST_ARCH=amd64 runvmtest -- go test -v\n$ VMTEST_ARCH=arm64 runvmtest -- go test -v\n$ VMTEST_ARCH=riscv64 runvmtest -- go test -v\n$ VMTEST_ARCH=arm runvmtest -- go test -v\n```\n\nThey all should pass.\n\nEvery test in this repository is written this way to test every feature on all\narchitectures.\n\n### Example: Go unit tests in VM\n\nSee [tests/gobench](./tests/gobench/bench_test.go)\n\n### Example: qemu API with u-root initramfs\n\n```go\nfunc TestStartVM(t *testing.T) {\n    vm := qemu.StartT(\n        t,\n        \"vm\", // Prefix for t.Logf serial console lines.\n        qemu.ArchUseEnvv,\n        quimage.WithUimageT(t,\n                uimage.WithInit(\"init\"),\n                uimage.WithUinit(\"shutdownafter\", \"--\", \"cat\", \"etc/thatfile\"),\n                uimage.WithBusyboxCommands(\n                        \"github.com/u-root/u-root/cmds/core/init\",\n                        \"github.com/u-root/u-root/cmds/core/cat\",\n                        \"github.com/hugelgupf/vmtest/vminit/shutdownafter\",\n                ),\n                uimage.WithFiles(\"./testdata/foo:etc/thatfile\"),\n        ),\n\n        // Other options...\n    )\n    // ...\n}\n```\n\n### Example: qemu API\n\nThe qemu API can be used without `testing.TB`, and any of the environment\nvariables can be overridden in the Go API:\n\n```go\nfunc TestStartVM(t *testing.T) {\n    vm, err := qemu.Start(\n        // Or use qemu.ArchUseEnvv and set VMTEST_ARCH=amd64 (values like GOARCH)\n        qemu.ArchAMD64,\n\n        // Or omit and set VMTEST_QEMU=\"qemu-system-x86_64 -enable-kvm\"\n        qemu.WithQEMUCommand(\"qemu-system-x86_64 -enable-kvm\"),\n\n        // Or omit and set VMTEST_KERNEL=./foobar\n        qemu.WithKernel(\"./foobar\"),\n\n        // Or omit and set VMTEST_INITRAMFS=./somewhere.cpio\n        // Or use u-root initramfs builder in quimage package. See example below.\n        qemu.WithInitramfs(\"./somewhere.cpio\"),\n\n        qemu.WithAppendKernel(\"console=ttyS0 earlyprintk=ttyS0\"),\n        qemu.LogSerialByLine(qemu.DefaultPrint(\"vm\", t.Logf)),\n    )\n    if err != nil {\n        t.Fatalf(\"Failed to start VM: %v\", err)\n    }\n    t.Logf(\"cmdline: %#v\", vm.CmdlineQuoted())\n\n    if _, err := vm.Console.ExpectString(\"Kernel command line:\"); err != nil {\n        t.Errorf(\"Error expecting kernel command line string: %v\", err)\n    }\n\n    if err := vm.Wait(); err != nil {\n        t.Fatalf(\"Error waiting for VM to exit: %v\", err)\n    }\n}\n```\n\n### Example: Tasks\n\n```go\nfunc TestStartVM(t *testing.T) {\n    vm, err := qemu.Start(\n        qemu.ArchUseEnvv,\n        // Other config ...\n\n        // Runs a goroutine alongside the QEMU process, which is canceled via\n        // context when QEMU exits.\n        qemu.WithTask(\n            func(ctx context.Context, n *qemu.Notifications) error {\n                // If this were an HTTP server or something not expected to exit\n                // cleanly when the guest exits, probably want to ignore SIGKILL error.\n                return exec.CommandContext(ctx, \"sleep\", \"900\").Run()\n            },\n        ),\n\n        // Task that runs when the VM exits.\n        qemu.WithTask(qemu.Cleanup(func() error {\n            // Do something.\n            return fmt.Errorf(\"this is returned by vm.Wait()\")\n        })),\n\n        // Task that only runs when VM starts.\n        qemu.WithTask(qemu.WaitVMStarted(...)),\n    )\n    // ...\n}\n```\n\n## Custom runvmtest configuration\n\n`runvmtest` tries to read a config from `.vmtest.yaml` in the current working\ndirectory or any of its parents.\n\nGiven this is a Go-based test framework, the recommendation would be to place\n`.vmtest.yaml` in the same directory as your `go.mod` so that the config is\navailable anywhere `go test` is for that module.\n\n`runvmtest` can be configured to set up any number of environment variables.\nConfig format looks like this:\n\n```\nVMTEST_ARCH:\n  ENV_VAR:\n    container: \u003ccontainer name\u003e\n    template: \"{{.somedir}} -foobar {{.somefile}}\"\n    files:\n      somefile: \u003cpath in container to copy to a tmpfile\u003e\n    directories:\n      somedir: \u003cpath in container to copy to a tmpdir\u003e\n```\n\nCheck out the example in\n[tools/runvmtest/example-vmtest.yaml](./tools/runvmtest/example-vmtest.yaml).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhugelgupf%2Fvmtest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhugelgupf%2Fvmtest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhugelgupf%2Fvmtest/lists"}