Skip to content

Commit 2c64edf

Browse files
committed
ctest: Replace check_same_hex with check_same_bytes
This allows us to print the entire slice on failure rather than only a single mismatched byte. Also use this as an opportunity to quote types in failure messages. A possible future improvement here would be to check if the type is an integer in our template and call `check_same` instead if so, which would let us print the decimal value as well.
1 parent bd848b0 commit 2c64edf

File tree

6 files changed

+277
-187
lines changed

6 files changed

+277
-187
lines changed

ctest-test/tests/all.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,26 @@ fn t2() {
4141
let (output, status) = output(&mut cmd("t2"));
4242
assert!(!status.success(), "output: {output}");
4343
let errors = [
44-
"bad T2Foo signed",
45-
"bad T2TypedefFoo signed",
46-
"bad T2TypedefInt signed",
47-
"bad T2Bar size",
48-
"bad T2Bar align",
49-
"bad T2Bar signed",
50-
"bad T2Baz size",
51-
"bad field offset a of T2Baz",
52-
"bad field type a of T2Baz",
53-
"bad field offset b of T2Baz",
54-
"bad field type b of T2Baz",
55-
"bad T2a function pointer",
56-
"bad T2C value at byte 0",
57-
"bad const T2S string",
58-
"bad T2Union size",
59-
"bad field type b of T2Union",
60-
"bad field offset b of T2Union",
61-
"bad enum_wrong_signedness signed",
62-
"bad enum_repr_too_small size",
63-
"bad enum_repr_too_small align",
44+
"bad `T2Foo` signed",
45+
"bad `T2TypedefFoo` signed",
46+
"bad `T2TypedefInt` signed",
47+
"bad `T2Bar` size",
48+
"bad `T2Bar` align",
49+
"bad `T2Bar` signed",
50+
"bad `T2Baz` size",
51+
"bad field offset `a` of `T2Baz`",
52+
"bad field pointer access `a` of `T2Baz`",
53+
"bad field offset `b` of `T2Baz`",
54+
"bad field pointer access `b` of `T2Baz`",
55+
"bad `T2a` function pointer",
56+
"bad `T2C` value at byte 0",
57+
"bad const `T2S` string",
58+
"bad `T2Union` size",
59+
"bad field offset `b` of `T2Union`",
60+
"bad field pointer access `b` of `T2Union`",
61+
"bad `enum_wrong_signedness` signed",
62+
"bad `enum_repr_too_small` size",
63+
"bad `enum_repr_too_small` align",
6464
];
6565
let mut errors = errors.iter().cloned().collect::<HashSet<_>>();
6666

ctest/templates/test.rs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod generated_tests {
1111
#![deny(improper_ctypes_definitions)]
1212
#[allow(unused_imports)]
1313
use std::ffi::{CStr, c_int, c_char, c_uint};
14-
use std::fmt::{Debug, LowerHex};
14+
use std::fmt::{Debug, Write};
1515
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1616
#[allow(unused_imports)]
1717
use std::{mem, ptr, slice};
@@ -35,17 +35,37 @@ mod generated_tests {
3535
}
3636
}
3737

38-
/// Check that the value returned from the Rust and C side in a certain test is equivalent.
39-
///
40-
/// Internally it will remember which checks failed and how many tests have been run. This
41-
/// method is the same as `check_same` but prints errors in bytes in hex.
42-
fn check_same_hex<T: PartialEq + LowerHex + Debug>(rust: T, c: T, attr: &str) {
43-
if rust != c {
44-
eprintln!("bad {attr}: rust: {rust:?} ({rust:#x}) != c {c:?} ({c:#x})");
45-
FAILED.store(true, Ordering::Relaxed);
46-
} else {
38+
fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) {
39+
if rust == c {
4740
NTESTS.fetch_add(1, Ordering::Relaxed);
41+
return;
42+
}
43+
44+
FAILED.store(true, Ordering::Relaxed);
45+
// Buffer to a string so we don't write individual bytes to stdio
46+
let mut s = String::new();
47+
if rust.len() == c.len() {
48+
for (i, (&rb, &cb)) in rust.iter().zip(c.iter()).enumerate() {
49+
if rb != cb {
50+
writeln!(
51+
s, "bad {attr} at byte {i}: rust: {rb:?} ({rb:#x}) != c {cb:?} ({cb:#x})"
52+
).unwrap();
53+
break;
54+
}
55+
}
56+
} else {
57+
writeln!(s, "bad {attr}: rust len {} != c len {}", rust.len(), c.len()).unwrap();
4858
}
59+
60+
write!(s, " rust bytes:").unwrap();
61+
for b in rust {
62+
write!(s, " {b:02x}").unwrap();
63+
}
64+
write!(s, "\n c bytes: ").unwrap();
65+
for b in c {
66+
write!(s, " {b:02x}").unwrap();
67+
}
68+
eprintln!("{s}");
4969
}
5070

5171
{%- for const_cstr in ctx.const_cstr_tests +%}
@@ -60,7 +80,7 @@ mod generated_tests {
6080
// SAFETY: we assume that `c_char` pointer consts are for C strings.
6181
let r_val = unsafe {
6282
let r_ptr: *const c_char = {{ const_cstr.rust_val }};
63-
assert!(!r_ptr.is_null(), "const {{ const_cstr.rust_val }} is null");
83+
assert!(!r_ptr.is_null(), "const `{{ const_cstr.rust_val }}` is null");
6484
CStr::from_ptr(r_ptr)
6585
};
6686

@@ -70,7 +90,7 @@ mod generated_tests {
7090
CStr::from_ptr(c_ptr)
7191
};
7292

73-
check_same(r_val, c_val, "const {{ const_cstr.rust_val }} string");
93+
check_same(r_val, c_val, "const `{{ const_cstr.rust_val }}` string");
7494
}
7595
{%- endfor +%}
7696

@@ -97,9 +117,7 @@ mod generated_tests {
97117
slice::from_raw_parts(c_ptr.cast::<u8>(), size_of::<T>())
98118
};
99119

100-
for (i, (&b1, &b2)) in r_bytes.iter().zip(c_bytes.iter()).enumerate() {
101-
check_same_hex(b1, b2, &format!("{{ constant.rust_val }} value at byte {}", i));
102-
}
120+
check_same_bytes(r_bytes, c_bytes, "`{{ constant.rust_val }}` value");
103121
}
104122
{%- endfor +%}
105123

@@ -118,8 +136,8 @@ mod generated_tests {
118136
let rust_align = align_of::<{{ item.rust_ty }}>() as u64;
119137
let c_align = unsafe { ctest_align_of__{{ item.id }}() };
120138

121-
check_same(rust_size, c_size, "{{ item.id }} size");
122-
check_same(rust_align, c_align, "{{ item.id }} align");
139+
check_same(rust_size, c_size, "`{{ item.id }}` size");
140+
check_same(rust_align, c_align, "`{{ item.id }}` align");
123141
}
124142
{%- endfor +%}
125143

@@ -138,7 +156,7 @@ mod generated_tests {
138156
let all_zeros = 0 as {{ alias.id }};
139157
let c_is_signed = unsafe { ctest_signededness_of__{{ alias.id }}() };
140158

141-
check_same((all_ones < all_zeros) as u32, c_is_signed, "{{ alias.id }} signed");
159+
check_same((all_ones < all_zeros) as u32, c_is_signed, "`{{ alias.id }}` signed");
142160
}
143161
{%- endfor +%}
144162

@@ -163,11 +181,11 @@ mod generated_tests {
163181
// SAFETY: FFI call with no preconditions
164182
let ctest_field_offset = unsafe { ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() };
165183
check_same(offset_of!({{ item.id }}, {{ item.field.ident() }}) as u64, ctest_field_offset,
166-
"field offset {{ item.field.ident() }} of {{ item.id }}");
184+
"field offset `{{ item.field.ident() }}` of `{{ item.id }}`");
167185
// SAFETY: FFI call with no preconditions
168186
let ctest_field_size = unsafe { ctest_size_of__{{ item.id }}__{{ item.field.ident() }}() };
169187
check_same(size_of_val(&val) as u64, ctest_field_size,
170-
"field size {{ item.field.ident() }} of {{ item.id }}");
188+
"field size `{{ item.field.ident() }}` of `{{ item.id }}`");
171189
}
172190
{%- endfor +%}
173191

