Skip to content

Commit a3b2323

Browse files
authored
Merge pull request #4554 from tgross35/ctest-tweaks
ctest: Small adjustments to existing tests
2 parents 81e4700 + d7fd2bd commit a3b2323

File tree

15 files changed

+228
-169
lines changed

15 files changed

+228
-169
lines changed

.github/workflows/ci.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,13 @@ jobs:
8585
- name: Execute build.sh
8686
run: |
8787
set -eux
88-
# Remove `-Dwarnings` at the MSRV since lints may be different
89-
[ "${{ matrix.toolchain }}" = "1.63.0" ] && export RUSTFLAGS=""
88+
if [ "${{ matrix.toolchain }}" = "1.63.0" ]; then
89+
# Remove `-Dwarnings` at the MSRV since lints may be different
90+
export RUSTFLAGS=""
91+
# Remove `ctest-next` which uses the 2024 edition
92+
perl -i -ne 'print unless /"ctest-next",/' Cargo.toml
93+
fi
94+
9095
./ci/verify-build.sh
9196
- name: Target size after job completion
9297
run: du -sh target | sort -k 2
@@ -314,6 +319,8 @@ jobs:
314319
echo "MSRV=$msrv" >> "$GITHUB_ENV"
315320
- name: Install Rust
316321
run: rustup update "$MSRV" --no-self-update && rustup default "$MSRV"
322+
- name: Remove edition 2024 crates
323+
run: perl -i -ne 'print unless /"ctest-next",/' Cargo.toml
317324
- uses: Swatinem/rust-cache@v2
318325
- run: cargo build -p ctest
319326

ctest-next/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "ctest-next"
33
version = "0.1.0"
4-
edition = "2021"
5-
rust-version = "1.87"
4+
edition = "2024"
5+
rust-version = "1.88"
66
license = "MIT OR Apache-2.0"
77
repository = "https://github.com/rust-lang/libc"
88
publish = false

ctest-next/src/generator.rs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use thiserror::Error;
1010
use crate::ffi_items::FfiItems;
1111
use crate::template::{CTestTemplate, RustTestTemplate};
1212
use crate::{
13-
expand, Const, Field, MapInput, Parameter, Result, Static, Struct, Type, VolatileItemKind,
13+
Const, Field, MapInput, Parameter, Result, Static, Struct, TranslationError, Type,
14+
VolatileItemKind, expand,
1415
};
1516

