-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ctest: add tests for size and alignment of structs, unions, and aliases, and signededness of aliases. #4556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -46,12 +46,15 @@ impl CTestTemplate { | |||||||||||
/// Stores all information necessary for generation of tests for all items. | ||||||||||||
#[derive(Clone, Debug, Default)] | ||||||||||||
pub(crate) struct TestTemplate { | ||||||||||||
pub signededness_tests: Vec<TestSignededness>, | ||||||||||||
pub size_align_tests: Vec<TestSizeAlign>, | ||||||||||||
pub const_cstr_tests: Vec<TestCStr>, | ||||||||||||
pub const_tests: Vec<TestConst>, | ||||||||||||
pub test_idents: Vec<BoxStr>, | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl TestTemplate { | ||||||||||||
/// Populate all tests for all items depending on the configuration provided. | ||||||||||||
pub(crate) fn new( | ||||||||||||
ffi_items: &FfiItems, | ||||||||||||
generator: &TestGenerator, | ||||||||||||
|
@@ -62,15 +65,20 @@ impl TestTemplate { | |||||||||||
translator: Translator::new(), | ||||||||||||
}; | ||||||||||||
|
||||||||||||
/* Figure out which tests are to be generated. */ | ||||||||||||
// FIXME(ctest): Populate more test information, maybe extract into separate methods. | ||||||||||||
// The workflow would be to create a struct that stores information for the new test, | ||||||||||||
// and populating that struct here, so that the also things that have to be added to | ||||||||||||
// the test templates are the new tests parameterized by that struct. | ||||||||||||
let mut template = Self::default(); | ||||||||||||
template.populate_const_and_cstr_tests(&helper)?; | ||||||||||||
template.populate_size_align_tests(&helper)?; | ||||||||||||
template.populate_signededness_tests(&helper)?; | ||||||||||||
|
||||||||||||
let mut const_tests = vec![]; | ||||||||||||
let mut const_cstr_tests = vec![]; | ||||||||||||
for constant in ffi_items.constants() { | ||||||||||||
Ok(template) | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Populates tests for constants and C-str constants, keeping track of the names of each test. | ||||||||||||
fn populate_const_and_cstr_tests( | ||||||||||||
&mut self, | ||||||||||||
helper: &TranslateHelper, | ||||||||||||
) -> Result<(), TranslationError> { | ||||||||||||
for constant in helper.ffi_items.constants() { | ||||||||||||
if let syn::Type::Ptr(ptr) = &constant.ty | ||||||||||||
&& let syn::Type::Path(path) = &*ptr.elem | ||||||||||||
&& path.path.segments.last().unwrap().ident == "c_char" | ||||||||||||
|
@@ -82,29 +90,94 @@ impl TestTemplate { | |||||||||||
rust_val: constant.ident().into(), | ||||||||||||
c_val: helper.c_ident(constant).into(), | ||||||||||||
}; | ||||||||||||
const_cstr_tests.push(item) | ||||||||||||
self.const_cstr_tests.push(item.clone()); | ||||||||||||
self.test_idents.push(item.test_name); | ||||||||||||
} else { | ||||||||||||
let item = TestConst { | ||||||||||||
id: constant.ident().into(), | ||||||||||||
test_name: const_test_ident(constant.ident()), | ||||||||||||
rust_val: constant.ident.clone(), | ||||||||||||
rust_val: constant.ident().into(), | ||||||||||||
rust_ty: constant.ty.to_token_stream().to_string().into_boxed_str(), | ||||||||||||
c_val: helper.c_ident(constant).into(), | ||||||||||||
c_ty: helper.c_type(constant)?.into(), | ||||||||||||
}; | ||||||||||||
const_tests.push(item) | ||||||||||||
self.const_tests.push(item.clone()); | ||||||||||||
self.test_idents.push(item.test_name); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
let mut test_idents = vec![]; | ||||||||||||
test_idents.extend(const_cstr_tests.iter().map(|test| test.test_name.clone())); | ||||||||||||
test_idents.extend(const_tests.iter().map(|test| test.test_name.clone())); | ||||||||||||
Ok(()) | ||||||||||||
} | ||||||||||||
|
||||||||||||
Ok(Self { | ||||||||||||
const_cstr_tests, | ||||||||||||
const_tests, | ||||||||||||
test_idents, | ||||||||||||
}) | ||||||||||||
/// Populates size and alignment tests for aliases, structs, and unions. | ||||||||||||
/// | ||||||||||||
/// It also keeps track of the names of each test. | ||||||||||||
fn populate_size_align_tests( | ||||||||||||
&mut self, | ||||||||||||
helper: &TranslateHelper, | ||||||||||||
) -> Result<(), TranslationError> { | ||||||||||||
for alias in helper.ffi_items.aliases() { | ||||||||||||
let item = TestSizeAlign { | ||||||||||||
test_name: size_align_test_ident(alias.ident()), | ||||||||||||
id: alias.ident().into(), | ||||||||||||
rust_ty: alias.ident().into(), | ||||||||||||
c_ty: helper.c_type(alias)?.into(), | ||||||||||||
}; | ||||||||||||
self.size_align_tests.push(item.clone()); | ||||||||||||
self.test_idents.push(item.test_name); | ||||||||||||
} | ||||||||||||
for struct_ in helper.ffi_items.structs() { | ||||||||||||
let item = TestSizeAlign { | ||||||||||||
test_name: size_align_test_ident(struct_.ident()), | ||||||||||||
id: struct_.ident().into(), | ||||||||||||
rust_ty: struct_.ident().into(), | ||||||||||||
c_ty: helper.c_type(struct_)?.into(), | ||||||||||||
}; | ||||||||||||
self.size_align_tests.push(item.clone()); | ||||||||||||
self.test_idents.push(item.test_name); | ||||||||||||
} | ||||||||||||
for union_ in helper.ffi_items.unions() { | ||||||||||||
let item = TestSizeAlign { | ||||||||||||
test_name: size_align_test_ident(union_.ident()), | ||||||||||||
id: union_.ident().into(), | ||||||||||||
rust_ty: union_.ident().into(), | ||||||||||||
c_ty: helper.c_type(union_)?.into(), | ||||||||||||
}; | ||||||||||||
self.size_align_tests.push(item.clone()); | ||||||||||||
self.test_idents.push(item.test_name); | ||||||||||||
} | ||||||||||||
|
||||||||||||
Ok(()) | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Populates signededness tests for aliases. | ||||||||||||
/// | ||||||||||||
/// It also keeps track of the names of each test. | ||||||||||||
fn populate_signededness_tests( | ||||||||||||
&mut self, | ||||||||||||
helper: &TranslateHelper, | ||||||||||||
) -> Result<(), TranslationError> { | ||||||||||||
for alias in helper.ffi_items.aliases() { | ||||||||||||
// && skip_signededness | ||||||||||||
let should_skip_signededness_test = helper | ||||||||||||
.generator | ||||||||||||
.skip_signededness | ||||||||||||
.as_ref() | ||||||||||||
.is_some_and(|skip| skip(alias.ident())); | ||||||||||||
if helper.translator.is_signed(helper.ffi_items, &alias.ty) | ||||||||||||
&& !should_skip_signededness_test | ||||||||||||
Comment on lines
+167
to
+168
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Early continue to unnest a level
Suggested change
|
||||||||||||
{ | ||||||||||||
let item = TestSignededness { | ||||||||||||
test_name: signededness_test_ident(alias.ident()), | ||||||||||||
id: alias.ident().into(), | ||||||||||||
c_ty: helper.c_type(alias)?.into(), | ||||||||||||
}; | ||||||||||||
self.signededness_tests.push(item.clone()); | ||||||||||||
self.test_idents.push(item.test_name); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
Ok(()) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
@@ -119,6 +192,21 @@ impl TestTemplate { | |||||||||||
* - `c_ty`: The C type of the constant, qualified with `struct` or `union` if needed. | ||||||||||||
*/ | ||||||||||||
|
||||||||||||
#[derive(Clone, Debug)] | ||||||||||||
pub(crate) struct TestSignededness { | ||||||||||||
pub test_name: BoxStr, | ||||||||||||
pub id: BoxStr, | ||||||||||||
pub c_ty: BoxStr, | ||||||||||||
} | ||||||||||||
|
||||||||||||
#[derive(Clone, Debug)] | ||||||||||||
pub(crate) struct TestSizeAlign { | ||||||||||||
pub test_name: BoxStr, | ||||||||||||
pub id: BoxStr, | ||||||||||||
pub rust_ty: BoxStr, | ||||||||||||
pub c_ty: BoxStr, | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Information required to test a constant CStr. | ||||||||||||
#[derive(Clone, Debug)] | ||||||||||||
pub(crate) struct TestCStr { | ||||||||||||
|
@@ -139,16 +227,18 @@ pub(crate) struct TestConst { | |||||||||||
pub c_ty: BoxStr, | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// The Rust name of the cstr test. | ||||||||||||
/// | ||||||||||||
/// The C name of this same test is the same with `__` prepended. | ||||||||||||
fn signededness_test_ident(ident: &str) -> BoxStr { | ||||||||||||
format!("ctest_signededness_{ident}").into() | ||||||||||||
} | ||||||||||||
|
||||||||||||
fn size_align_test_ident(ident: &str) -> BoxStr { | ||||||||||||
format!("ctest_size_align_{ident}").into() | ||||||||||||
} | ||||||||||||
|
||||||||||||
fn cstr_test_ident(ident: &str) -> BoxStr { | ||||||||||||
format!("ctest_const_cstr_{ident}").into() | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// The Rust name of the const test. | ||||||||||||
/// | ||||||||||||
/// The C name of this test is the same with `__` prepended. | ||||||||||||
fn const_test_ident(ident: &str) -> BoxStr { | ||||||||||||
format!("ctest_const_{ident}").into() | ||||||||||||
} | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ use syn::spanned::Spanned; | |
use thiserror::Error; | ||
|
||
use crate::BoxStr; | ||
use crate::ffi_items::FfiItems; | ||
|
||
/// An error that occurs during translation, detailing cause and location. | ||
#[derive(Debug, Error)] | ||
|
@@ -314,6 +315,29 @@ impl Translator { | |
} | ||
} | ||
} | ||
|
||
/// Determine whether a C type is a signed type. | ||
/// | ||
/// For primitive types it checks against a known list of signed types, but for aliases | ||
/// which are the only thing other than primitives that can be signed, it recursively checks | ||
/// the underlying type of the alias. | ||
pub(crate) fn is_signed(&self, ffi_items: &FfiItems, ty: &syn::Type) -> bool { | ||
match ty { | ||
syn::Type::Path(path) => { | ||
let ident = path.path.segments.last().unwrap().ident.clone(); | ||
if let Some(aliased) = ffi_items.aliases().iter().find(|a| ident == a.ident()) { | ||
return self.is_signed(ffi_items, &aliased.ty); | ||
} | ||
match self.translate_primitive_type(&ident).as_str() { | ||
"char" | "short" | "int" | "long" | "long long" | "int8_t" | "int16_t" | ||
| "int32_t" | "int64_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" | ||
| "size_t" | "ssize_t" => true, | ||
Comment on lines
+332
to
+334
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just add an arm |
||
s => s.starts_with("signed ") || s.starts_with("unsigned "), | ||
} | ||
} | ||
_ => false, | ||
} | ||
} | ||
} | ||
|
||
/// Translate a simple Rust expression to C. | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -34,3 +34,21 @@ static {{ constant.c_ty }} ctest_const_{{ constant.id }}_val_static = {{ constan | |||||||
return &ctest_const_{{ constant.id }}_val_static; | ||||||||
} | ||||||||
{%- endfor +%} | ||||||||
|
||||||||
{%- for item in ctx.size_align_tests +%} | ||||||||
|
||||||||
// Return the size of a type. | ||||||||
uint64_t ctest_size_of__{{ item.id }}(void) { return sizeof({{ item.c_ty }}); } | ||||||||
|
||||||||
// Return the alignment of a type. | ||||||||
uint64_t ctest_align_of__{{ item.id }}(void) { return _Alignof({{ item.c_ty }}); } | ||||||||
{%- endfor +%} | ||||||||
|
||||||||
{%- for alias in ctx.signededness_tests +%} | ||||||||
|
||||||||
// Return `1` if the type is signed, otherwise return `0`. | ||||||||
// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` | ||||||||
uint32_t ctest_signededness_of__{{ alias.id }}(void) { | ||||||||
return ((({{ alias.c_ty }}) -1) < 0); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a bit easier to read
Suggested change
|
||||||||
} | ||||||||
{%- endfor +%} | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is missing a trailing newline. Does your editor have an option to insert this by default? E.g. vscode has |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.