diff options
Diffstat (limited to 'src/testkit')
-rw-r--r-- | src/testkit/display.rs | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/testkit/display.rs b/src/testkit/display.rs new file mode 100644 index 0000000..a745b5c --- /dev/null +++ b/src/testkit/display.rs @@ -0,0 +1,166 @@ +//! tools to test the correctness of `yaxpeax-arch` trait implementations. + +use core::fmt; +use core::fmt::Write; + +use crate::display::DisplaySink; + +/// `DisplaySinkValidator` is a `DisplaySink` that panics if invariants required of +/// `DisplaySink`-writing functions are not upheld. +/// +/// there are two categories of invariants that `DisplaySinkValidator` validates. +/// +/// first, this panics if spans are not `span_end_*`-ed in first-in-last-out order with +/// corresponding `span_start_*. second, this panics if `write_lt_*` functions are ever provided +/// inputs longer than the corresponding maximum length. +/// +/// functions that write to a `DisplaySink` are strongly encouraged to come with fuzzing that for +/// all inputs `DisplaySinkValidator` does not panic. +pub struct DisplaySinkValidator { + spans: alloc::vec::Vec<&'static str>, +} + +impl fmt::Write for DisplaySinkValidator { + fn write_str(&mut self, _s: &str) -> Result<(), fmt::Error> { + Ok(()) + } + fn write_char(&mut self, _c: char) -> Result<(), fmt::Error> { + Ok(()) + } +} + +impl DisplaySink for DisplaySinkValidator { + unsafe fn write_lt_32(&mut self, s: &str) -> Result<(), fmt::Error> { + if s.len() >= 32 { + panic!("DisplaySinkValidator::write_lt_32 was given a string longer than the maximum permitted length"); + } + + self.write_str(s) + } + unsafe fn write_lt_16(&mut self, s: &str) -> Result<(), fmt::Error> { + if s.len() >= 16 { + panic!("DisplaySinkValidator::write_lt_16 was given a string longer than the maximum permitted length"); + } + + self.write_str(s) + } + unsafe fn write_lt_8(&mut self, s: &str) -> Result<(), fmt::Error> { + if s.len() >= 8 { + panic!("DisplaySinkValidator::write_lt_8 was given a string longer than the maximum permitted length"); + } + + self.write_str(s) + } + + fn span_start_immediate(&mut self) { + self.spans.push("immediate"); + } + + fn span_end_immediate(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "immediate"); + } + + fn span_start_register(&mut self) { + self.spans.push("register"); + } + + fn span_end_register(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "register"); + } + + fn span_start_opcode(&mut self) { + self.spans.push("opcode"); + } + + fn span_end_opcode(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "opcode"); + } + + fn span_start_program_counter(&mut self) { + self.spans.push("program counter"); + } + + fn span_end_program_counter(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "program counter"); + } + + fn span_start_number(&mut self) { + self.spans.push("number"); + } + + fn span_end_number(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "number"); + } + + fn span_start_address(&mut self) { + self.spans.push("address"); + } + + fn span_end_address(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "address"); + } + + fn span_start_function_expr(&mut self) { + self.spans.push("function expr"); + } + + fn span_end_function_expr(&mut self) { + let last = self.spans.pop().expect("item to pop"); + assert_eq!(last, "function expr"); + } +} + +/// `DisplaySinkWriteComparator` helps test that two `DisplaySink` implementations which should +/// produce the same output actually do. +/// +/// this is most useful for cases like testing specialized `write_lt_*` functions, which ought to +/// behave the same as if `write_str()` were called instead and so can be used as a very simple +/// oracle. +/// +/// this is somewhat less useful when the sinks are expected to produce unequal text, such as when +/// one sink writes ANSI color sequences and the other does not. +pub struct DisplaySinkWriteComparator<'sinks, T: DisplaySink, U: DisplaySink> { + sink1: &'sinks mut T, + sink1_check: fn(&T) -> &str, + sink2: &'sinks mut U, + sink2_check: fn(&U) -> &str, +} + +impl<'sinks, T: DisplaySink, U: DisplaySink> DisplaySinkWriteComparator<'sinks, T, U> { + fn compare_sinks(&self) { + let sink1_text = (self.sink1_check)(self.sink1); + let sink2_text = (self.sink2_check)(self.sink2); + + if sink1_text != sink2_text { + panic!("sinks produced different output: {} != {}", sink1_text, sink2_text); + } + } +} + +impl<'sinks, T: DisplaySink, U: DisplaySink> DisplaySink for DisplaySinkWriteComparator<'sinks, T, U> { + fn write_u8(&mut self, v: u8) -> Result<(), fmt::Error> { + self.sink1.write_u8(v).expect("write to sink1 succeeds"); + self.sink2.write_u8(v).expect("write to sink2 succeeds"); + self.compare_sinks(); + Ok(()) + } +} + +impl<'sinks, T: DisplaySink, U: DisplaySink> fmt::Write for DisplaySinkWriteComparator<'sinks, T, U> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.sink1.write_str(s).expect("write to sink1 succeeds"); + self.sink2.write_str(s).expect("write to sink2 succeeds"); + Ok(()) + } + fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { + self.sink1.write_char(c).expect("write to sink1 succeeds"); + self.sink2.write_char(c).expect("write to sink2 succeeds"); + Ok(()) + } +} |