summaryrefslogtreecommitdiffstats
path: root/openpgp-ffi/tests
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2021-12-10 13:02:52 +0100
committerJustus Winter <justus@sequoia-pgp.org>2021-12-10 13:02:52 +0100
commitcf4c70b46040a53bf3992a124ba70b1b1ca852f1 (patch)
treec321c5f549dc2d69519bd68bf540f78e7a294735 /openpgp-ffi/tests
parent34b930595f7e125cbbad64404c79de454c6e62ef (diff)
Remove the ffi crates.
- Remove the general-purpose ffi crates. They will be moved into their own repository. Note that we consider general-purpose ffi crates to be a dead end: exposing Sequoia's interface requires a large number of types and functions, and using the interface from C turned out to be verbose and error-prone. Instead, we prefer to write point solutions in Rust that implement exactly the functionality the downstream consumer needs, then expose this via ffi bindings. - See https://gitlab.com/sequoia-pgp/sequoia-ffi.
Diffstat (limited to 'openpgp-ffi/tests')
-rw-r--r--openpgp-ffi/tests/c-tests.rs322
1 files changed, 0 insertions, 322 deletions
diff --git a/openpgp-ffi/tests/c-tests.rs b/openpgp-ffi/tests/c-tests.rs
deleted file mode 100644
index ecd68bcf..00000000
--- a/openpgp-ffi/tests/c-tests.rs
+++ /dev/null
@@ -1,322 +0,0 @@
-use anyhow::{Result, Context};
-
-use std::cmp::min;
-use std::env::{self, var_os};
-use std::ffi::OsStr;
-use std::fs;
-use std::io::{self, BufRead, Write};
-use std::path::{Path, PathBuf};
-use std::process::Command;
-use std::str::FromStr;
-
-/// Hooks into Rust's test system to extract, compile and run c tests.
-#[test]
-fn c_doctests() -> Result<()> {
- let stderr = &mut io::stderr();
-
- // The location of this crate's (i.e., the ffi crate's) source.
- let manifest_dir = PathBuf::from(
- var_os("CARGO_MANIFEST_DIR")
- .as_ref()
- .expect("CARGO_MANIFEST_DIR not set"));
-
- let src = manifest_dir.join("src");
- let includes = vec![
- manifest_dir.join("include"),
- ];
-
- // The top-level directory.
- let toplevel = manifest_dir.parent().unwrap();
-
- // The location of the binaries.
- let target_dir = if let Some(dir) = var_os("CARGO_TARGET_DIR") {
- PathBuf::from(dir)
- } else {
- toplevel.join("target")
- };
-
- // The debug target.
- let debug = target_dir.join("debug");
- // Where we put our files.
- let target = target_dir.join("c-tests");
- fs::create_dir_all(&target).unwrap();
-
- // First of all, make sure the shared object is built.
- build_so(toplevel).unwrap();
-
- let mut n = 0;
- let mut passed = 0;
- for_all_rs(&src, |path| {
- for_all_tests(path, |src, lineno, name, lines, run_it| {
- n += 1;
- write!(stderr, " test {} ... ", name)?;
- match build(&includes, &debug, &target, src, lineno, name, lines) {
- Ok(_) if ! run_it => {
- writeln!(stderr, "ok")?;
- passed += 1;
- },
- Ok(exe) => match run(&debug, &exe) {
- Ok(()) => {
- writeln!(stderr, "ok")?;
- passed += 1;
- },
- Err(e) =>
- writeln!(stderr, "{}", e)?,
- },
- Err(e) =>
- writeln!(stderr, "{}", e)?,
- }
- Ok(())
- })
- }).unwrap();
- writeln!(stderr, " test result: {} passed; {} failed", passed, n - passed)?;
- if n != passed {
- panic!("ffi test failures");
- }
- Ok(())
-}
-
-/// Builds the shared object.
-fn build_so(base: &Path) -> Result<()> {
- let st = Command::new("cargo")
- .current_dir(base)
- .arg("build")
- .arg("--quiet")
- .arg("--package")
- .arg("sequoia-openpgp-ffi")
- .status().unwrap();
- if ! st.success() {
- return Err(io::Error::new(io::ErrorKind::Other, "compilation failed")
- .into());
- }
-
- Ok(())
-}
-
-/// Maps the given function `fun` over all Rust files in `src`.
-fn for_all_rs<F>(src: &Path, mut fun: F)
- -> Result<()>
- where F: FnMut(&Path) -> Result<()> {
- let mut dirs = vec![src.to_path_buf()];
-
- while let Some(dir) = dirs.pop() {
- for entry in fs::read_dir(dir).unwrap() {
- let entry = entry?;
- let path = entry.path();
- if path.is_file() && path.extension() == Some(OsStr::new("rs")) {
- fun(&path)?;
- }
- if path.is_dir() {
- dirs.push(path.clone());
- }
- }
- }
- Ok(())
-}
-
-/// If this looks like an exported function, returns its name.
-fn exported_function_name(line: &str) -> Option<&str> {
- if line.starts_with("pub extern \"C\" fn ")
- || line.starts_with("fn pgp_")
- {
- let fn_i = line.find("fn ")?;
- let name_start = fn_i + 3;
- (&line[name_start..]).split(|c| !is_valid_identifier(c)).next()
- } else {
- None
- }
-}
-
-fn is_valid_identifier(c: char) -> bool {
- char::is_alphanumeric(c) || c == '_'
-}
-
-/// Maps the given function `fun` over all tests found in `path`.
-///
-/// XXX: We need to parse the file properly with syn.
-fn for_all_tests<F>(path: &Path, mut fun: F)
- -> Result<()>
- where F: FnMut(&Path, usize, &str, Vec<String>, bool) -> Result<()> {
- let mut lineno = 0;
- let mut test_starts_at = 0;
- let f = fs::File::open(path)?;
- let reader = io::BufReader::new(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.starts_with("/// ```c") || line.starts_with("//! ```c"))
- && ! line.contains("ignore")
- {
- run = ! line.contains("no-run");
- in_test = true;
- test_starts_at = lineno + 1;
- continue;
- }
-
- if let Some(name) = exported_function_name(&line) {
- if !test.is_empty() {
- fun(path, test_starts_at, name, std::mem::take(&mut test),
- run)?;
- test.clear();
- }
- }
- } else {
- if line == "/// ```" {
- in_test = false;
- continue;
- }
-
- if line == "//! ```" && !test.is_empty() {
- let name = format!("{}_{}",
- path.file_stem().unwrap().to_string_lossy(),
- lineno); // XXX: nicer to point to the top
-
- fun(path, test_starts_at, &name, std::mem::take(&mut test),
- run)?;
- test.clear();
- in_test = false;
- continue;
- }
-
- test.push(String::from_str(&line[min(line.len(), 4)..]).unwrap());
- }
- }
- Ok(())
-}
-
-/// Writes and builds the c test iff it is out of date.
-fn build(include_dirs: &[PathBuf], ldpath: &Path, target_dir: &Path,
- src: &Path, lineno: usize, name: &str, mut lines: Vec<String>)
- -> Result<PathBuf> {
- let target = target_dir.join(&format!("{}", name));
- let target_c = target_dir.join(&format!("{}.c", name));
- let meta_rs = fs::metadata(&src).expect("rust source must be there");
- let dirty = if let Ok(meta_c) = fs::metadata(&target_c) {
- meta_rs.modified().unwrap().duration_since(meta_c.modified().unwrap())
- .map(|d| d.as_secs() > 1)
- .unwrap_or(false)
- } else {
- true
- };
-
- wrap_with_main(&mut lines, lineno);
-
- if dirty {
- let mut f = fs::File::create(&target_c)?;
- writeln!(f, "#line {} {:?}", lineno, src)?;
- for line in lines {
- writeln!(f, "{}", line)?
- }
- drop(f);
-
- // Change the modification time of the c source to match the
- // rust source.
- use filetime::FileTime;
- let mtime = FileTime::from_last_modification_time(&meta_rs);
- filetime::set_file_times(target_c, mtime, mtime).unwrap();
- }
-
- let includes =
- include_dirs.iter().map(|dir| format!("-I{:?}", dir))
- .collect::<Vec<String>>().join(" ");
- let st = Command::new("make")
- .env("CFLAGS", &format!("-Wall -O0 -ggdb {} {}", includes,
- env::var("CFLAGS").unwrap_or_else(|_| "".into())))
- .env("LDFLAGS", &format!("-L{:?}", ldpath))
- .env("LDLIBS", "-lsequoia_openpgp_ffi")
- .arg("-C").arg(&target_dir)
- .arg("--quiet")
- // XXX: We don't track the header files as dependencies for
- // the build. The quick fix is to always rebuild the test,
- // the more involved fix is to compute the maximum mtime of
- // the rust source and all the header files.
- .arg("--always-make")
- .arg(target.file_name().unwrap())
- .status()
- .context("Compiling the C-tests requires Make \
- and a cc-compatible compiler")?;
- if ! st.success() {
- return Err(io::Error::new(io::ErrorKind::Other, "compilation failed")
- .into());
- }
-
- Ok(target)
-}
-
-/// Runs the test case.
-fn run(ldpath: &Path, exe: &Path) -> Result<()> {
- let st =
- if let Ok(valgrind) = env::var("SEQUOIA_CTEST_VALGRIND") {
- Command::new(valgrind)
- .env("LD_LIBRARY_PATH", ldpath)
- .args(&["--error-exitcode=123",
- "--leak-check=yes",
- "--quiet",
- "--",
- exe.to_str().unwrap()])
- .status()?
- } else {
- Command::new(exe)
- .env("LD_LIBRARY_PATH", ldpath)
- .status()?
- };
- if ! st.success() {
- return Err(io::Error::new(io::ErrorKind::Other, "failed").into());
- }
- Ok(())
-}
-
-/// Wraps the code in a main function if none exists.
-fn wrap_with_main(test: &mut Vec<String>, offset: usize) {
- let needs_wrapping = ! has_main(test);
-
- // Replace glibc-style error handling.
- test.iter_mut().for_each(|l| {
- if l == "#include <error.h>" { *l = "".into() }
- });
-
- 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,
- "#define error(S, E, F, ...) do { \\\n\
- fprintf (stderr, (F), __VA_ARGS__); \\\n\
- int s = (S), e = (E); \\\n\
- if (e) { fprintf (stderr, \": %s\", strerror (e)); } \\\n\
- fprintf (stderr, \"\\n\"); \\\n\
- fflush (stderr); \\\n\
- if (s) { exit (s); } \\\n\
- } while (0)".into());
-
- if needs_wrapping {
- test.insert(last_include + 1, "int main() {".into());
- }
- test.insert(last_include + 2, format!("#line {}", last_include + offset
- + if needs_wrapping {1} else {0}));
- if needs_wrapping {
- test.push("}".into());
- }
- test.insert(0, "#include <string.h>".into());
- test.insert(0, "#include <stdlib.h>".into());
- test.insert(0, "#include <stdio.h>".into());
- test.insert(0, "#define _GNU_SOURCE".into());
-}
-
-/// Checks if the code contains a main function.
-fn has_main(test: &mut Vec<String>) -> bool {
- test.iter().any(|line| {
- line.contains("main()") || line.contains("main ()")
- || line.contains("main(int argc, char **argv)")
- || line.contains("main (int argc, char **argv)")
- })
-}