#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
use proc_macro2::TokenStream;
use std::borrow::Cow;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{self, Stdio};
use tempfile::TempDir;
mod smoke_test;
pub struct Test {
temp_dir: TempDir,
generated_cc: PathBuf,
}
impl Test {
#[must_use]
pub fn new(cxx_bridge: TokenStream) -> Self {
let prefix = concat!(env!("CARGO_CRATE_NAME"), "-");
let scratch = scratch::path("cxx-test-suite");
let temp_dir = TempDir::with_prefix_in(prefix, scratch).unwrap();
let generated_h = temp_dir.path().join("cxx_bridge.generated.h");
let generated_cc = temp_dir.path().join("cxx_bridge.generated.cc");
let opt = cxx_gen::Opt::default();
let generated = cxx_gen::generate_header_and_cc(cxx_bridge, &opt).unwrap();
fs::write(&generated_h, &generated.header).unwrap();
fs::write(&generated_cc, &generated.implementation).unwrap();
Self {
temp_dir,
generated_cc,
}
}
pub fn write_file(&self, filename: impl AsRef<Path>, contents: &str) {
fs::write(self.temp_dir.path().join(filename), contents).unwrap();
}
#[must_use]
pub fn compile(&self) -> CompilationResult {
let mut build = cc::Build::new();
build
.include(self.temp_dir.path())
.out_dir(self.temp_dir.path())
.cpp(true);
build.std("c++20");
build
.opt_level(3)
.host(target_triple::TARGET)
.target(target_triple::TARGET);
let mut command = build.get_compiler().to_command();
command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(self.temp_dir.path())
.arg("-c")
.arg(&self.generated_cc);
let output = command.spawn().unwrap().wait_with_output().unwrap();
CompilationResult(output)
}
}
pub struct CompilationResult(process::Output);
impl CompilationResult {
fn stdout(&self) -> Cow<str> {
String::from_utf8_lossy(&self.0.stdout)
}
fn stderr(&self) -> Cow<str> {
String::from_utf8_lossy(&self.0.stderr)
}
fn dump_output_and_panic(&self, msg: &str) -> ! {
eprintln!("{}", self.stdout());
eprintln!("{}", self.stderr());
panic!("{msg}");
}
fn error_lines(&self) -> Vec<String> {
assert!(!self.0.status.success());
let stdout = self.stdout();
let stderr = self.stderr();
let all_lines = stdout.lines().chain(stderr.lines());
all_lines
.filter(|line| {
line.contains(": error")
})
.map(str::to_owned)
.collect()
}
pub fn assert_success(&self) {
if !self.0.status.success() {
self.dump_output_and_panic("Compiler reported an error");
}
}
#[must_use]
pub fn expect_single_error(&self) -> String {
let error_lines = self.error_lines();
if error_lines.is_empty() {
self.dump_output_and_panic("No error lines found, despite non-zero exit code?");
}
if error_lines.len() > 1 {
self.dump_output_and_panic("Unexpectedly more than 1 error line was present");
}
let single_error_line = error_lines.into_iter().next().unwrap();
eprintln!("Got single error as expected: {single_error_line}");
single_error_line
}
}