Skip to content

Commit 81e4700

Browse files
authored
Merge pull request #4551 from mbyx/ctest-move-template-logic
ctest: extract bindings from template into source
2 parents 6f7da72 + 8f9853b commit 81e4700

File tree

11 files changed

+219
-95
lines changed

11 files changed

+219
-95
lines changed

ctest-next/src/generator.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,9 @@ impl TestGenerator {
605605
.map_err(GenerationError::OsError)?
606606
.write_all(
607607
RustTestTemplate::new(&ffi_items, self)
608+
.map_err(|e| {
609+
GenerationError::TemplateRender("Rust".to_string(), e.to_string())
610+
})?
608611
.render()
609612
.map_err(|e| {
610613
GenerationError::TemplateRender("Rust".to_string(), e.to_string())
@@ -619,6 +622,7 @@ impl TestGenerator {
619622
.map_err(GenerationError::OsError)?
620623
.write_all(
621624
CTestTemplate::new(&ffi_items, self)
625+
.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?
622626
.render()
623627
.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?
624628
.as_bytes(),
@@ -657,12 +661,12 @@ impl TestGenerator {
657661
}
658662

659663
/// Maps Rust identifiers or types to C counterparts, or defaults to the original name.
660-
pub(crate) fn map<'a>(&self, item: impl Into<MapInput<'a>>) -> Result<String, GenerationError> {
664+
pub(crate) fn map<'a>(&self, item: impl Into<MapInput<'a>>) -> String {
661665
let item = item.into();
662666
if let Some(mapped) = self.mapped_names.iter().find_map(|f| f(&item)) {
663-
return Ok(mapped);
667+
return mapped;
664668
}
665-
Ok(match item {
669+
match item {
666670
MapInput::Const(c) => c.ident().to_string(),
667671
MapInput::Fn(f) => f.ident().to_string(),
668672
MapInput::Static(s) => s.ident().to_string(),
@@ -672,6 +676,6 @@ impl TestGenerator {
672676
MapInput::StructType(ty) => format!("struct {ty}"),
673677
MapInput::UnionType(ty) => format!("union {ty}"),
674678
MapInput::Type(ty) => ty.to_string(),
675-
})
679+
}
676680
}
677681
}

ctest-next/src/template.rs

Lines changed: 139 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,185 @@ use askama::Template;
22
use quote::ToTokens;
33

44
use crate::ffi_items::FfiItems;
5-
use crate::generator::GenerationError;
65
use crate::translator::Translator;
7-
use crate::{MapInput, Result, TestGenerator};
6+
use crate::{BoxStr, MapInput, Result, TestGenerator, TranslationError};
87

98
/// Represents the Rust side of the generated testing suite.
109
#[derive(Template, Clone)]
1110
#[template(path = "test.rs")]
12-
pub(crate) struct RustTestTemplate<'a> {
13-
ffi_items: &'a FfiItems,
14-
#[expect(unused)]
15-
generator: &'a TestGenerator,
11+
pub(crate) struct RustTestTemplate {
12+
pub(crate) template: TestTemplate,
13+
}
14+
15+
impl RustTestTemplate {
16+
pub(crate) fn new(
17+
ffi_items: &FfiItems,
18+
generator: &TestGenerator,
19+
) -> Result<Self, TranslationError> {
20+
Ok(Self {
21+
template: TestTemplate::new(ffi_items, generator)?,
22+
})
23+
}
1624
}
1725

1826
/// Represents the C side of the generated testing suite.
1927
#[derive(Template, Clone)]
2028
#[template(path = "test.c")]
21-
pub(crate) struct CTestTemplate<'a> {
22-
translator: Translator,
23-
ffi_items: &'a FfiItems,
24-
generator: &'a TestGenerator,
29+
pub(crate) struct CTestTemplate {
30+
pub(crate) template: TestTemplate,
31+
pub(crate) headers: Vec<String>,
2532
}
2633

27-
impl<'a> RustTestTemplate<'a> {
28-
/// Create a new test template to test the given items.
29-
pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
30-
Self {
31-
ffi_items,
32-
generator,
33-
}
34+
impl CTestTemplate {
35+
pub(crate) fn new(
36+
ffi_items: &FfiItems,
37+
generator: &TestGenerator,
38+
) -> Result<Self, TranslationError> {
39+
Ok(Self {
40+
template: TestTemplate::new(ffi_items, generator)?,
41+
headers: generator.headers.clone(),
42+
})
3443
}
3544
}
3645

37-
impl<'a> CTestTemplate<'a> {
38-
/// Create a new test template to test the given items.
39-
pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
40-
Self {
46+
/// Stores all information necessary for generation of tests for all items.
47+
#[derive(Clone, Debug, Default)]
48+
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>,
52+
}
53+
54+
impl TestTemplate {
55+
pub(crate) fn new(
56+
ffi_items: &FfiItems,
57+
generator: &TestGenerator,
58+
) -> Result<Self, TranslationError> {
59+
let helper = TranslateHelper {
4160
ffi_items,
42-
translator: Translator::new(),
4361
generator,
62+
translator: Translator::new(),
63+
};
64+
65+
/* Figure out which tests are to be generated. */
66+
// FIXME(ctest): Populate more test information, maybe extract into separate methods.
67+
// The workflow would be to create a struct that stores information for the new test,
68+
// and populating that struct here, so that the also things that have to be added to
69+
// the test templates are the new tests parameterized by that struct.
70+
71+
let mut const_tests = vec![];
72+
let mut const_cstr_tests = vec![];
73+
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+
}
90+
} else {
91+
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(),
97+
};
98+
const_tests.push(item)
99+
}
44100
}
101+
102+
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()));
105+
106+
Ok(Self {
107+
const_cstr_tests,
108+
const_tests,
109+
test_idents,
110+
})
45111
}
112+
}
113+
114+
/// Information required to test a constant CStr.
115+
#[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,
121+
}
122+
123+
/// Information required to test a constant.
124+
#[derive(Clone, Debug)]
125+
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,
131+
}
132+
133+
/// The Rust name of the cstr test.
134+
///
135+
/// The C name of this same test is the same with `__` prepended.
136+
fn cstr_test_ident(ident: &str) -> BoxStr {
137+
format!("ctest_const_cstr_{ident}").into()
138+
}
139+
140+
/// The Rust name of the const test.
141+
///
142+
/// The C name of this test is the same with `__` prepended.
143+
fn const_test_ident(ident: &str) -> BoxStr {
144+
format!("ctest_const_{ident}").into()
145+
}
46146

