Testing¶
This document covers Shed's testing strategy, how to run each tier of tests, and the conventions used across the codebase.
Test Tiers¶
| Tier | Scope | Build Tags | Requirements | CI |
|---|---|---|---|---|
| Unit | Pure logic, no external deps | None (or linux for Firecracker) |
Go toolchain | Yes |
| Integration | Docker API, real containers | integration |
Docker daemon | Yes |
| E2E (Firecracker) | Full VM lifecycle via API | e2e |
KVM, root, Firecracker assets | No |
Running Tests¶
Unit Tests¶
# All unit tests (skips linux-only tests on macOS)
make test
# Single package
go test ./internal/config/...
# With race detection
go test -race ./...
Firecracker unit tests (metadata, rootfs, networking) use the linux build tag and run automatically on Linux but are skipped on macOS:
Integration Tests¶
Integration tests require a running Docker daemon and use the integration build tag:
E2E (Firecracker)¶
Firecracker e2e tests exercise the full VM lifecycle: create, start, exec, stop, delete. They require KVM access, root privileges, and pre-built Firecracker assets.
# Build prerequisites
make build
sudo scripts/build-firecracker-rootfs.sh
scripts/download-firecracker.sh
# Run e2e tests
sudo go test -v -tags=e2e ./e2e/firecracker/...
Why Firecracker e2e can't run in CI
GitHub Actions runners do not support KVM (no nested virtualization). Firecracker requires /dev/kvm and root privileges to launch microVMs. These tests must be run manually on a bare-metal Linux host or a VM with nested virt enabled.
Build Tag Conventions¶
| Tag | Purpose | Example |
|---|---|---|
//go:build linux |
Code that uses Linux-only APIs (vsock, TAP, etc.) | internal/firecracker/*.go |
//go:build integration |
Tests requiring Docker | integration/*_test.go |
//go:build e2e |
Tests requiring KVM + Firecracker | e2e/firecracker/*_test.go |
All internal/firecracker/ source and test files carry the linux build tag because they depend on Linux-specific syscalls (vsock, netlink).
Test Patterns¶
Table-Driven Tests¶
All test files use Go's standard table-driven pattern:
tests := []struct {
name string
input string
wantErr bool
}{
{"valid", "my-shed", false},
{"empty", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateShedName(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
})
}
Modify Pattern for Config Validation¶
Config validation tests use a modify func(*) pattern to test individual field changes against a known-good baseline:
Test Helpers¶
The internal/firecracker package provides shared helpers in testutil_test.go:
| Helper | Purpose |
|---|---|
mustTempDir(t, prefix) |
Creates a temp directory with automatic cleanup |
testMetadata(name) |
Returns a valid Metadata with sensible defaults |
testFirecrackerConfig(tmpDir) |
Returns a valid FirecrackerConfig for testing |
createTestInstance(t, dir, name) |
Creates a complete test instance on disk |
Conventions¶
- Place test files alongside the code they test (
foo.go/foo_test.go) - Use
t.Helper()in all test helper functions - Use
t.Cleanup()for resource teardown instead ofdeferwhere possible - Prefer
t.Fatalffor setup failures,t.Errorffor assertion failures - Use
os.MkdirTempwitht.Cleanupfor temporary directories