summaryrefslogtreecommitdiffstats
path: root/ffi/tests
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2018-07-26 14:52:17 +0200
committerJustus Winter <justus@sequoia-pgp.org>2018-07-26 14:59:25 +0200
commit3f6b0c5b303d78e3fa9d6e6206f86f7de93deebb (patch)
tree39636530a7bfbf99b149a56f7c0e2d0df41aa0e7 /ffi/tests
parent6ca5eea8136ac5f1a95f2120f1a6e803b1155026 (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.rs79
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
+}