| name | test |
| description | Run and write tests for the Incan compiler. Use when the user asks to test changes, add tests, run the test suite, verify a feature, or says /test. Guides test selection, provides correct patterns, and runs the right commands. |
Test — Incan Compiler
Step 1: Determine what changed
Identify which compiler stages are affected by the current changes:
| Change area | Stages affected |
|---|
| New syntax or keyword | Parser, Typechecker, Lowering, Emission |
| New semantic rule or validation | Typechecker |
| New or changed codegen output | Lowering, Emission |
| Stdlib addition | Typechecker (loader), Emission, possibly Runtime |
| CLI behavior | Integration tests |
| Formatter change | Property tests, integration tests |
For non-trivial changes, do a quick pattern intake before writing or changing tests:
- Identify the active area and downstream stages the behavior must survive.
- Read 2-3 nearby tests or fixtures that already cover a similar behavior shape.
- Name the source-of-truth boundary, such as the RFC, diagnostics catalog, stdlib registry, ownership policy, CLI reference, or docs contract.
- Pick the narrowest failing test first, then the downstream verification that proves the behavior reaches the right stage.
Step 2: Pick the right tests to write
Decision tree
Did you change the parser?
→ Add a test in crates/incan_syntax/src/parser/tests.rs
Did you change the typechecker?
→ Add a test in src/frontend/typechecker/tests.rs
Did you change lowering or emission (codegen output)?
→ Add a .incn file in tests/codegen_snapshots/
→ Add a test function in tests/codegen_snapshot_tests.rs
→ Run: INSTA_UPDATE=1 cargo test --test codegen_snapshot_tests
Did you change end-to-end behavior (CLI, build, multi-file)?
→ Add a test in tests/integration_tests.rs
Did you change the formatter?
→ Property tests in tests/property_tests.rs verify idempotency
→ Also add a codegen snapshot if formatting affects output
Did you add a diagnostic?
→ Add a fixture in tests/fixtures/invalid/ that triggers it
→ Add an integration test that asserts the diagnostic message
What to always do
For any pipeline feature (parser through emission), write both:
- A unit test at the stage you changed (parser or typechecker)
- A codegen snapshot test that exercises the feature in expressions, not just declarations
Step 3: Write the tests
Parser test pattern
File: crates/incan_syntax/src/parser/tests.rs
#[test]
fn test_parse_my_feature() -> Result<(), Vec<CompileError>> {
let source = r#"
model Example:
field: str
"#;
let program = parse_str(source)?;
assert_eq!(program.declarations.len(), 1);
Ok(())
}
Helpers available: parse_str(source), parse_str_with_module_path(source, path).
Typechecker test pattern
File: src/frontend/typechecker/tests.rs
#[test]
fn test_my_feature_valid() {
assert_check_ok(r#"
def example() -> str:
return "hello"
"#);
}
#[test]
fn test_my_feature_invalid() {
let result = check_str(r#"
def example() -> int:
return "hello"
"#);
assert!(result.is_err());
let errs = result.unwrap_err();
assert!(errs.iter().any(|e| e.message.contains("expected")));
}
Helpers available: check_str(source), assert_check_ok(source), check_str_with_library_index(source, index).
Important: test functions that do fallible work must return Result and use ?. Never use .unwrap() or .expect().
Codegen snapshot test pattern
- Create
tests/codegen_snapshots/my_feature.incn:
def example() -> str:
return "hello"
- Add to
tests/codegen_snapshot_tests.rs:
#[test]
fn test_my_feature_codegen() {
let source = load_test_file("my_feature");
let rust_code = generate_rust(&source);
insta::assert_snapshot!("my_feature", rust_code);
}
- Generate the snapshot:
INSTA_UPDATE=1 cargo test --test codegen_snapshot_tests -- test_my_feature_codegen
- Review:
cargo insta review or check tests/snapshots/codegen_snapshot_tests__my_feature.snap.
Helpers available: load_test_file(name) (loads from tests/codegen_snapshots/<name>.incn), generate_rust(source), generate_rust_with_widgets_manifest(source) (for library import tests).
Integration test pattern
File: tests/integration_tests.rs
#[test]
fn test_my_feature_compiles() -> Result<(), Vec<String>> {
compile_source(r#"
def main():
let x: str = "hello"
print(x)
"#)
}
Helpers available: compile_source(source), compile_file(path).
Invalid fixture pattern
- Create
tests/fixtures/invalid/my_error_case.incn with code that should fail.
- Add an integration test that asserts the expected diagnostic.
Step 4: Run the tests
During development (targeted)
cargo test --test codegen_snapshot_tests -- test_my_feature
cargo test -p incan --lib typechecker::tests
cargo test -p incan_syntax --lib parser::tests
cargo test --test integration_tests
Before finishing (full suite)
make test
make pre-commit
INSTA_UPDATE=1 cargo test --test codegen_snapshot_tests
Step 5: Verify
After tests pass, check:
Rust compiler error workflow
When the task is to debug a Rust compiler error, collect the full context before changing code:
- exact command that failed;
- full diagnostic text, including error code, notes, help text, and secondary spans;
- active features or build mode, especially default build vs.
rust-metadata;
- relevant function signature and nearby type definitions;
- local precedent files or tests that show the intended pattern.
Classify the root cause before proposing a fix: lifetime/borrow across an await or compiler boundary, missing trait bound, feature-gate/build-mode mismatch, orphan/coherence rule, missing import, or incomplete parser/typechecker/lowering/emission wiring. Prefer fixing the owning boundary over adding local conversion or clone workarounds.