147+
/// Wrap methods that depend on both ffi items and the generator.
148+
struct TranslateHelper<'a> {
149+
ffi_items: &'a FfiItems,
150+
generator: &'a TestGenerator,
151+
translator: Translator,
152+
}
153+
154+
impl<'a> TranslateHelper<'a> {
47155
/// Returns the equivalent C/Cpp identifier of the Rust item.
48-
pub(crate) fn c_ident(&self, item: impl Into<MapInput<'a>>) -> Result<String, GenerationError> {
156+
pub(crate) fn c_ident(&self, item: impl Into<MapInput<'a>>) -> String {
49157
self.generator.map(item)
50158
}
51159

52160
/// Returns the equivalent C/Cpp type of the Rust item.
53-
pub(crate) fn c_type(&self, item: impl Into<MapInput<'a>>) -> Result<String, GenerationError> {
54-
let item: MapInput<'a> = item.into();
161+
pub(crate) fn c_type(&self, item: impl Into<MapInput<'a>>) -> Result<String, TranslationError> {
162+
let item: MapInput = item.into();
55163

56164
let (ident, ty) = match item {
57-
MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)),
58-
MapInput::Alias(a) => (a.ident(), self.translator.translate_type(&a.ty)),
59-
MapInput::Field(_, f) => (f.ident(), self.translator.translate_type(&f.ty)),
60-
MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)),
165+
MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)?),
166+
MapInput::Alias(a) => (a.ident(), self.translator.translate_type(&a.ty)?),
167+
MapInput::Field(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?),
168+
MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)?),
61169
MapInput::Fn(_) => unimplemented!(),
62170
MapInput::Struct(_) => unimplemented!(),
63171
MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"),
64172
MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"),
65173
MapInput::Type(_) => panic!("MapInput::Type is not allowed!"),
66174
};
67175

68-
let ty = ty.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?;
69-
70176
let item = if self.ffi_items.contains_struct(ident) {
71177
MapInput::StructType(&ty)
72178
} else if self.ffi_items.contains_union(ident) {
73179
MapInput::UnionType(&ty)
74180
} else {
75181
MapInput::Type(&ty)
76182
};
77-
self.generator.map(item)
183+
184+
Ok(self.generator.map(item))
78185
}
79186
}

ctest-next/templates/test.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
11
/* This file was autogenerated by ctest; do not modify directly */
22
{#-Doesn't apply here, this is the template! +#}
33

4+
{%- let ctx = self.template +%}
5+
46
#include <stdbool.h>
57
#include <stddef.h>
68
#include <stdint.h>
79
#include <stdio.h>
810

9-
{%- for header in generator.headers +%}
11+
{%- for header in self.headers +%}
12+
1013
#include <{{ header }}>
1114
{%- endfor +%}
1215

13-
{%- for constant in ffi_items.constants() +%}
14-
{%- let ident = constant.ident() +%}
15-
{%- let c_type = self.c_type(*constant)? +%}
16-
{%- let c_ident = self.c_ident(*constant)? +%}
16+
{%- for const_cstr in ctx.const_cstr_tests +%}
17+
18+
static {{ const_cstr.c_type }} ctest_const_{{ const_cstr.rust_ident }}_val_static = {{ const_cstr.c_ident }};
19+
20+
// Define a function that returns a pointer to the value of the constant to test.
21+
// 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;
24+
}
25+
{%- endfor +%}
26+
27+
{%- for constant in ctx.const_tests +%}
1728

