Files
Notes/notes-service/vendor/defmt/tests/encode.rs
T
2026-08-01 16:11:49 +03:00

1014 lines
24 KiB
Rust

#![cfg(feature = "unstable-test")]
// NOTE these tests should live in `defmt-macros` but the expansion of the macros defined there
// depend on `defmt` and `defmt` depends on `defmt-macros` -- the circular dependency may get in
// the way of `cargo test`
// NOTE string interning is mocked when testing so that it does not do real interning. Instead
// the "interner" always returns a **7-bit** `u8` value that's bumped on every interning operation.
//
// In practice, this means that the following operation:
// ```
// fn foo(f: &mut Formatter) {
// write!(f, "Hello")
// }
// ```
// writes a *different* index on each call. With real interning this operation always writes the
// same index.
//
// `fetch_string_index` returns the current index of the mocked interner. Use this
// when writing the expected output of a unit test.
//
// ```
// let mut f = Internalexport::make_formatter();
// let index = defmt::export::fetch_string_index();
// foo(&mut f); // NOTE increases the interner index
// assert_eq!(fetch_bytes(), [index]);
//
// let mut f = Internalexport::make_formatter();
// foo(&mut f);
// assert_eq!(fetch_bytes(), [index.wrapping_add(1)]);
// ^^^^^^^^^^^^^^^ account for the previous `foo` call
// ```
//
// The mocked string index is thread local so you can run unit tests in parallel.
// `fetch_string_index` returns the thread-local interner index.
//
// Additional notes:
//
// - the mocked index is 7 bits so its LEB128 encoding is the input byte
use defmt::{export::fetch_string_index, write, Debug2Format, Display2Format, Format, Formatter};
// Increase the 7-bit mocked interned index
fn inc(index: u16, n: u16) -> u16 {
index.wrapping_add(n)
}
fn write_format<T: Format + ?Sized>(val: &T) {
defmt::export::istr(&T::_format_tag());
val._format_data();
}
macro_rules! check {
([$($x:expr),* $(,)?]) => {
{
let mut v = Vec::<u8>::new();
$(
v.extend(&($x).to_le_bytes());
)*
assert_eq!(defmt::export::fetch_bytes(), v);
}
};
}
macro_rules! check_format {
($format:expr, [$($x:expr),* $(,)?] $(,)?) => {
{
let mut v = Vec::<u8>::new();
$(
v.extend(&($x).to_le_bytes());
)*
write_format($format);
assert_eq!(defmt::export::fetch_bytes(), v);
}
}
}
#[test]
fn write() {
let index = fetch_string_index();
let g = defmt::export::make_formatter();
write!(g, "The answer is {=u8}", 42);
check!([
index, // "The answer is {=u8}",
42u8, // u8 value
]);
let g = defmt::export::make_formatter();
write!(g, "The answer is {=?}", 42u8);
check!([
inc(index, 1), // "The answer is {=?}"
inc(index, 2), // "{=u8}" / impl Format for u8
42u8, // u8 value
]);
}
#[test]
fn bitfields_mixed() {
let index = fetch_string_index();
let g = defmt::export::make_formatter();
write!(
g,
"bitfields {0=7..12}, {1=0..5}",
0b1110_0101_1111_0000u16, 0b1111_0000u8
);
check!([
index, // bitfields {0=7..12}, {1=0..5}",
0b1111_0000u8,
0b1110_0101u8, // u16
0b1111_0000u8, // u8
]);
}
#[test]
fn bitfields_across_octets() {
let index = fetch_string_index();
let g = defmt::export::make_formatter();
write!(g, "bitfields {0=0..7} {0=9..14}", 0b0110_0011_1101_0010u16);
check!([
index, // bitfields {0=0..7} {0=9..14}",
0b1101_0010u8,
0b0110_0011u8, // u16
]);
}
#[test]
fn bitfields_truncate_lower() {
let index = fetch_string_index();
let g = defmt::export::make_formatter();
write!(
g,
"bitfields {0=9..14}",
0b0000_0000_0000_1111_0110_0011_1101_0010u32
);
check!([
index, // bitfields {0=9..14}",
0b0110_0011u8, // the first octet should have been truncated away
]);
}
#[test]
fn bitfields_assert_range_exclusive() {
let index = fetch_string_index();
let g = defmt::export::make_formatter();
write!(g, "bitfields {0=6..8}", 0b1010_0101u8,);
check!([
index, // "bitfields {0=6..8}"
0b1010_0101u8
]);
}
#[test]
fn debug_attr_struct() {
#[derive(Debug)]
struct DebugOnly {
_a: i32,
}
#[derive(Format)]
struct X {
y: bool,
#[defmt(Debug2Format)]
d: DebugOnly,
}
let index = fetch_string_index();
check_format!(
&X {
y: false,
d: DebugOnly { _a: 3 }
},
[
index, // "X {{ y: {=bool}, d: {=?} }}"
0b0u8, // y
inc(index, 1), // DebugOnly's format string
b'D', // Text of the Debug output
b'e',
b'b',
b'u',
b'g',
b'O',
b'n',
b'l',
b'y',
b' ',
b'{',
b' ',
b'_',
b'a',
b':',
b' ',
b'3',
b' ',
b'}',
0xffu8
],
)
}
#[test]
fn display_attr_enum() {
use std::fmt;
struct DisplayOnly {}
impl fmt::Display for DisplayOnly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Display")
}
}
#[derive(Format)]
enum X {
#[allow(dead_code)]
Bool(bool),
Display(#[defmt(Display2Format)] DisplayOnly),
}
let index = fetch_string_index();
check_format!(
&X::Display(DisplayOnly {}),
[
index, // "Bool({=bool})|Display({=?})"
0b1u8, // Variant: Display
inc(index, 1), // DisplayOnly's format string
b'D', // Text of the Display output
b'i',
b's',
b'p',
b'l',
b'a',
b'y',
0xffu8
],
)
}
#[test]
fn boolean_struct() {
#[derive(Format)]
struct X {
y: bool,
z: bool,
}
let index = fetch_string_index();
check_format!(
&X { y: false, z: true },
[
index, // "X {{ y: {=bool}, z: {=bool} }}"
0b0u8, // y
0b1u8, // z
],
)
}
#[test]
fn single_struct() {
#[derive(Format)]
struct X {
y: u8,
z: u16,
}
let index = fetch_string_index();
check_format!(
&X { y: 1, z: 2 },
[
index, // "X {{ y: {=u8}, z: {=u16} }}"
1u8, // x
2u8, // y.low
0u8, // y.high
],
)
}
#[test]
fn single_struct_manual() {
// Above `#[derive]`d impl should be equivalent to this:
struct X {
y: u8,
z: u16,
}
impl Format for X {
fn format(&self, f: Formatter) {
defmt::write!(f, "X {{ y: {=u8}, z: {=u16} }}", self.y, self.z)
}
}
let index = fetch_string_index();
check_format!(
&X { y: 1, z: 2 },
[
index, // "{=__internal_FormatSequence}"
inc(index, 1), // "X {{ y: {=u8}, z: {=u16} }}"
1u8, // y
2u16, // z
0u16, // terminator
],
)
}
#[test]
fn single_struct_manual_multiwrite() {
// Above `#[derive]`d impl should be equivalent to this:
struct X {
y: u8,
z: u16,
}
impl Format for X {
fn format(&self, f: Formatter) {
defmt::write!(f, "y={=u8}", self.y);
defmt::write!(f, "z={=u16}", self.z);
}
}
let index = fetch_string_index();
check_format!(
&X { y: 1, z: 2 },
[
index, // "{=__internal_FormatSequence}"
inc(index, 1), // "y={=u8}"
1u8, // y
inc(index, 2), // "z={=u16}"
2u16, // z
0u16, // terminator
],
)
}
#[test]
fn slice_struct_manual_multiwrite() {
// Above `#[derive]`d impl should be equivalent to this:
struct X {
y: u8,
z: u16,
}
impl Format for X {
fn format(&self, f: Formatter) {
defmt::write!(f, "y={=u8}", self.y);
defmt::write!(f, "z={=u16}", self.z);
}
}
let index = fetch_string_index();
check_format!(
&[X { y: 1, z: 2 }, X { y: 3, z: 4 }][..],
[
index, // "{=[?]}"
2u32, // len
inc(index, 1), // "{=__internal_FormatSequence}"
// first element
inc(index, 2), // "y={=u8}"
1u8, // y
inc(index, 3), // "z={=u16}"
2u16, // z
0u16, // terminator
// second element
inc(index, 4), // "y={=u8}"
3u8, // y
inc(index, 5), // "z={=u16}"
4u16, // z
0u16, // terminator
],
)
}
#[test]
fn nested_struct() {
#[derive(Format)]
struct X {
y: Y,
}
#[derive(Format)]
struct Y {
z: u8,
}
let val = 42u8;
let index = fetch_string_index();
check_format!(
&X { y: Y { z: val } },
[
index, // "X {{ y: {=?} }}"
inc(index, 1), // "Y {{ z: {=u8} }}"
val,
],
);
}
#[test]
fn tuple_struct() {
#[derive(Format)]
struct Struct(u8, u16);
let index = fetch_string_index();
check_format!(
&Struct(0x1f, 0xaaaa),
[
index, // "Struct({=u8}, {=u16})"
0x1fu8, // u8
0xaaaau16, // u16
],
);
}
#[test]
fn c_like_enum() {
#[derive(Format)]
#[allow(dead_code)]
enum Enum {
A,
B,
C,
}
let index = fetch_string_index();
check_format!(
&Enum::A,
[
index, //
0u8, // `Enum::A`
],
);
let index = fetch_string_index();
check_format!(
&Enum::B,
[
index, //
1u8, // `Enum::B`
],
);
}
#[test]
#[allow(dead_code)]
fn uninhabited_enum() {
#[derive(Format)]
enum Void {}
}
/// Tests that univariant enums do not encode the discriminant (since the variant in use is always
/// the same).
#[test]
fn univariant_enum() {
#[derive(Format)]
enum NoData {
Variant,
}
let index = fetch_string_index();
check_format!(
&NoData::Variant,
[
index, //
],
);
#[derive(Format)]
enum Data {
Variant(u8, u16),
}
let index = fetch_string_index();
check_format!(
&Data::Variant(0x1f, 0xaaaa),
[
index, //
0x1fu8, // u8
0xaaaau16, // u16
],
);
}
#[test]
fn nested_enum() {
#[derive(Format)]
#[allow(dead_code)]
enum CLike {
A,
B,
C,
}
#[derive(Format)]
enum Inner {
A(CLike, u8),
_B,
}
#[derive(Format)]
enum Outer {
Variant1 { pre: u8, inner: Inner, post: u8 },
Variant2,
Variant3(Inner),
}
let index = fetch_string_index();
check_format!(
&Outer::Variant1 {
pre: 0xEE,
inner: Inner::A(CLike::B, 0x07),
post: 0xAB,
},
[
index, //
0u8, // `Outer::Variant1`
0xEEu8, // u8 pre
inc(index, 1), // `Inner`'s formatting string
0u8, // `Inner::A`
inc(index, 2), // `CLike`'s formatting string
1u8, // `CLike::B`
0x07u8, // u8
0xABu8, // u8 post
],
);
let index = fetch_string_index();
check_format!(
&Outer::Variant2,
[
index, //
1u8, // `Outer::Variant2`
],
);
let index = fetch_string_index();
check_format!(
&Outer::Variant3(Inner::A(CLike::B, 0x07)),
[
index, //
2u8, // `Outer::Variant3`
inc(index, 1), // `Inner`'s formatting string
0u8, // `Inner::A`
inc(index, 2), // `CLike`'s formatting string
1u8, // `CLike::B`
0x07u8, // u8
],
);
}
#[test]
fn slice() {
let index = fetch_string_index();
let val: &[u8] = &[23u8, 42u8];
check_format!(
val,
[
index, // "{=[?]}"
val.len() as u32, // length
inc(index, 1), // "{=u8}"
23u8, // val[0]
42u8, // val[1]
],
)
}
#[test]
fn slice_of_usize() {
let index = fetch_string_index();
let val: &[usize] = &[23usize, 42];
check_format!(
val,
[
index, // "{=[?]}"
val.len() as u32, // length
inc(index, 1), // "{=usize}"
23u32, // val[0]
42u32, // val[1]
],
)
}
#[test]
fn slice_of_bools() {
let index = fetch_string_index();
let val: &[bool] = &[true, true, false];
check_format!(
val,
[
index, // "{=[?]}"
val.len() as u32, // length
inc(index, 1), // "{=bool}"
0b1u8,
0b1u8,
0b0u8,
],
)
}
#[test]
fn format_primitives() {
let index = fetch_string_index();
check_format!(
&42u8,
[
index, // "{=u8}"
42u8,
],
);
check_format!(
&42u16,
[
inc(index, 1), // "{=u16}"
42u16,
],
);
check_format!(
&513u16,
[
inc(index, 2), // "{=u16}"
513u16,
],
);
check_format!(
&42u32,
[
inc(index, 3), // "{=u32}"
42u32,
],
);
check_format!(
&513u32,
[
inc(index, 4), // "{=u32}"
513u32,
],
);
check_format!(
&5.13f32,
[
inc(index, 5), // "{=f32}"
246u8,
40u8,
164u8,
64u8,
],
);
check_format!(
&42i8,
[
inc(index, 6), // "{=i8}"
42u8,
],
);
check_format!(
&-42i8,
[
inc(index, 7), // "{=i8}"
-42i8 as u8,
],
);
check_format!(
&None::<u8>,
[
inc(index, 8), // "<option-format-string>"
0u8, // None discriminant
],
);
check_format!(
&Some(42u8),
[
inc(index, 9), // "<option-format-string>"
1u8, // Some discriminant
inc(index, 10), // "{=u8}"
42u8, // Some.0 field
],
);
check_format!(&-1isize, [inc(index, 11), -1i32]);
check_format!(&-128isize, [inc(index, 12), -128i32]);
check_format!(
&true,
[
inc(index, 13), // "{=bool}"
0b1u8,
],
);
check_format!(
&513u64,
[
inc(index, 14), // "{=u64}"
513u64,
],
);
check_format!(
&-2i64,
[
inc(index, 15), // "{=i64}"
-2i64,
],
);
check_format!(
&'a',
[
inc(index, 16), // "{=char}"
0x61u8,
0x00u8,
0x00u8,
0x00u8,
],
);
}
#[test]
fn istr() {
let index = fetch_string_index();
let interned = defmt::intern!("interned string contents");
check_format!(
&interned,
[
inc(index, 1), // "{=istr}"
index,
],
);
}
#[test]
fn format_arrays() {
let index = fetch_string_index();
let array: [u16; 0] = [];
check_format!(
&array,
[
index, // "{=[?;0]}"
inc(index, 1), // "{=u16}"
]
);
let index = fetch_string_index();
let array: [u16; 3] = [1, 256, 257];
check_format!(
&array,
[
index, // "{=[?;3]}"
inc(index, 1), // "{=u16}"
1u16, // [0]
256u16, // [1]
257u16, // [2]
],
);
}
#[test]
fn format_slice_of_primitives() {
let index = fetch_string_index();
let slice: &[u16] = &[1, 256, 257];
check_format!(
slice,
[
index, // "{=[?]}"
slice.len() as u32, //
inc(index, 1), // "{=u16}"
1u16, // [0]
256u16, // [1]
257u16, // [2]
],
);
}
#[test]
fn format_slice_of_structs() {
#[derive(Format)]
struct X {
y: Y,
}
#[derive(Format)]
struct Y {
z: u8,
}
let index = fetch_string_index();
let slice: &[_] = &[X { y: Y { z: 42 } }, X { y: Y { z: 24 } }];
check_format!(
slice,
[
index, // "{=[?]}"
slice.len() as u32, //
// first element
inc(index, 1), // "X {{ y: {=?} }}"
inc(index, 2), // "Y {{ z: {=u8} }}"
42u8, // [0].y.z
// second element: no outer tag
inc(index, 3), // "Y {{ z: {=u8} }}"
24u8, // [1].y.z
],
);
}
#[test]
fn format_slice_of_slices() {
let index = fetch_string_index();
let slice: &[&[u16]] = &[&[256, 257], &[258, 259, 260]];
check_format!(
slice,
[
index, // "{=[?]}"
slice.len() as u32, //
inc(index, 1), // "{=[?]}"
// first slice
slice[0].len() as u32,
inc(index, 2), // "{=u16}"
256u16, // [0][0]
257u16, // [0][1]
// second slice
slice[1].len() as u32,
inc(index, 3), // "{=u16}"
258u16, // [1][0]
259u16, // [1][1]
260u16, // [1][2]
],
);
}
#[test]
fn format_slice_enum_slice() {
let index = fetch_string_index();
let slice: &[Option<&[u8]>] = &[None, Some(&[42, 43])];
check_format!(
slice,
[
index, // "{=[?]}"
slice.len() as u32, //
// first optional slice
inc(index, 1), // "None|Some({=?})"
0u8, // discriminant
// second optional slice
// omitted: "None|Some({=?})" index
1u8, // discriminant
inc(index, 2), // "{=[?]}" (the ? behind "Some({=?})")
2u32, // length of second optional slice
inc(index, 3), // "{=u8}" (the ? behind "{=[?]}")
42u8,
// omitted: "{=u8}" index
43u8,
],
);
}
#[test]
fn format_slice_enum_generic_struct() {
#[derive(Format)]
struct S<T> {
x: u8,
y: T,
}
let index = fetch_string_index();
let slice: &[Option<S<u8>>] = &[None, Some(S { x: 42, y: 43 })];
check_format!(
slice,
[
index, // "{=[?]}"
slice.len() as u32, //
// first optional element
inc(index, 1), // "None|Some({=?})"
0u8, // discriminant
// second optional element
// omitted: "None|Some({=?})" index
1u8, // discriminant
inc(index, 2), // "S {{ x: {=u8}, y: {=?} }}" (the ? behind "Some({=?})")
42u8, // S.x
inc(index, 3), // "{=u8}" (the ? behind S.y)
43u8, // S. y
],
);
}
#[test]
fn derive_with_bounds() {
#[derive(Format)]
struct S<T: Copy> {
val: T,
}
#[derive(Format)]
struct S2<'a: 'b, 'b> {
a: &'a u8,
b: &'b u8,
}
let index = fetch_string_index();
check_format!(
&S { val: 0 },
[
index, // "S {{ val: {=?} }}"
inc(index, 1), // "{=i32}"
0u32,
],
);
let index = fetch_string_index();
check_format!(
&S2 { a: &1, b: &2 },
[
index, // "S2 { a: {=u8}, b: {=u8} }}"
1u8, 2u8,
],
);
}
#[test]
fn format_bools() {
#[derive(Format)]
struct A(bool);
#[derive(Format)]
struct B(bool);
let index = fetch_string_index();
check_format!(
&(A(true), B(true)),
[
index, // "({=?}, {=?})"
inc(index, 1), // "A({=bool})"
0b1u8, // A
inc(index, 2), // "B({=bool})"
0b1u8, // B
],
);
}
#[test]
fn enum_variants() {
#[allow(dead_code)]
#[derive(Format)]
enum EnumSmall {
A0,
A1,
A2,
}
#[rustfmt::skip]
#[allow(dead_code)]
#[derive(Format)]
enum EnumLarge {
A000, A001, A002, A003, A004, A005, A006, A007, A008, A009, A010, A011, A012, A013, A014, A015, A016, A017,
A018, A019, A020, A021, A022, A023, A024, A025, A026, A027, A028, A029, A030, A031, A032, A033, A034, A035,
A036, A037, A038, A039, A040, A041, A042, A043, A044, A045, A046, A047, A048, A049, A050, A051, A052, A053,
A054, A055, A056, A057, A058, A059, A060, A061, A062, A063, A064, A065, A066, A067, A068, A069, A070, A071,
A072, A073, A074, A075, A076, A077, A078, A079, A080, A081, A082, A083, A084, A085, A086, A087, A088, A089,
A090, A091, A092, A093, A094, A095, A096, A097, A098, A099, A100, A101, A102, A103, A104, A105, A106, A107,
A108, A109, A110, A111, A112, A113, A114, A115, A116, A117, A118, A119, A120, A121, A122, A123, A124, A125,
A126, A127, A128, A129, A130, A131, A132, A133, A134, A135, A136, A137, A138, A139, A140, A141, A142, A143,
A144, A145, A146, A147, A148, A149, A150, A151, A152, A153, A154, A155, A156, A157, A158, A159, A160, A161,
A162, A163, A164, A165, A166, A167, A168, A169, A170, A171, A172, A173, A174, A175, A176, A177, A178, A179,
A180, A181, A182, A183, A184, A185, A186, A187, A188, A189, A190, A191, A192, A193, A194, A195, A196, A197,
A198, A199, A200, A201, A202, A203, A204, A205, A206, A207, A208, A209, A210, A211, A212, A213, A214, A215,
A216, A217, A218, A219, A220, A221, A222, A223, A224, A225, A226, A227, A228, A229, A230, A231, A232, A233,
A234, A235, A236, A237, A238, A239, A240, A241, A242, A243, A244, A245, A246, A247, A248, A249, A250, A251,
A252, A253, A254, A255, A256, A257, A258, A259, A260, A261, A262, A263, A264, A265, A266, A267, A268, A269,
}
let e = EnumSmall::A2;
let index = fetch_string_index();
check_format!(&e, [index, 2u8]);
let e = EnumLarge::A002;
let index = fetch_string_index();
check_format!(&e, [index, 2u16]);
let e = EnumLarge::A269;
let index = fetch_string_index();
check_format!(&e, [index, 269u16]);
}
#[test]
fn derive_str() {
#[derive(Format)]
struct S {
x: &'static str,
}
let s = S { x: "hi" };
let index = fetch_string_index();
check_format!(
&s,
[
index, // "S {{ s: {:str} }}" (NOTE: `s` field is not {:?})
// so no extra format string index here
2u32, // s.x.len()
104u8, // b'h'
105u8, // b'i'
],
);
}
#[test]
fn core_fmt_adapters() {
let index = fetch_string_index();
check_format!(&Debug2Format(&123u8), [index, b'1', b'2', b'3', 0xffu8]);
let index = fetch_string_index();
check_format!(&Display2Format(&123u8), [index, b'1', b'2', b'3', 0xffu8]);
}