@@ -188,7 +206,7 @@ mod generated_tests {
188206
// SAFETY: FFI call with no preconditions
189207
let ctest_field_ptr = unsafe { ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}(ty_ptr) };
190208
check_same(field_ptr.cast(), ctest_field_ptr,
191-
"field type {{ item.field.ident() }} of {{ item.id }}");
209+
"field pointer access `{{ item.field.ident() }}` of `{{ item.id }}`");
192210
}
193211

194212
{%- endfor +%}
@@ -279,7 +297,7 @@ mod generated_tests {
279297
if SIZE != c_size {
280298
FAILED.store(true, Ordering::Relaxed);
281299
eprintln!(
282-
"size of {{ item.c_ty }} is {c_size} in C and {SIZE} in Rust\n",
300+
"size of `{{ item.c_ty }}` is {c_size} in C and {SIZE} in Rust\n",
283301
);
284302
return;
285303
}
@@ -295,7 +313,7 @@ mod generated_tests {
295313
let rust = unsafe { *input_ptr.add(i) };
296314
let c = c_value_bytes[i];
297315
if rust != c {
298-
eprintln!("rust[{}] = {} != {} (C): Rust \"{{ item.id }}\" -> C", i, rust, c);
316+
eprintln!("rust[{}] = {} != {} (C): Rust `{{ item.id }}` -> C", i, rust, c);
299317
FAILED.store(true, Ordering::Relaxed);
300318
}
301319
}
@@ -307,7 +325,7 @@ mod generated_tests {
307325
let c = unsafe { (&raw const r).cast::<u8>().add(i).read_volatile() as usize };
308326
if rust != c {
309327
eprintln!(
310-
"rust [{i}] = {rust} != {c} (C): C \"{{ item.id }}\" -> Rust",
328+
"rust [{i}] = {rust} != {c} (C): C `{{ item.id }}` -> Rust",
311329
);
312330
FAILED.store(true, Ordering::Relaxed);
313331
}
@@ -324,7 +342,7 @@ mod generated_tests {
324342
}
325343
let actual = unsafe { ctest_foreign_fn__{{ item.id }}() } as u64;
326344
let expected = {{ item.id }} as u64;
327-
check_same(actual, expected, "{{ item.id }} function pointer");
345+
check_same(actual, expected, "`{{ item.id }}` function pointer");
328346
}
329347
{%- endfor +%}
330348

@@ -339,7 +357,7 @@ mod generated_tests {
339357
let expected = unsafe {
340358
ctest_static__{{ static_.id }}().addr()
341359
};
342-
check_same(actual, expected, "{{ static_.id }} static");
360+
check_same(actual, expected, "`{{ static_.id }}` static");
343361
}
344362
{%- endfor +%}
345363
}