1617
/// A function that takes a mappable input and returns its mapping as Some, otherwise
@@ -46,6 +47,12 @@ pub enum GenerationError {
4647
MacroExpansion(PathBuf, String),
4748
#[error("unable to parse expanded crate {0}: {1}")]
4849
RustSyntax(String, String),
50+
#[error("unable to prepare template input: {0}")]
51+
Translation(#[from] TranslationError),
52+
#[error("unable to render Rust template: {0}")]
53+
RustTemplateRender(askama::Error),
54+
#[error("unable to render C template: {0}")]
55+
CTemplateRender(askama::Error),
4956
#[error("unable to render {0} template: {1}")]
5057
TemplateRender(String, String),
5158
#[error("unable to create or write template file: {0}")]
@@ -600,33 +607,32 @@ impl TestGenerator {
600607
.unwrap_or_else(|| env::var("OUT_DIR").unwrap().into());
601608
let output_file_path = output_directory.join(output_file_path);
602609

610+
let ensure_trailing_newline = |s: &mut String| {
611+
s.truncate(s.trim_end().len());
612+
s.push('\n');
613+
};
614+
615+
let mut rust_file = RustTestTemplate::new(&ffi_items, self)?
616+
.render()
617+
.map_err(GenerationError::RustTemplateRender)?;
618+
ensure_trailing_newline(&mut rust_file);
619+
603620
// Generate the Rust side of the tests.
604621
File::create(output_file_path.with_extension("rs"))
605622
.map_err(GenerationError::OsError)?
606-
.write_all(
607-
RustTestTemplate::new(&ffi_items, self)
608-
.map_err(|e| {
609-
GenerationError::TemplateRender("Rust".to_string(), e.to_string())
610-
})?
611-
.render()
612-
.map_err(|e| {
613-
GenerationError::TemplateRender("Rust".to_string(), e.to_string())
614-
})?
615-
.as_bytes(),
616-
)
623+
.write_all(rust_file.as_bytes())
617624
.map_err(GenerationError::OsError)?;
618625

626+
let mut c_file = CTestTemplate::new(&ffi_items, self)?
627+
.render()
628+
.map_err(GenerationError::CTemplateRender)?;
629+
ensure_trailing_newline(&mut c_file);
630+
619631
// Generate the C/Cxx side of the tests.
620632
let c_output_path = output_file_path.with_extension("c");
621633
File::create(&c_output_path)
622634
.map_err(GenerationError::OsError)?
623-
.write_all(
624-
CTestTemplate::new(&ffi_items, self)
625-
.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?
626-
.render()
627-
.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?
628-
.as_bytes(),
629-
)
635+
.write_all(c_file.as_bytes())
630636
.map_err(GenerationError::OsError)?;
631637

632638
Ok(output_file_path)

ctest-next/src/runner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::env;
2-
use std::fs::{canonicalize, File};
2+
use std::fs::{File, canonicalize};
33
use std::io::Write;
44
use std::path::{Path, PathBuf};
55
use std::process::Command;

ctest-next/src/template.rs

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{BoxStr, MapInput, Result, TestGenerator, TranslationError};
99
#[derive(Template, Clone)]
1010
#[template(path = "test.rs")]
1111
pub(crate) struct RustTestTemplate {
12-
pub(crate) template: TestTemplate,
12+
pub template: TestTemplate,
1313
}
1414

1515
impl RustTestTemplate {
@@ -27,8 +27,8 @@ impl RustTestTemplate {
2727
#[derive(Template, Clone)]
2828
#[template(path = "test.c")]
2929
pub(crate) struct CTestTemplate {
30-
pub(crate) template: TestTemplate,
31-
pub(crate) headers: Vec<String>,
30+
pub template: TestTemplate,
31+
pub headers: Vec<String>,
3232
}
3333

3434
impl CTestTemplate {
@@ -46,9 +46,9 @@ impl CTestTemplate {
4646
/// Stores all information necessary for generation of tests for all items.
4747
#[derive(Clone, Debug, Default)]
4848
pub(crate) struct TestTemplate {
49-
pub(crate) const_cstr_tests: Vec<TestCstr>,
50-
pub(crate) const_tests: Vec<TestConst>,
51-
pub(crate) test_idents: Vec<BoxStr>,
49+
pub const_cstr_tests: Vec<TestCStr>,
50+
pub const_tests: Vec<TestConst>,
51+
pub test_idents: Vec<BoxStr>,
5252
}
5353

5454
impl TestTemplate {
@@ -71,37 +71,34 @@ impl TestTemplate {
7171
let mut const_tests = vec![];
7272
let mut const_cstr_tests = vec![];
7373
for constant in ffi_items.constants() {
74-
if let syn::Type::Ptr(ptr) = &constant.ty {
75-
let is_const_c_char_ptr = matches!(
76-
&*ptr.elem,
77-
syn::Type::Path(path)
78-
if path.path.segments.last().unwrap().ident == "c_char"
79-
&& ptr.mutability.is_none()
80-
);
81-
if is_const_c_char_ptr {
82-
let item = TestCstr {
83-
test_ident: cstr_test_ident(constant.ident()),
84-
rust_ident: constant.ident().into(),
85-
c_ident: helper.c_ident(constant).into(),
86-
c_type: helper.c_type(constant)?.into(),
87-
};
88-
const_cstr_tests.push(item)
89-
}
74+
if let syn::Type::Ptr(ptr) = &constant.ty
75+
&& let syn::Type::Path(path) = &*ptr.elem
76+
&& path.path.segments.last().unwrap().ident == "c_char"
77+
&& ptr.mutability.is_none()
78+
{
79+
let item = TestCStr {
80+
id: constant.ident().into(),
81+
test_name: cstr_test_ident(constant.ident()),
82+
rust_val: constant.ident().into(),
83+
c_val: helper.c_ident(constant).into(),
84+
};
85+
const_cstr_tests.push(item)
9086
} else {
9187
let item = TestConst {
92-
test_ident: const_test_ident(constant.ident()),
93-
rust_ident: constant.ident.clone(),
94-
rust_type: constant.ty.to_token_stream().to_string().into_boxed_str(),
95-
c_ident: helper.c_ident(constant).into(),
96-
c_type: helper.c_type(constant)?.into(),
88+
id: constant.ident().into(),
89+
test_name: const_test_ident(constant.ident()),
90+
rust_val: constant.ident.clone(),
91+
rust_ty: constant.ty.to_token_stream().to_string().into_boxed_str(),
92+
c_val: helper.c_ident(constant).into(),
93+
c_ty: helper.c_type(constant)?.into(),
9794
};
9895
const_tests.push(item)
9996
}
10097
}
10198

10299
let mut test_idents = vec![];
103-
test_idents.extend(const_cstr_tests.iter().map(|test| test.test_ident.clone()));
104-
test_idents.extend(const_tests.iter().map(|test| test.test_ident.clone()));
100+
test_idents.extend(const_cstr_tests.iter().map(|test| test.test_name.clone()));
101+
test_idents.extend(const_tests.iter().map(|test| test.test_name.clone()));
105102

106103
Ok(Self {
107104
const_cstr_tests,
@@ -111,23 +108,35 @@ impl TestTemplate {
111108
}
112109
}
113110

111+
/* Many test structures have the following fields:
112+
*
113+
* - `test_name`: The function name.
114+
* - `id`: An identifier that can be used to create functions related to this type without conflict,
115+
* usually also part of `test_name`.
116+
* - `rust_val`: Identifier for a Rust value, with path qualifications if needed.
117+
* - `rust_ty`: The Rust type of the relevant item, with path qualifications if needed.
118+
* - `c_val`: Identifier for a C value (e.g. `#define`)
119+
* - `c_ty`: The C type of the constant, qualified with `struct` or `union` if needed.
120+
*/
121+
114122
/// Information required to test a constant CStr.
115123
#[derive(Clone, Debug)]
116-
pub(crate) struct TestCstr {
117-
pub(crate) test_ident: BoxStr,
118-
pub(crate) rust_ident: BoxStr,
119-
pub(crate) c_ident: BoxStr,
120-
pub(crate) c_type: BoxStr,
124+
pub(crate) struct TestCStr {
125+
pub test_name: BoxStr,
126+
pub id: BoxStr,
127+
pub rust_val: BoxStr,
128+
pub c_val: BoxStr,
121129
}
122130

123131
/// Information required to test a constant.
124132
#[derive(Clone, Debug)]
125133
pub(crate) struct TestConst {
126-
pub(crate) test_ident: BoxStr,
127-
pub(crate) rust_ident: BoxStr,
128-
pub(crate) rust_type: BoxStr,
129-
pub(crate) c_ident: BoxStr,
130-
pub(crate) c_type: BoxStr,
134+
pub test_name: BoxStr,
135+
pub id: BoxStr,
136+
pub rust_val: BoxStr,
137+
pub c_val: BoxStr,
138+
pub rust_ty: BoxStr,
139+
pub c_ty: BoxStr,
131140
}
132141

133142
/// The Rust name of the cstr test.

ctest-next/src/translator.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ pub(crate) enum TranslationErrorKind {
6767
HasLifetimes,
6868

6969
/// A type that is not ffi compatible was found.
70-
#[error("this type is not guaranteed to have a C compatible layout. See improper_ctypes_definitions lint")]
70+
#[error(
71+
"this type is not guaranteed to have a C compatible layout. See improper_ctypes_definitions lint"
72+
)]
7173
NotFfiCompatible,
7274
}
7375

ctest-next/templates/test.c

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,22 @@
1515

1616
{%- for const_cstr in ctx.const_cstr_tests +%}
1717

18-
static {{ const_cstr.c_type }} ctest_const_{{ const_cstr.rust_ident }}_val_static = {{ const_cstr.c_ident }};
18+
static char *ctest_const_{{ const_cstr.id }}_val_static = {{ const_cstr.c_val }};
1919

2020
// Define a function that returns a pointer to the value of the constant to test.
2121
// This will later be called on the Rust side via FFI.
22-
{{ const_cstr.c_type }}* __{{ const_cstr.test_ident }}(void) {
23-
return &ctest_const_{{ const_cstr.rust_ident }}_val_static;
22+
char *ctest_const_cstr__{{ const_cstr.id }}(void) {
23+
return ctest_const_{{ const_cstr.id }}_val_static;
2424
}
2525
{%- endfor +%}
2626

2727
{%- for constant in ctx.const_tests +%}
2828

29-
static {{ constant.c_type }} ctest_const_{{ constant.rust_ident }}_val_static = {{ constant.c_ident }};
29+
static {{ constant.c_ty }} ctest_const_{{ constant.id }}_val_static = {{ constant.c_val }};
3030

3131
// Define a function that returns a pointer to the value of the constant to test.
3232
// This will later be called on the Rust side via FFI.
33-
{{ constant.c_type }}* __{{ constant.test_ident }}(void) {
34-
return &ctest_const_{{ constant.rust_ident }}_val_static;
33+
{{ constant.c_ty }} *ctest_const__{{ constant.id }}(void) {
34+
return &ctest_const_{{ constant.id }}_val_static;
3535
}
3636
{%- endfor +%}
37-

ctest-next/templates/test.rs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,48 +44,60 @@ mod generated_tests {
4444
}
4545
}
4646

47-
{%- for const_cstr in ctx.const_cstr_tests +%}
47+
{%- for const_cstr in ctx.const_cstr_tests +%}
4848

4949
// Test that the string constant is the same in both Rust and C.
5050
// While fat pointers can't be translated, we instead use * const c_char.
51-
pub fn {{ const_cstr.test_ident }}() {
51+
pub fn {{ const_cstr.test_name }}() {
5252
extern "C" {
53-
fn __{{ const_cstr.test_ident }}() -> *const *const u8;
54-
}
55-
let val = {{ const_cstr.rust_ident }};
56-
unsafe {
57-
let ptr = *__{{ const_cstr.test_ident }}();
58-
let val = CStr::from_ptr(ptr.cast::<c_char>());
59-
let val = val.to_str().expect("const {{ const_cstr.rust_ident }} not utf8");
60-
let c = ::std::ffi::CStr::from_ptr(ptr as *const _);
61-
let c = c.to_str().expect("const {{ const_cstr.rust_ident }} not utf8");
62-
check_same(val, c, "{{ const_cstr.rust_ident }} string");
53+
fn ctest_const_cstr__{{ const_cstr.id }}() -> *const c_char;
6354
}
55+
56+
// SAFETY: we assume that `c_char` pointer consts are for C strings.
57+
let r_val = unsafe {
58+
let r_ptr: *const c_char = {{ const_cstr.rust_val }};
59+
assert!(!r_ptr.is_null(), "const {{ const_cstr.rust_val }} is null");
60+
CStr::from_ptr(r_ptr)
61+
};
62+
63+
// SAFETY: FFI call returns a valid C string.
64+
let c_val = unsafe {
65+
let c_ptr: *const c_char = unsafe { ctest_const_cstr__{{ const_cstr.id }}() };
66+
CStr::from_ptr(c_ptr)
67+
};
68+
69+
check_same(r_val, c_val, "const {{ const_cstr.rust_val }} string");
6470
}
65-
{%- endfor +%}
71+
{%- endfor +%}
6672

67-
{%- for constant in ctx.const_tests +%}
73+
{%- for constant in ctx.const_tests +%}
6874

6975
// Test that the value of the constant is the same in both Rust and C.
7076
// This performs a byte by byte comparision of the constant value.
71-
pub fn {{ constant.test_ident }}() {
77+
pub fn {{ constant.test_name }}() {
78+
type T = {{ constant.rust_ty }};
7279
extern "C" {
73-
fn __{{ constant.test_ident }}() -> *const {{ constant.rust_type }};
80+
fn ctest_const__{{ constant.id }}() -> *const T;
7481
}
75-
let val = {{ constant.rust_ident }};
76-
unsafe {
77-
let ptr1 = ptr::from_ref(&val).cast::<u8>();
78-
let ptr2 = __{{ constant.test_ident }}().cast::<u8>();
79-
let ptr1_bytes = slice::from_raw_parts(ptr1, mem::size_of::<{{ constant.rust_type }}>());
80-
let ptr2_bytes = slice::from_raw_parts(ptr2, mem::size_of::<{{ constant.rust_type }}>());
81-
for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() {
82-
// HACK: This may read uninitialized data! We do this because
83-
// there isn't a good way to recursively iterate all fields.
84-
check_same_hex(b1, b2, &format!("{{ constant.rust_ident }} value at byte {}", i));
85-
}
82+
83+
/* HACK: The slices may contian uninitialized data! We do this because
84+
* there isn't a good way to recursively iterate all fields. */
85+
86+
let r_val: T = {{ constant.rust_val }};
87+
let r_bytes = unsafe {
88+
slice::from_raw_parts(ptr::from_ref(&r_val).cast::<u8>(), size_of::<T>())
89+
};
90+
91+
let c_bytes = unsafe {
92+
let c_ptr: *const T = unsafe { ctest_const__{{ constant.id }}() };
93+
slice::from_raw_parts(c_ptr.cast::<u8>(), size_of::<T>())
94+
};
95+
96+
for (i, (&b1, &b2)) in r_bytes.iter().zip(c_bytes.iter()).enumerate() {
97+
check_same_hex(b1, b2, &format!("{{ constant.rust_val }} value at byte {}", i));
8698
}
8799
}
88-
{%- endfor +%}
100+
{%- endfor +%}
89101
}
90102

91103
use generated_tests::*;
@@ -109,4 +121,3 @@ fn run_all() {
109121
{{ test }}();
110122
{%- endfor +%}
111123
}
112-

0 commit comments

Comments
 (0)