Skip to content

Commit d1382d3

Browse files
committed
ctest-next: miscellaneous filtering bug fixes
1 parent e634372 commit d1382d3

File tree

9 files changed

+115
-71
lines changed

9 files changed

+115
-71
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ jobs:
8989
# Remove `-Dwarnings` at the MSRV since lints may be different
9090
export RUSTFLAGS=""
9191
# Remove `ctest-next` which uses the 2024 edition
92-
perl -i -ne 'print unless /"ctest-(next|test)",/' Cargo.toml
92+
perl -i -ne 'print unless /"ctest-(next|test)",/ || /"libc-test",/' Cargo.toml
9393
fi
9494
9595
./ci/verify-build.sh
@@ -320,7 +320,7 @@ jobs:
320320
- name: Install Rust
321321
run: rustup update "$MSRV" --no-self-update && rustup default "$MSRV"
322322
- name: Remove edition 2024 crates
323-
run: perl -i -ne 'print unless /"ctest-(next|test)",/' Cargo.toml
323+
run: perl -i -ne 'print unless /"ctest-(next|test)",/ || /"libc-test",/' Cargo.toml
324324
- uses: Swatinem/rust-cache@v2
325325
- run: cargo build -p ctest
326326

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ctest-next/src/ast/union.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::{BoxStr, Field};
33
/// Represents a union defined in Rust.
44
#[derive(Debug, Clone)]
55
pub struct Union {
6-
#[expect(unused)]
76
pub(crate) public: bool,
87
pub(crate) ident: BoxStr,
98
pub(crate) fields: Vec<Field>,

ctest-next/src/generator.rs

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ pub struct TestGenerator {
3939
cfg: Vec<(String, Option<String>)>,
4040
mapped_names: Vec<MappedName>,
4141
pub(crate) skips: Vec<Skip>,
42-
verbose_skip: bool,
42+
pub(crate) verbose_skip: bool,
4343
pub(crate) volatile_items: Vec<VolatileItem>,
4444
array_arg: Option<ArrayArg>,
45-
skip_private: bool,
45+
pub(crate) skip_private: bool,
4646
pub(crate) skip_roundtrip: Option<SkipTest>,
4747
pub(crate) skip_signededness: Option<SkipTest>,
4848
}
@@ -872,8 +872,6 @@ impl TestGenerator {
872872
let mut ffi_items = FfiItems::new();
873873
ffi_items.visit_file(&ast);
874874

875-
self.filter_ffi_items(&mut ffi_items);
876-
877875
let output_directory = self
878876
.out_dir
879877
.clone()
@@ -912,37 +910,6 @@ impl TestGenerator {
912910
Ok(output_file_path)
913911
}
914912

915-
/// Skips entire items such as structs, constants, and aliases from being tested.
916-
///
917-
/// Does not skip specific tests or specific fields. If `skip_private` is true,
918-
/// it will skip tests for all private items.
919-
fn filter_ffi_items(&self, ffi_items: &mut FfiItems) {
920-
let verbose = self.verbose_skip;
921-
922-
macro_rules! filter {
923-
($field:ident, $variant:ident, $label:literal) => {{
924-
let skipped: Vec<_> = ffi_items
925-
.$field
926-
.extract_if(.., |item| {
927-
self.skips.iter().any(|f| f(&MapInput::$variant(item)))
928-
|| (self.skip_private && !item.public)
929-
})
930-
.collect();
931-
if verbose {
932-
skipped
933-
.iter()
934-
.for_each(|item| eprintln!("Skipping {} \"{}\"", $label, item.ident()));
935-
}
936-
}};
937-
}
938-
939-
filter!(aliases, Alias, "alias");
940-
filter!(constants, Const, "const");
941-
filter!(structs, Struct, "struct");
942-
filter!(foreign_functions, Fn, "fn");
943-
filter!(foreign_statics, Static, "static");
944-
}
945-
946913
/// Maps Rust identifiers or types to C counterparts, or defaults to the original name.
947914
pub(crate) fn rty_to_cty<'a>(&self, item: impl Into<MapInput<'a>>) -> String {
948915
let item = item.into();

ctest-next/src/template.rs

Lines changed: 86 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::ops::Deref;
22

33
use askama::Template;
4+
use proc_macro2::Span;
45
use quote::ToTokens;
56
use syn::spanned::Spanned;
67

@@ -65,11 +66,7 @@ impl TestTemplate {
6566
ffi_items: &FfiItems,
6667
generator: &TestGenerator,
6768
) -> Result<Self, TranslationError> {
68-
let helper = TranslateHelper {
69-
ffi_items,
70-
generator,
71-
translator: Translator::new(),
72-
};
69+
let helper = TranslateHelper::new(ffi_items, generator);
7370

7471
let mut template = Self::default();
7572
template.populate_const_and_cstr_tests(&helper)?;
@@ -87,7 +84,7 @@ impl TestTemplate {
8784
&mut self,
8885
helper: &TranslateHelper,
8986
) -> Result<(), TranslationError> {
90-
for constant in helper.ffi_items.constants() {
87+
for constant in helper.filtered_ffi_items.constants() {
9188
if let syn::Type::Ptr(ptr) = &constant.ty
9289
&& let syn::Type::Path(path) = &*ptr.elem
9390
&& path.path.segments.last().unwrap().ident == "c_char"
@@ -125,7 +122,7 @@ impl TestTemplate {
125122
&mut self,
126123
helper: &TranslateHelper,
127124
) -> Result<(), TranslationError> {
128-
for alias in helper.ffi_items.aliases() {
125+
for alias in helper.filtered_ffi_items.aliases() {
129126
let item = TestSizeAlign {
130127
test_name: size_align_test_ident(alias.ident()),
131128
id: alias.ident().into(),
@@ -135,7 +132,7 @@ impl TestTemplate {
135132
self.size_align_tests.push(item.clone());
136133
self.test_idents.push(item.test_name);
137134
}
138-
for struct_ in helper.ffi_items.structs() {
135+
for struct_ in helper.filtered_ffi_items.structs() {
139136
let item = TestSizeAlign {
140137
test_name: size_align_test_ident(struct_.ident()),
141138
id: struct_.ident().into(),
@@ -145,7 +142,7 @@ impl TestTemplate {
145142
self.size_align_tests.push(item.clone());
146143
self.test_idents.push(item.test_name);
147144
}
148-
for union_ in helper.ffi_items.unions() {
145+
for union_ in helper.filtered_ffi_items.unions() {
149146
let item = TestSizeAlign {
150147
test_name: size_align_test_ident(union_.ident()),
151148
id: union_.ident().into(),
@@ -166,7 +163,7 @@ impl TestTemplate {
166163
&mut self,
167164
helper: &TranslateHelper,
168165
) -> Result<(), TranslationError> {
169-
for alias in helper.ffi_items.aliases() {
166+
for alias in helper.filtered_ffi_items.aliases() {
170167
let should_skip_signededness_test = helper
171168
.generator
172169
.skip_signededness
@@ -200,7 +197,7 @@ impl TestTemplate {
200197
let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input));
201198

202199
let struct_fields = helper
203-
.ffi_items
200+
.filtered_ffi_items
204201
.structs()
205202
.iter()
206203
.flat_map(|struct_| struct_.fields.iter().map(move |field| (struct_, field)))
@@ -254,15 +251,15 @@ impl TestTemplate {
254251
&mut self,
255252
helper: &TranslateHelper,
256253
) -> Result<(), TranslationError> {
257-
for alias in helper.ffi_items.aliases() {
254+
for alias in helper.filtered_ffi_items.aliases() {
258255
let c_ty = helper.c_type(alias)?;
259256
self.add_roundtrip_test(helper, alias.ident(), &[], &c_ty, true);
260257
}
261-
for struct_ in helper.ffi_items.structs() {
258+
for struct_ in helper.filtered_ffi_items.structs() {
262259
let c_ty = helper.c_type(struct_)?;
263260
self.add_roundtrip_test(helper, struct_.ident(), &struct_.fields, &c_ty, false);
264261
}
265-
for union_ in helper.ffi_items.unions() {
262+
for union_ in helper.filtered_ffi_items.unions() {
266263
let c_ty = helper.c_type(union_)?;
267264
self.add_roundtrip_test(helper, union_.ident(), &union_.fields, &c_ty, false);
268265
}
@@ -306,14 +303,14 @@ impl TestTemplate {
306303
let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input));
307304

308305
let struct_fields = helper
309-
.ffi_items
306+
.filtered_ffi_items
310307
.structs()
311308
.iter()
312309
.flat_map(|s| s.fields.iter().map(move |f| (s, f)))
313310
.filter(|(s, f)| {
314-
!should_skip(MapInput::StructField(s, f))
315-
&& !should_skip(MapInput::StructFieldType(s, f))
316-
&& f.public
311+
!(should_skip(MapInput::StructField(s, f))
312+
|| should_skip(MapInput::StructFieldType(s, f))
313+
|| !f.public)
317314
})
318315
.map(|(s, f)| {
319316
(
@@ -335,14 +332,14 @@ impl TestTemplate {
335332
)
336333
});
337334
let union_fields = helper
338-
.ffi_items
335+
.filtered_ffi_items
339336
.unions()
340337
.iter()
341338
.flat_map(|u| u.fields.iter().map(move |f| (u, f)))
342339
.filter(|(u, f)| {
343-
!should_skip(MapInput::UnionField(u, f))
344-
&& !should_skip(MapInput::UnionFieldType(u, f))
345-
&& f.public
340+
!(should_skip(MapInput::UnionField(u, f))
341+
|| should_skip(MapInput::UnionFieldType(u, f))
342+
|| !f.public)
346343
})
347344
.map(|(u, f)| {
348345
(
@@ -483,20 +480,61 @@ fn roundtrip_test_ident(ident: &str) -> BoxStr {
483480

484481
/// Wrap methods that depend on both ffi items and the generator.
485482
pub(crate) struct TranslateHelper<'a> {
483+
filtered_ffi_items: FfiItems,
486484
ffi_items: &'a FfiItems,
487485
generator: &'a TestGenerator,
488486
translator: Translator,
489487
}
490488

491489
impl<'a> TranslateHelper<'a> {
492490
/// Create a new translation helper.
493-
#[cfg_attr(not(test), expect(unused))]
494491
pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
495-
Self {
492+
let filtered_ffi_items = ffi_items.clone();
493+
let mut helper = Self {
494+
filtered_ffi_items,
496495
ffi_items,
497496
generator,
498497
translator: Translator::new(),
498+
};
499+
helper.filter_ffi_items();
500+
501+
helper
502+
}
503+
504+
/// Skips entire items such as structs, constants, and aliases from being tested.
505+
///
506+
/// Does not skip specific tests or specific fields. If `skip_private` is true,
507+
/// it will skip tests for all private items.
508+
fn filter_ffi_items(&mut self) {
509+
let verbose = self.generator.verbose_skip;
510+
511+
macro_rules! filter {
512+
($field:ident, $variant:ident, $label:literal) => {{
513+
let skipped: Vec<_> = self
514+
.filtered_ffi_items
515+
.$field
516+
.extract_if(.., |item| {
517+
self.generator
518+
.skips
519+
.iter()
520+
.any(|f| f(&MapInput::$variant(item)))
521+
|| (self.generator.skip_private && !item.public)
522+
})
523+
.collect();
524+
if verbose {
525+
skipped
526+
.iter()
527+
.for_each(|item| eprintln!("Skipping {} \"{}\"", $label, item.ident()));
528+
}
529+
}};
499530
}
531+
532+
filter!(aliases, Alias, "alias");
533+
filter!(constants, Const, "const");
534+
filter!(structs, Struct, "struct");
535+
filter!(unions, Union, "union");
536+
filter!(foreign_functions, Fn, "fn");
537+
filter!(foreign_statics, Static, "static");
500538
}
501539

502540
/// Returns the equivalent C/Cpp identifier of the Rust item.
@@ -517,7 +555,14 @@ impl<'a> TranslateHelper<'a> {
517555
// inside of `Fn` when parsed.
518556
MapInput::Fn(_) => unimplemented!(),
519557
// For structs/unions/aliases, their type is the same as their identifier.
520-
MapInput::Alias(a) => (a.ident(), a.ident().to_string()),
558+
// FIXME(ctest): For some specific primitives such as c_uint, they don't exist on the
559+
// C side and have to be manually translated. If they are removed to use `std::ffi`,
560+
// then this becomes unneeded (although it won't break).
561+
MapInput::Alias(a) => (
562+
a.ident(),
563+
self.translator
564+
.translate_primitive_type(&syn::Ident::new(a.ident(), Span::call_site())),
565+
),
521566
MapInput::Struct(s) => (s.ident(), s.ident().to_string()),
522567
MapInput::Union(u) => (u.ident(), u.ident().to_string()),
523568

@@ -528,7 +573,7 @@ impl<'a> TranslateHelper<'a> {
528573
MapInput::Type(_) => panic!("MapInput::Type is not allowed!"),
529574
};
530575

531-
let item = if self.ffi_items.contains_struct(ident) {
576+
let item = if self.ffi_items.contains_struct(&ty) {
532577
MapInput::StructType(&ty)
533578
} else if self.ffi_items.contains_union(ident) {
534579
MapInput::UnionType(&ty)
@@ -546,21 +591,30 @@ impl<'a> TranslateHelper<'a> {
546591
fn basic_c_type(&self, ty: &syn::Type) -> Result<String, TranslationError> {
547592
let type_name = match ty {
548593
syn::Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
549-
syn::Type::Ptr(p) => self.basic_c_type(&p.elem)?,
550-
syn::Type::Reference(r) => self.basic_c_type(&r.elem)?,
594+
// Using recursion here causes breakage.
595+
// FIXME(ctest): Might be possible to extract this part into a function.
596+
syn::Type::Ptr(p) => match p.elem.deref() {
597+
syn::Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
598+
_ => p.to_token_stream().to_string(),
599+
},
600+
syn::Type::Reference(r) => match r.elem.deref() {
601+
syn::Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
602+
_ => r.to_token_stream().to_string(),
603+
},
551604
_ => ty.to_token_stream().to_string(),
552605
};
553606

554607
let unmapped_c_type = self.translator.translate_type(ty)?;
555608
let item = if self.ffi_items.contains_struct(&type_name) {
556-
MapInput::StructType(&unmapped_c_type)
609+
MapInput::StructType(&type_name)
557610
} else if self.ffi_items.contains_union(&type_name) {
558-
MapInput::UnionType(&unmapped_c_type)
611+
MapInput::UnionType(&type_name)
559612
} else {
560-
MapInput::Type(&unmapped_c_type)
613+
MapInput::Type(&type_name)
561614
};
562615

563-
Ok(self.generator.rty_to_cty(item))
616+
let mapped_type = self.generator.rty_to_cty(item.clone());
617+
Ok(unmapped_c_type.replace(&type_name, &mapped_type))
564618
}
565619

566620
/// Partially translate a Rust bare function type into its equivalent C type.

ctest-next/src/translator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ impl Translator {
228228
}
229229

230230
/// Translate a Rust primitive type into its C equivalent.
231-
fn translate_primitive_type(&self, ty: &syn::Ident) -> String {
231+
pub(crate) fn translate_primitive_type(&self, ty: &syn::Ident) -> String {
232232
match ty.to_string().as_str() {
233233
"usize" => "size_t".to_string(),
234234
"isize" => "ssize_t".to_string(),

libc-test/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ annotate-snippets = { version = "0.11.5", features = ["testing-colors"] }
2121
[build-dependencies]
2222
cc = "1.2.29"
2323
ctest = { path = "../ctest" }
24+
ctest-next = { path = "../ctest-next" }
2425
regex = "1.11.1"
2526

2627
[features]
@@ -33,6 +34,11 @@ name = "main"
3334
path = "test/main.rs"
3435
harness = false
3536

37+
[[test]]
38+
name = "main_next"
39+
path = "test/main_next.rs"
40+
harness = false
41+
3642
[[test]]
3743
name = "linux-fcntl"
3844
path = "test/linux_fcntl.rs"

0 commit comments

Comments
 (0)