Skip to content

Commit a0c27ab

Browse files
committed
ctest-next: miscellaneous filtering bug fixes
1 parent 7f3d696 commit a0c27ab

File tree

9 files changed

+108
-61
lines changed

9 files changed

+108
-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
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
}
@@ -873,8 +873,6 @@ impl TestGenerator {
873873
let mut ffi_items = FfiItems::new();
874874
ffi_items.visit_file(&ast);
875875

876-
self.filter_ffi_items(&mut ffi_items);
877-
878876
let output_directory = self
879877
.out_dir
880878
.clone()
@@ -913,37 +911,6 @@ impl TestGenerator {
913911
Ok(output_file_path)
914912
}
915913

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

ctest-next/src/template.rs

Lines changed: 69 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::Translator;
89
use crate::{
@@ -84,7 +85,7 @@ impl TestTemplate {
8485
&mut self,
8586
helper: &TranslateHelper,
8687
) -> Result<(), TranslationError> {
87-
for constant in helper.ffi_items.constants() {
88+
for constant in helper.filtered_ffi_items.constants() {
8889
if let syn::Type::Ptr(ptr) = &constant.ty
8990
&& let syn::Type::Path(path) = &*ptr.elem
9091
&& path.path.segments.last().unwrap().ident == "c_char"
@@ -122,7 +123,7 @@ impl TestTemplate {
122123
&mut self,
123124
helper: &TranslateHelper,
124125
) -> Result<(), TranslationError> {
125-
for alias in helper.ffi_items.aliases() {
126+
for alias in helper.filtered_ffi_items.aliases() {
126127
let item = TestSizeAlign {
127128
test_name: size_align_test_ident(alias.ident()),
128129
id: alias.ident().into(),
@@ -132,7 +133,7 @@ impl TestTemplate {
132133
self.size_align_tests.push(item.clone());
133134
self.test_idents.push(item.test_name);
134135
}
135-
for struct_ in helper.ffi_items.structs() {
136+
for struct_ in helper.filtered_ffi_items.structs() {
136137
let item = TestSizeAlign {
137138
test_name: size_align_test_ident(struct_.ident()),
138139
id: struct_.ident().into(),
@@ -142,7 +143,7 @@ impl TestTemplate {
142143
self.size_align_tests.push(item.clone());
143144
self.test_idents.push(item.test_name);
144145
}
145-
for union_ in helper.ffi_items.unions() {
146+
for union_ in helper.filtered_ffi_items.unions() {
146147
let item = TestSizeAlign {
147148
test_name: size_align_test_ident(union_.ident()),
148149
id: union_.ident().into(),
@@ -163,7 +164,7 @@ impl TestTemplate {
163164
&mut self,
164165
helper: &TranslateHelper,
165166
) -> Result<(), TranslationError> {
166-
for alias in helper.ffi_items.aliases() {
167+
for alias in helper.filtered_ffi_items.aliases() {
167168
let should_skip_signededness_test = helper
168169
.generator
169170
.skip_signededness
@@ -195,7 +196,7 @@ impl TestTemplate {
195196
let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input));
196197

197198
let struct_fields = helper
198-
.ffi_items
199+
.filtered_ffi_items
199200
.structs()
200201
.iter()
201202
.flat_map(|struct_| struct_.fields.iter().map(move |field| (struct_, field)))
@@ -211,7 +212,7 @@ impl TestTemplate {
211212
)
212213
});
213214
let union_fields = helper
214-
.ffi_items
215+
.filtered_ffi_items
215216
.unions()
216217
.iter()
217218
.flat_map(|union_| union_.fields.iter().map(move |field| (union_, field)))
@@ -249,15 +250,15 @@ impl TestTemplate {
249250
&mut self,
250251
helper: &TranslateHelper,
251252
) -> Result<(), TranslationError> {
252-
for alias in helper.ffi_items.aliases() {
253+
for alias in helper.filtered_ffi_items.aliases() {
253254
let c_ty = helper.c_type(alias)?;
254255
self.add_roundtrip_test(helper, alias.ident(), &[], &c_ty, true);
255256
}
256-
for struct_ in helper.ffi_items.structs() {
257+
for struct_ in helper.filtered_ffi_items.structs() {
257258
let c_ty = helper.c_type(struct_)?;
258259
self.add_roundtrip_test(helper, struct_.ident(), &struct_.fields, &c_ty, false);
259260
}
260-
for union_ in helper.ffi_items.unions() {
261+
for union_ in helper.filtered_ffi_items.unions() {
261262
let c_ty = helper.c_type(union_)?;
262263
self.add_roundtrip_test(helper, union_.ident(), &union_.fields, &c_ty, false);
263264
}
@@ -301,14 +302,14 @@ impl TestTemplate {
301302
let should_skip = |map_input| helper.generator.skips.iter().any(|f| f(&map_input));
302303

303304
let struct_fields = helper
304-
.ffi_items
305+
.filtered_ffi_items
305306
.structs()
306307
.iter()
307308
.flat_map(|s| s.fields.iter().map(move |f| (s, f)))
308309
.filter(|(s, f)| {
309-
!should_skip(MapInput::StructField(s, f))
310-
&& !should_skip(MapInput::StructFieldType(s, f))
311-
&& f.public
310+
!(should_skip(MapInput::StructField(s, f))
311+
|| should_skip(MapInput::StructFieldType(s, f))
312+
|| !f.public)
312313
})
313314
.map(|(s, f)| {
314315
(
@@ -330,14 +331,14 @@ impl TestTemplate {
330331
)
331332
});
332333
let union_fields = helper
333-
.ffi_items
334+
.filtered_ffi_items
334335
.unions()
335336
.iter()
336337
.flat_map(|u| u.fields.iter().map(move |f| (u, f)))
337338
.filter(|(u, f)| {
338-
!should_skip(MapInput::UnionField(u, f))
339-
&& !should_skip(MapInput::UnionFieldType(u, f))
340-
&& f.public
339+
!(should_skip(MapInput::UnionField(u, f))
340+
|| should_skip(MapInput::UnionFieldType(u, f))
341+
|| !f.public)
341342
})
342343
.map(|(u, f)| {
343344
(
@@ -487,6 +488,7 @@ fn roundtrip_test_ident(ident: &str) -> BoxStr {
487488

488489
/// Wrap methods that depend on both ffi items and the generator.
489490
pub(crate) struct TranslateHelper<'a> {
491+
filtered_ffi_items: FfiItems,
490492
ffi_items: &'a FfiItems,
491493
generator: &'a TestGenerator,
492494
translator: Translator<'a>,
@@ -495,11 +497,52 @@ pub(crate) struct TranslateHelper<'a> {
495497
impl<'a> TranslateHelper<'a> {
496498
/// Create a new translation helper.
497499
pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
498-
Self {
500+
let filtered_ffi_items = ffi_items.clone();
501+
let mut helper = Self {
502+
filtered_ffi_items,
499503
ffi_items,
500504
generator,
501505
translator: Translator::new(ffi_items, generator),
506+
};
507+
helper.filter_ffi_items();
508+
509+
helper
510+
}
511+
512+
/// Skips entire items such as structs, constants, and aliases from being tested.
513+
///
514+
/// Does not skip specific tests or specific fields. If `skip_private` is true,
515+
/// it will skip tests for all private items.
516+
fn filter_ffi_items(&mut self) {
517+
let verbose = self.generator.verbose_skip;
518+
519+
macro_rules! filter {
520+
($field:ident, $variant:ident, $label:literal) => {{
521+
let skipped: Vec<_> = self
522+
.filtered_ffi_items
523+
.$field
524+
.extract_if(.., |item| {
525+
self.generator
526+
.skips
527+
.iter()
528+
.any(|f| f(&MapInput::$variant(item)))
529+
|| (self.generator.skip_private && !item.public)
530+
})
531+
.collect();
532+
if verbose {
533+
skipped
534+
.iter()
535+
.for_each(|item| eprintln!("Skipping {} \"{}\"", $label, item.ident()));
536+
}
537+
}};
502538
}
539+
540+
filter!(aliases, Alias, "alias");
541+
filter!(constants, Const, "const");
542+
filter!(structs, Struct, "struct");
543+
filter!(unions, Union, "union");
544+
filter!(foreign_functions, Fn, "fn");
545+
filter!(foreign_statics, Static, "static");
503546
}
504547

505548
/// Returns the equivalent C/Cpp identifier of the Rust item.
@@ -520,9 +563,12 @@ impl<'a> TranslateHelper<'a> {
520563
// inside of `Fn` when parsed.
521564
MapInput::Fn(_) => unimplemented!(),
522565
// For structs/unions/aliases, their type is the same as their identifier.
523-
MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), cdecl::Constness::Mut)),
524-
MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), cdecl::Constness::Mut)),
525-
MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), cdecl::Constness::Mut)),
566+
// FIXME(ctest): For some specific primitives such as c_uint, they don't exist on the
567+
// C side and have to be manually translated. If they are removed to use `std::ffi`,
568+
// then this becomes unneeded (although it won't break).
569+
MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), Constness::Mut)),
570+
MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), Constness::Mut)),
571+
MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), Constness::Mut)),
526572

527573
MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"),
528574
MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"),
@@ -539,7 +585,7 @@ impl<'a> TranslateHelper<'a> {
539585
)
540586
})?;
541587

542-
let item = if self.ffi_items.contains_struct(ident) {
588+
let item = if self.ffi_items.contains_struct(&ty) {
543589
MapInput::StructType(&ty)
544590
} else if self.ffi_items.contains_union(ident) {
545591
MapInput::UnionType(&ty)

ctest-next/src/translator.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,16 @@ pub(crate) fn ptr_with_inner(
363363
/// This function will just pass the expression as is in most cases.
364364
pub(crate) fn translate_expr(expr: &syn::Expr) -> String {
365365
match expr {
366+
// This is done to deal with things like 3usize.
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+
syn::Expr::Lit(l) => match &l.lit {
373+
syn::Lit::Int(i) => i.base10_digits().to_string(),
374+
_ => l.to_token_stream().to_string(),
375+
},
366376
syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
367377
syn::Expr::Cast(c) => translate_expr(c.expr.deref()),
368378
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 = "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"

libc-test/build.rs

Lines changed: 12 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,13 @@ 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+
std::fs::write(
174+
format!("{}/main_next.rs", std::env::var("OUT_DIR").unwrap()),
175+
"\nfn main() { println!(\"test result: ok\"); }\n",
176+
)
177+
.unwrap_or_default();
178+
167179
do_cc();
168180
do_ctest();
169181
do_semver();

libc-test/test/main_next.rs

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

0 commit comments

Comments
 (0)