Compiler and runtime tests
Running the suite
Run
python test/run_tests.py --profile baseline --cc native --require-clang
after a normal compiler build. CI runs this on Windows, macOS and
Linux.
The runners work on -j/--jobs things at
once (by default one per logical processor, up to 32), on threads that
mostly wait for the compiler, the C compiler and the programs they
start. The log comes out in the same order whatever the number of jobs:
each piece of work prints into a buffer of its own, which shows once
everything before it has, so two logs diff cleanly. -j1
runs everything in order on one thread. Everything one fixture runs
happens one run after another, since a program may write a file
(stdlib_os.goose does) that another run of it would see.
The Goose-in-Goose chains and the samples' runner
(samples/run_samples.py, a process of its own with as many
jobs) start first: the -O0 bootstrap chain is the longest piece of work,
and the suite takes about as long as it does. --gpu-jobs N
limits how many gfx programs run at once within each runner, should a
GPU driver not take many headless devices at a time; by default nothing
limits them.
The log comes in sections (parse, roundtrip and typecheck; generated
C; JIT runs; errors_tc/; samples; ...), each ending in a
line counting its checks,
== typecheck errors (...): 1668 ok. What a section prints
besides its ok lines shows in place, under the section's title: a
failure with what led to it, and the skips. -v prints a
line for every check that passed as well. Under GitHub Actions those
lines are kept in a collapsed group per section, closed before anything
else prints. After the log, the FAIL lines repeat with the titles of
their sections, as GitHub annotations there.
The compiler runs per fixture are as few as the checks allow. One
run, goose --roundtrip --check, parses, checks that the
dump parses again to the same dump, and typechecks (with
--bce-test where the fixture has annotations,
--parse instead of --check for a
parse-only one, and --dump-file writing the
dump for a dump-runtime one). Every run that gets past
resolution prints parsed ok, which tells a parse failure
from a typecheck failure in one --check of an
errors_tc/ fixture. Where a level both builds the C and
runs the program through TinyCC, one
goose -O<n> --jit -o <file> does both: it
writes the C, then runs the program in-process.
Most of those runs, and those of the errors/ and
errors_tc/ fixtures, share a compiler process with others:
goose --multi-test <flags> a.goose b.goose ...
compiles each file in turn as a run on it alone would, and ends each
file's part of stdout and of stderr with a line
==== goose --multi-test: exit <code> <file>,
from which the runner hands every fixture exactly the exit code and
output a run of its own would have given. A compiler process starting
costs about as much as checking a small fixture, so a batch of 20 checks
in a tenth of the time. A batch that crashes or aborts gets through the
files before the one it was on; that file and the rest run one to a
process, so a crash is reported as that fixture's. Runs writing a dump
(dump-runtime), with runtime-define defines,
or running a program (TinyCC) stay one to a process. The C of the
fixtures not linking a native layer builds a batch at a time as well, at
each level and for the dump and GS_DEBUG programs: one C
compiler run compiles each program's unit with its main
renamed, beside a main that runs the program its first
argument names, and links them once against the runtime object
(CC.compile_programs in scripts/toolchain.py).
Every run of a program is still a process of its own. Where a batch does
not build, each program in it builds on its own and shows why. A C
compiler run's start and link cost most of its time on these programs: a
batch of 20 builds in a fifth of the time. --batch N sets
how many files go to one compiler process and how many programs to one
executable (by default a number that leaves every job some batches);
--batch 1 gives each a process and an executable of its
own.
Fixture layout
Test fixtures are grouped by category; run_tests.py
stays at the root of test/:
Path under test/ |
Coverage |
|---|---|
syntax/ |
Lexer, parser, dump grouping, imports and namespace resolution.
Imported helpers live in syntax/ns/ and
syntax/sub/. raw_strings_crlf.goose is the one
file checked out with CRLF line breaks
(.gitattributes). |
typing/ |
Types, generics, constants, optional narrowing, dispatch results and recursive return contracts. |
lifetimes/ |
Reference roots, byte views, borrowed contents and shrinking while references are live. |
codegen/ |
C names, evaluation order, representations, frame/stack layout and aliasing. |
optimizer/ |
Inlining, tail recursion and bounds-check elimination. |
runtime/ |
Runtime diagnostics, reusable slot and slice pool operations and array size checks. |
storage/ |
Relative references, pools and serialization. |
threads/ |
Workers, queues, shared globals, contained workers' aborts and the
native runtime lifecycle test. Imported helpers live in
threads/spawn/. |
stdlib/ |
Standard-library modules. |
goose_in_goose/ |
A Goose-written compiler used as one multi-file bootstrap regression: build and run three generations, check a C fixed point and repeated self-checks of the stage-2 compiler, and compare JIT/native self-compilation. |
gfx/ |
The gfx graphics module: headless rendering, textures,
compute, frames and input, a runtime misuse, shaders from files and from
the program; shader and threading rejections (fixtures with
// error: markers). The programs hold their shaders; the
shader files beside them are for the file form of
embed_shader and --compile-shader.
gfx/window/ is the windowed showcase, not part of the
suite. |
physics/ |
The physics module: worlds, bodies, every kind of shape
and geometry, all joint kinds, queries, events, recording and replay, a
runtime misuse, and the threading rejection. |
ui/ |
The ui module: fonts, windows and layout, widgets,
popups and menus, text editing, style, drawing and the input queries,
charts, rendering and input through headless gfx, whether the keys and
the mouse are the ui's, a runtime misuse, the layer's misuse messages,
and the threading rejection. |
errors/, errors_tc/ |
Expected parser/resolver and semantic rejections. |
errors_tc/recovery/ |
Programs with several errors, which the parser, resolution and the
checker go on past (docs/design/language_server.md §5 D):
each // error: marker is part of the message of the error
in its position, in the order the compiler reports them, with no error
besides, in text and under --diagnostics json.
imported/ holds a module imports.goose
imports, which is no fixture of its own. |
lsp/ |
The language server's fixtures, driven over the protocol by
lsp_protocol.py rather than compiled as programs:
basics.goose, an entry file whose // lsp:
lines state the queries and their answers, helper.goose,
which it imports, recovery.goose, a program with an error
in each of two functions, whose // lsp: error lines say
where they are published and whose queries answer from what was checked,
and broken_decls.goose, whose struct, enum and functions
have errors inside their declarations and whose import finds nothing.,
and exemplar.goose, generic functions nothing calls, whose
queries answer from their exemplar checks
(docs/design/language_server.md §11), and
roots_lib.goose, a module with an error that
roots_app.goose and roots_app2.goose import,
opened and closed beside them as roots come and go, and
source_index.goose, globals whose initializers leave
defaults out and expand a const for, a generic function
matching over a const for of arms, and calls with commas in
literals and comments, and two_a.goose and
two_b.goose, two entry files importing
two_shared.goose, the second alone instantiating its
generic function. |
expected/ |
Shared output and runtime-diagnostic expectations. |
run_tests.py |
The Python test runner. |
compiler_roots.cpp |
Root-set join laws, feedback on weakening, order-independent equality and the deliberate comparison projections; compiled and run in both native profiles. |
compiler_containers.cpp |
The compiler's small_vector and flat_map
against std::vector and std::unordered_map
under random operations, across in-place/heap storage, growth and
erasure, and flat_map's reuse of an erased key's value
cell; compiled and run in both native profiles. |
resource_paths.py |
Unicode source and C output paths, JIT entry-source resource roots, native executable roots with separate and standalone runtimes, relocation, and unchanged working-directory file access. Called by the main runner. |
extern_types.py |
One imported extern interface and C adapter with unused and partially used functions: slice, reference-pointee and return types are available to the header at O0/O2 through native C and TinyCC, with no implementation needed for an unused extern. Called by the main runner. |
runtime_cache.py, runtime_cache_cc.c |
Concurrent first builds share one completed runtime object; a failed compile cannot poison the cache, retry and reuse work, and dry runs leave the cache untouched. A controlled C compiler exercises both driver flag styles in debug and release configurations. Called by the main runner. |
native_cache.py |
Concurrent first builds of programs using one package with C compile
that C once: the others wait on the lock of the package's build
directory and link the objects it made, which the controlled C compiler
of runtime_cache_cc.c checks are complete; a warm build
compiles nothing. Both driver flag styles. Called by the main
runner. |
runtime_io.py |
A program built by goose build, and run by
goose run where the JIT runs, reads piped stdin:
read_stdin gets every byte, a \r\n and a
Ctrl-Z among them, which Windows' text mode would translate and stop at,
and read_line into a builder already ending in a
\r keeps that \r. On POSIX,
write_file_atomic over a file keeps its permissions. Called
by the main runner. |
commit_limit.py |
Windows only: a program run in a job object that lets its processes commit 100 MB grows a data stack past that, and is told it is out of memory committing a data stack, not that the stack overran its reservation. Called by the main runner. |
Positive fixtures are discovered one level below test/;
nested import helpers run through their entry programs.
goose_in_goose/ is handled separately as one bootstrap
program, rather than discovering its modules as standalone fixtures.
Keep fixture stems unique across categories, since expectations and
generated build artifacts use those names. The runner rejects duplicate
names.
Configurations and backends
| Configuration | Purpose |
|---|---|
Debug Goose compiler; Goose -O0 / native C
-O0 versus Goose -O2 / native C
-O2 |
Compare actual unoptimized and optimized executables, including native optimizer effects; check output against the existing fixtures. |
Goose -O2, native C -O2,
GS_DEBUG=1, selected fixtures |
Exercise checked arithmetic/conversions and critical runtime paths
with optimization enabled. Selection is codegen_exec.goose
plus files with runtime-debug on their first line. |
Additional Clang C build of codegen_exec.goose, native
-O1, release/debug runtime |
Keep a second C-front-end check on the central codegen coverage file without repeating the whole suite for every backend. |
Goose -O0 and -O2 through the in-process
TinyCC backend, plus GS_DEBUG=1 on the same selected
fixtures, over every test and every sample |
Check the generated C against a third, very different C implementation, and that a program means the same whichever backend builds it. |
| One Linux Clang ASan/UBSan job | Instrument the C++ compiler, generated C, selected debug-runtime cases, all samples and the direct runtime lifecycle test. |
| Goose-in-Goose bootstrap, both profiles | Build stage 1 with the real compiler, stage 2 with stage 1, and stage 3 with stage 2. Build all three executables, compare stage-2/stage-3 C, and run two self-checks in stage 2, which the fixed point makes the same compiler as stage 3, while stage 3 is emitted and built. Baseline requires equal self-compiled C across host/native O0 and O2 and JIT/native execution; sanitize instruments every native stage. |
Run just the bootstrap with
python test/run_tests.py --goose-in-goose-only --exe <goose> --cc native.
The usual --profile, --no-jit, and
--nocgen options apply. With no native C compiler, it still
checks the source and, when available, self-compiles through the JIT,
but reports the native chain skipped. Sources and the exact stage
contract are in goose_in_goose/README.md;
artifacts and logs go in
build/gen/<profile>/goose_in_goose/O<level>/.
The sanitizer job builds Goose with
-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all,
then runs
python test/run_tests.py --exe <instrumented-goose> --profile sanitize --cc clang.
Generated C uses native -O1 and disables only UBSan's
alignment check because the current C backend deliberately performs
unaligned packed accesses. The C++ compiler retains alignment checking.
ASan observes C allocations, but does not know logical object boundaries
inside Goose's custom virtual-memory arenas.
Generated C is built as goose -o writes it, against the
separate runtime (docs/implementation.md §7):
GooseRuntime (scripts/toolchain.py) has the
compiler under test write the runtime's C (--emit-runtime)
once per run, compiles it once per toolchain configuration -- compiler,
optimization level, defines and extra flags, so the sanitize profile's
runtime is instrumented as well -- into build/gen/runtime/
(the samples' into their own directory), and
CC.compile(..., runtime=...) links that object with each
program built that way. Stages 2 and 3 of the bootstrap hold their own
runtime and link none. The single-unit form, which
--standalone writes, is what every TinyCC run compiles; the
benchmarks build that form too.
The direct test/threads/runtime_threads_lifecycle.c
regression checks allocations, mappings and Windows handles across
worker churn, including unjoined workers, concurrent/repeated waits,
children outliving their parents and contained workers that abort. It
runs in both profiles. The normal suite also checks user-visible worker
error diagnostics.
The gfx/ tests exercise the gfx package (https://github.com/aardappel/goose_gfx,
its docs/gfx.md). They always parse, typecheck and generate
C. When the compiler includes the gfx layer (configured with the
package, GOOSE_BUILTIN_LIBS), the tests also link against
the libraries listed by goose --gfx-link and run at -O0 and
-O2 and through TinyCC. They render headless into textures and read back
only pixel-aligned results, so their output is the same on every backend
and GPU; a machine with no GPU device (the program prints
gfx: no GPU device) reports them skipped, as does a
compiler without the layer. Linux CI runs them on Mesa's lavapipe. The
runners set GOOSE_GFX_HEADLESS=1, so no test or sample
opens a window. The hidden --compile-shader flag is probed
on gfx/probe.frag.
The physics/ tests exercise the physics package (https://github.com/aardappel/goose_physics)
in the same way. They always generate C. When the compiler was
configured with the package (GOOSE_BUILTIN_LIBS),
goose build links them and they run at -O0 and -O2 and
through TinyCC. Box3D is deterministic across platforms and worker
counts, so they print physics results, rounded, and compare them
exactly. Between them they call every function of the layer.
The ui/ tests exercise the ui module of the gfx package
(its docs/ui.md) in the same way, linking
goose --ui-link and, for the one that renders through gfx,
goose --gfx-link too: a test needs the layers of its own
category and of every module it imports, and is skipped where one is
missing. They drive a context with input of their own and print what
comes back and what was drawn; ui_render draws through
headless gfx and reads back pixels only where the ui drew solid color.
Between them they call every function of the layer.
Each package's own test/api_check.py compares the
functions, structs, and constants in its modules with their C headers.
This catches interface mismatches that can compile successfully on both
sides. It also rejects a struct the layer passes by value that TinyCC
would pass differently from the C compilers on System V x86-64: one of
at most 16 bytes with a misaligned field, or with floats alone in one
eightbyte and integers in the other.
test/gfx/window/run_window_test.py is run by hand, on a
machine with a display and a GPU: it runs the showcase
gfx_showcase.goose with a window in-process and built by
every C toolchain found, on each GPU driver of the platform, checks what
the program reports about its own readbacks, and decodes the screenshots
it saves (under build/gfx-window/) to check them for detail
and compare them across runs.
The TinyCC runs are skipped under the sanitizers, where the program
runs uninstrumented inside an instrumented compiler and still holds its
runtime allocations when that compiler exits. A test the backend cannot
run yet is reported as a skip, not a failure: either because the
compiler refuses the program outright
(JIT mode does not support) or because the test's first
line says no-jit. Prefer portable assertions to skipping a
backend; the math tests allow small rounding differences in
transcendental results. See docs/design/jit_backend.md for
the backend's remaining limitations and possible solutions.
Expected output and diagnostics
An explicitly requested compiler must exist; CI does not silently
skip it. Every runnable fixture requires an
expected/<name>.out, including an empty file when the
program should be silent. Expected aborts additionally require
.aborts and a nonempty .stderr containing the
expected runtime diagnostic substrings. Sanitizer reports fail even when
a program was expected to abort. Resource-bound fixtures can set a limit
in their first line, such as
// runtime-define: GS_MAX_STACKS=1; the runner passes it as
a -D to every compile, where the compiler checks
GS_MAX_STACKS against the program's static stack count and
the generated C takes the rest.
append_destination_storage.goose uses this to detect
temporary result stacks that output-only comparisons would miss,
cycle_scratch_locals.goose to show that a recursion's
scratch locals take the same few stacks at every level, and
discarded_resizable_results.goose shrinks each stack's
reservation (GS_STACK_RESERVE) so that its loops overflow
one unless every discarded result is released, as
guard_flat_storage.goose does for what a guard's condition,
its else and the rest of its block take, the rest emitted without braces
of its own. cycle_static_stacks.goose runs the shapes a
call into a recursive cycle allows (§7.8), the
errors_tc/cycle_temporary_*,
cycle_construction_* and cycle_result_*
fixtures being the rejected ones, and codegen fails with an internal
error should any program hold a data stack across such a call.
thread_cap.goose shrinks the address space budget
(GS_STACK_BUDGET) so that hardware_threads()
reports its floor of one wherever it runs, and
stack_stats.goose has its program report its stack use
(GS_STACK_STATS), checked through
expected/stack_stats.stderr. The runner also checks the
--stacks report of thread_cap.goose and
cycle_scratch_locals.goose for its shape, not its counts,
and that of thread_spawn_discarded.goose, whose worker
holds no regions, for the thread cap that leaves: none. A fixture
expecting a debug-only abort sets GS_DEBUG=1 the same way,
which makes every run of it a debug build:
cast_abort_location.goose and
overflow_abort_location.goose check the location a failing
check reports, and thread_contained.goose that the debug
checks end a contained worker alone.
Parser/resolver errors live in test/errors/; semantic
errors live in test/errors_tc/ and must get past parsing
and resolution (parsed ok) before they fail. Each source
declares one or more // error: <diagnostic substring>
lines. The compiler must exit with 1, and all markers must occur in
diagnostic headers or in const for expansion context lines,
excluding echoed source. The compiler goes on past the errors it can
recover from, so a fixture may print more errors than it declares;
test/errors_tc/recovery/ holds the fixtures that declare
every one of theirs, in order (the table above). Use the specific
rejection reason and relevant types/roots; omit source paths, line
numbers and specification section numbers. These inline assertions
replace the old compiler-error .stderr files.
A fixture declares the warnings its --check prints the
same way, with one // warning: <message substring>
line per warning; a positive fixture that declares none must print none.
Markers and warning lines pair up one to one, each marker part of the
message of a line of its own, so a warning printed twice fails the
fixture as a missing or an undeclared one does (a warning prints once
for its node as the source has it, docs/implementation.md
§3.12). Two &s on one line that warn alike print two
identical lines and take two markers, as r6 in
branch_reference_copies.goose does. The markers' order does
not matter: without line numbers it could not say where a warning is,
and warnings print in the order checking reaches their nodes -- a
callee's body at its first call, a loop's and a construct's once their
checks settle, redundant casts by line after the whole program -- which
a change to the checker may rearrange without changing what a program is
told. A rejection test answers for its warnings only if it declares
some: checking stops at its error, so which warnings print first depends
on how far it got. cycle_local_store.goose, for one, warns
about its recursive call's &local before the error that
call reveals, at the store on the line above.
Additional compiler checks
The runner checks more than exit status and runtime output:
| Check | Contract |
|---|---|
lexer_tokens.goose |
Exact token stream, including keyword classification, decoded literals and longest-match punctuation. |
| Every positive Goose fixture | Successful parse, successful initial dump, identical
dump/reparse/dump, and typechecking with exactly the warnings it
declares, unless its first line contains parse-only.
Parsing also resolves type names; dumping alone does not. |
First-line dump-runtime |
Compile and execute the dumped source against the original output.
control_expression_dump.goose uses this to check grouping
semantics, which a stable dump alone cannot establish. |
Every fixture with // bce:elide or
// bce:keep |
Run -O1 --check --bce-test, including expected-abort
regressions. Native/JIT O0 and O2 runs independently check
behavior. |
gfx_err_shader_syntax.goose,
gfx_err_shader_part.goose |
Besides their markers, the error is reported at the program's line holding the offending GLSL. |
Generated call_chain.goose,
call_chain_too_deep.goose
(build/gen/<profile>/) |
A compile-time call path through 2000 distinct functions checks at
-O0, and runs through TinyCC; at -O2, where
the inliner may fold its single-use functions into one body only 64 C
blocks deep at a time, it builds with the native C compiler and runs
(docs/implementation.md §4). One through 6000, each calling
the next from 32 blocks deep, is rejected as too deep for the compiler's
stack instead of overflowing it (§3.1). |
Generated guard_runs.goose
(build/gen/<profile>/) |
300 guards in a row, in a function's body and in a loop's, build
with the native C compiler and run at -O0 and
-O2: the rest of the block after a guard whose else leaves
opens no C block (docs/implementation.md §6.2), where MSVC
stops at 128 levels and clang at 256. A chain of 200 single-use
functions, each calling the next behind four guards, folds at
-O2 into bodies 64 blocks deep, which would nest past both
limits if the inliner's count left out blocks codegen opens (§4). |
loop_shapes.goose at O1 |
Read the generated C: every shape_ function has the
restated loop it is named for, marked by codegen's
/* loop shape: ... */ comment, and no keep_
function has one (docs/implementation.md §6.12). Its runs
check that each restated loop computes what a reference loop does. |
member_parameters.goose at O1 |
Read the generated C: walk, which takes two slices and
a T[>..]&, has each as two scalar C parameters (data
pointer and count, header pointer and stack; spec C.3,
ParamFields). Its runs pass slices, builders and pool
references through calls, a recursion, a case-function dispatch, nested
functions' free variables, a simd function, a
format overload and an exported wrapper. |
member_stores.goose at O1 |
Read the generated C: store_links stores to fields
reached through references that cannot be rebound, with no index on the
way, as C member stores rather than through a pointer taken first
(StableDest); its runs check the stores, and that a right
side rebinding the reference on the way still stores where the
destination named before it ran (evaluation_order.goose has
a right side writing the destination itself). |
optimize.goose at O0/O1/O2 |
Inspect named tail-recursion bodies in --specs:
supported integer accumulator/plain recursion becomes loops, a list walk
handing on a node read through its parameter included; modulo,
floating-point reassociation, nonlocal-return frames, returns inside
nested loops and a reference to a local passed down retain self calls.
Mixed operators retain the ineligible call. Leading locals prevent
base-case inlining from consuming these cases first. The
gb_ functions count the copies of their first statement:
guarded base-case inlining copies the body once per self-call site where
the test compares unassigned parameters, UFCS-spelled self-calls
included, and not where it reads a global or a node, does arithmetic, or
reads an assigned parameter. A u64 above i64.max that the optimizer
makes, a fold's and tre_and's accumulator start, dumps as
that unsigned value. |
docs/reference.md |
Every ```goose block of the language reference compiles
(as written, or wrapped in a main), a block marked
// error fails, none warns, and where the JIT runs, each
complete program prints the block shown after it.
scripts/check_doc_examples.py does the work in a process of
its own, as the samples' runner does, writing the blocks under
build/gen/<profile>/doc_examples/; its docstring has
the markers a document uses for a block that is only a fragment or is
compiled without being run. Run it by hand on any document:
python scripts/check_doc_examples.py docs/reference.md --run -v. |
| doc_comments.py | goose --dump-docs
prints the documentation comments of a program's declarations, fields
and variants: the run of // lines directly above each, less
its indentation; a blank line, a block comment or any token ends a run,
a comment after code starts none, and a banner followed by a blank line
belongs to nothing. The program still checks and roundtrips. | |
lsp_protocol.py | goose lsp driven over its
own protocol: --probe, then a client of the runner's
initializes with test/lsp/basics.goose as an entry file and
the VS Code extension's commands, opens it, waits for its diagnostics
(none) and asks what its // lsp: lines say to ask: hovers
that must contain a text, definitions that must land at a place or in a
file, or nowhere, document symbols, signature help with its active
parameter, semantic token types at a place, or none there, the number of
references of a name, renames that must make a number of edits (each to
the name's leaf alone) or be refused, inlay hints with their text at a
place and how many a line has (none where a default the check filled in
is written), folding ranges by their lines, selection range chains that
must hold a range and nest, code lenses by line and title (an
instantiation count with as many lines as its data and the command that
lists them, "Run" with the file's URI, "not instantiated" a title
alone), completions that must offer or lack a label
(complete.goose, whose last statement is the
p. an editor has when it asks), incoming and outgoing calls
by the function at their other end with the lines of the calls (none for
a function nothing calls, and no item for a name that is no function),
and a workspace symbol from the imported helper.goose, and
checks the analysis asked the client to refresh its lenses, semantic
tokens and inlay hints. Then a burst of edits into an error and back,
whose diagnostics must follow the last text, with the analysis a change
interrupts cancelled; then a line put above everything and a hover asked
at once at the moved position, which must answer from the text after the
change, not the analysis before it; then recovery.goose
(its document symbols asked at once on opening, which must be its
analysis's rather than none) and broken_decls.goose opened
as roots of their own, with every error published and the queries
answered; then source_index.goose, whose globals'
initializers, which the check rewrites in place, must be indexed as
written, whose const for arms' bindings must resolve, and
whose calls' literals and comments hold commas signature help must not
count; then errors_tc/recovery/inside_braces.goose, whose
errors inside the braces of a struct literal and of a match are
published where they are, and no others; then
exemplar.goose and
errors_tc/recovery/exemplar_unchecked.goose, whose generic
functions nothing calls answer from their exemplars, the second's with
no diagnostic though no binding could make their bodies right (its one
error is main's, as goose check finds); then roots coming
and going, judged by the diagnostics last published for a file once a
request has run the analyses due: roots_lib.goose, a module
with an error, opened alone, then with roots_app.goose
importing it open, then with that closed (when its hover and document
symbols must answer again), then with roots_app.goose and
roots_app2.goose both importing it, the one error published
once, and with the first edited to stop importing it, its error
published at every step; and helper.goose left open as
basics.goose closes and stops being an entry file, whose
hover must answer from a root of its own; and a clean shutdown. Then a
second client with two_a.goose and two_b.goose
as its entry files renames a function of the
two_shared.goose both import, and asks its references and
incoming calls, which must be found in both programs, each place once,
and the callers of a function only the second program's instantiation of
a generic function calls; and that server's shutdown. | |
lsp_sweep.py | Not a section of the runner but a script
beside it: a robustness and latency sweep of goose lsp over
the repository's Goose programs (every sample, the goose_in_goose
bootstrap, every stdlib module, every positive fixture, every
errors/ and errors_tc/ fixture), each opened
as a root of its own: its diagnostics awaited, then hover, definition,
completion and signature help asked at every identifier start and after
every ., with the incoming and outgoing calls of a function
named there, semantic tokens, symbols, folding, inlay hints and code
lenses once, a burst of ten random one-character edits with their
diagnostics awaited, and the text put back, whose diagnostics must be
the first ones again. It fails on a server exit, an error reply, a
request over 2 s or a server that stops answering, and prints the
slowest files and requests; --quick sweeps a handful of
files in a second or two. The baseline is in
docs/design/language_server.md §12. | |
packages_git.py | Besides the fetches,
goose check --offline rejects a package not in the cache at
its import, naming goose fetch, without fetching, and
accepts it once fetched. | | diagnostics_json.py |
goose check --diagnostics json prints an array with one
object per error or warning, whose text is what the text
mode printed on stderr, with the progress lines on stderr instead of
stdout. A lexer error is at its caret's column, one byte wide, and the
errors after a """ string the lexer rejected are at their
own lines; a typecheck error ranges over the node it is at, with its
instantiation chain's entries at the calls they name; a warning has a
range too; an import error covers the whole import; a
checkfree function's kept checks are several errors at
once; an error with no place in the program is its text alone. |
Fixture audit
The fixture audit retained the small lifetime, optional-narrowing, alias-cycle, dispatch and frame-layout regressions. Similar diagnostics do not make them duplicates: they exercise different expression visitors, specialization/cache states, root propagation, or generated layouts. Whole-program globals and call graphs are also part of many regressions, so combining them can change the property under test.
The audit removed three redundant fixtures:
all_tests.goose only re-ran seven standalone suites
(imported-main behavior remains covered by the namespace tests);
spec_examples.goose executed a word scanner already covered
by typecheck.goose and bce.goose, while its
other declarations were unused sketches;
errors_tc/shrink_live_slice.goose duplicated the live-slice
clear rejection in clear_live_slice.goose. Dedicated tests
retain recursive relative structures, dispatch, nonlocal returns and
dictionary execution previously suggested by those unused sketches.
Two overlapping positive fixtures are now covered by their broader suites:
Removed fixture (and its .out) |
Retained coverage |
|---|---|
lifetimes/borrowed_context_store.goose |
lifetimes/store_reached_types.goose saves a slice
through a borrowed context, keeps it in a local binding, stores it in
the context's input table, and reads both the argument and the stored
text. The same suite exercises the other destination forms. |
codegen/resizable_adt_tag.goose |
codegen/adt_adaptation.goose matches both variants of a
resizable ADT through a reference parameter and directly. The resizable
payload has no binder; the fixed payload is bound and read. |
The small return_from_cycle.goose remains beside
return_from_cached_call.goose: the former checks replay
through a recursion back edge without an additional cached-call path,
which could otherwise mask a missing propagation step.
CI uses four jobs to cover these configurations without testing every combination of platform, sanitizer, optimization level, and runtime mode. Benchmarks run separately from CI.
Goose