Adding a test case

To add a new test case, simply create a new .c file. For example:

  1. void example(unsigned buffer_size, int buffer[]) {
  2. /* your code here */
  3. }

Then create a new .rs file with the following skeleton (does not need to be a buffer, can check return values as well):

  1. #![allow(unused_variables)]
  2. fn main() {
  3. extern crate libc;
  4. use c_file::rust_example;
  5. use self::libc::c_int;
  6. #[link(name = "test")]
  7. extern "C" {
  8. #[no_mangle]
  9. fn example(_: c_uint, _: *mut c_int);
  10. }
  11. // The length can be any value
  12. const BUFFER_SIZE: usize = 1024;
  13. pub fn test_example() {
  14. let mut buffer = [0; BUFFER_SIZE];
  15. let mut rust_buffer = [0; BUFFER_SIZE];
  16. let expected_buffer = [/* this can be used as another measure of correctness */];
  17. unsafe {
  18. example(BUFFER_SIZE as u32, buffer.as_mut_ptr());
  19. rust_example(BUFFER_SIZE as u32, rust_buffer.as_mut_ptr());
  20. }
  21. assert_eq!(buffer, rust_buffer);
  22. assert_eq!(buffer, expected_buffer);
  23. }
  24. }

The C code can do one of two things: modify some sort of buffer or return a value.

To completely skip the translation of a C file, you must add the comment //! skip_translation at the top of the file. That will prevent the case from showing up as red in the console output.

You can also mark a Rust file as unexpected to compile, by adding //! xfail to the top of the file, or just expect an individual test function to fail to run by adding // xfail prior to the function definition.

Adding //! extern_crate_X to the top of a test file will ensure extern crate X; gets added to the main binary driver. Be sure to also add the X crate to the test directory's Cargo.toml.

Similarly, //! feature_X adds #![feature(X)] to the top of the main driver file.

Running the tests

From the project root, run ./scripts/test_translator.py tests to run all of the tests in thetests folder. Here are a couple other handy options:

  1. # run a subset of the tests
  2. $ ./scripts/test_translator.py --only-directories="loops" tests
  3. # show output of failed tests
  4. $ ./scripts/test_translator.py --log ERROR tests
  5. # keep all of the files generated during testing
  6. $ ./scripts/test_translator.py --keep=all tests
  7. # get help with the command line options
  8. $ ./scripts/test_translator.py --help

What happens under the hood

This tests directory contains regression, feature, and unit tests. A test directory goes through the following set of steps:

  • A compile_commands.json file is created for the Clang plugin in c2rust-ast-exporter to recognize its C source input

  • This JSON and the C source file are fed to the c2rust-ast-exporter to produce CBOR data of the Clang type-annotated abstract syntax tree.

  • This CBOR data is fed to the c2rust-transpile to produce a Rust source file supposedly preserving the semantics of the initial C source file.

  • Rust test files (test_xyz.rs) are compiled into a single main wrapper and main test binary and are automatically linked against other Rust and C files thanks to cargo.

  • The executable from the previous step is run one or more times parameterized to a specific test function.