Michał Zabielski
Michał Zabielski

Reputation: 434

How to test result of core::fmt::Display trait implementation in #![no_std] env

I have following struct and want to test implementation of Display trait:

use core::fmt::{Display, Formatter, Result};

struct B {}

impl Display for B {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "A {} C", "B")
    }
}

#[test]
fn it_works() {
    assert_eq!(format!("{}", B {}), "A B C")
}

it works in general case, however in #![no_std] environment it yields an error due to the fact that format! macro is missing (it allocates, so cannot be used in no_std).

Is there idiomatic way to test core::std::Display trait in no_std scenarios? I've tried to create my own core::std::Formatter instance with custom buffer implementation to somehow circumvent it, and test fmt method directly, but instancing this type is considered compiler internal.

Upvotes: 1

Views: 1157

Answers (3)

Michał Zabielski
Michał Zabielski

Reputation: 434

It's possible to do it without allocation, and without any buffer, just by implementing core::fmt::Write trait, as @Masklinn mentioned.

Here is sample implementation:

#![no_std]
use core::fmt::{Display, Formatter, Result, Write};

struct B {}

impl Display for B {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "A {} C", "B")
    }
}

struct Comparator<'a> {
    valid: bool,
    to_compare: &'a str
}

impl<'a> Comparator<'a> {
    fn new(s: &'a str) -> Self {
        Self { valid: true, to_compare: s }
    }

    fn is_valid(self) -> bool {
        self.valid && self.to_compare.is_empty()
    }
}


impl<'a> Write for Comparator<'a> {
    fn write_str(&mut self, s: &str) -> Result {
        if s.eq(self.to_compare) {
            self.valid = self.valid && true;
            self.to_compare = "";
            return Ok(());
        }

        if self.to_compare.starts_with(s) && self.to_compare.len() >= s.len() {
            self.to_compare = &self.to_compare[s.len()..];
        } else {
            self.valid = false
        }
        Ok(())
    }
}

#[test]
fn it_works() {
    let mut cmp = Comparator::new("A B C");
    let _ = write!(&mut cmp, "{}", B{});
    assert!(cmp.is_valid());
}

#[test]
fn too_short() {
    let mut cmp = Comparator::new("A B");
    let _ = write!(&mut cmp, "{}", B{});
    assert!(!cmp.is_valid());
}

#[test]
fn too_long() {
    let mut cmp = Comparator::new("A B C D");
    let _ = write!(&mut cmp, "{}", B{});
    assert!(!cmp.is_valid());
}

EDIT: fixed bug in the code, was:

 if self.to_compare.starts_with(s) && self.to_compare.len() >= s.len() {
            self.to_compare = &self.to_compare[s.len()..];
            self.valid = true
        } else {
            self.valid = false
        }

fixed:

 if self.to_compare.starts_with(s) && self.to_compare.len() >= s.len() {
            self.to_compare = &self.to_compare[s.len()..];
        } else {
            self.valid = false
        }

Upvotes: 1

Masklinn
Masklinn

Reputation: 42462

Why not just implement core::fmt::Write on your custom buffer and use core::fmt::write! to it?

format! is basically a write! to a string buffer, both really call Write::write_fmt, with format! having the conveniences that it provides its own (string) buffer and panics on error.

macro_rules! write {
    ($dst:expr, $($arg:tt)*) => {
        $dst.write_fmt($crate::format_args!($($arg)*))
    };
}
macro_rules! format {
    ($($arg:tt)*) => {{
        let res = $crate::fmt::format($crate::__export::format_args!($($arg)*));
        res
    }}
}
pub fn format(args: Arguments<'_>) -> string::String {
    let capacity = args.estimated_capacity();
    let mut output = string::String::with_capacity(capacity);
    output.write_fmt(args).expect("a formatting trait implementation returned an error");
    output
}

Upvotes: 2

Chayim Friedman
Chayim Friedman

Reputation: 71210

The best option is to enable std for tests:

#![cfg_attr(no_std, not(test))]

If you cannot do it, and you know an upper limit for the format size, you can implement core::fmt::Write for a u8 array wrapper and using it. The arrayvec has already got you covered with ArrayString:

let mut formatted = ArrayString::<10>::new();
use core::fmt::Write;
write!(formatted, "{}", B {}).expect("failed to format, probably buffer too small");
assert_eq!(&formatted, "A B C")

Playground.

Upvotes: 1

Related Questions