Skip to content

tools: add qbs, quick broad shallow tests for connected boards - #6037

Closed
dgryski wants to merge 5 commits into
tinygo-org:devfrom
dgryski:qbs
Closed

dgryski wants to merge 5 commits into
tinygo-org:devfrom
dgryski:qbs

Conversation

@dgryski

@dgryski dgryski commented Oct 25, 2026

Copy link
Copy Markdown
Member

qbs flashes a few small programs onto a connected board and checks the results over its serial port. It is meant to find obvious problems on many boards quickly, not to test any one feature in depth.

go run ./tools/qbs -target <target>

The tool builds with the standard Go compiler. The programs it flashes build with TinyGo.

Classes:

  • timing covers sleep accuracy, a monotonic clock, loop speed, and the device clock against the host clock.
  • compute runs the same computations on the device and the host and compares the results. It covers integers, bits, sorting, maps, hashes, encodings, strings, float32 and float64, math, strconv and fmt.
  • gc covers large blocks, linked lists, slice and string growth, map churn, fragmentation and collection cycles. Sizes come from the free heap.
  • serial covers echo, RX buffer overrun, line and byte transfer, and output with goroutines.

A class is skipped when its program does not fit on the target. A test is skipped when the board cannot run it, for example when there is not enough heap. The README explains how to add tests. If tools is not the right place I am happy to move it.

This supersedes #6023. That PR added the serial tests as a standalone tool. They are the serial class here, along with host drivers for the tests that need the host to send data, so #6023 can be closed.

Boards I ran it on:

The programs also build for pico, pico2-w, arduino-uno-r4-wifi, feather-m4 and stm32f4disco. On the arduino-nano, timing, gc and serial build and compute does not fit. I have not run it on a Nano.

qbs flashes small programs onto a connected board and checks the results over serial. The classes are timing, compute, gc and serial.
The runner can run the programs with tinygo run instead of flashing a board. On wasip1 the programs use standard input and output for serial, because machine.Serial needs host functions that wasmtime does not have.
The riscv-qemu and cortex-m-qemu targets have no UART receive, and cortex-m-qemu has no machine package and a simulated clock. With -noinput the programs start without the host and the serial class is skipped. The timing and gc tests skip when the clock does not advance or the heap is very large.
simavr prints nothing when its output is a pipe and colours each line, so -pty starts the emulator under a pseudo terminal and removes the colour codes. -scheduler and -gc change how the programs are built, because an explicit runtime.GC fails with the tasks scheduler of the simavr target. A second @@go fails the class as a restart and -class-timeout ends a class that keeps printing. The gc tests that build data with pointers run last, and reclaims only skips the class when runtime.GC does not run a collection. A class that does not fit now shows the linker error.
@dgryski

dgryski commented Oct 26, 2026

Copy link
Copy Markdown
Member Author

I added three commits to this PR. qbs can now run without a board.

Emulators. -emulator runs each class with tinygo run and talks to the program over stdin and stdout, for example go run ./tools/qbs -target wasip1 -emulator. To make that work on wasip1, the programs now use a small serial layer, dev.Serial. On boards it wraps machine.Serial. On wasip1 it uses stdin and stdout.

Targets that cannot receive input. -noinput is for targets with no UART receive, such as riscv-qemu and cortex-m-qemu. The programs start without the host and the serial class is skipped. The timing tests skip when the clock does not advance or follows a simulated time source, and the gc tests use at most 4 MB of heap.

simavr. simavr holds back its output when it is not a terminal and colours each line, so -pty runs it under a pseudo terminal and cleans the lines. -scheduler and -gc change how the programs are built. The simavr target needs -scheduler none because an explicit runtime.GC() fails with its tasks scheduler.

Runs that never finish. A second @@GO now fails the class as a restart. -class-timeout ends a class that keeps printing, which a program does after it runs out of memory. A class that does not fit on the target shows the linker error.

I ran wasip1, riscv-qemu and cortex-m-qemu with no failures except one gc linked-list growth on wasip1 that I cannot explain. simavr passes timing, skips compute because it does not fit, and fails gc because memory use grows on every run. That also happens in a small program without qbs, so I think it is a problem on AVR. I have not filed an issue for it yet. I ran the emulators before the last change to the gc tests and have not repeated them since.

…se for simavr

On AVR the conservative collector mistakes many values for heap addresses and the gc tests run out of memory, while the precise collector keeps a list flat. The growth check now uses eight runs and also needs more than one and a half structures of growth, so the wobble of a precise collector is not reported as a leak. The tests that keep the most memory alive run last.
@dgryski dgryski closed this Oct 26, 2026
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