--- id: testing title: Testing --- This page describes how bfc is tested. This is primarily useful for contributors, but is also interesting for readers working on their own PL implementations. ## Unit Tests You can run all bfc's unit tests with Cargo. ``` $ cargo test ``` This runs basic unit tests, property-based tests, and LLVM snapshots tests. ### Quickcheck bfc includes property-based tests using the [Rust quickcheck library](https://github.com/BurntSushi/quickcheck). These operate on the intermediate representation used in bfc, called BFIR. These tests verify several properties for optimisations: runtime improvement, idempotence and behavioural equivalence. ### Verifying Improvement bfc optimisation should never increase the number of BFIR instructions. `f(program)` should have fewer (or the same) number of instructions as `program`. ### Verifying Idempotence All bfc optimisation passes are idempotent. Running an optimisation twice should produce the same output as running it once. This property applies to individual peephole optimisations, as well as the combined optimiser. Testing this catches phase ordering issues, as well as optimisations that need to compute a fixpoint. `f(f(program))` should be the same as `f(program)`. ### Verifying Behavioural Equivalence bfc opimisation passes should never change the semantics of a program. `eval(program)` should give the same result as `eval(f(program))`, provided that `eval(program)` terminates within a specific number of steps. bfc checks this by evaluating the code within a sandbox. This same evaluator is also used during compilation, for compile-time evaluation of code! If `program` relies on runtime inputs, the test can verify the program state prior to the first runtime input, or choose an arbitrary input. Some tests also check the cell state after executing `program`, which should be the same as executing `f(program)`. This is not true of all optimisation passes: dead code may modify cells that are unused. ### Finding Interesting Programs BFIR defines 7 different expressions. Randomly generated IR would only have a 1/7 chance of producing a loop, so only a 1/49 chance of producing a nested loop. Nested loops, particularly including multiply loops, tend to expose interesting bugs. BFIR generation in tests overweights these kind of loops, so tests spend more time exercising more complicated loops. ### LLVM Snapshot Tests The file `llvm_tests.rs` tests that certain BF programs produce the expected LLVM IR output. These tests tend to be brittle, especially when upgrading major LLVM versions. They are useful for verifying that unrelated refactoring hasn't changed the compiled output. ## Integration Tests ``` $ ./integration_tests.sh ``` This script compiles real BF programs, runs them, and verifies their output matches the corresponding `.out` file. This is the final step in bfc testing. It catches issues that only occur in larger, real-world BF programs.