Beskid blog · Compiler
The Corelib Test Pass: 300 Tests, Two Weeks, One Green Gate
June 2026. The corelib test matrix was red. Not sometimes red — always red. Two weeks of commits: corelib testing optimizations, further work on corelib shapes, progress on full corelib test pass, fixes to concurrency stack, Generic Assertions. Then the gate went green. Then we had to keep it green.
← All postsThe corelib test matrix was red. Not the kind of red where a flaky test fails once every ten CI runs and someone says “re-run it.” The kind of red where the matrix is a wall of scarlet and nobody has seen it green in weeks. Thirty tests failing. Then fifty. Then a hundred. The corelib was growing faster than the test suite could validate it, and the tests that existed were testing things that had already changed.
This is the unglamorous part of building a standard library. The compiler gets the glory — the type system refactors, the ISLE lowering, the Cranelift integration. The corelib gets the grind. Every data structure needs tests. Every test needs edge cases. Every edge case reveals an assumption in the runtime that isn’t true yet.
The marathon started with three commits that set the tone: 30f4db97 — “fixes to corelib,” the kind of commit message you write when you’ve fixed too many things to list. 59b34f0d — “expression-bodied method parser,” because the corelib uses expression-bodied methods everywhere and the parser didn’t handle them. 025963b1 — “further progress on corelib expansions and test coverage, modularization efforts for beskid cli.” Three commits, three different angles: fixes, parser features, test infrastructure.
Then the daily grind. Not the dramatic grind of a rewrite. The incremental grind of making one more test pass, finding two more that don’t, fixing the runtime assumption that was wrong, re-running the suite, and doing it again. 87960b00 — “Corelib further fixes.” That commit message is almost passive-aggressive in its vagueness. But look at the diff: module resolution for corelib types, import path canonicalization, error message spans that pointed at the wrong line. Nothing you’d put in a changelog. Everything you need to make a standard library work.
9f58509d — “corelib testing optimizations.” The test suite was slow. Not “go get coffee” slow — “go get lunch” slow. Three hundred tests running sequentially, each one compiling a Beskid program from scratch, running it, and checking the output. The optimization commit parallelized the test runner and added a compilation cache. Test suite time dropped from twenty minutes to four. That is not a feature. That is the difference between running the tests before every commit and running them when you remember.
e628d088 — “Further work on corelib shapes.” The corelib’s type hierarchy was drifting. List<T> had a count property but Map<K,V> had a length property. Result<T,E> had isOk() but Option<T> had hasValue(). The shapes commit normalized the API surface: count everywhere, is_ok and is_some everywhere, consistent naming across the entire standard library. Breaking changes, all of them. But better to break the API in week one of the test pass than to ship inconsistency and be stuck with it forever.
dbafae10 — “Progress on full corelib test pass.” This was the midpoint. A hundred and fifty tests passing. A hundred and fifty to go. The commit message is optimistic in a way that only midpoint commits can be: “progress on,” not “completed.” The remaining failures clustered around three areas: the concurrency stack, the I/O primitives, and the generic assertion macros. But to understand why those three, you have to understand the test matrix itself — not just the count, but the shape.
The corelib test matrix was organized into six subsystems, each with its own test file, its own failure mode, and its own relationship to the runtime.
Concurrency (47 tests): spawn, channel, mutex, rwlock, semaphore, condition. These tests were the hardest to make pass and the hardest to keep passing. Every concurrency test is a scheduler test. Every scheduler test is a timing test. A test that passes on a 16-core runner fails on a 2-core runner because the fiber interleaving changes. The concurrency suite ran each test with three scheduler seeds — deterministic, random, and stress — to catch ordering-dependent bugs. The stress seed found mutex.lock()’s three-fiber race condition. The deterministic seed made it reproducible. Together they turned “sometimes fails” into “fails reliably,” which is the difference between a bug and a ghost.
I/O (62 tests): File, Socket, Pipe, Console, Path, Directory. The I/O tests were the platform matrix within the test matrix. A file test that passes on Linux fails on Windows because Windows locks open files by default. A socket test that passes on macOS fails on Linux because of different SO_REUSEADDR semantics. The I/O suite used platform-conditional test fixtures — #[test_only(unix)] and #[test_only(windows)] — to isolate platform-specific behavior without fragmenting the test file. The goal was one test file per I/O primitive, with platform branches inside the tests, not platform branches in the test structure.
Assertions (38 tests): assert_eq, assert_ne, assert_true, assert_false, assert_throws, assert_ok, assert_err. The assertion tests were the canary for the type system refactor in compiler-01. If generic type inference worked, assert_eq(Option.Some(42), Option.Some(42)) compiled and passed. If it didn’t, the test didn’t even reach the assertion — it failed at the type-checking stage. The assertion suite thus tested two things at once: the assertion macros and the type checker. When a6359c04 landed, 32 of 38 assertion tests went green in the same CI run.
Collections (71 tests): List, Map, Set, Queue, Stack, Range, Iterator. The collections tests were the most stable — data structures are pure logic, no platform surface, no scheduler interleaving. But they were also the most numerous, and they exercised the generics system exhaustively. Every collection is generic. Every collection method is a generic function. If generic lowering had a bug, the collections tests would find it — not because collections are buggy, but because they use generics in every possible pattern.
Serialization (34 tests): JSON, binary, and URL-encoding codecs. These tests exercised the trait system, which was still maturing. A serialization test that said “a List<Person> should round-trip through JSON” required the compiler to derive JsonEncode and JsonDecode implementations for Person and List<T>, which required trait resolution to work across module boundaries with generic parameters. When it didn’t, the test output was a type error three screens long.
Runtime boundary (48 tests): syscall wrappers, memory allocation, error propagation, panic handling. These tests didn’t test the corelib. They tested the contract between the corelib and the runtime. Every syscall wrapper test said: “when the OS returns EACCES, the corelib returns Result.Err(FileError.PermissionDenied), not a crash.” These tests were the last to go green because they depended on everything else working first. You can’t test error propagation if the error type doesn’t compile. You can’t test panic handling if the panic service isn’t authorized (see 98fe8aaa in compiler-01 — same binder, different bug).
Three hundred tests, six subsystems, one green gate. The subsystems didn’t pass in order — they passed in waves, each commit pushing one or two tests over the line in three or four different subsystems simultaneously. The concurrency fix that made channel.close() work also fixed a serialization test that used channels in its test harness. The assertion fix that made assert_eq generic also fixed a collections test that used assert_eq with a Map<K,V>. The test matrix was a web, not a ladder.
b9566604 — “Fixes to concurrency stack.” Beskid’s concurrency model is fibers, not threads. The corelib’s spawn, channel, and mutex primitives all run on fibers. Testing them means testing the fiber scheduler, the channel buffer, the mutex fairness guarantees, and the interaction between all three. The concurrency stack fixes touched the runtime’s fiber parking, the channel’s backpressure behavior, and a race condition in mutex.lock() that only manifested when three fibers contended for the same lock in a specific order. These are the bugs that make concurrency testing feel like exorcism.
ee88edf5 — “BSOL Stability Pass.” BSOL is the Beskid Standard Operating Language — the set of conventions, APIs, and behavioral guarantees that the corelib provides. The stability pass audited every public function in the corelib for its stability guarantee: stable, experimental, or internal. Functions that had been accidentally exported were marked internal. Functions that had been stable but had unstable implementations were fixed. This is the kind of work that nobody notices until it isn’t done, at which point a minor release breaks everyone’s code.
39b8751a — “BSOL: Updated normative and informative docs.” The stability pass produced documentation. Normative docs for the guarantees. Informative docs for the rationale. Every function got a stability badge, a since-version, and a deprecation path if applicable. The docs were not optional. They were part of the contract.
fee18fb2 — “Work on unifying the project model.” The corelib lived in one directory. The compiler in another. The runtime in a third. Each had its own build configuration, its own test runner, its own set of assumptions about where files lived. The project model commit unified them: one build system, one test harness, one source of truth for dependency versions. This is not corelib work. This is the work that makes corelib work possible.
a6359c04 — “Generic Assertions.” The assertion macros — assert_eq, assert_ne, assert_true, assert_false — were not generic. They worked on concrete types but failed on generic type parameters. The fix required the macro expander to understand type variables, which required the type checker to be available at macro expansion time, which required threading the type context through the syntax pipeline. One commit. Three subsystems touched.
b336f7e3 — “Further work on runtime and beskid boundary.” The boundary between the corelib and the runtime is where the abstraction leaks. The corelib says File.read(path); the runtime does open(2), read(2), close(2). Every syscall is a place where the runtime can return an error the corelib doesn’t handle. The boundary commit audited every syscall path and added error handling for every error code the OS could return. Not just ENOENT. EACCES, EINTR, EAGAIN, ENOSPC. The corelib now handles them all or documents why it doesn’t.
1204645e — “Progress on runtime stabilization.” The final stretch. The test count climbed: two hundred, two hundred and fifty, two hundred and eighty. Each passing test was a contract signed between the corelib, the compiler, and the runtime. Each failing test was a contract that couldn’t be honored yet.
Then the gate went green. 268f8baa — “Green CI: init beskid_bsol in compiler gates and publish ui-react 0.2.2.” Not a dramatic commit message. No exclamation marks. Just: the gate is green, the BSOL is initialized in the compiler gates, and we published a UI component library while we were at it. Two weeks of commits, three hundred tests, one green gate.
But green is not permanent. The gate went green on a Thursday. By Friday evening, a new commit — a7d3f119, adding Stream<T> to the corelib — had turned six tests red. Not because Stream was buggy, but because the new generic type parameter interacted with the assertion macros in a way nobody had tested. The fix was c8e2ba04 — “assertion macro: handle nested generic type params in Stream.collect” — and the gate was green again by Saturday morning. The lesson: the test pass is not a certificate you earn and frame. It is a fire alarm. It goes off when something burns. Silence is not proof of safety. Silence is proof that nothing is burning right now.
The corelib test pass is not a milestone. Milestones are things you reach and move past. The corelib test pass is a continuous contract. Every new feature adds tests. Every test that fails blocks the merge. The gate stays green or the change doesn’t land. Two weeks got it green. Discipline keeps it green.
Cross-reference Book chapter “Corelib: batteries with opinions.” The corelib is not a minimal standard library. It is an opinionated one. It includes HTTP, JSON, SQLite, and a testing framework. It includes concurrency primitives and I/O abstractions. The opinions are documented. The tests prove the opinions work. The gate enforces both.