Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ctest-test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fn test_ctest() {
// public C typedefs have to manually be specified because they are identical to normal
// structs on the Rust side.
.rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string()))
.alias_is_c_enum(|e| e == "enum_repr_too_small" || e == "enum_wrong_signedness")
.skip_roundtrip(|_| true);
ctest::generate_test(&mut t2gen, "src/t2.rs", "t2gen.rs").unwrap();
}
Expand Down
8 changes: 8 additions & 0 deletions ctest-test/src/t2.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ static void T2a(void) {}

#define T2C 4
#define T2S "a"

enum enum_repr_too_small {
ENUM_REPR_TOO_SMALL_A
};

enum enum_wrong_signedness {
ENUM_WRONG_SIGNEDNESS_A
};
14 changes: 14 additions & 0 deletions ctest-test/src/t2.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(non_camel_case_types)]

use std::ffi::{c_char, c_int};

pub type T2Foo = u32;
Expand Down Expand Up @@ -34,3 +36,15 @@ i! {
extern "C" {
pub fn T2a();
}

#[cfg(target_env = "msvc")]
pub type enum_repr_too_small = i16;
#[cfg(not(target_env = "msvc"))]
pub type enum_repr_too_small = u16;
pub const ENUM_REPR_TOO_SMALL_A: enum_repr_too_small = 0;

#[cfg(target_env = "msvc")]
pub type enum_wrong_signedness = u32;
#[cfg(not(target_env = "msvc"))]
pub type enum_wrong_signedness = i32;
pub const ENUM_WRONG_SIGNEDNESS_A: enum_wrong_signedness = 0;
3 changes: 3 additions & 0 deletions ctest-test/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ fn t2() {
"bad T2Union size",
"bad field type b of T2Union",
"bad field offset b of T2Union",
"bad enum_wrong_signedness signed",
"bad enum_repr_too_small size",
"bad enum_repr_too_small align",
];
let mut errors = errors.iter().cloned().collect::<HashSet<_>>();

Expand Down
42 changes: 42 additions & 0 deletions ctest/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type VolatileItem = Box<dyn Fn(VolatileItemKind) -> bool>;
type ArrayArg = Box<dyn Fn(crate::Fn, Parameter) -> bool>;
/// A function that determines whether to skip a test, taking in the identifier name.
type SkipTest = Box<dyn Fn(&str) -> bool>;
/// A function that determines whether a type alias is a c enum.
type CEnum = Box<dyn Fn(&str) -> bool>;

/// A builder used to generate a test suite.
#[derive(Default)]
Expand All @@ -42,6 +44,7 @@ pub struct TestGenerator {
pub(crate) skips: Vec<Skip>,
pub(crate) verbose_skip: bool,
pub(crate) volatile_items: Vec<VolatileItem>,
pub(crate) c_enums: Vec<CEnum>,
pub(crate) array_arg: Option<ArrayArg>,
pub(crate) skip_private: bool,
pub(crate) skip_roundtrip: Option<SkipTest>,
Expand Down Expand Up @@ -206,6 +209,20 @@ impl TestGenerator {
self
}

/// Indicate that a type alias is actually a C enum.
///
/// # Examples
/// ```no_run
/// use ctest::TestGenerator;
///
/// let mut cfg = TestGenerator::new();
/// cfg.alias_is_c_enum(|e| e == "pid_type");
/// ```
pub fn alias_is_c_enum(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self {
self.c_enums.push(Box::new(f));
self
}

/// Indicate that a struct field should be marked `volatile`.
///
/// # Examples
Expand Down Expand Up @@ -516,6 +533,30 @@ impl TestGenerator {
self
}

/// Configures whether tests for a C enum are generated.
///
/// A C enum consists of a type alias, as well as constants that have the same type. Tests
/// for both the alias as well as the constants are skipped.
///
/// # Examples
///
/// ```no_run
/// use ctest::TestGenerator;
///
/// let mut cfg = TestGenerator::new();
/// cfg.skip_c_enum(|e| e == "pid_type");
/// ```
pub fn skip_c_enum(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self {
self.skips.push(Box::new(move |item| {
if let MapInput::CEnumType(e) = item {
f(e)
} else {
false
}
}));
self
}

/// Add a flag to the C compiler invocation.
///
/// # Examples
Expand Down Expand Up @@ -976,6 +1017,7 @@ impl TestGenerator {
MapInput::UnionField(_, f) => f.ident().to_string(),
MapInput::StructType(ty) => format!("struct {ty}"),
MapInput::UnionType(ty) => format!("union {ty}"),
MapInput::CEnumType(ty) => format!("enum {ty}"),
MapInput::StructFieldType(_, f) => f.ident().to_string(),
MapInput::UnionFieldType(_, f) => f.ident().to_string(),
MapInput::Type(ty) => translate_primitive_type(ty),
Expand Down
1 change: 1 addition & 0 deletions ctest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub(crate) enum MapInput<'a> {
/// This variant is used for renaming the struct type.
StructType(&'a str),
UnionType(&'a str),
CEnumType(&'a str),
StructFieldType(&'a Struct, &'a Field),
UnionFieldType(&'a Union, &'a Field),
}
Expand Down
33 changes: 33 additions & 0 deletions ctest/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,36 @@ impl<'a> TranslateHelper<'a> {
fn filter_ffi_items(&mut self) {
let verbose = self.generator.verbose_skip;

let skipped = self.filtered_ffi_items.aliases.extract_if(.., |alias| {
self.generator
.skips
.iter()
.any(|f| f(&MapInput::CEnumType(alias.ident())))
});

for item in skipped {
if verbose {
eprintln!("Skipping C enum type {}", item.ident());
}
}

let skipped = self
.filtered_ffi_items
.constants
.extract_if(.., |constant| {
self.generator.skips.iter().any(|f| {
f(&MapInput::CEnumType(
&constant.ty.to_token_stream().to_string(),
))
})
});

for item in skipped {
if verbose {
eprintln!("Skipping C enum constant {}", item.ident());
}
}

macro_rules! filter {
($field:ident, $variant:ident, $label:literal) => {{
let skipped = self.filtered_ffi_items.$field.extract_if(.., |item| {
Expand Down Expand Up @@ -647,6 +677,7 @@ impl<'a> TranslateHelper<'a> {

MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"),
MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"),
MapInput::CEnumType(_) => panic!("MapInput::CEnumType is not allowed!"),
MapInput::StructFieldType(_, _) => panic!("MapInput::StructFieldType is not allowed!"),
MapInput::UnionFieldType(_, _) => panic!("MapInput::UnionFieldType is not allowed!"),
MapInput::Type(_) => panic!("MapInput::Type is not allowed!"),
Expand All @@ -664,6 +695,8 @@ impl<'a> TranslateHelper<'a> {
MapInput::StructType(&ty)
} else if self.ffi_items.contains_union(ident) {
MapInput::UnionType(&ty)
} else if self.generator.c_enums.iter().any(|f| f(&ty)) {
MapInput::CEnumType(&ty)
} else {
MapInput::Type(&ty)
};
Expand Down
5 changes: 4 additions & 1 deletion ctest/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ fn test_skip_simple() {

let (mut gen_, out_dir) = default_generator(1, "simple.h").unwrap();
gen_.skip_const(|c| c.ident() == "B" || c.ident() == "A")
.skip_c_enum(|e| e == "Color")
.skip_alias(|a| a.ident() == "Byte")
.skip_struct(|s| s.ident() == "Person")
.skip_union(|u| u.ident() == "Word")
Expand All @@ -108,7 +109,9 @@ fn test_map_simple() {
let library_path = "simple.out.with-renames.a";

let (mut gen_, out_dir) = default_generator(1, "simple.h").unwrap();
gen_.rename_constant(|c| (c.ident() == "B").then(|| "C_B".to_string()));
gen_.rename_constant(|c| (c.ident() == "B").then(|| "C_B".to_string()))
.alias_is_c_enum(|e| e == "Color")
.skip_signededness(|ty| ty == "Color");

check_entrypoint(&mut gen_, out_dir, crate_path, library_path, include_path);
}
Expand Down
7 changes: 7 additions & 0 deletions ctest/tests/input/simple.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,12 @@ union Word
#define A "abc"
#define C_B "bac"

enum Color
{
RED,
BLUE,
GREEN
};

extern void *calloc(size_t num, size_t size);
extern Byte byte;
57 changes: 57 additions & 0 deletions ctest/tests/input/simple.out.with-renames.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,42 @@ char *ctest_const_cstr__B(void) {
return ctest_const_B_val_static;
}

static enum Color ctest_const_RED_val_static = RED;

// Define a function that returns a pointer to the value of the constant to test.
// This will later be called on the Rust side via FFI.
enum Color *ctest_const__RED(void) {
return &ctest_const_RED_val_static;
}

static enum Color ctest_const_BLUE_val_static = BLUE;

// Define a function that returns a pointer to the value of the constant to test.
// This will later be called on the Rust side via FFI.
enum Color *ctest_const__BLUE(void) {
return &ctest_const_BLUE_val_static;
}

static enum Color ctest_const_GREEN_val_static = GREEN;

// Define a function that returns a pointer to the value of the constant to test.
// This will later be called on the Rust side via FFI.
enum Color *ctest_const__GREEN(void) {
return &ctest_const_GREEN_val_static;
}

// Return the size of a type.
uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); }

// Return the alignment of a type.
uint64_t ctest_align_of__Byte(void) { return _Alignof(Byte); }

// Return the size of a type.
uint64_t ctest_size_of__Color(void) { return sizeof(enum Color); }

// Return the alignment of a type.
uint64_t ctest_align_of__Color(void) { return _Alignof(enum Color); }

// Return the size of a type.
uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); }

Expand Down Expand Up @@ -178,6 +208,33 @@ Byte ctest_roundtrip__Byte(
return value;
}

// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
// remains unchanged.
// It checks if the size is the same as well as if the padding bytes are all in the correct place.
enum Color ctest_roundtrip__Color(
enum Color value,
const uint8_t is_padding_byte[sizeof(enum Color)],
uint8_t value_bytes[sizeof(enum Color)]
) {
int size = (int)sizeof(enum Color);
// Mark `p` as volatile so that the C compiler does not optimize away the pattern we create.
// Otherwise the Rust side would not be able to see it.
volatile uint8_t* p = (volatile uint8_t*)&value;
int i = 0;
for (i = 0; i < size; ++i) {
// We skip padding bytes in both Rust and C because writing to it is undefined.
// Instead we just make sure the the placement of the padding bytes remains the same.
if (is_padding_byte[i]) { continue; }
value_bytes[i] = p[i];
// After we check that the pattern remained unchanged from Rust to C, we invert the pattern
// and send it back to Rust to make sure that it remains unchanged from C to Rust.
uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256);
d = d == 0 ? 42: d;
p[i] = d;
}
return value;
}

// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
// remains unchanged.
// It checks if the size is the same as well as if the padding bytes are all in the correct place.
Expand Down
Loading
Loading