test: run ESP32 programs in QEMU

This commit is contained in:
Jake Bailey
2026-08-10 11:22:49 -07:00
committed by Ron Evans
parent 1991b00395
commit 1958d6ff9d
10 changed files with 93 additions and 12 deletions
+12
View File
@@ -196,6 +196,10 @@ jobs:
cat /proc/cpuinfo
sudo apt-get update
sudo apt-get install --no-install-recommends \
libgcrypt20 \
libpixman-1-0 \
libsdl2-2.0-0 \
libslirp0 \
qemu-system-arm \
qemu-system-riscv32 \
qemu-user \
@@ -216,6 +220,14 @@ jobs:
version: "29.0.1"
- name: Setup `wasm-tools`
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Install Espressif QEMU
run: |
release=esp-develop-9.2.2-20260417
archive=qemu-xtensa-softmmu-${release//-/_}-x86_64-linux-gnu.tar.xz
curl -fL --retry 3 -o "$RUNNER_TEMP/$archive" \
"https://github.com/espressif/qemu/releases/download/$release/$archive"
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/qemu/bin" >> "$GITHUB_PATH"
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
+30
View File
@@ -261,6 +261,21 @@ func TestTimerStopResetRace(t *testing.T) {
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
}
func TestESP32QEMU(t *testing.T) {
t.Parallel()
options := optionsFromTarget("esp32-qemu", sema)
emuCheck(t, options)
machines, err := exec.Command("qemu-system-xtensa", "-machine", "help").Output()
if err != nil {
t.Fatal("failed to list qemu-system-xtensa machines:", err)
}
if !regexp.MustCompile(`(?m)^esp32\s`).Match(machines) {
t.Skip("qemu-system-xtensa does not support the ESP32 machine")
}
runTest("print.go", options, t, nil, nil)
}
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options)
@@ -539,6 +554,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
if config.EmulatorName() == "simavr" {
actual = cleanSimAVRTestOutput(actual)
}
if config.EmulatorName() == "qemu-system-xtensa" {
actual = cleanESP32QEMUOutput(actual)
}
if name == "testing.go" {
// Strip actual time.
re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`)
@@ -564,6 +582,18 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
}
}
func cleanESP32QEMUOutput(output []byte) []byte {
entryLine := bytes.Index(output, []byte("\nentry "))
if entryLine < 0 {
return output
}
entryLineEnd := bytes.IndexByte(output[entryLine+1:], '\n')
if entryLineEnd < 0 {
return output
}
return output[entryLine+1+entryLineEnd+1:]
}
func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
-6
View File
@@ -78,12 +78,6 @@ var _ebss [0]byte
//go:extern _vector_table
var _vector_table [0]uintptr
func abort() {
for {
device.Asm("waiti 0")
}
}
// interruptInit installs the Xtensa vector table by writing its address
// to the VECBASE special register and ensures all CPU interrupts are
// initially disabled.
+11
View File
@@ -0,0 +1,11 @@
//go:build esp32 && !qemu
package runtime
import "device"
func abort() {
for {
device.Asm("waiti 0")
}
}
+28
View File
@@ -0,0 +1,28 @@
//go:build esp32 && qemu
package runtime
import "device"
func exit(code int) {
qemuExit(code)
}
func abort() {
qemuExit(1)
}
func qemuExit(code int) {
// QEMU semihosting expects a2 = SYS_exit (1) and a3 = the exit code.
// AsmFull cannot declare these clobbers, so no Go code may run afterward.
device.AsmFull(
"mov a3, {code}\n"+
"movi a2, 1\n"+
"simcall",
map[string]interface{}{"code": code},
)
// This loop ensures the clobbered registers are never used again.
for {
device.Asm("waiti 0")
}
}
-4
View File
@@ -55,10 +55,6 @@ func ticksToNanoseconds(ticks timeUnit) int64 {
return int64(ticks) * 25
}
func exit(code int) {
abort()
}
func putchar(c byte) {
machine.Serial.WriteByte(c)
}
+7
View File
@@ -0,0 +1,7 @@
//go:build (esp32 && !qemu) || esp32c3 || esp32c6
package runtime
func exit(code int) {
abort()
}
+5
View File
@@ -0,0 +1,5 @@
{
"inherits": ["esp32-generic"],
"build-tags": ["qemu"],
"emulator": "qemu-system-xtensa -machine esp32 -nographic -semihosting -drive file={img},if=mtd,format=raw"
}
-1
View File
@@ -25,6 +25,5 @@
],
"binary-format": "esp32",
"flash-method": "esp32flash",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}
-1
View File
@@ -25,6 +25,5 @@
],
"binary-format": "esp32s3",
"flash-method": "esp32jtag",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}