Skip to content

Commit 6b304ae

Browse files
committed
ctest-next: miscellaneous filtering bug fixes
1 parent 954727f commit 6b304ae

File tree

10 files changed

+118
-61
lines changed

10 files changed

+118
-61
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
@@ -40,10 +40,10 @@ pub struct TestGenerator {
4040
cfg: Vec<(String, Option<String>)>,
4141
mapped_names: Vec<MappedName>,
4242
pub(crate) skips: Vec<Skip>,
43-
verbose_skip: bool,
43+
pub(crate) verbose_skip: bool,
4444
pub(crate) volatile_items: Vec<VolatileItem>,
4545
pub(crate) array_arg: Option<ArrayArg>,
46-
skip_private: bool,
46+
pub(crate) skip_private: bool,
4747
pub(crate) skip_roundtrip: Option<SkipTest>,
4848
pub(crate) skip_signededness: Option<SkipTest>,
4949
pub(crate) skip_fn_ptrcheck: Option<SkipTest>,
@@ -898,8 +898,6 @@ impl TestGenerator {
898898
let mut ffi_items = FfiItems::new();
899899
ffi_items.visit_file(&ast);
900900

901-
self.filter_ffi_items(&mut ffi_items);
902-
903901
let output_directory = self
904902
.out_dir
905903
.clone()
@@ -938,37 +936,6 @@ impl TestGenerator {
938936
Ok(output_file_path)
939937
}
940938

941-
/// Skips entire items such as structs, constants, and aliases from being tested.
942-
///
943-
/// Does not skip specific tests or specific fields. If `skip_private` is true,
944-
/// it will skip tests for all private items.
945-
fn filter_ffi_items(&self, ffi_items: &mut FfiItems) {
946-
let verbose = self.verbose_skip;
947-
948-
macro_rules! filter {
949-
($field:ident, $variant:ident, $label:literal) => {{
950-
let skipped: Vec<_> = ffi_items
951-
.$field
952-
.extract_if(.., |item| {
953-
self.skips.iter().any(|f| f(&MapInput::$variant(item)))
954-
|| (self.skip_private && !item.public)
955-
})
956-
.collect();
957-
if verbose {
958-
skipped
959-
.iter()
960-
.for_each(|item| eprintln!("Skipping {} \"{}\"", $label, item.ident()));
961-
}
962-
}};
963-
}
964-
965-
filter!(aliases, Alias, "alias");
966-
filter!(constants, Const, "const");
967-
filter!(structs, Struct, "struct");
968-
filter!(foreign_functions, Fn, "fn");
969-
filter!(foreign_statics, Static, "static");
970-
}
971-
972939
/// Maps Rust identifiers or types to C counterparts, or defaults to the original name.
973940
pub(crate) fn rty_to_cty<'a>(&self, item: impl Into<MapInput<'a>>) -> String {
974941
let item = item.into();

ctest-next/src/template.rs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use proc_macro2::Span;
33
use quote::ToTokens;
44
use syn::spanned::Spanned;
55

6+
use crate::cdecl::Constness;
67
use crate::ffi_items::FfiItems;
78
use crate::translator::{TranslationErrorKind, Translator};
89
use crate::{
@@ -86,7 +87,7 @@ impl TestTemplate {
8687
&mut self,
8788
helper: &TranslateHelper,
8889
) -> Result<(), TranslationError> {
89-
for constant in helper.ffi_items.constants() {
90+
for constant in helper.filtered_ffi_items.constants() {
9091
if let syn::Type::Ptr(ptr) = &constant.ty
9192
&& let syn::Type::Path(path) = &*ptr.elem
9293
&& path.path.segments.last().unwrap().ident == "c_char"
@@ -124,7 +125,7 @@ impl TestTemplate {
124125
&mut self,
125126
helper: &TranslateHelper,
126127
) -> Result<(), TranslationError> {
127-
for alias in helper.ffi_items.aliases() {
128+
for alias in helper.filtered_ffi_items.aliases() {
128129
let item = TestSizeAlign {
129130
test_name: size_align_test_ident(alias.ident()),
130131
id: alias.ident().into(),
@@ -134,7 +135,7 @@ impl TestTemplate {
134135
self.size_align_tests.push(item.clone());
135136
self.test_idents.push(item.test_name);
136137
}
137-
for struct_ in helper.ffi_items.structs() {
138+
for struct_ in helper.filtered_ffi_items.structs() {
138139
let item = TestSizeAlign {
139140
test_name: size_align_test_ident(struct_.ident()),
140141
id: struct_.ident().into(),
@@ -144,7 +145,7 @@ impl TestTemplate {
144145
self.size_align_tests.push(item.clone());
145146
self.test_idents.push(item.test_name);
146147
}
147-
for union_ in helper.ffi_items.unions() {
148+
for union_ in helper.filtered_ffi_items.unions() {
148149
let item = TestSizeAlign {
149150
test_name: size_align_test_ident(union_.ident()),
150151
id: union_.ident().into(),
@@ -165,7 +166,7 @@ impl TestTemplate {
165166
&mut self,
166167
helper: &TranslateHelper,
167168
) -> Result<(), TranslationError> {
168-
for alias in helper.ffi_items.aliases() {
169+
for alias in helper.filtered_ffi_items.aliases() {
169170
let should_skip_signededness_test = helper
170171
.generator
171172
.skip_signededness
@@ -197,7 +198,7 @@ impl TestTemplate {
197198
let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input));
198199

199200
let struct_fields = helper
200-
.ffi_items
201+
.filtered_ffi_items
201202
.structs()
202203
.iter()
203204
.flat_map(|struct_| struct_.fields.iter().map(move |field| (struct_, field)))
@@ -213,7 +214,7 @@ impl TestTemplate {
213214
)
214215
});
215216
let union_fields = helper
216-
.ffi_items
217+
.filtered_ffi_items
217218
.unions()
218219
.iter()
219220
.flat_map(|union_| union_.fields.iter().map(move |field| (union_, field)))
@@ -251,15 +252,15 @@ impl TestTemplate {
251252
&mut self,
252253
helper: &TranslateHelper,
253254
) -> Result<(), TranslationError> {
254-
for alias in helper.ffi_items.aliases() {
255+
for alias in helper.filtered_ffi_items.aliases() {
255256
let c_ty = helper.c_type(alias)?;
256257
self.add_roundtrip_test(helper, alias.ident(), &[], &c_ty, true);
257258
}
258-
for struct_ in helper.ffi_items.structs() {
259+
for struct_ in helper.filtered_ffi_items.structs() {
259260
let c_ty = helper.c_type(struct_)?;
260261
self.add_roundtrip_test(helper, struct_.ident(), &struct_.fields, &c_ty, false);
261262
}
262-
for union_ in helper.ffi_items.unions() {
263+
for union_ in helper.filtered_ffi_items.unions() {
263264
let c_ty = helper.c_type(union_)?;
264265
self.add_roundtrip_test(helper, union_.ident(), &union_.fields, &c_ty, false);
265266
}
@@ -303,14 +304,14 @@ impl TestTemplate {
303304
let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input));
304305

305306
let struct_fields = helper
306-
.ffi_items
307+
.filtered_ffi_items
307308
.structs()
308309
.iter()
309310
.flat_map(|s| s.fields.iter().map(move |f| (s, f)))
310311
.filter(|(s, f)| {
311-
!should_skip(MapInput::StructField(s, f))
312-
&& !should_skip(MapInput::StructFieldType(s, f))
313-
&& f.public
312+
!(should_skip(MapInput::StructField(s, f))
313+
|| should_skip(MapInput::StructFieldType(s, f))
314+
|| !f.public)
314315
})
315316
.map(|(s, f)| {
316317
(
@@ -332,14 +333,14 @@ impl TestTemplate {
332333
)
333334
});
334335
let union_fields = helper
335-
.ffi_items
336+
.filtered_ffi_items
336337
.unions()
337338
.iter()
338339
.flat_map(|u| u.fields.iter().map(move |f| (u, f)))
339340
.filter(|(u, f)| {
340-
!should_skip(MapInput::UnionField(u, f))
341-
&& !should_skip(MapInput::UnionFieldType(u, f))
342-
&& f.public
341+
!(should_skip(MapInput::UnionField(u, f))
342+
|| should_skip(MapInput::UnionFieldType(u, f))
343+
|| !f.public)
343344
})
344345
.map(|(u, f)| {
345346
(
@@ -532,6 +533,7 @@ fn foreign_fn_test_ident(ident: &str) -> BoxStr {
532533

533534
/// Wrap methods that depend on both ffi items and the generator.
534535
pub(crate) struct TranslateHelper<'a> {
536+
filtered_ffi_items: FfiItems,
535537
ffi_items: &'a FfiItems,
536538
generator: &'a TestGenerator,
537539
translator: Translator<'a>,
@@ -540,11 +542,49 @@ pub(crate) struct TranslateHelper<'a> {
540542
impl<'a> TranslateHelper<'a> {
541543
/// Create a new translation helper.
542544
pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
543-
Self {
545+
let filtered_ffi_items = ffi_items.clone();
546+
let mut helper = Self {
547+
filtered_ffi_items,
544548
ffi_items,
545549
generator,
546550
translator: Translator::new(ffi_items, generator),
551+
};
552+
helper.filter_ffi_items();
553+
554+
helper
555+
}
556+
557+
/// Skips entire items such as structs, constants, and aliases from being tested.
558+
///
559+
/// Does not skip specific tests or specific fields. If `skip_private` is true,
560+
/// it will skip tests for all private items.
561+
fn filter_ffi_items(&mut self) {
562+
let verbose = self.generator.verbose_skip;
563+
564+
macro_rules! filter {
565+
($field:ident, $variant:ident, $label:literal) => {{
566+
let skipped = self.filtered_ffi_items.$field.extract_if(.., |item| {
567+
(self.generator.skip_private && !item.public)
568+
|| self
569+
.generator
570+
.skips
571+
.iter()
572+
.any(|f| f(&MapInput::$variant(item)))
573+
});
574+
for item in skipped {
575+
if verbose {
576+
eprintln!("Skipping {} \"{}\"", $label, item.ident())
577+
}
578+
}
579+
}};
547580
}
581+
582+
filter!(aliases, Alias, "alias");
583+
filter!(constants, Const, "const");
584+
filter!(structs, Struct, "struct");
585+
filter!(unions, Union, "union");
586+
filter!(foreign_functions, Fn, "fn");
587+
filter!(foreign_statics, Static, "static");
548588
}
549589

550590
/// Returns the equivalent C/Cpp identifier of the Rust item.
@@ -565,9 +605,9 @@ impl<'a> TranslateHelper<'a> {
565605
// inside of `Fn` when parsed.
566606
MapInput::Fn(_) => unimplemented!(),
567607
// For structs/unions/aliases, their type is the same as their identifier.
568-
MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), cdecl::Constness::Mut)),
569-
MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), cdecl::Constness::Mut)),
570-
MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), cdecl::Constness::Mut)),
608+
MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), Constness::Mut)),
609+
MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), Constness::Mut)),
610+
MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), Constness::Mut)),
571611

572612
MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"),
573613
MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"),
@@ -584,7 +624,7 @@ impl<'a> TranslateHelper<'a> {
584624
)
585625
})?;
586626

587-
let item = if self.ffi_items.contains_struct(ident) {
627+
let item = if self.ffi_items.contains_struct(&ty) {
588628
MapInput::StructType(&ty)
589629
} else if self.ffi_items.contains_union(ident) {
590630
MapInput::UnionType(&ty)

ctest-next/src/tests.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,11 @@ fn test_translate_helper_array_1d_2d() {
127127
assert_r2cdecl("[u8; 10]", "uint8_t foo[10]");
128128
assert_r2cdecl("[[u8; 64]; 32]", "uint8_t foo[32][64]");
129129
}
130+
131+
#[test]
132+
fn test_translate_expr_literal_types() {
133+
assert_eq!(
134+
r2cdecl("[u8; 10usize]", "foo").unwrap(),
135+
"uint8_t foo[(size_t)10]"
136+
);
137+
}

ctest-next/src/translator.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,24 @@ pub(crate) fn ptr_with_inner(
364364
/// This function will just pass the expression as is in most cases.
365365
pub(crate) fn translate_expr(expr: &syn::Expr) -> String {
366366
match expr {
367+
syn::Expr::Index(i) => {
368+
let base = translate_expr(&i.expr);
369+
let index = translate_expr(&i.index);
370+
format!("{base}[{index}]")
371+
}
372+
// This is done to deal with things like 3usize.
373+
syn::Expr::Lit(l) => match &l.lit {
374+
syn::Lit::Int(i) => {
375+
let suffix = translate_primitive_type(i.suffix());
376+
let val = i.base10_digits().to_string();
377+
if suffix.is_empty() {
378+
val
379+
} else {
380+
format!("({suffix}){val}")
381+
}
382+
}
383+
_ => l.to_token_stream().to_string(),
384+
},
367385
syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
368386
syn::Expr::Cast(c) => translate_expr(c.expr.deref()),
369387
expr => expr.to_token_stream().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 = "ctest"
3334
path = "test/ctest.rs"
3435
harness = false
3536

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

libc-test/build.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ fn ctest_cfg() -> ctest::TestGenerator {
7777
ctest::TestGenerator::new()
7878
}
7979

80+
#[expect(unused)]
81+
fn ctest_next_cfg() -> ctest_next::TestGenerator {
82+
ctest_next::TestGenerator::new()
83+
}
84+
8085
fn do_semver() {
8186
let mut out = PathBuf::from(env::var("OUT_DIR").unwrap());
8287
out.push("semver.rs");
@@ -164,6 +169,14 @@ fn main() {
164169
let re = regex::bytes::Regex::new(r"(?-u:\b)crate::").unwrap();
165170
copy_dir_hotfix(Path::new("../src"), &hotfix_dir, &re, b"::");
166171

172+
// FIXME(ctest): Only needed until ctest-next supports all tests.
173+
// Provide a default for targets that don't yet use `ctest-next`.
174+
std::fs::write(
175+
format!("{}/main_next.rs", std::env::var("OUT_DIR").unwrap()),
176+
"\nfn main() { println!(\"test result: ok\"); }\n",
177+
)
178+
.unwrap();
179+
167180
do_cc();
168181
do_ctest();
169182
do_semver();

libc-test/test/ctest_next.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[allow(deprecated)]
2+
#[allow(unused_imports)]
3+
use libc::*;
4+
5+
include!(concat!(env!("OUT_DIR"), "/main_next.rs"));

0 commit comments

Comments
 (0)