Skip to content

Commit

Permalink
Add C++ support
Browse files Browse the repository at this point in the history
* add a `language` method that accepts a `Lang` enum specifying the language (we default to C)
* use the appropriate file extension for each language (e.g. `.c` for C and `.cpp` for C++)
* use the appropriate `extern "C"` linkage when generating tests for C++
  • Loading branch information
gnzlbg committed Feb 11, 2019
1 parent 75722df commit 413f843
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 15 deletions.
82 changes: 67 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ macro_rules! t {
};
}

/// Programming language
pub enum Lang {
/// The C programming language.
C,
/// The C++ programming language.
CXX,
}

/// A builder used to generate a test suite.
///
/// This builder has a number of configuration options which modify how the
Expand All @@ -59,6 +67,7 @@ macro_rules! t {
pub struct TestGenerator {
headers: Vec<String>,
includes: Vec<PathBuf>,
lang: Lang,
flags: Vec<String>,
target: Option<String>,
out_dir: Option<PathBuf>,
Expand Down Expand Up @@ -109,6 +118,7 @@ impl TestGenerator {
Self {
headers: Vec::new(),
includes: Vec::new(),
lang: Lang::C,
flags: Vec::new(),
target: None,
out_dir: None,
Expand Down Expand Up @@ -183,6 +193,24 @@ impl TestGenerator {
self
}

/// Sets the programming language.
///
/// # Examples
///
/// ```no_run
/// use std::env;
/// use std::path::PathBuf;
///
/// use ctest::{TestGenerator, Lang};
///
/// let mut cfg = TestGenerator::new();
/// cfg.language(Lang::CXX);
/// ```
pub fn language(&mut self, lang: Lang) -> &mut Self {
self.lang = lang;
self
}

/// Add a flag to the C compiler invocation.
///
/// This can be useful for tweaking the warning settings of the underlying
Expand Down Expand Up @@ -654,7 +682,14 @@ impl TestGenerator {

// Compile our C shim to be linked into tests
let mut cfg = cc::Build::new();
cfg.file(&out.with_extension("c"));
if let Lang::CXX = self.lang {
cfg.cpp(true);
}
let ext = match self.lang {
Lang::C => "c",
Lang::CXX => "cpp",
};
cfg.file(&out.with_extension(ext));
if target.contains("msvc") {
cfg.flag("/W3").flag("/Wall").flag("/WX")
// ignored warnings
Expand Down Expand Up @@ -705,7 +740,11 @@ impl TestGenerator {
.clone()
.unwrap_or_else(|| PathBuf::from(env::var_os("OUT_DIR").unwrap()));
let out_file = out_dir.join(out_file);
let c_file = out_file.with_extension("c");
let ext = match self.lang {
Lang::C => "c",
Lang::CXX => "cpp",
};
let c_file = out_file.with_extension(ext);
let rust_out = BufWriter::new(t!(File::create(&out_file)));
let c_out = BufWriter::new(t!(File::create(&c_file)));
let mut sess = ParseSess::new(FilePathMapping::empty());
Expand Down Expand Up @@ -953,6 +992,13 @@ fn default_cfg(target: &str) -> Vec<(String, Option<String>)> {
ret
}

fn linkage(lang: &Lang) -> &'static str {
match lang {
Lang::CXX => "extern \"C\"",
Lang::C => "",
}
}

impl<'a> Generator<'a> {
fn rust2c_test(&self, ty: &str) -> bool {
let rustc_types = [
Expand Down Expand Up @@ -1049,18 +1095,19 @@ impl<'a> Generator<'a> {
t!(writeln!(
self.c,
r#"
uint64_t __test_offset_{ty}_{rust_field}(void) {{
{linkage} uint64_t __test_offset_{ty}_{rust_field}(void) {{
return offsetof({cstructty}, {c_field});
}}
uint64_t __test_fsize_{ty}_{rust_field}(void) {{
{linkage} uint64_t __test_fsize_{ty}_{rust_field}(void) {{
{cstructty}* foo = NULL;
return sizeof(foo->{c_field});
}}
"#,
ty = ty,
cstructty = cty,
rust_field = name,
c_field = cfield
c_field = cfield,
linkage = linkage(&self.opts.lang)
));

t!(writeln!(
Expand Down Expand Up @@ -1093,12 +1140,13 @@ impl<'a> Generator<'a> {
t!(writeln!(
self.c,
r#"
{sig} {{
{linkage} {sig} {{
return &b->{c_field};
}}
"#,
sig = sig,
c_field = cfield
c_field = cfield,
linkage = linkage(&self.opts.lang)
));
t!(writeln!(
self.rust,
Expand Down Expand Up @@ -1135,12 +1183,13 @@ impl<'a> Generator<'a> {
t!(writeln!(
self.c,
r#"
uint64_t __test_size_{ty}(void) {{ return sizeof({cty}); }}
uint64_t __test_align_{ty}(void) {{ return {align_of}({cty}); }}
{linkage} uint64_t __test_size_{ty}(void) {{ return sizeof({cty}); }}
{linkage} uint64_t __test_align_{ty}(void) {{ return {align_of}({cty}); }}
"#,
ty = rust,
cty = c,
align_of = align_of
align_of = align_of,
linkage = linkage(&self.opts.lang)
));
t!(writeln!(
self.rust,
Expand Down Expand Up @@ -1192,12 +1241,13 @@ impl<'a> Generator<'a> {
t!(writeln!(
self.c,
r#"
uint32_t __test_signed_{ty}(void) {{
{linkage} uint32_t __test_signed_{ty}(void) {{
return ((({cty}) -1) < 0);
}}
"#,
ty = rust,
cty = c
cty = c,
linkage = linkage(&self.opts.lang)
));
t!(writeln!(
self.rust,
Expand Down Expand Up @@ -1246,14 +1296,15 @@ impl<'a> Generator<'a> {
t!(writeln!(
self.c,
r#"
static {cty} __test_const_{name}_val = {cname};
{cty}* __test_const_{name}(void) {{
static const {cty} __test_const_{name}_val = {cname};
{linkage} const {cty}* __test_const_{name}(void) {{
return &__test_const_{name}_val;
}}
"#,
name = name,
cname = cname,
cty = cty
cty = cty,
linkage = linkage(&self.opts.lang)
));

if rust_ty == "&str" {
Expand Down Expand Up @@ -1298,6 +1349,7 @@ impl<'a> Generator<'a> {
"#,
ty = rust_ty,
name = name

));
}
self.tests.push(format!("const_{}", name));
Expand Down
8 changes: 8 additions & 0 deletions testcrate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ test = false
[[bin]]
name = "t2"
test = false

[[bin]]
name = "t1_cxx"
test = false

[[bin]]
name = "t2_cxx"
test = false
26 changes: 26 additions & 0 deletions testcrate/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,30 @@ fn main() {
t => t.to_string(),
})
.generate("src/t2.rs", "t2gen.rs");

ctest::TestGenerator::new()
.header("t1.h")
.language(ctest::Lang::CXX)
.include("src")
.fn_cname(|a, b| b.unwrap_or(a).to_string())
.type_name(move |ty, is_struct, is_union| match ty {
"T1Union" => ty.to_string(),
t if is_struct => format!("struct {}", t),
t if is_union => format!("union {}", t),
t => t.to_string(),
})
.generate("src/t1.rs", "t1gen_cxx.rs");
ctest::TestGenerator::new()
.header("t2.h")
.language(ctest::Lang::CXX)
.include("src")
.type_name(move |ty, is_struct, is_union| match ty {
"T2Union" => ty.to_string(),
t if is_struct => format!("struct {}", t),
t if is_union => format!("union {}", t),
t => t.to_string(),
})
.generate("src/t2.rs", "t2gen_cxx.rs");


}
9 changes: 9 additions & 0 deletions testcrate/src/bin/t1_cxx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![cfg(not(test))]
#![allow(bad_style)]

extern crate libc;
extern crate testcrate;
use libc::*;
use testcrate::t1::*;

include!(concat!(env!("OUT_DIR"), "/t1gen_cxx.rs"));
7 changes: 7 additions & 0 deletions testcrate/src/bin/t2_cxx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![cfg(not(test))]
#![allow(bad_style)]

extern crate testcrate;
use testcrate::t2::*;

include!(concat!(env!("OUT_DIR"), "/t2gen_cxx.rs"));
1 change: 1 addition & 0 deletions testcrate/src/t1.cpp
1 change: 1 addition & 0 deletions testcrate/src/t2.cpp
51 changes: 51 additions & 0 deletions testcrate/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ fn t1() {
assert!(!o.contains("bad "), o);
}

#[test]
fn t1_cxx() {
let (o, status) = output(&mut cmd("t1_cxx"));
assert!(status.success(), o);
assert!(!o.contains("bad "), o);
}

#[test]
fn t2() {
let (o, status) = output(&mut cmd("t2"));
Expand Down Expand Up @@ -62,6 +69,50 @@ fn t2() {
}
}

#[test]
fn t2_cxx() {
let (o, status) = output(&mut cmd("t2_cxx"));
assert!(!status.success(), o);
let errors = [
"bad T2Foo signed",
"bad T2TypedefFoo signed",
"bad T2TypedefInt signed",
"bad T2Bar size",
"bad T2Bar align",
"bad T2Bar signed",
"bad T2Baz size",
"bad field offset a of T2Baz",
"bad field type a of T2Baz",
"bad field offset b of T2Baz",
"bad field type b of T2Baz",
"bad T2a function pointer",
"bad T2C value at byte 0",
"bad T2S string",
"bad T2Union size",
"bad field type b of T2Union",
"bad field offset b of T2Union",
];
let mut errors = errors.iter().cloned().collect::<HashSet<_>>();

let mut bad = false;
for line in o.lines().filter(|l| l.starts_with("bad ")) {
let msg = &line[..line.find(":").unwrap()];
if !errors.remove(&msg) {
println!("unknown error: {}", msg);
bad = true;
}
}

for error in errors {
println!("didn't find error: {}", error);
bad = true;
}
if bad {
panic!();
}
}


fn output(cmd: &mut Command) -> (String, ExitStatus) {
let output = cmd.output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
Expand Down

0 comments on commit 413f843

Please sign in to comment.