Write -betterC (@betterC) tests
@betterC marks a test as runnable without druntime. Such tests run normally under dub test, and --better-c additionally extracts them into a standalone program compiled with -betterC and executes it:
dub test :base -- --better-c > text.readers.readInteger.advancesOnSuccess [libs/base/src/sparkles/base/text/readers.d:122]
...
17 @betterC tests passedA failing assert aborts with the original file:line (the generated code carries #line directives):
betterc_tests: libs/base/src/.../readers.d:124: Assertion `r.hasValue' failed.Wiring in the tested module
Import the attribute unconditionally (not under version (unittest)): the compiler resolves unittest UDAs even in builds that skip the bodies.
import sparkles.test_runner.attributes : betterC;
@("text.readers.tryConsume")
@betterC @safe pure nothrow @nogc
unittest { /* ... */ }What an extracted test can use
The generated program imports the test's module and re-emits the test body as a named function, so the body can only reference:
- the module's public symbols (module-scope
privateimports are invisible — add body-local imports for anything else); - the test's own module, which the runner compiles in by default (
-i=<module>), so ordinary functions work and not only templates; - non-template functions of other modules explicitly compiled in with
--include-import=<pattern>(maps to the compiler's-i=<pattern>), e.g. a sibling module or--include-import=std.ascii.
Every module compiled in must be betterC-codegen-clean: no GC allocations, exceptions, or TypeInfo in any of its non-template functions. That is why only the test's own module is included automatically and nothing beyond it — one blanket "include everything" would drag in the first transitively imported module that touches std.uni and break the build.
Tests in a module that is not betterC-clean
A module can be far from -betterC-compatible and still host a @betterC test, as long as that test needs nothing from it. Say so on the attribute:
@("extract.braceCounting.betterC")
@betterC(selfContained: true) @safe pure nothrow @nogc
unittest
{
int depth;
foreach (c; "{ { } }")
depth += c == '{' ? 1 : c == '}' ? -1 : 0;
assert(depth == 0);
}selfContained: true keeps the module out of the generated program entirely, so the body is limited to templates and CTFE-able code — and asserting that self-containment is usually the point of such a test. The runner's own dogfooding tests work exactly this way, since their modules import Phobos.
--no-auto-include applies the same strictness to a whole run, which is a quick way to check whether a test really is self-contained.
Toolchain
--compiler=<dc> (or $DC) picks the compiler; ldc2 then dmd from $PATH is the default. Import paths are derived from the discovered tests' module/file pairs plus a best-effort dub describe; add unusual roots with -I/--import-path. --keep preserves the generated program for inspection.