Conversation
Go's own test suites check thousands of Linux behaviours, but nothing could run them inside NONOS: go test runs each test binary from its package's directory, beside testdata/, and the boot guest always starts at /. NONOS_LINUX_GO_SUITE=1 now enrols the test binaries that `go test -c` makes for the packages NONOS_LINUX_GO_SUITE_PKGS names, as guests gs<package>, ids 5042 upward in list order. gostd (5040) changes to the directory named first and becomes the program named second. A suite store holds gostd, the packages NONOS_LINUX_GO_SUITE_STORE names, their testdata/ at the paths they have on the build host, and Go's zone database, and nothing else, since one test binary is 4 to 15 MB against the store's 16 MiB. The default build does not change.
A Go test that hung inside the guest could not be looked into: the boot guest's environment is fixed, so GODEBUG and GOTRACEBACK could not be set. gostd now adds NAME=value words that come between the directory and the program to the program's environment, as env(1) does. A run can ask Go's scheduler to trace itself, or turn a Go setting on or off, from the boot file alone.
Go runs each test binary in its package's directory, and some tests read their own sources there: sync's ExampleOnceValues reads example_test.go. The suite image carried only testdata/, so from / that example read nothing and its result never came back, and a package without testdata/ had no directory for gostd to change into. A suite image now also carries each package's *_test.go files at their build-host paths, so every package runs from its own directory, as go test runs it. NONOS_LINUX_GO_SUITE_SOURCES=0 leaves them out for runtime, whose testdata/ alone nearly fills the store's 128 entries.
gostd was a Go program, and the Go runtime starts its own threads and asks for SIGURG to preempt them. A Go test run with async preemption turned off still hung before its first test: the SIGURG went to gostd's own handler, before it reached the test, and a Go setting given to the test cannot reach the program that starts it. gostd is now static C: it changes directory, adds the NAME=value words to the environment and execs the test, with no runtime, no threads and no signal handlers. time now reaches its tests through it.
A test may read any file of its package, not only its own: io/fs TestGlob globs for glob.go and path/filepath TestGlob for match.go. The image carried only *_test.go, so both failed on NONOS and passed on the host. Every .go file of the package is now packed when they fit the store's 128 entries, and only *_test.go otherwise (os and syscall, with 153 and 298 files). io/fs now passes all 18 of the tests the host passes.
A run read each program it starts or execs whole into one buffer that
doubles as it fills, inside a fixed 16 MiB heap. A Go test binary of
4.9 MB was enough: its execve asked for an 8 MiB buffer, the allocation
failed, and the personality ended with every guest it hosted
("memory allocation of 8388608 bytes failed", exit 134).
A run now takes a 40 MiB heap: the 24 MiB peak of reading the largest
program the kernel verifies (16 MiB), on top of the 16 MiB every run had.
Go's runtime (13.5 MB) and encoding/json (11.9 MB) test binaries now load.
The heap is committed when it is made, so each Linux run holds 40 MiB.
The program a run starts was held in the heap for the whole life of the personality, though nothing reads it after it is mapped into the guest. Every execve that followed had that much less room to read its own program into. start() now takes the launch rather than borrowing it, and its bytes are freed when the program is running.
The kernel's debug channel refuses a line over 256 bytes whole, and the console ignored that and told the guest every byte was written. Longer writes vanished: Go prints a parallel test's results in one write, so context's TestCause and its 20 subtests never reached the log. The console now forwards in pieces of at most 256 bytes and returns the count it carried, a short write if the log refuses part way. context now reports all 65 of its tests.
The boot guest's file was read with a 1024-byte limit, and the store cuts a longer file short without saying so. A long argument lost its end, and the program ran with an argument nobody gave it: a -test.skip pattern lost its closing parenthesis and the Go test binary exited at once. A file over 1024 bytes is now refused with a line that says why.
Nothing showed whether the personality let a program's bytes go once it was running: the Go test binaries that exec themselves open /dev/null first, and fail there before any second image is read. execbig is a static C program with 12 MiB of initialised data that execs itself once and prints a line from each image. With the first image's bytes released it prints both and ends with status 0, as on Linux; with them held, the personality fails to allocate 16 MiB for the second read. NONOS_LINUX_GO_SUITE_EXECBIG=1 enrols it on the id pair after the packages, and the word execbig in NONOS_LINUX_GO_SUITE_STORE packs it.
The suite's block had grown Guests.mk, the registry every lane adds to, by 37 lines, past the size a file here is kept to. It now lives in GoSuite.mk, which Guests.mk includes at the same place; nothing it builds or packs changes. A suite store for io/fs still holds 27 entries and io/fs still passes all 18 of the tests the host passes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Go's standard library ships thousands of tests, and most of them check how Linux behaves: files, pipes, timers, signals, sockets, processes. This branch runs those tests inside NØNOS as Linux guests and compares every result with the same test binary run on an ordinary Linux machine. Where the two disagree, NØNOS is wrong until shown otherwise.
It also fixes four things in the Linux personality that the tests turned up, and it lists everything else they found, with the test that shows it.
This branch builds on
linux/go-guests(#582) and should merge after it. Its own work is the 11 commits and 10 files listed below.The commits
f57a39231Go's test binaries can be enrolled as guests.NONOS_LINUX_GO_SUITE=1turns it on; the normal build does not change.861d2000eThe small start program,gostd, can set environment variables such asGODEBUGfor the test it starts.c73bd4469A package's test files go into the image with it, so each test runs from its own directory, asgo testruns it.e006fb8c8gostdis rewritten in C. As a Go program it received a signal of its own before the test began and hung there.87c953f36All of a package's source files go into the image when they fit. Some tests read them.c948c98a9The personality's heap grows from 16 to 40 MiB. Starting a 4.9 MB program ran it out of memory and took down every guest it was hosting.65d9c3fd2The first program's bytes are freed once it is running. They were kept for the life of the personality.51f0dc8b6Console output longer than 256 bytes now reaches the log. It was silently dropped, while the program was told it had been written.f8c4711f0A boot file longer than 1024 bytes is refused with a message. It used to be cut short without a word.fc6e26ba3A 12 MB test program,execbig, that runs itself a second time. It proves the fix in65d9c3fd2.f5b957b46The suite's build rules move into their own file,GoSuite.mk, so the shared guest list grows by 3 lines instead of 37.Results
Each test binary ran on the host from its package directory, with
-test.v -test.short. On NØNOS it ran on one virtual CPU under QEMU's software emulation, withGODEBUG=asyncpreemptoff=1(the reason is the first finding below). When a test hung or brought the whole program down, it was skipped and the package was run again, until every test had been reached. Counts include subtests.Sixteen packages match the host exactly: bufio, bytes, strings, strconv, sort, math, regexp, unicode/utf8, io/fs, encoding/binary, encoding/base64, hash/crc32, crypto/aes, crypto/ed25519, log and fmt.
crypto/sha256 passes four more tests on NØNOS than on the host, because the emulated CPU has instructions this host's CPU lacks, so four tests that skip on the host can run.
runtime and net/http are not fully reached; the reasons are under "Found, not fixed here".
How each fix was proven
Each fix was taken out again, the image rebuilt, and the same program booted. Every time, the old failure came back.
execbigprints its first line, then "memory allocation of 16777216 bytes failed"TestCauseand its 20 subtests never appear in the logBefore these fixes, the first
syncrun reached none of its 56 tests.Found, not fixed here
These belong to other parts of the Linux personality. Each item names a test that shows it.
Signals and processes
sigaltstackis accepted but ignored, so a signal handler always runs on the stack of the code it interrupted. Go asks for a separate signal stack. When a signal lands on a goroutine's stack instead, Go's handler waits forever. This is why every package hung at random points until preemption signals were turned off. Seen intimeTestSleepandos/signalTestSignalTrace.timeTestIssue5745andsync/atomicTestNilDeref.flagTestExitCode.pidfd_openandwaitidare not implemented.Sockets
setsockoptandgetsockoptare not implemented. Go sets options on every socket it opens, so every Go server fails to listen.socketpairare missing.Files and system
/proc/self/exedoes not exist. Go uses it to find its own program, so mostos/exectests fail./dev/null,/dev/zero,/etc/group,/etc/passwdand/etc/hostsare missing.os.Rootuses, are not implemented.lstatreports what a link points to instead of the link itself, and creating a file over a dangling link succeeds where Linux refuses.copy_file_range,pwrite64andtruncateare not implemented, and file timestamps cannot be set.Not a fault of NØNOS
timeTestStopResultpasses when given an hour.Build
Checks
Running it
boot-timeholds one argument per line:/bin/gostd, the package directory,GODEBUG=asyncpreemptoff=1,/bin/gstime, then the test flags. Boot the image and read the--- PASS,--- FAILand--- SKIPlines from the serial log.