Skip to content

Commit a48b315

Browse files
committed
ctest: add suport for c enum
1 parent 2e670a8 commit a48b315

File tree

9 files changed

+361
-4
lines changed

9 files changed

+361
-4
lines changed

ctest-next/src/generator.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type VolatileItem = Box<dyn Fn(VolatileItemKind) -> bool>;
2626
type ArrayArg = Box<dyn Fn(crate::Fn, Parameter) -> bool>;
2727
/// A function that determines whether to skip a test, taking in the identifier name.
2828
type SkipTest = Box<dyn Fn(&str) -> bool>;
29+
/// A function that determines whether a type alias is a c enum.
30+
type CEnum = Box<dyn Fn(&str) -> bool>;
2931

3032
/// A builder used to generate a test suite.
3133
#[derive(Default)]
@@ -42,6 +44,7 @@ pub struct TestGenerator {
4244
pub(crate) skips: Vec<Skip>,
4345
pub(crate) verbose_skip: bool,
4446
pub(crate) volatile_items: Vec<VolatileItem>,
47+
pub(crate) c_enums: Vec<CEnum>,
4548
pub(crate) array_arg: Option<ArrayArg>,
4649
pub(crate) skip_private: bool,
4750
pub(crate) skip_roundtrip: Option<SkipTest>,
@@ -206,6 +209,20 @@ impl TestGenerator {
206209
self
207210
}
208211

212+
/// Indicate that a type alias is actually a C enum.
213+
///
214+
/// # Examples
215+
/// ```no_run
216+
/// use ctest_next::TestGenerator;
217+
///
218+
/// let mut cfg = TestGenerator::new();
219+
/// cfg.c_enum(|e| e == "pid_type");
220+
/// ```
221+
pub fn c_enum(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self {
222+
self.c_enums.push(Box::new(f));
223+
self
224+
}
225+
209226
/// Indicate that a struct field should be marked `volatile`.
210227
///
211228
/// # Examples
@@ -516,6 +533,30 @@ impl TestGenerator {
516533
self
517534
}
518535

536+
/// Configures whether tests for a C enum are generated.
537+
///
538+
/// A C enum consists of a type alias, as well as constants that have the same type. Tests
539+
/// for both the alias as well as the constants are skipped.
540+
///
541+
/// # Examples
542+
///
543+
/// ```no_run
544+
/// use ctest_next::TestGenerator;
545+
///
546+
/// let mut cfg = TestGenerator::new();
547+
/// cfg.skip_c_enum(|e| e == "pid_type");
548+
/// ```
549+
pub fn skip_c_enum(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self {
550+
self.skips.push(Box::new(move |item| {
551+
if let MapInput::CEnumType(e) = item {
552+
f(e)
553+
} else {
554+
false
555+
}
556+
}));
557+
self
558+
}
559+
519560
/// Add a flag to the C compiler invocation.
520561
///
521562
/// # Examples
@@ -976,6 +1017,7 @@ impl TestGenerator {
9761017
MapInput::UnionField(_, f) => f.ident().to_string(),
9771018
MapInput::StructType(ty) => format!("struct {ty}"),
9781019
MapInput::UnionType(ty) => format!("union {ty}"),
1020+
MapInput::CEnumType(ty) => format!("enum {ty}"),
9791021
MapInput::StructFieldType(_, f) => f.ident().to_string(),
9801022
MapInput::UnionFieldType(_, f) => f.ident().to_string(),
9811023
MapInput::Type(ty) => translate_primitive_type(ty),

ctest-next/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub(crate) enum MapInput<'a> {
6666
/// This variant is used for renaming the struct type.
6767
StructType(&'a str),
6868
UnionType(&'a str),
69+
CEnumType(&'a str),
6970
StructFieldType(&'a Struct, &'a Field),
7071
UnionFieldType(&'a Union, &'a Field),
7172
}

ctest-next/src/template.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,36 @@ impl<'a> TranslateHelper<'a> {
597597
fn filter_ffi_items(&mut self) {
598598
let verbose = self.generator.verbose_skip;
599599

600+
let skipped = self.filtered_ffi_items.aliases.extract_if(.., |alias| {
601+
self.generator
602+
.skips
603+
.iter()
604+
.any(|f| f(&MapInput::CEnumType(alias.ident())))
605+
});
606+
607+
for item in skipped {
608+
if verbose {
609+
eprintln!("Skipping C enum type {}", item.ident());
610+
}
611+
}
612+
613+
let skipped = self
614+
.filtered_ffi_items
615+
.constants
616+
.extract_if(.., |constant| {
617+
self.generator.skips.iter().any(|f| {
618+
f(&MapInput::CEnumType(
619+
&constant.ty.to_token_stream().to_string(),
620+
))
621+
})
622+
});
623+
624+
for item in skipped {
625+
if verbose {
626+
eprintln!("Skipping C enum constant {}", item.ident());
627+
}
628+
}
629+
600630
macro_rules! filter {
601631
($field:ident, $variant:ident, $label:literal) => {{
602632
let skipped = self.filtered_ffi_items.$field.extract_if(.., |item| {
@@ -647,6 +677,7 @@ impl<'a> TranslateHelper<'a> {
647677

648678
MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"),
649679
MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"),
680+
MapInput::CEnumType(_) => panic!("MapInput::CEnumType is not allowed!"),
650681
MapInput::StructFieldType(_, _) => panic!("MapInput::StructFieldType is not allowed!"),
651682
MapInput::UnionFieldType(_, _) => panic!("MapInput::UnionFieldType is not allowed!"),
652683
MapInput::Type(_) => panic!("MapInput::Type is not allowed!"),
@@ -664,6 +695,8 @@ impl<'a> TranslateHelper<'a> {
664695
MapInput::StructType(&ty)
665696
} else if self.ffi_items.contains_union(ident) {
666697
MapInput::UnionType(&ty)
698+
} else if self.generator.c_enums.iter().any(|f| f(&ty)) {
699+
MapInput::CEnumType(&ty)
667700
} else {
668701
MapInput::Type(&ty)
669702
};

ctest-next/tests/basic.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ fn test_skip_simple() {
9292

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

110111
let (mut gen_, out_dir) = default_generator(1, "simple.h").unwrap();
111-
gen_.rename_constant(|c| (c.ident() == "B").then(|| "C_B".to_string()));
112+
gen_.rename_constant(|c| (c.ident() == "B").then(|| "C_B".to_string()))
113+
.c_enum(|e| e == "Color")
114+
.skip_signededness(|ty| ty == "Color");
112115

113116
check_entrypoint(&mut gen_, out_dir, crate_path, library_path, include_path);
114117
}

ctest-next/tests/input/simple.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,12 @@ union Word
2020
#define A "abc"
2121
#define C_B "bac"
2222

23+
enum Color
24+
{
25+
RED,
26+
BLUE,
27+
GREEN
28+
};
29+
2330
extern void *calloc(size_t num, size_t size);
2431
extern Byte byte;

ctest-next/tests/input/simple.out.with-renames.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,42 @@ char *ctest_const_cstr__B(void) {
2525
return ctest_const_B_val_static;
2626
}
2727

28+
static enum Color ctest_const_RED_val_static = RED;
29+
30+
// Define a function that returns a pointer to the value of the constant to test.
31+
// This will later be called on the Rust side via FFI.
32+
enum Color *ctest_const__RED(void) {
33+
return &ctest_const_RED_val_static;
34+
}
35+
36+
static enum Color ctest_const_BLUE_val_static = BLUE;
37+
38+
// Define a function that returns a pointer to the value of the constant to test.
39+
// This will later be called on the Rust side via FFI.
40+
enum Color *ctest_const__BLUE(void) {
41+
return &ctest_const_BLUE_val_static;
42+
}
43+
44+
static enum Color ctest_const_GREEN_val_static = GREEN;
45+
46+
// Define a function that returns a pointer to the value of the constant to test.
47+
// This will later be called on the Rust side via FFI.
48+
enum Color *ctest_const__GREEN(void) {
49+
return &ctest_const_GREEN_val_static;
50+
}
51+
2852
// Return the size of a type.
2953
uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); }
3054

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

58+
// Return the size of a type.
59+
uint64_t ctest_size_of__Color(void) { return sizeof(enum Color); }
60+
61+
// Return the alignment of a type.
62+
uint64_t ctest_align_of__Color(void) { return _Alignof(enum Color); }
63+
3464
// Return the size of a type.
3565
uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); }
3666

@@ -178,6 +208,33 @@ Byte ctest_roundtrip__Byte(
178208
return value;
179209
}
180210

211+
// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
212+
// remains unchanged.
213+
// It checks if the size is the same as well as if the padding bytes are all in the correct place.
214+
enum Color ctest_roundtrip__Color(
215+
enum Color value,
216+
const uint8_t is_padding_byte[sizeof(enum Color)],
217+
uint8_t value_bytes[sizeof(enum Color)]
218+
) {
219+
int size = (int)sizeof(enum Color);
220+
// Mark `p` as volatile so that the C compiler does not optimize away the pattern we create.
221+
// Otherwise the Rust side would not be able to see it.
222+
volatile uint8_t* p = (volatile uint8_t*)&value;
223+
int i = 0;
224+
for (i = 0; i < size; ++i) {
225+
// We skip padding bytes in both Rust and C because writing to it is undefined.
226+
// Instead we just make sure the the placement of the padding bytes remains the same.
227+
if (is_padding_byte[i]) { continue; }
228+
value_bytes[i] = p[i];
229+
// After we check that the pattern remained unchanged from Rust to C, we invert the pattern
230+
// and send it back to Rust to make sure that it remains unchanged from C to Rust.
231+
uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256);
232+
d = d == 0 ? 42: d;
233+
p[i] = d;
234+
}
235+
return value;
236+
}
237+
181238
// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
182239
// remains unchanged.
183240
// It checks if the size is the same as well as if the padding bytes are all in the correct place.

0 commit comments

Comments
 (0)