ctest/tests/input/hierarchy.out.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod generated_tests {
88
#![deny(improper_ctypes_definitions)]
99
#[allow(unused_imports)]
1010
use std::ffi::{CStr, c_int, c_char, c_uint};
11-
use std::fmt::{Debug, LowerHex};
11+
use std::fmt::{Debug, Write};
1212
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1313
#[allow(unused_imports)]
1414
use std::{mem, ptr, slice};
@@ -32,17 +32,37 @@ mod generated_tests {
3232
}
3333
}
3434

35-
/// Check that the value returned from the Rust and C side in a certain test is equivalent.
36-
///
37-
/// Internally it will remember which checks failed and how many tests have been run. This
38-
/// method is the same as `check_same` but prints errors in bytes in hex.
39-
fn check_same_hex<T: PartialEq + LowerHex + Debug>(rust: T, c: T, attr: &str) {
40-
if rust != c {
41-
eprintln!("bad {attr}: rust: {rust:?} ({rust:#x}) != c {c:?} ({c:#x})");
42-
FAILED.store(true, Ordering::Relaxed);
43-
} else {
35+
fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) {
36+
if rust == c {
4437
NTESTS.fetch_add(1, Ordering::Relaxed);
38+
return;
39+
}
40+
41+
FAILED.store(true, Ordering::Relaxed);
42+
// Buffer to a string so we don't write individual bytes to stdio
43+
let mut s = String::new();
44+
if rust.len() == c.len() {
45+
for (i, (&rb, &cb)) in rust.iter().zip(c.iter()).enumerate() {
46+
if rb != cb {
47+
writeln!(
48+
s, "bad {attr} at byte {i}: rust: {rb:?} ({rb:#x}) != c {cb:?} ({cb:#x})"
49+
).unwrap();
50+
break;
51+
}
52+
}
53+
} else {
54+
writeln!(s, "bad {attr}: rust len {} != c len {}", rust.len(), c.len()).unwrap();
4555
}
56+
57+
write!(s, " rust bytes:").unwrap();
58+
for b in rust {
59+
write!(s, " {b:02x}").unwrap();
60+
}
61+
write!(s, "\n c bytes: ").unwrap();
62+
for b in c {
63+
write!(s, " {b:02x}").unwrap();
64+
}
65+
eprintln!("{s}");
4666
}
4767

4868
// Test that the value of the constant is the same in both Rust and C.
@@ -66,9 +86,7 @@ mod generated_tests {
6686
slice::from_raw_parts(c_ptr.cast::<u8>(), size_of::<T>())
6787
};
6888

69-
for (i, (&b1, &b2)) in r_bytes.iter().zip(c_bytes.iter()).enumerate() {
70-
check_same_hex(b1, b2, &format!("ON value at byte {}", i));
71-
}
89+
check_same_bytes(r_bytes, c_bytes, "`ON` value");
7290
}
7391

