diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-07-26 14:52:17 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2018-07-26 14:59:25 +0200 |
commit | 3f6b0c5b303d78e3fa9d6e6206f86f7de93deebb (patch) | |
tree | 39636530a7bfbf99b149a56f7c0e2d0df41aa0e7 /ffi/tests | |
parent | 6ca5eea8136ac5f1a95f2120f1a6e803b1155026 (diff) |
ffi: Improve c doctests.
- Wrap code in a main function if none exists.
- Derive names for tests not bound to a function.
- Honor no-run and ignore.
- Fix all examples that are now tests.
Diffstat (limited to 'ffi/tests')
-rw-r--r-- | ffi/tests/c-tests.rs | 79 |
1 files changed, 74 insertions, 5 deletions
diff --git a/ffi/tests/c-tests.rs b/ffi/tests/c-tests.rs index 1c9b88c2..6d4bc78a 100644 --- a/ffi/tests/c-tests.rs +++ b/ffi/tests/c-tests.rs @@ -1,8 +1,10 @@ extern crate libc; +extern crate nettle; use std::cmp::min; use std::env::var_os; use std::ffi::OsStr; +use std::fmt::Write as FmtWrite; use std::fs; use std::io::{self, BufRead, Write}; use std::os::unix::io::AsRawFd; @@ -10,6 +12,9 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; use std::time; +use std::mem::replace; + +use nettle::hash::{Hash, Sha256}; /// Hooks into Rust's test system to extract, compile and run c tests. #[test] @@ -32,10 +37,14 @@ fn c_doctests() { let mut n = 0; let mut passed = 0; for_all_rs(&src, |path| { - for_all_tests(path, |src, lineno, name, lines| { + for_all_tests(path, |src, lineno, name, lines, run_it| { n += 1; eprint!(" test {} ... ", name); match build(&include, &debug, &target, src, lineno, name, lines) { + Ok(_) if ! run_it => { + eprintln!("ok"); + passed += 1; + }, Ok(exe) => match run(&debug, &exe) { Ok(()) => { eprintln!("ok"); @@ -88,7 +97,7 @@ fn for_all_rs<F>(src: &Path, mut fun: F) /// Maps the given function `fun` over all tests found in `path`. fn for_all_tests<F>(path: &Path, mut fun: F) -> io::Result<()> - where F: FnMut(&Path, u32, &str, &[String]) -> io::Result<()> { + where F: FnMut(&Path, usize, &str, Vec<String>, bool) -> io::Result<()> { let mut lineno = 0; let mut test_starts_at = 0; let f = fs::File::open(path)?; @@ -96,12 +105,16 @@ fn for_all_tests<F>(path: &Path, mut fun: F) let mut in_test = false; let mut test = Vec::new(); + let mut run = false; for line in reader.lines() { let line = line?; lineno += 1; if ! in_test { - if line == "/// ```c" { + if (line.starts_with("/// ```c") || line.starts_with("//! ```c")) + && ! line.contains("ignore") + { + run = ! line.contains("no-run"); in_test = true; test_starts_at = lineno + 1; continue; @@ -110,7 +123,8 @@ fn for_all_tests<F>(path: &Path, mut fun: F) if line.starts_with("pub extern \"system\" fn ") && test.len() > 0 { let name = &line[23..].split_terminator('(') .next().unwrap().to_owned(); - fun(path, test_starts_at, &name, &test)?; + fun(path, test_starts_at, &name, replace(&mut test, Vec::new()), + run)?; test.clear(); } } else { @@ -119,6 +133,27 @@ fn for_all_tests<F>(path: &Path, mut fun: F) continue; } + if line == "//! ```" && test.len() > 0 { + let mut hash = Sha256::default(); + for line in test.iter() { + writeln!(&mut hash as &mut Hash, "{}", line).unwrap(); + } + let mut digest = vec![0; hash.digest_size()]; + hash.digest(&mut digest); + let mut name = String::new(); + write!(&mut name, "{}_", + path.file_stem().unwrap().to_string_lossy()).unwrap(); + for b in digest { + write!(&mut name, "{:02x}", b).unwrap(); + } + + fun(path, test_starts_at, &name, replace(&mut test, Vec::new()), + run)?; + test.clear(); + in_test = false; + continue; + } + test.push(String::from_str(&line[min(line.len(), 4)..]).unwrap()); } } @@ -127,7 +162,7 @@ fn for_all_tests<F>(path: &Path, mut fun: F) /// Writes and builds the c test iff it is out of date. fn build(include_dir: &Path, ldpath: &Path, target_dir: &Path, - src: &Path, lineno: u32, name: &str, lines: &[String]) + src: &Path, lineno: usize, name: &str, mut lines: Vec<String>) -> io::Result<PathBuf> { let target = target_dir.join(&format!("{}", name)); let target_c = target_dir.join(&format!("{}.c", name)); @@ -140,6 +175,8 @@ fn build(include_dir: &Path, ldpath: &Path, target_dir: &Path, true }; + wrap_with_main(&mut lines, lineno); + if dirty { let mut f = fs::File::create(&target_c)?; writeln!(f, "#line {} {:?}", lineno, src)?; @@ -193,3 +230,35 @@ fn run(ldpath: &Path, exe: &Path) -> io::Result<()> { } Ok(()) } + +/// Wraps the code in a main function if none exists. +fn wrap_with_main(test: &mut Vec<String>, offset: usize) { + if has_main(test) { + return; + } + + let mut last_include = 0; + for (n, line) in test.iter().enumerate() { + if line.starts_with("#include") { + last_include = n; + } + } + + test.insert(last_include + 1, "int main() {".into()); + test.insert(last_include + 2, format!("#line {}", last_include + 1 + offset)); + let last = test.len(); + test.insert(last, "}".into()); + test.insert(0, "#define _GNU_SOURCE".into()); +} + +/// Checks if the code contains a main function. +fn has_main(test: &mut Vec<String>) -> bool { + for line in test { + if line.contains("main()") + || line.contains("main(int argc, char **argv)") + { + return true; + } + } + false +} |