Skip to content

Commit e019371

Browse files
committed
ctest: add defines for specific headers and language selection
1 parent 586ccc1 commit e019371

File tree

12 files changed

+164
-32
lines changed

12 files changed

+164
-32
lines changed

Cargo.lock

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

ctest/src/generator.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Configuration of the test generator.
22
3+
use std::collections::BTreeMap;
34
use std::env;
45
use std::fs::File;
56
use std::io::Write;
@@ -13,8 +14,8 @@ use crate::ffi_items::FfiItems;
1314
use crate::template::{CTestTemplate, RustTestTemplate};
1415
use crate::translator::translate_primitive_type;
1516
use crate::{
16-
Const, Field, MapInput, Parameter, Result, Static, Struct, TranslationError, Type, Union,
17-
VolatileItemKind, expand,
17+
Const, Field, Language, MapInput, Parameter, Result, Static, Struct, TranslationError, Type,
18+
Union, VolatileItemKind, expand,
1819
};
1920

2021
/// A function that takes a mappable input and returns its mapping as `Some`, otherwise
@@ -35,14 +36,16 @@ type CEnum = Box<dyn Fn(&str) -> bool>;
3536
#[derive(Default)]
3637
#[expect(missing_debug_implementations)]
3738
pub struct TestGenerator {
38-
pub(crate) headers: Vec<String>,
39+
pub(crate) headers: BTreeMap<String, Vec<String>>,
3940
pub(crate) target: Option<String>,
4041
pub(crate) includes: Vec<PathBuf>,
4142
out_dir: Option<PathBuf>,
4243
pub(crate) flags: Vec<String>,
4344
pub(crate) defines: Vec<(String, Option<String>)>,
4445
cfg: Vec<(String, Option<String>)>,
4546
mapped_names: Vec<MappedName>,
47+
/// The programming language to generate tests in.
48+
pub(crate) language: Language,
4649
pub(crate) skips: Vec<Skip>,
4750
pub(crate) verbose_skip: bool,
4851
pub(crate) volatile_items: Vec<VolatileItem>,
@@ -100,7 +103,46 @@ impl TestGenerator {
100103
/// .header("bar.h");
101104
/// ```
102105
pub fn header(&mut self, header: &str) -> &mut Self {
103-
self.headers.push(header.to_string());
106+
self.headers.entry(header.to_string()).or_default();
107+
self
108+
}
109+
110+
/// Add a header to be included as part of the generated C file, as well as defines for it.
111+
///
112+
/// The generated C test will be compiled by a C compiler, and this can be
113+
/// used to ensure that all the necessary header files are included to test
114+
/// all FFI definitions. The defines are only set for the inclusion of that header file, and are
115+
/// undefined immediately after.
116+
///
117+
/// # Examples
118+
///
119+
/// ```no_run
120+
/// use ctest::TestGenerator;
121+
///
122+
/// let mut cfg = TestGenerator::new();
123+
/// cfg.header_with_defines("foo.h", vec![])
124+
/// .header_with_defines("bar.h", vec!["DEBUG", "DEPRECATED"]);
125+
/// ```
126+
pub fn header_with_defines(&mut self, header: &str, defines: Vec<&str>) -> &mut Self {
127+
self.headers
128+
.entry(header.to_string())
129+
.or_default()
130+
.extend(defines.iter().map(|d| d.to_string()));
131+
self
132+
}
133+
134+
/// Sets the programming language, by default it is C.
135+
///
136+
/// # Examples
137+
///
138+
/// ```no_run
139+
/// use ctest::{TestGenerator, Language};
140+
///
141+
/// let mut cfg = TestGenerator::new();
142+
/// cfg.language(Language::CXX);
143+
/// ```
144+
pub fn language(&mut self, language: Language) -> &mut Self {
145+
self.language = language;
104146
self
105147
}
106148

@@ -999,7 +1041,7 @@ impl TestGenerator {
9991041
ensure_trailing_newline(&mut c_file);
10001042

10011043
// Generate the C/Cxx side of the tests.
1002-
let c_output_path = output_file_path.with_extension("c");
1044+
let c_output_path = output_file_path.with_extension(self.language.extension());
10031045
File::create(&c_output_path)
10041046
.map_err(GenerationError::OsError)?
10051047
.write_all(c_file.as_bytes())

ctest/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,35 @@ pub(crate) enum MapInput<'a> {
7474
UnionFieldType(&'a Union, &'a Field),
7575
}
7676

77+
/// The language used to generate the tests.
78+
#[derive(Debug, Default, Clone)]
79+
#[non_exhaustive]
80+
pub enum Language {
81+
/// The C Programming Language.
82+
#[default]
83+
C,
84+
/// The C++ Programming Language.
85+
CXX,
86+
}
87+
88+
impl Language {
89+
/// Return the file extension of the programming language.
90+
pub fn extension(&self) -> &str {
91+
match self {
92+
Self::C => "c",
93+
Self::CXX => "cpp",
94+
}
95+
}
96+
97+
/// Return the linkage to use for each language.
98+
pub fn linkage(&self) -> &str {
99+
match self {
100+
Self::C => "",
101+
Self::CXX => r#"extern "C" "#,
102+
}
103+
}
104+
}
105+
77106
/* The From impls make it easier to write code in the test templates. */
78107

79108
impl<'a> From<&'a Const> for MapInput<'a> {

ctest/src/runner.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
77
use std::process::Command;
88

99
use crate::generator::GenerationError;
10-
use crate::{EDITION, Result, TestGenerator};
10+
use crate::{EDITION, Language, Result, TestGenerator};
1111

1212
/// Generate all tests for the given crate and output the Rust side to a file.
1313
#[doc(hidden)]
@@ -34,7 +34,7 @@ pub fn generate_test(
3434
.map_err(|_| GenerationError::EnvVarNotFound("HOST, HOST_PLATFORM".to_string()))?;
3535

3636
let mut cfg = cc::Build::new();
37-
cfg.file(output_file_path.with_extension("c"));
37+
cfg.file(output_file_path.with_extension(generator.language.extension()));
3838
cfg.host(&host);
3939

4040
if target.contains("msvc") {
@@ -78,6 +78,8 @@ pub fn generate_test(
7878
cfg.define(k, v.as_ref().map(|s| &s[..]));
7979
}
8080

81+
cfg.cpp(matches!(generator.language, Language::CXX));
82+
8183
let stem: &str = output_file_path.file_stem().unwrap().to_str().unwrap();
8284
cfg.target(&target)
8385
.out_dir(output_file_path.parent().unwrap())

ctest/src/template.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Generation of tests from templates for both Rust and C.
22
3+
use std::collections::BTreeMap;
4+
35
use askama::Template;
46
use proc_macro2::Span;
57
use quote::ToTokens;
@@ -35,7 +37,9 @@ impl RustTestTemplate {
3537
#[template(path = "test.c")]
3638
pub(crate) struct CTestTemplate {
3739
pub template: TestTemplate,
38-
pub headers: Vec<String>,
40+
// Use a `BTreeMap` instead of a `HashMap` because order matters.
41+
pub headers: BTreeMap<String, Vec<String>>,
42+
pub linkage: String,
3943
}
4044

4145
impl CTestTemplate {
@@ -46,6 +50,7 @@ impl CTestTemplate {
4650
Ok(Self {
4751
template: TestTemplate::new(ffi_items, generator)?,
4852
headers: generator.headers.clone(),
53+
linkage: generator.language.linkage().to_string(),
4954
})
5055
}
5156
}

ctest/templates/test.c

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,23 @@
88
#include <stdint.h>
99
#include <stdio.h>
1010

11-
{%- for header in self.headers +%}
11+
{%- for header in self.headers.keys() +%}
12+
{%- for define in self.headers[header] +%}
1213

14+
#define {{ define }}
15+
{%- endfor +%}
1316
#include <{{ header }}>
17+
{%- for define in self.headers[header] +%}
18+
#undef {{ define }}
19+
{%- endfor +%}
1420
{%- endfor +%}
1521

22+
#if defined(__cplusplus)
23+
#define CTEST_ALIGNOF(T) alignof(T)
24+
#else
25+
#define CTEST_ALIGNOF(T) _Alignof(T)
26+
#endif
27+
1628
typedef void (*ctest_void_func)(void);
1729

1830
{%- for const_cstr in ctx.const_cstr_tests +%}
@@ -21,7 +33,7 @@ static char *ctest_const_{{ const_cstr.id }}_val_static = {{ const_cstr.c_val }}
2133

2234
// Define a function that returns a pointer to the value of the constant to test.
2335
// This will later be called on the Rust side via FFI.
24-
char *ctest_const_cstr__{{ const_cstr.id }}(void) {
36+
{{ self.linkage }}char *ctest_const_cstr__{{ const_cstr.id }}(void) {
2537
return ctest_const_{{ const_cstr.id }}_val_static;
2638
}
2739
{%- endfor +%}
@@ -32,25 +44,25 @@ static {{ constant.c_ty }} ctest_const_{{ constant.id }}_val_static = {{ constan
3244

3345
// Define a function that returns a pointer to the value of the constant to test.
3446
// This will later be called on the Rust side via FFI.
35-
{{ constant.c_ty }} *ctest_const__{{ constant.id }}(void) {
47+
{{ self.linkage }}{{ constant.c_ty }} *ctest_const__{{ constant.id }}(void) {
3648
return &ctest_const_{{ constant.id }}_val_static;
3749
}
3850
{%- endfor +%}
3951

4052
{%- for item in ctx.size_align_tests +%}
4153

4254
// Return the size of a type.
43-
uint64_t ctest_size_of__{{ item.id }}(void) { return sizeof({{ item.c_ty }}); }
55+
{{ self.linkage }}uint64_t ctest_size_of__{{ item.id }}(void) { return sizeof({{ item.c_ty }}); }
4456

4557
// Return the alignment of a type.
46-
uint64_t ctest_align_of__{{ item.id }}(void) { return _Alignof({{ item.c_ty }}); }
58+
{{ self.linkage }}uint64_t ctest_align_of__{{ item.id }}(void) { return CTEST_ALIGNOF({{ item.c_ty }}); }
4759
{%- endfor +%}
4860

4961
{%- for alias in ctx.signededness_tests +%}
5062

5163
// Return `1` if the type is signed, otherwise return `0`.
5264
// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0`
53-
uint32_t ctest_signededness_of__{{ alias.id }}(void) {
65+
{{ self.linkage }}uint32_t ctest_signededness_of__{{ alias.id }}(void) {
5466
{{ alias.c_ty }} all_ones = ({{ alias.c_ty }}) -1;
5567
return all_ones < 0;
5668
}
@@ -59,12 +71,12 @@ uint32_t ctest_signededness_of__{{ alias.id }}(void) {
5971
{%- for item in ctx.field_size_offset_tests +%}
6072

6173
// Return the offset of a struct/union field.
62-
uint64_t ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}(void) {
74+
{{ self.linkage }}uint64_t ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}(void) {
6375
return offsetof({{ item.c_ty }}, {{ item.c_field }});
6476
}
6577

6678
// Return the size of a struct/union field.
67-
uint64_t ctest_size_of__{{ item.id }}__{{ item.field.ident() }}(void) {
79+
{{ self.linkage }}uint64_t ctest_size_of__{{ item.id }}__{{ item.field.ident() }}(void) {
6880
return sizeof((({{ item.c_ty }}){}).{{ item.c_field }});
6981
}
7082
{%- endfor +%}
@@ -75,7 +87,7 @@ uint64_t ctest_size_of__{{ item.id }}__{{ item.field.ident() }}(void) {
7587
// This field can have a normal data type, or it could be a function pointer or an array, which
7688
// have different syntax. A typedef is used for convenience, but the syntax must be precomputed.
7789
typedef {{ item.volatile_keyword }}{{ item.field_return_type }};
78-
ctest_field_ty__{{ item.id }}__{{ item.field.ident() }}
90+
{{ self.linkage }}ctest_field_ty__{{ item.id }}__{{ item.field.ident() }}
7991
ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) {
8092
return &b->{{ item.c_field }};
8193
}
@@ -92,7 +104,7 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) {
92104
// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
93105
// remains unchanged.
94106
// It checks if the size is the same as well as if the padding bytes are all in the correct place.
95-
{{ item.c_ty }} ctest_roundtrip__{{ item.id }}(
107+
{{ self.linkage }}{{ item.c_ty }} ctest_roundtrip__{{ item.id }}(
96108
{{ item.c_ty }} value,
97109
const uint8_t is_padding_byte[sizeof({{ item.c_ty }})],
98110
uint8_t value_bytes[sizeof({{ item.c_ty }})]
@@ -130,7 +142,8 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) {
130142

131143
{%- for item in ctx.foreign_fn_tests +%}
132144

133-
ctest_void_func ctest_foreign_fn__{{ item.id }}(void) {
145+
// Return a function pointer.
146+
{{ self.linkage }}ctest_void_func ctest_foreign_fn__{{ item.id }}(void) {
134147
return (ctest_void_func){{ item.c_val }};
135148
}
136149
{%- endfor +%}
@@ -142,7 +155,7 @@ ctest_void_func ctest_foreign_fn__{{ item.id }}(void) {
142155
{%- for static_ in ctx.foreign_static_tests +%}
143156

144157
// Return a pointer to the static variable content.
145-
void *ctest_static__{{ static_.id }}(void) {
158+
{{ self.linkage }}void *ctest_static__{{ static_.id }}(void) {
146159
// FIXME(ctest): Not correct due to casting the function to a data pointer.
147160
return (void *)&{{ static_.c_val }};
148161
}

ctest/tests/basic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ fn test_entrypoint_macro() {
130130
let library_path = "macro.out.a";
131131

132132
let (mut gen_, out_dir) = default_generator(1, "macro.h").unwrap();
133+
gen_.header_with_defines("macro.h", vec!["UNSIGNED"]);
134+
133135
check_entrypoint(&mut gen_, out_dir, crate_path, library_path, include_path);
134136
}
135137

ctest/tests/input/hierarchy.out.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
#include <stddef.h>
55
#include <stdint.h>
66
#include <stdio.h>
7-
87
#include <hierarchy.h>
98

9+
#if defined(__cplusplus)
10+
#define CTEST_ALIGNOF(T) alignof(T)
11+
#else
12+
#define CTEST_ALIGNOF(T) _Alignof(T)
13+
#endif
14+
1015
typedef void (*ctest_void_func)(void);
1116

1217
static bool ctest_const_ON_val_static = ON;
@@ -21,7 +26,7 @@ bool *ctest_const__ON(void) {
2126
uint64_t ctest_size_of__in6_addr(void) { return sizeof(in6_addr); }
2227

2328
// Return the alignment of a type.
24-
uint64_t ctest_align_of__in6_addr(void) { return _Alignof(in6_addr); }
29+
uint64_t ctest_align_of__in6_addr(void) { return CTEST_ALIGNOF(in6_addr); }
2530

2631
// Return `1` if the type is signed, otherwise return `0`.
2732
// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0`
@@ -73,6 +78,7 @@ in6_addr ctest_roundtrip__in6_addr(
7378
# pragma warning(disable:4191)
7479
#endif
7580

81+
// Return a function pointer.
7682
ctest_void_func ctest_foreign_fn__malloc(void) {
7783
return (ctest_void_func)malloc;
7884
}

ctest/tests/input/macro.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <stdint.h>
22

3+
#ifdef UNSIGNED
34
struct VecU8
45
{
56
uint8_t x;
@@ -11,3 +12,16 @@ struct VecU16
1112
uint16_t x;
1213
uint16_t y;
1314
};
15+
#else
16+
struct VecI8
17+
{
18+
int8_t x;
19+
int8_t y;
20+
};
21+
22+
struct VecI16
23+
{
24+
int16_t x;
25+
int16_t y;
26+
};
27+
#endif

0 commit comments

Comments
 (0)