Architecture
The test framework is located in theintegration-tests crate and consists of several key components:
Test Discovery
Tests are automatically discovered by scanning thewriting-a-c-compiler-tests/tests/ directory structure:
- Scans chapter directories:
chapter_1/,chapter_2/, etc. - Within each chapter, scans test kind subdirectories:
valid/- Tests that should compile and run successfullyinvalid_parse/- Tests that should fail during lexing or parsinginvalid_tacky/- Tests that should fail during lowering to TACKY IRinvalid_semantics/- Tests that should fail during semantic analysisinvalid_codegen/- Tests that should fail during code generation
- Discovers all
.cfiles within each kind directory - Generates test names in the format:
chapter_{n}::{kind}::{filename}
Test Case Structure
Each test case is represented by theTestCase struct:
Kind enum distinguishes between valid and invalid tests:
Expected Results
Expected test results are loaded fromexpected_results.json in the test suite:
- The program compiles successfully
- The executable returns the expected exit code
- Standard output matches the expected output (if specified)
- Compilation fails at the expected stage
- Diagnostics are emitted appropriately
Integration with libtest-mimic
The test framework uses libtest-mimic to provide a familiar Rust test runner interface. EachTestCase can be converted into a libtest-mimic Trial:
- Standard test filtering and selection
- Parallel test execution
- Familiar output formatting
- Support for ignored tests
Test Execution
Callbacks Trait
Test execution leverages themcc_driver::Callbacks trait to intercept compilation at various stages:
- Detect errors at specific compilation stages
- Verify that invalid tests fail at the expected stage
- Execute compiled binaries and verify output
- Return early with success/failure without completing the full pipeline
Compilation Pipeline
The test framework runs the full MCC compilation pipeline:- Preprocessing - Run system C preprocessor via
cc -E -P - Parsing - Tree-sitter-based parsing with error recovery
- Lowering - Transform AST to Three Address Code (TACKY IR)
- Codegen - Generate target-agnostic assembly IR
- Rendering - Render assembly IR to text with OS-specific conventions
- Assembling - Invoke system compiler to create executable
- Execution - Run binary and verify output (for valid tests)
Test Configuration
The main test binary is configured via constants:Ignored Tests
Tests can be marked as ignored for structural differences between MCC and the book:Tests should only be ignored when MCC’s design structurally differs from the book (e.g., reporting an error at type-check instead of parse). Do NOT ignore tests for unimplemented features—leave them failing until the feature is implemented.
Test Database
Each test creates a fresh Salsa database:- Test isolation (no shared state between tests)
- Clean incremental compilation cache per test
- Predictable test behavior