aboutsummaryrefslogtreecommitdiff
path: root/src/testkit/display.rs
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2024-06-22 11:03:43 -0700
committeriximeow <me@iximeow.net>2024-06-22 11:03:59 -0700
commita66be66c22bc31526ac35c1cffdb28992a392ccf (patch)
tree6f49a2e49114256121d3239c2d7223cf635d3c9e /src/testkit/display.rs
parentc21a5f2956d8e0fa3eace14661a8aed124c6e995 (diff)
move DisplaySink code out from yaxpeax-x86
it was built in-place around yaxpeax-x86, hoisted out once it seemed suitable and could be generalized. yay! also include a Makefile in yaxpeax-arch now to test that various crate feature flag combinations.. work.
Diffstat (limited to 'src/testkit/display.rs')
-rw-r--r--src/testkit/display.rs166
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(())
+ }
+}