A reasonable configuration language rcl-lang.org
configuration-language json
at master 98 lines 4.9 kB view raw view rendered
1# Testing 2 3RCL is tested extensively. The two primary means of testing are golden tests 4and fuzzing. 5 6## Golden tests 7 8Golden tests are located in the `golden` directory as files ending in `.test`. 9Every file consists of two sections: an input, then a line 10 11 # output: 12 13and then an expected output. The test runner `golden/run.py` takes these files, 14executes <abbr>RCL</abbr>, and compares the actual output against the expected 15output. The mode in which `run.py` executes <abbr>RCL</abbr> depends on the 16subdirectory that the tests are in. See the docstring in `run.py` for more 17information. 18 19The goal of the golden tests is to cover all relevant branches of the code. For 20example, every error message that <abbr>RCL</abbr> can generate should be 21accompanied by a test that triggers it. It is instructive to inspect the 22coverage report to see what tests are missing, or to discover pieces of dead 23code. The easiest way to generate the report is with Nix: 24 25 nix build .#coverage --out-link result 26 xdg-open result/index.html 27 28Alternatively, you can build with coverage support and run `grcov` manually, 29see `flake.nix` for the exact flags and commands. 30 31## Fuzz tests 32 33The other means of testing are fuzz tests. RCL contains two primary fuzzers 34called `fuzz_source` and `fuzz_smith`. The input to the source-based fuzzer 35are valid <abbr>RCL</abbr> files that contain a little bit of metadata about 36what mode to exercise. Putting everything in one fuzzer enables sharing the 37corpus across the different modes. The input to the smith-based fuzzer are 38small bytecode programs for a _smith_ that synthesizes <abbr>RCL</abbr> programs 39from them. The smith fuzzer is a lot faster than the source-based fuzzer, 40because it does not have to overcome trivial obstacles like having balanced 41parentheses or using the same name in a variable definition and usage site. 42Getting past such obstacles is hard for the source-based fuzzer because it 43requires two separate mutations to work together to make a valid file. The 44downside of the smith-based fuzzer is that it does not explore the full input 45space, in particular regarding parse errors, so that’s why a combination of both 46fuzzers is ideal. 47 48The basic modes of the fuzzer just test that the lexer, parser, and evaluator 49do not crash. They are useful for finding mistakes such as slicing a string 50across a code point binary, or trying to consume tokens past the end of the 51document. However the more interesting fuzzers are the formatter, and the value 52pretty-printer. They verify the following properties: 53 54 * **The formatter is idempotent.** Running `rcl fmt` on an already-formatted 55 file should not change it. This sounds obvious, but the fuzzer caught a few 56 interesting cases related to trailing whitespace and multiline strings. 57 * **The evaluator is idempotent.** RCL can evaluate to json, and is itself a 58 superset of json, so evaluating the same input again should not change it. 59 * **The formatter agrees with the pretty-printer.** When <abbr>RCL</abbr> 60 evaluates to json, it pretty-prints the json document using the value pretty 61 printer. The formatter that formats source files on the other hand, 62 pretty-prints the concrete syntax tree. This check tests that formatting a 63 pretty-printed json value is a no-op. 64 * **The json output can be parsed by Serde.** Even if <abbr>RCL</abbr> can read 65 back a json output, that is no guarantee that a third-party parser considers 66 the output valid, so we also test against the widely used Serde deserializer. 67 * **The toml output can be parsed by Serde.** Same as above, but for toml 68 output. 69 70## Running the fuzzers 71 72To run the fuzzers you need [`cargo-fuzz`][cargo-fuzz] and a nightly toolchain. 73Then start e.g. the smith fuzzer or source-based fuzzer: 74 75 cargo +nightly-2023-11-09 fuzz run fuzz_smith -- -timeout=3 76 cargo +nightly-2023-11-09 fuzz run fuzz_source -- -dict=fuzz/dictionary.txt -timeout=3 77 78Unlike the other fuzzers, the inputs to the smith fuzzer are not human-readable. 79To see what kind of inputs the fuzzer is exploring, you can use the `smithctl` 80program to interpret the smith programs: 81 82 cargo build --manifest-path=fuzz/Cargo.toml --bin smithctl 83 84To print all inputs discovered in the past 5 minutes: 85 86 fd . fuzz/corpus/fuzz_smith --changed-within 5m --exec target/debug/smithctl print 87 88[cargo-fuzz]: https://github.com/rust-fuzz/cargo-fuzz 89 90## Unit tests 91 92There is less of a focus on unit tests. Constructing all the right environment 93and values for even a very basic test quickly becomes unwieldy; even the 94<abbr>AST</abbr> of a few lines of code, when constructed directly in Rust, 95becomes hundreds of lines of code. Rather than constructing the <abbr>AST</abbr> 96by hand, we can just use the parser to construct one. Similarly, for expected 97output values, instead of constructing these in Rust, we can format them, and 98express the entire process as a golden test instead.