blob: 15cd627cc9bbaf4f34d2fcd82d1fa7549452f0c7 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use assert_cmd::Command;
use proc_macro2::Span;
use quote::quote;
use std::{
borrow::Cow,
fs::File,
io::Write,
path::{Path, PathBuf},
};
use syn::Token;
use tempfile::tempdir;
static INPUT_H: &str = indoc::indoc! {"
inline int DoMath(int a) {
return a * 3;
}
struct First {
First() {}
int foo;
};
struct Second {
Second(const First& a) {}
int bar;
};
struct WithMethods {
First get_first();
Second get_second();
int a;
};
"};
enum Input {
Header(String),
ReproCase(PathBuf),
}
#[test]
fn test_reduce_direct_header() -> Result<(), Box<dyn std::error::Error>> {
do_reduce(|header, _| Ok(Input::Header(header.into())), false)
}
#[test]
fn test_reduce_direct_repro_case() -> Result<(), Box<dyn std::error::Error>> {
do_reduce(
|header, demo_code_dir| {
let config = format!(
"#include \"{header}\" generate_all!() block!(\"First\")
safety!(unsafe_ffi)"
);
let json = serde_json::json!({
"header": INPUT_H,
"config": config
});
let repropath = demo_code_dir.join("repro.json");
let f = File::create(&repropath)?;
serde_json::to_writer(f, &json)?;
Ok(Input::ReproCase(repropath))
},
false,
)
}
#[test]
#[ignore] // takes absolutely ages but you can run using cargo test -- --ignored
fn test_reduce_preprocessed_repro_case() -> Result<(), Box<dyn std::error::Error>> {
do_reduce(
|header, demo_code_dir| {
write_minimal_rs_code(header, demo_code_dir);
let repro = demo_code_dir.join("autocxx-repro.json");
let mut cmd = Command::cargo_bin("autocxx-gen")?;
cmd.arg("--inc")
.arg(demo_code_dir.to_str().unwrap())
.arg(demo_code_dir.join("main.rs"))
.env("AUTOCXX_REPRO_CASE", repro.to_str().unwrap())
.arg("--outdir")
.arg(demo_code_dir.to_str().unwrap())
.arg("--gen-cpp")
.arg("--suppress-system-headers")
.assert()
.success();
Ok(Input::ReproCase(repro))
},
false,
)
}
#[test]
#[ignore] // takes absolutely ages but you can run using cargo test -- --ignored
fn test_reduce_preprocessed() -> Result<(), Box<dyn std::error::Error>> {
do_reduce(
|header, demo_code_dir| {
write_minimal_rs_code(header, demo_code_dir);
let prepro = demo_code_dir.join("autocxx-preprocessed.h");
let mut cmd = Command::cargo_bin("autocxx-gen")?;
cmd.arg("--inc")
.arg(demo_code_dir.to_str().unwrap())
.arg(demo_code_dir.join("main.rs"))
.env("AUTOCXX_PREPROCESS", prepro.to_str().unwrap())
.arg("--outdir")
.arg(demo_code_dir.to_str().unwrap())
.arg("--gen-cpp")
.arg("--suppress-system-headers")
.assert()
.success();
Ok(Input::Header("autocxx-preprocessed.h".into()))
},
false,
)
}
#[test]
#[ignore] // takes absolutely ages but you can run using cargo test -- --ignored
fn test_reduce_preprocessed_include_cxx_h() -> Result<(), Box<dyn std::error::Error>> {
do_reduce(
|header, demo_code_dir| {
write_minimal_rs_code(header, demo_code_dir);
let prepro = demo_code_dir.join("autocxx-preprocessed.h");
let mut cmd = Command::cargo_bin("autocxx-gen")?;
cmd.arg("--inc")
.arg(demo_code_dir.to_str().unwrap())
.arg(demo_code_dir.join("main.rs"))
.env("AUTOCXX_PREPROCESS", prepro.to_str().unwrap())
.arg("--outdir")
.arg(demo_code_dir.to_str().unwrap())
.arg("--gen-cpp")
.arg("--suppress-system-headers")
.assert()
.success();
Ok(Input::Header("autocxx-preprocessed.h".into()))
},
true,
)
}
fn write_minimal_rs_code(header: &str, demo_code_dir: &Path) {
let hexathorpe = Token![#](Span::call_site());
write_to_file(
demo_code_dir,
"main.rs",
quote! {
autocxx::include_cpp! {
#hexathorpe include #header
generate!("WithMethods")
block!("First")
safety!(unsafe_ffi)
}
}
.to_string()
.as_bytes(),
);
}
fn do_reduce<F>(get_repro_case: F, include_cxx_h: bool) -> Result<(), Box<dyn std::error::Error>>
where
F: FnOnce(&str, &Path) -> Result<Input, Box<dyn std::error::Error>>,
{
if creduce_is_broken() {
return Ok(());
}
let tmp_dir = tempdir()?;
let demo_code_dir = tmp_dir.path().join("demo");
std::fs::create_dir(&demo_code_dir).unwrap();
let input_header = if include_cxx_h {
Cow::Owned(format!("#include \"cxx.h\"\n{INPUT_H}"))
} else {
Cow::Borrowed(INPUT_H)
};
write_to_file(&demo_code_dir, "input.h", input_header.as_bytes());
write_to_file(&demo_code_dir, "cxx.h", cxx_gen::HEADER.as_bytes());
let output_path = tmp_dir.path().join("min.h");
let repro_case = get_repro_case("input.h", &demo_code_dir)?;
let mut cmd = Command::cargo_bin("autocxx-reduce")?;
let mut cmd = cmd
.arg("-o")
.arg(output_path.to_str().unwrap())
.arg("-p")
.arg("type marked as blocked")
.arg("-k");
match repro_case {
Input::Header(header_name) => {
cmd = cmd
.arg("file")
.arg("--inc")
.arg(demo_code_dir.to_str().unwrap())
.arg("--header")
.arg(header_name)
.arg("-d")
.arg("generate!(\"WithMethods\")")
.arg("-d")
.arg("block!(\"First\")");
}
Input::ReproCase(repro_case) => {
cmd = cmd.arg("repro").arg("-r").arg(repro_case);
}
}
eprintln!("Running {cmd:?}");
let o = cmd.output()?;
println!("Reduce output: {}", std::str::from_utf8(&o.stdout).unwrap());
println!("Reduce error: {}", std::str::from_utf8(&o.stderr).unwrap());
if !o.status.success() {
panic!("autocxx-reduce returned non-zero result code");
}
let minimized = std::fs::read_to_string(output_path)?;
assert!(minimized.contains("First"));
assert!(!minimized.contains("DoMath"));
Ok(())
}
fn write_to_file(dir: &Path, filename: &str, content: &[u8]) {
let path = dir.join(filename);
let mut f = File::create(path).expect("Unable to create file");
f.write_all(content).expect("Unable to write file");
}
fn creduce_is_broken() -> bool {
// On some machines, creduce immediately segfaults
Command::new("creduce").arg("--version").ok().is_err()
}