SchOS/docs/guides/testing.md
Nexoniarz 57aff40228 docs: four directories, and a rule for when a thing leaves the roadmap
docs/ was ten files where a plan, a description, a guide and a bug report all
sat next to each other. It is now architecture/ (what SchOS is), plan/ (what it
will be), guides/ (how to work on it) and history/ (why it is like this), with
an index that states the one rule holding it together: when something is
finished it moves from plan/ to architecture/ and is rewritten as a description
of the thing rather than a record of building it.

A ticked checkbox says the work happened. It does not say what the work
*produced*, and six months later the checkbox is all that is left. The network
stack proved the point: finished, verified end to end, and for a month the only
description of it anywhere was a checkbox four levels of indentation deep.

So roadmap.md went from 534 lines to 181 -- nothing deleted, the done work
graduated. Seven subsystems that were finished and undescribed now have
descriptions: boot, kernel, syscalls, filesystem, networking, graphics, usb,
init. Every one ends with "What it does not do", which is the section that pays;
a document listing only strengths is marketing, and the reader who needs it most
is the one about to trip over a limitation nobody wrote down.

schfs.md still opened with "phases 1 through 3 1/2 are done, phase 4 is next"
long after all five had landed. It is a description now, with the phase record
and the hard-won bugs moved to history/.

Three corrections found on the way, all load-bearing:

  * projects/schcom/README.md claimed SCM_RIGHTS was done. There is no
    sendmsg/recvmsg in the kernel at all, and an entire GUI plan rested on it.
  * The root README said "No TCP/IP" months after the stack landed.
  * history/kernel.md sec.7 was marked "in progress" on a problem that process
    groups solved.

Also adds plan/1.0.md, which draws a finish line so the open list stops being
infinite, and marks schwm/ and schde/ superseded: in Wayland the compositor is
the window manager, so that split dissolves rather than being abandoned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-12 17:13:35 +02:00

6.3 KiB

Testing

Booting to a prompt proves nothing. A filesystem can mount and still fail the first real command, and that has happened here.

So the harnesses drive the system and assert on what comes back, rather than reporting that it started.


The three levels

What it runs What it proves
make schfs-check Host-native unit suites The filesystem's logic, in about a second
tools/windows/bootcheck.ps1 A real boot, driven over the serial console That the system answers correctly
make crashqr, make schcom-test A boot, then pixel assertions from the host That the screen is right

The levels exist because each catches what the one below cannot. A unit test cannot tell you the kernel fails to boot; a boot test cannot tell you the screen is blank.


make schfs-check — the fast one

Run this first and run it often. It is host-native, so a test takes a second instead of a boot.

make schfs-check          # B-tree, object and image suites
make schfs-test           # the B-tree alone
make schfs-objtest        # the object layer alone
make schfs-image-test     # build an image with mkschfs, check it with fsck

This works because the kernel and the host tools compile the same SchFS source. mkschfs cannot disagree with the kernel about the on-disk format, because there is only one implementation — so format bugs are findable on the host, where the loop is fast.

bootcheck — the boot suite

tools/windows/bootcheck.ps1                 # the default run
tools/windows/bootcheck.ps1 -Tty -Ps -Net   # switches compose

It boots SchOS under QEMU, types commands at the serial console, and greps the transcript for the answers they should produce.

It is PowerShell because pwsh runs on every supported host — Linux, macOS and the BSDs as well as Windows. brew install powershell, or your distribution's package. It finds QEMU on PATH.

The switches, and why each exists

Each of these turns on something the default run cannot reach. That is the point: a subsystem nothing exercises looks identical whether it works or not.

Switch Why it is not in the default run
-Tty Pipes, isatty, SIGPIPE, Ctrl-C. None can be checked from a screenshot, and all were once broken in ways that looked like broken commands.
-Usb Attaches an xHCI controller with a keyboard, mouse and mass-storage device. Nothing on the default machine has a USB port, so without it a broken driver looks exactly like a working one.
-Sock Unix sockets. A socket layer that accepted every call and dropped every byte would look identical from outside. socktest has one process write and another read.
-Evdev Raw input. Needs the QEMU monitor, because a key has to be pressed — characters typed at the serial port never touch the keyboard controller.
-Ps Process enumeration. /proc's whole reason for existing is one directory per process, and nothing else in the suite touches it.
-Net TCP/IP. A listener runs on the host and the guest connects out.
-Iso Boots schos.iso from an emulated optical drive with no hard disk attached, which is the only way to prove the ISO is self-contained. Needs make iso first.
-Fb Framebuffer checks.

-Net deserves its own note, because it is the shape every integration test should copy: both ends are asserted. The guest reports what it received and the listener reports what arrived. A stack that completed a handshake and then dropped every byte would pass any check made from one side alone.

Under QEMU's user-mode networking the host is 10.0.2.2 seen from inside, and SLIRP connects to the host's loopback — so the listener binds there.

Pixel assertions

make crashshot && make crashqr   # crash the kernel, decode the QR out of the framebuffer
make schcom-test                 # boot, run the compositor and a client, check the pixels

tools/windows/splashshot.ps1 counts colours. That exists because a log-only harness once passed on a blank screen.

schcom-test asserts from the host — the compositor's own report that it composited something proves nothing. It runs two overlapping clients and checks that the newer one wins in the overlap, because a compositor that cannot order two surfaces cannot host a window manager.

Other capture tools, all in tools/windows/: dispshot, fbshot, splashshot, textshot, ttyshot.


Two lessons worth keeping

Read the transcript, not the verdict. A change to the input grab once passed every assertion green while not having been applied at all — a scripted string replacement had silently failed to match, and the grep that should have caught it returned three hits (a comment, a #define, and the release call) that were read as confirmation without counting what they were. What caught it was four bare # prompts in the transcript, which is precisely the symptom the change was meant to remove.

A test that stops finding bugs has a standing cost. A ported 1993 shooter served as the full-stack acceptance test for a while and was a genuinely harsh one, because it was not written against our bugs — it found three real defects (O_NONBLOCK being a documented no-op, evdev replaying its backlog, and no EVIOCGRAB). It was removed once it stopped finding anything, because carrying it meant 33 MB of non-redistributable data staged into sysroot/, which is linked into the kernel binary for a ramroot build.

The shape of that test is worth copying even though the program is gone: something large that we did not write, driven to a real pixel-level assertion rather than a log line.


What is not tested

  • No automated test runs on real hardware. Everything above is QEMU.
  • SMP is exercised but not stressed — boots on 1, 2 and 4 processors are checked; nothing hunts for races.
  • No fuzzing anywhere, and no fsx/xfstests against SchFS. Getting SchFS under those is the main argument for the FUSE implementation in ../plan/roadmap.md.
  • The SchFS round-trip test needs symlinks and hard links, so it skips on a Git Bash host and only runs on Unix.
  • make ports does not work on Windows. musl, toybox and mksh must be built once on a Unix host and staged.