Testing¶
fastC ships a one-command test harness at scripts/test.sh. The
script wraps cargo test, format checks, doc tests, and an examples
smoke test through a single entry point with colorized output and
clear pass/fail counts.
You should never need to remember which cargo incantation matches
which level of "is my change OK to push?" — pick a mode below and
the harness handles the rest.
Quick start¶
bash scripts/test.sh quick # ~20s — unit + integration only
bash scripts/test.sh ci # mirror what CI runs
bash scripts/test.sh # full — everything
All three modes exit 0 on success, 1 on failure, and 2 if
cargo itself isn't installed. They detect TTY for colorized
output, so piping through tee or to a log file gives clean
plaintext.
Mode matrix¶
| Mode | Time | What runs |
|---|---|---|
quick |
~20s | cargo test -p fastc only |
ci |
~30s | cargo fmt --check + cargo clippy + cargo test --workspace |
full (default) |
~2 min | ci + doc-tests + release build + examples smoke |
When to use each¶
quick— while iterating on a single change. You want the tightest feedback loop, so the harness only touches thefastccrate and skips format / clippy / examples.ci— right before you push. Same checks the GitHub Actions workflow runs, so ifcipasses locally, CI will pass too.full(default) — before opening a PR, or any time you changed the lower pass, runtime, or examples. This also smoke-tests a representative subset ofexamples/*.fcend-to-end throughfastc compile+ccso emit / runtime regressions surface before review.
The harness prints a banner per step, the exact cargo command it
runs, and a per-step duration — so when something fails you can
re-run the offending command directly.
scripts/check.sh — pre-PR alias¶
check.sh is a one-line alias for test.sh ci. It exists so you
can wire a single command into a pre-push hook or muscle memory
without thinking about modes. Same exit codes, same output, same
duration as test.sh ci.
If you'd like it to run automatically before every push, drop this
into .git/hooks/pre-push and make it executable:
scripts/bench.sh — benchmark suite¶
bench.sh wraps benchmarks/run_all.sh and runs the
cross-language benchmark suite (fastC vs. C, Rust, Zig, Go).
Expect ~5 minutes on a modern laptop. It produces a results.csv
and prints a per-benchmark summary table.
This is not part of test.sh — benchmarks are slow and noisy
under CPU contention, so they're a separate script you opt into
when you're measuring perf changes.
See fastc bench for the per-project benchmark
runner exposed through the CLI.
Writing tests¶
Tests live where Cargo expects them — the harness doesn't need
updating when you add a new file, because it delegates to
cargo test which discovers everything automatically.
- Unit tests —
#[cfg(test)] mod testsinsidecrates/fastc/src/<module>.rs. Use these for anything that exercises a single function or a small set of helpers. - Integration tests — a new
.rsfile undercrates/fastc/tests/. Each file is a separate crate, so it only sees the public API offastc. Use these for full-pipeline tests that drivecompile()/check()/parse()end-to-end. - Doc tests —
///triple-slash comments with# Examplesblocks containing runnable Rust. Doc tests run undertest.sh full(and on CI).
If you're testing fastC source code rather than Rust internals, the common pattern is:
use fastc::compile;
#[test]
fn my_feature_works() {
let src = r#"
fn main() -> i32 {
return 42;
}
"#;
let c = compile(src, "my_test.fc").expect("should compile");
assert!(c.contains("return 42"));
}
The integration tests under crates/fastc/tests/ are the best
existing reference — they cover modules, the CLI, source maps,
discharge, supply chain, and more.
Inline test { } blocks¶
fastC 1.0 ships an inline test { } block syntax for assertions
that live alongside the code they exercise:
test {
fn it_adds() -> i32 {
return ((2 + 2) - 4); // 0 = pass
}
fn it_doubles() -> i32 {
return 0;
}
}
fn main() -> i32 {
return 0;
}
How it works:
- Every
fninside atest { }block is implicitly marked@test. - In a normal build, the v1.0 driver strips
@testfns from the AST before lowering — they don't appear in the emitted C, so there's zero runtime cost to leaving them in your source. fastc compile --test(a v1.x follow-up) will gate them in and generate a runnermainthat invokes each test fn and reports pass / fail.
The contextual keyword only hijacks test immediately followed by
an open brace, so a user fn named fn test() continues to parse
exactly as before.
When to use it:
- Quick assertions inside a single source file you're iterating on.
- One-file demos that ship with their own self-tests.
- Not yet a full test runner — for a real test suite, write a
Rust integration test in
crates/fastc/tests/against thecompile()API. The inline block is convenience syntax, not a replacement for the harness.
What CI runs¶
The GitHub Actions workflow at .github/workflows/ci.yml invokes
scripts/test.sh ci on every push and pull request. That means
your local bash scripts/test.sh ci (or bash scripts/check.sh)
gives you the exact same pass/fail signal CI will — no surprises
between your machine and the green checkmark.
If a step is optional in the harness (currently cargo clippy
and doc-tests), CI treats it the same way: a non-zero exit is
flagged but does not fail the build. The summary at the end of
each run shows skipped vs. failed counts separately.
Test count¶
As of v1.0, the workspace has 340+ tests passing across unit,
integration, doc, and end-to-end smoke layers. test.sh quick
runs the bulk of them in ~20 seconds; test.sh full covers the
rest plus the examples smoke test in ~2 minutes.
If you add a substantial feature, please add at least one
integration test under crates/fastc/tests/ that exercises it
through the public compile() / check() API — that way the
harness will catch regressions automatically forever.
Cross-links¶
- CLI:
fastc bench— per-project benchmark runner. - CLI:
fastc check— fast type-check without emitting C. - Why benchmarks — the rationale behind the
numbers in
bench.sh. scripts/README.mdin the source tree — the canonical usage matrix this page mirrors.