18-
static {{ c_type }} __test_const_{{ ident }}_val = {{ c_ident }};
29+
static {{ constant.c_type }} ctest_const_{{ constant.rust_ident }}_val_static = {{ constant.c_ident }};
1930

2031
// Define a function that returns a pointer to the value of the constant to test.
2132
// This will later be called on the Rust side via FFI.
22-
{{ c_type }}* __test_const_{{ ident }}(void) {
23-
return &__test_const_{{ ident }}_val;
33+
{{ constant.c_type }}* __{{ constant.test_ident }}(void) {
34+
return &ctest_const_{{ constant.rust_ident }}_val_static;
2435
}
2536
{%- endfor +%}
2637

ctest-next/templates/test.rs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* This file was autogenerated by ctest; do not modify directly */
22
{#- ↑ Doesn't apply here, this is the template! +#}
33

4+
{%- let ctx = self.template +%}
5+
46
/// As this file is sometimes built using rustc, crate level attributes
57
/// are not allowed at the top-level, so we hack around this by keeping it
68
/// inside of a module.
@@ -42,51 +44,47 @@ mod generated_tests {
4244
}
4345
}
4446

45-
{%- for constant in ffi_items.constants() +%}
46-
{%- let ty = constant.ty.to_token_stream().to_string() +%}
47-
{%- let ident = constant.ident() +%}
48-
49-
{%- if ty == "* const c_char" +%}
47+
{%- for const_cstr in ctx.const_cstr_tests +%}
5048

5149
// Test that the string constant is the same in both Rust and C.
5250
// While fat pointers can't be translated, we instead use * const c_char.
53-
pub fn const_{{ ident }}() {
51+
pub fn {{ const_cstr.test_ident }}() {
5452
extern "C" {
55-
fn __test_const_{{ ident }}() -> *const *const u8;
53+
fn __{{ const_cstr.test_ident }}() -> *const *const u8;
5654
}
57-
let val = {{ ident }};
55+
let val = {{ const_cstr.rust_ident }};
5856
unsafe {
59-
let ptr = *__test_const_{{ ident }}();
57+
let ptr = *__{{ const_cstr.test_ident }}();
6058
let val = CStr::from_ptr(ptr.cast::<c_char>());
61-
let val = val.to_str().expect("const {{ ident }} not utf8");
59+
let val = val.to_str().expect("const {{ const_cstr.rust_ident }} not utf8");
6260
let c = ::std::ffi::CStr::from_ptr(ptr as *const _);
63-
let c = c.to_str().expect("const {{ ident }} not utf8");
64-
check_same(val, c, "{{ ident }} string");
61+
let c = c.to_str().expect("const {{ const_cstr.rust_ident }} not utf8");
62+
check_same(val, c, "{{ const_cstr.rust_ident }} string");
6563
}
6664
}
65+
{%- endfor +%}
6766

68-
{%- else +%}
67+
{%- for constant in ctx.const_tests +%}
6968

7069
// Test that the value of the constant is the same in both Rust and C.
7170
// This performs a byte by byte comparision of the constant value.
72-
pub fn const_{{ ident }}() {
71+
pub fn {{ constant.test_ident }}() {
7372
extern "C" {
74-
fn __test_const_{{ ident }}() -> *const {{ ty }};
73+
fn __{{ constant.test_ident }}() -> *const {{ constant.rust_type }};
7574
}
76-
let val = {{ ident }};
75+
let val = {{ constant.rust_ident }};
7776
unsafe {
7877
let ptr1 = ptr::from_ref(&val).cast::<u8>();
79-
let ptr2 = __test_const_{{ ident }}().cast::<u8>();
80-
let ptr1_bytes = slice::from_raw_parts(ptr1, mem::size_of::<{{ ty }}>());
81-
let ptr2_bytes = slice::from_raw_parts(ptr2, mem::size_of::<{{ ty }}>());
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 }}>());
8281
for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() {
8382
// HACK: This may read uninitialized data! We do this because
8483
// there isn't a good way to recursively iterate all fields.
85-
check_same_hex(b1, b2, &format!("{{ ident }} value at byte {}", i));
84+
check_same_hex(b1, b2, &format!("{{ constant.rust_ident }} value at byte {}", i));
8685
}
8786
}
8887
}
89-
{%- endif +%}
9088
{%- endfor +%}
9189
}
9290

@@ -107,8 +105,8 @@ fn main() {
107105

108106
// Run all tests by calling the functions that define them.
109107
fn run_all() {
110-
{%- for constant in ffi_items.constants() +%}
111-
const_{{ constant.ident() }}();
108+
{%- for test in ctx.test_idents +%}
109+
{{ test }}();
112110
{%- endfor +%}
113111
}
114112

0 commit comments

Comments
 (0)