7492
/// Compare the size and alignment of the type in Rust and C, making sure they are the same.
@@ -84,8 +102,8 @@ mod generated_tests {
84102
let rust_align = align_of::<in6_addr>() as u64;
85103
let c_align = unsafe { ctest_align_of__in6_addr() };
86104

87-
check_same(rust_size, c_size, "in6_addr size");
88-
check_same(rust_align, c_align, "in6_addr align");
105+
check_same(rust_size, c_size, "`in6_addr` size");
106+
check_same(rust_align, c_align, "`in6_addr` align");
89107
}
90108

91109
/// Make sure that the signededness of a type alias in Rust and C is the same.
@@ -101,7 +119,7 @@ mod generated_tests {
101119
let all_zeros = 0 as in6_addr;
102120
let c_is_signed = unsafe { ctest_signededness_of__in6_addr() };
103121

104-
check_same((all_ones < all_zeros) as u32, c_is_signed, "in6_addr signed");
122+
check_same((all_ones < all_zeros) as u32, c_is_signed, "`in6_addr` signed");
105123
}
106124

107125
/// Generates a padding map for a specific type.
@@ -179,7 +197,7 @@ mod generated_tests {
179197
if SIZE != c_size {
180198
FAILED.store(true, Ordering::Relaxed);
181199
eprintln!(
182-
"size of in6_addr is {c_size} in C and {SIZE} in Rust\n",
200+
"size of `in6_addr` is {c_size} in C and {SIZE} in Rust\n",
183201
);
184202
return;
185203
}
@@ -195,7 +213,7 @@ mod generated_tests {
195213
let rust = unsafe { *input_ptr.add(i) };
196214
let c = c_value_bytes[i];
197215
if rust != c {
198-
eprintln!("rust[{}] = {} != {} (C): Rust \"in6_addr\" -> C", i, rust, c);
216+
eprintln!("rust[{}] = {} != {} (C): Rust `in6_addr` -> C", i, rust, c);
199217
FAILED.store(true, Ordering::Relaxed);
200218
}
201219
}
@@ -207,7 +225,7 @@ mod generated_tests {
207225
let c = unsafe { (&raw const r).cast::<u8>().add(i).read_volatile() as usize };
208226
if rust != c {
209227
eprintln!(
210-
"rust [{i}] = {rust} != {c} (C): C \"in6_addr\" -> Rust",
228+
"rust [{i}] = {rust} != {c} (C): C `in6_addr` -> Rust",
211229
);
212230
FAILED.store(true, Ordering::Relaxed);
213231
}
@@ -221,7 +239,7 @@ mod generated_tests {
221239
}
222240
let actual = unsafe { ctest_foreign_fn__malloc() } as u64;
223241
let expected = malloc as u64;
224-
check_same(actual, expected, "malloc function pointer");
242+
check_same(actual, expected, "`malloc` function pointer");
225243
}
226244

227245
// Tests if the pointer to the static variable matches in both Rust and C.
@@ -233,7 +251,7 @@ mod generated_tests {
233251
let expected = unsafe {
234252
ctest_static__in6addr_any().addr()
235253
};
236-
check_same(actual, expected, "in6addr_any static");
254+
check_same(actual, expected, "`in6addr_any` static");
237255
}
238256
}
239257

0 commit comments

Comments
 (0)