From 9dffd607455fac176a2eece2571d3c49901175f4 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 3 Dec 2019 06:26:48 -0500 Subject: start restructuring the test suite --- Cargo.toml | 4 + tests/helpers/fixtures.rs | 320 ++++++++++++++++++++++++++++++++++++++++++++++ tests/helpers/mod.rs | 216 +++++++++++++++++++++++++++++++ tests/window_contents.rs | 186 ++++++--------------------- 4 files changed, 576 insertions(+), 150 deletions(-) create mode 100644 tests/helpers/fixtures.rs create mode 100644 tests/helpers/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 0232570..9e60834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,7 @@ log = "0.4" unicode-normalization = { version = "0.1", optional = true } unicode-width = "0.1" vte = "0.3" + +[dev-dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/tests/helpers/fixtures.rs b/tests/helpers/fixtures.rs new file mode 100644 index 0000000..8f9bfdb --- /dev/null +++ b/tests/helpers/fixtures.rs @@ -0,0 +1,320 @@ +use serde::de::Deserialize as _; +use std::io::Read as _; + +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +pub struct FixtureCell { + contents: String, + #[serde(default, skip_serializing_if = "is_default")] + is_wide: bool, + #[serde(default, skip_serializing_if = "is_default")] + is_wide_continuation: bool, + #[serde( + default, + deserialize_with = "deserialize_color", + serialize_with = "serialize_color", + skip_serializing_if = "is_default" + )] + fgcolor: vt100::Color, + #[serde( + default, + deserialize_with = "deserialize_color", + serialize_with = "serialize_color", + skip_serializing_if = "is_default" + )] + bgcolor: vt100::Color, + #[serde(default, skip_serializing_if = "is_default")] + bold: bool, + #[serde(default, skip_serializing_if = "is_default")] + italic: bool, + #[serde(default, skip_serializing_if = "is_default")] + underline: bool, + #[serde(default, skip_serializing_if = "is_default")] + inverse: bool, +} + +impl FixtureCell { + pub fn from_cell(cell: &vt100::Cell) -> Self { + Self { + contents: cell.contents(), + is_wide: cell.is_wide(), + is_wide_continuation: cell.is_wide_continuation(), + fgcolor: cell.fgcolor(), + bgcolor: cell.bgcolor(), + bold: cell.bold(), + italic: cell.italic(), + underline: cell.underline(), + inverse: cell.inverse(), + } + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct FixtureScreen { + contents: String, + cells: std::collections::BTreeMap, + cursor_position: (u16, u16), + #[serde(default, skip_serializing_if = "is_default")] + title: String, + #[serde(default, skip_serializing_if = "is_default")] + icon_name: String, + #[serde(default, skip_serializing_if = "is_default")] + application_keypad: bool, + #[serde(default, skip_serializing_if = "is_default")] + application_cursor: bool, + #[serde(default, skip_serializing_if = "is_default")] + hide_cursor: bool, + #[serde(default, skip_serializing_if = "is_default")] + bracketed_paste: bool, + #[serde( + default, + deserialize_with = "deserialize_mouse_protocol_mode", + serialize_with = "serialize_mouse_protocol_mode", + skip_serializing_if = "is_default" + )] + mouse_protocol_mode: vt100::MouseProtocolMode, + #[serde( + default, + deserialize_with = "deserialize_mouse_protocol_encoding", + serialize_with = "serialize_mouse_protocol_encoding", + skip_serializing_if = "is_default" + )] + mouse_protocol_encoding: vt100::MouseProtocolEncoding, +} + +impl FixtureScreen { + fn load(r: R) -> Self { + serde_json::from_reader(r).unwrap() + } + + #[allow(dead_code)] + pub fn from_screen(screen: &vt100::Screen) -> Self { + let mut cells = std::collections::BTreeMap::new(); + let (rows, cols) = screen.size(); + for row in 0..rows { + for col in 0..cols { + let cell = screen.cell(row, col).unwrap(); + if cell != &vt100::Cell::default() { + cells.insert( + format!("{},{}", row, col), + FixtureCell::from_cell(cell), + ); + } + } + } + Self { + contents: screen.contents(), + cells, + cursor_position: screen.cursor_position(), + title: screen.title().to_string(), + icon_name: screen.icon_name().to_string(), + application_keypad: screen.application_keypad(), + application_cursor: screen.application_cursor(), + hide_cursor: screen.hide_cursor(), + bracketed_paste: screen.bracketed_paste(), + mouse_protocol_mode: screen.mouse_protocol_mode(), + mouse_protocol_encoding: screen.mouse_protocol_encoding(), + } + } +} + +fn is_default(t: &T) -> bool { + t == &T::default() +} + +fn deserialize_color<'a, D>( + deserializer: D, +) -> std::result::Result +where + D: serde::de::Deserializer<'a>, +{ + let val = >::deserialize(deserializer)?; + match val { + None => Ok(vt100::Color::Default), + Some(x) if x.starts_with('#') => { + let x = x.as_bytes(); + if x.len() != 7 { + return Err(serde::de::Error::custom("invalid rgb color")); + } + let r = + super::hex(x[1], x[2]).map_err(serde::de::Error::custom)?; + let g = + super::hex(x[3], x[4]).map_err(serde::de::Error::custom)?; + let b = + super::hex(x[5], x[6]).map_err(serde::de::Error::custom)?; + Ok(vt100::Color::Rgb(r, g, b)) + } + Some(x) => Ok(vt100::Color::Idx( + x.parse().map_err(serde::de::Error::custom)?, + )), + } +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn serialize_color( + color: &vt100::Color, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + let s = match color { + vt100::Color::Default => unreachable!(), + vt100::Color::Idx(n) => format!("{}", n), + vt100::Color::Rgb(r, g, b) => format!("#{:02x}{:02x}{:02x}", r, g, b), + }; + serializer.serialize_str(&s) +} + +fn deserialize_mouse_protocol_mode<'a, D>( + deserializer: D, +) -> std::result::Result +where + D: serde::de::Deserializer<'a>, +{ + let name = ::deserialize(deserializer)?; + match name.as_ref() { + "none" => Ok(vt100::MouseProtocolMode::None), + "press" => Ok(vt100::MouseProtocolMode::Press), + "press_release" => Ok(vt100::MouseProtocolMode::PressRelease), + "button_motion" => Ok(vt100::MouseProtocolMode::ButtonMotion), + "any_motion" => Ok(vt100::MouseProtocolMode::AnyMotion), + _ => unimplemented!(), + } +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn serialize_mouse_protocol_mode( + mode: &vt100::MouseProtocolMode, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + let s = match mode { + vt100::MouseProtocolMode::None => "none", + vt100::MouseProtocolMode::Press => "press", + vt100::MouseProtocolMode::PressRelease => "press_release", + vt100::MouseProtocolMode::ButtonMotion => "button_motion", + vt100::MouseProtocolMode::AnyMotion => "any_motion", + }; + serializer.serialize_str(s) +} + +fn deserialize_mouse_protocol_encoding<'a, D>( + deserializer: D, +) -> std::result::Result +where + D: serde::de::Deserializer<'a>, +{ + let name = ::deserialize(deserializer)?; + match name.as_ref() { + "default" => Ok(vt100::MouseProtocolEncoding::Default), + "utf8" => Ok(vt100::MouseProtocolEncoding::Utf8), + "sgr" => Ok(vt100::MouseProtocolEncoding::Sgr), + _ => unimplemented!(), + } +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn serialize_mouse_protocol_encoding( + encoding: &vt100::MouseProtocolEncoding, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + let s = match encoding { + vt100::MouseProtocolEncoding::Default => "default", + vt100::MouseProtocolEncoding::Utf8 => "utf8", + vt100::MouseProtocolEncoding::Sgr => "sgr", + }; + serializer.serialize_str(s) +} + +fn load_input(name: &str, i: usize) -> Option> { + let mut file = std::fs::File::open(format!( + "tests/data/fixtures/{}/{}.typescript", + name, i + )) + .ok()?; + let mut input = vec![]; + file.read_to_end(&mut input).unwrap(); + Some(input) +} + +fn load_screen(name: &str, i: usize) -> Option { + let mut file = std::fs::File::open(format!( + "tests/data/fixtures/{}/{}.json", + name, i + )) + .ok()?; + Some(FixtureScreen::load(&mut file)) +} + +fn assert_produces(input: &[u8], expected: &FixtureScreen) { + let mut parser = vt100::Parser::default(); + parser.process(input); + + assert_eq!(parser.screen().contents(), expected.contents); + assert_eq!(parser.screen().cursor_position(), expected.cursor_position); + assert_eq!(parser.screen().title(), expected.title); + assert_eq!(parser.screen().icon_name(), expected.icon_name); + assert_eq!( + parser.screen().application_keypad(), + expected.application_keypad + ); + assert_eq!( + parser.screen().application_cursor(), + expected.application_cursor + ); + assert_eq!(parser.screen().hide_cursor(), expected.hide_cursor); + assert_eq!(parser.screen().bracketed_paste(), expected.bracketed_paste); + assert_eq!( + parser.screen().mouse_protocol_mode(), + expected.mouse_protocol_mode + ); + assert_eq!( + parser.screen().mouse_protocol_encoding(), + expected.mouse_protocol_encoding + ); + + let (rows, cols) = parser.screen().size(); + for row in 0..rows { + for col in 0..cols { + let expected_cell = expected + .cells + .get(&format!("{},{}", row, col)) + .cloned() + .unwrap_or_else(FixtureCell::default); + let got_cell = parser.screen().cell(row, col).unwrap(); + assert_eq!(got_cell.contents(), expected_cell.contents); + assert_eq!(got_cell.is_wide(), expected_cell.is_wide); + assert_eq!( + got_cell.is_wide_continuation(), + expected_cell.is_wide_continuation + ); + assert_eq!(got_cell.fgcolor(), expected_cell.fgcolor); + assert_eq!(got_cell.bgcolor(), expected_cell.bgcolor); + assert_eq!(got_cell.bold(), expected_cell.bold); + assert_eq!(got_cell.italic(), expected_cell.italic); + assert_eq!(got_cell.underline(), expected_cell.underline); + assert_eq!(got_cell.inverse(), expected_cell.inverse); + } + } +} + +#[allow(dead_code)] +pub fn fixture(name: &str) { + let mut i = 1; + let mut prev_input = vec![]; + while let Some(input) = load_input(name, i) { + super::assert_reproduces_state_from(&input, &prev_input); + prev_input.extend(input); + + let expected = load_screen(name, i).unwrap(); + assert_produces(&prev_input, &expected); + + i += 1; + } + assert!(i > 1, "couldn't find fixtures to test"); +} diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs new file mode 100644 index 0000000..8ec0ac3 --- /dev/null +++ b/tests/helpers/mod.rs @@ -0,0 +1,216 @@ +mod fixtures; +pub use fixtures::fixture; +pub use fixtures::FixtureScreen; + +macro_rules! is { + ($got:expr, $expected:expr) => { + if ($got) != ($expected) { + eprintln!("{} != {}:", stringify!($got), stringify!($expected)); + eprintln!(" got: {:?}", $got); + eprintln!("expected: {:?}", $expected); + return false; + } + }; +} +macro_rules! ok { + ($e:expr) => { + if !($e) { + eprintln!("!{}", stringify!($e)); + return false; + } + }; +} + +pub fn compare_screens( + got: &vt100::Screen, + expected: &vt100::Screen, +) -> bool { + is!(got.contents(), expected.contents()); + is!(got.contents_formatted(), expected.contents_formatted()); + is!( + got.contents_diff(vt100::Parser::default().screen()), + expected.contents_diff(vt100::Parser::default().screen()) + ); + + let (rows, cols) = got.size(); + + for row in 0..rows { + for col in 0..cols { + let expected_cell = expected.cell(row, col); + let got_cell = got.cell(row, col); + is!(got_cell, expected_cell); + } + } + + is!(got.cursor_position(), expected.cursor_position()); + ok!(got.cursor_position().0 <= rows); + ok!(expected.cursor_position().0 <= rows); + ok!(got.cursor_position().1 <= cols); + ok!(expected.cursor_position().1 <= cols); + + is!(got.title(), expected.title()); + is!(got.icon_name(), expected.icon_name()); + + is!( + got.audible_bell_count() > 0, + expected.audible_bell_count() > 0 + ); + is!( + got.visual_bell_count() > 0, + expected.visual_bell_count() > 0 + ); + + is!(got.application_keypad(), expected.application_keypad()); + is!(got.application_cursor(), expected.application_cursor()); + is!(got.hide_cursor(), expected.hide_cursor()); + is!(got.bracketed_paste(), expected.bracketed_paste()); + is!(got.mouse_protocol_mode(), expected.mouse_protocol_mode()); + is!( + got.mouse_protocol_encoding(), + expected.mouse_protocol_encoding() + ); + + true +} + +#[allow(dead_code)] +pub fn contents_formatted_reproduces_state(input: &[u8]) -> bool { + let mut parser = vt100::Parser::default(); + parser.process(input); + contents_formatted_reproduces_screen(parser.screen()) +} + +pub fn contents_formatted_reproduces_screen(screen: &vt100::Screen) -> bool { + let empty_screen = vt100::Parser::default().screen().clone(); + + let mut new_input = screen.contents_formatted(); + new_input.extend(screen.input_mode_formatted()); + new_input.extend(screen.title_formatted()); + new_input.extend(screen.bells_diff(&empty_screen)); + let mut new_parser = vt100::Parser::default(); + new_parser.process(&new_input); + let got_screen = new_parser.screen().clone(); + + compare_screens(&got_screen, &screen) +} + +fn assert_contents_formatted_reproduces_state(input: &[u8]) { + assert!(contents_formatted_reproduces_state(input)); +} + +#[allow(dead_code)] +pub fn contents_diff_reproduces_state(input: &[u8]) -> bool { + contents_diff_reproduces_state_from(input, &[]) +} + +pub fn contents_diff_reproduces_state_from( + input: &[u8], + prev_input: &[u8], +) -> bool { + let mut parser = vt100::Parser::default(); + parser.process(prev_input); + let prev_screen = parser.screen().clone(); + parser.process(input); + + contents_diff_reproduces_state_from_screens(&prev_screen, parser.screen()) +} + +pub fn contents_diff_reproduces_state_from_screens( + prev_screen: &vt100::Screen, + screen: &vt100::Screen, +) -> bool { + let mut diff_input = screen.contents_diff(&prev_screen); + diff_input.extend(screen.input_mode_diff(&prev_screen)); + diff_input.extend(screen.title_diff(&prev_screen)); + diff_input.extend(screen.bells_diff(&prev_screen)); + + let mut diff_prev_input = prev_screen.contents_formatted(); + diff_prev_input.extend(screen.input_mode_formatted()); + diff_prev_input.extend(screen.title_formatted()); + diff_prev_input + .extend(screen.bells_diff(vt100::Parser::default().screen())); + + let mut new_parser = vt100::Parser::default(); + new_parser.process(&diff_prev_input); + new_parser.process(&diff_input); + let got_screen = new_parser.screen().clone(); + + compare_screens(&got_screen, &screen) +} + +#[allow(dead_code)] +pub fn assert_contents_diff_reproduces_state_from_screens( + prev_screen: &vt100::Screen, + screen: &vt100::Screen, +) { + assert!(contents_diff_reproduces_state_from_screens( + prev_screen, + screen, + )); +} + +fn assert_contents_diff_reproduces_state_from( + input: &[u8], + prev_input: &[u8], +) { + assert!(contents_diff_reproduces_state_from(input, prev_input)); +} + +#[allow(dead_code)] +pub fn assert_reproduces_state(input: &[u8]) { + assert_reproduces_state_from(input, &[]); +} + +pub fn assert_reproduces_state_from(input: &[u8], prev_input: &[u8]) { + let full_input: Vec<_> = + prev_input.iter().chain(input.iter()).copied().collect(); + assert_contents_formatted_reproduces_state(&full_input); + assert_contents_diff_reproduces_state_from(input, prev_input); +} + +#[allow(dead_code)] +pub fn format_bytes(bytes: &[u8]) -> String { + let mut v = vec![]; + for b in bytes { + match *b { + 10 => v.extend(b"\\n"), + 13 => v.extend(b"\\r"), + 27 => v.extend(b"\\e"), + c if c < 32 => v.extend(format!("\\x{:02x}", c).as_bytes()), + b => v.push(b), + } + } + String::from_utf8_lossy(&v).to_string() +} + +fn hex_char(c: u8) -> Result { + match c { + b'0' => Ok(0), + b'1' => Ok(1), + b'2' => Ok(2), + b'3' => Ok(3), + b'4' => Ok(4), + b'5' => Ok(5), + b'6' => Ok(6), + b'7' => Ok(7), + b'8' => Ok(8), + b'9' => Ok(9), + b'a' => Ok(10), + b'b' => Ok(11), + b'c' => Ok(12), + b'd' => Ok(13), + b'e' => Ok(14), + b'f' => Ok(15), + b'A' => Ok(10), + b'B' => Ok(11), + b'C' => Ok(12), + b'D' => Ok(13), + b'E' => Ok(14), + b'F' => Ok(15), + _ => Err("invalid hex char".to_string()), + } +} + +pub fn hex(upper: u8, lower: u8) -> Result { + Ok(hex_char(upper)? * 16 + hex_char(lower)?) +} diff --git a/tests/window_contents.rs b/tests/window_contents.rs index 352ffd5..39c8ad7 100644 --- a/tests/window_contents.rs +++ b/tests/window_contents.rs @@ -1,18 +1,18 @@ -#![allow(clippy::cognitive_complexity)] +mod helpers; use std::io::Read as _; #[test] fn formatted() { let mut parser = vt100::Parser::default(); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert_eq!( parser.screen().contents_formatted(), b"\x1b[?25h\x1b[m\x1b[H\x1b[J" ); parser.process(b"foobar"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert!(!parser.screen().cell(0, 2).unwrap().bold()); assert!(!parser.screen().cell(0, 3).unwrap().bold()); assert!(!parser.screen().cell(0, 4).unwrap().bold()); @@ -23,7 +23,7 @@ fn formatted() { ); parser.process(b"\x1b[1;4H\x1b[1;7m\x1b[33mb"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert!(!parser.screen().cell(0, 2).unwrap().bold()); assert!(parser.screen().cell(0, 3).unwrap().bold()); assert!(!parser.screen().cell(0, 4).unwrap().bold()); @@ -34,7 +34,7 @@ fn formatted() { ); parser.process(b"\x1b[1;5H\x1b[22;42ma"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert!(!parser.screen().cell(0, 2).unwrap().bold()); assert!(parser.screen().cell(0, 3).unwrap().bold()); assert!(!parser.screen().cell(0, 4).unwrap().bold()); @@ -46,14 +46,14 @@ fn formatted() { ); parser.process(b"\x1b[1;6H\x1b[35mr\r\nquux"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert_eq!( parser.screen().contents_formatted(), &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jfoo\x1b[33;1;7mb\x1b[42;22ma\x1b[35mr\r\nquux"[..] ); parser.process(b"\x1b[2;1H\x1b[45mquux"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert_eq!( parser.screen().contents_formatted(), &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jfoo\x1b[33;1;7mb\x1b[42;22ma\x1b[35mr\r\n\x1b[45mquux"[..] @@ -61,7 +61,7 @@ fn formatted() { parser .process(b"\x1b[2;2H\x1b[38;2;123;213;231mu\x1b[38;5;254mu\x1b[39mx"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert_eq!(parser.screen().contents_formatted(), &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jfoo\x1b[33;1;7mb\x1b[42;22ma\x1b[35mr\r\n\x1b[45mq\x1b[38;2;123;213;231mu\x1b[38;5;254mu\x1b[39mx"[..]); } @@ -69,7 +69,7 @@ fn formatted() { fn empty_cells() { let mut parser = vt100::Parser::default(); parser.process(b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo"); - compare_formatted(parser.screen()); + helpers::contents_formatted_reproduces_screen(parser.screen()); assert_eq!(parser.screen().contents(), "foo bar"); assert_eq!( parser.screen().contents_formatted(), @@ -181,9 +181,8 @@ fn rows() { String::new(), ] ); - assert_eq!( - screen1.rows_formatted(0, 80).collect::>>(), - vec![ + assert_eq!(screen1.rows_formatted(0, 80).collect::>>(), { + let x: Vec> = vec![ vec![], vec![], vec![], @@ -208,8 +207,9 @@ fn rows() { vec![], vec![], vec![], - ] - ); + ]; + x + }); assert_eq!( screen1.rows(5, 15).collect::>(), vec![ @@ -239,9 +239,8 @@ fn rows() { String::new(), ] ); - assert_eq!( - screen1.rows_formatted(5, 15).collect::>>(), - vec![ + assert_eq!(screen1.rows_formatted(5, 15).collect::>>(), { + let x: Vec> = vec![ vec![], vec![], vec![], @@ -266,8 +265,9 @@ fn rows() { vec![], vec![], vec![], - ] - ); + ]; + x + }); parser .process(b"\x1b[31mfoo\x1b[10;10H\x1b[32mbar\x1b[20;20H\x1b[33mbaz"); @@ -459,25 +459,29 @@ fn diff_basic() { parser.process(b"\x1b[5C\x1b[32m bar"); let screen2 = parser.screen().clone(); assert_eq!(screen2.contents_diff(&screen1), b"\x1b[5C\x1b[32m bar"); - compare_diff(&screen1, &screen2, b""); + helpers::assert_contents_diff_reproduces_state_from_screens( + &screen1, &screen2, + ); parser.process(b"\x1b[H\x1b[31mfoo"); let screen3 = parser.screen().clone(); assert_eq!(screen3.contents_diff(&screen2), b"\x1b[H\x1b[31mfoo"); - compare_diff(&screen2, &screen3, b"\x1b[5C\x1b[32m bar"); + helpers::assert_contents_diff_reproduces_state_from_screens( + &screen2, &screen3, + ); parser.process(b"\x1b[1;7H\x1b[32mbaz"); let screen4 = parser.screen().clone(); assert_eq!(screen4.contents_diff(&screen3), b"\x1b[5C\x1b[32mz"); - compare_diff(&screen3, &screen4, b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo"); + helpers::assert_contents_diff_reproduces_state_from_screens( + &screen3, &screen4, + ); parser.process(b"\x1b[1;8H\x1b[X"); let screen5 = parser.screen().clone(); assert_eq!(screen5.contents_diff(&screen4), b"\x1b[1;8H\x1b[X"); - compare_diff( - &screen4, - &screen5, - b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo\x1b[1;7H\x1b[32mbaz", + helpers::assert_contents_diff_reproduces_state_from_screens( + &screen4, &screen5, ); } @@ -523,137 +527,19 @@ fn diff_crawl(i: usize) { let mut frame = vec![]; file.read_to_end(&mut frame).unwrap(); parser.process(&frame); - (frame.clone(), parser.screen().clone()) + parser.screen().clone() }) .collect(); - let mut all_frames: Vec = vec![]; for two_screens in screens.windows(2) { match two_screens { - [(prev_frame, prev_screen), (_, screen)] => { - all_frames.extend(prev_frame); - compare_diff(prev_screen, screen, &all_frames); + [prev_screen, screen] => { + helpers::assert_contents_diff_reproduces_state_from_screens( + prev_screen, + screen, + ); } _ => unreachable!(), } } } - -fn compare_formatted(screen: &vt100::Screen) { - let (rows, cols) = screen.size(); - let mut parser = vt100::Parser::new(rows, cols, 0); - let contents = screen.contents_formatted(); - parser.process(&contents); - compare_cells(screen, parser.screen()); -} - -fn compare_diff( - prev_screen: &vt100::Screen, - screen: &vt100::Screen, - prev_parsed: &[u8], -) { - let (rows, cols) = screen.size(); - let mut parser = vt100::Parser::new(rows, cols, 0); - parser.process(prev_parsed); - // need to reparse the formatted contents here in case we're in the middle - // of parsing an escape sequence, since applying the diff at that location - // directly won't work in that case - let contents = parser.screen().contents_formatted(); - let mut parser = vt100::Parser::new(rows, cols, 0); - parser.process(&contents); - compare_cells(parser.screen(), &prev_screen); - assert_eq!(parser.screen().hide_cursor(), prev_screen.hide_cursor()); - assert_eq!( - parser.screen().cursor_position(), - prev_screen.cursor_position() - ); - assert_eq!( - parser.screen().contents_formatted(), - prev_screen.contents_formatted() - ); - - parser.process(&screen.contents_diff(prev_screen)); - if parser.screen().contents_formatted() != screen.contents_formatted() { - use std::io::Write as _; - let mut prev_screen_file = - std::fs::File::create("prev_screen").unwrap(); - prev_screen_file - .write_all(&prev_screen.contents_formatted()) - .unwrap(); - let mut screen_file = std::fs::File::create("screen").unwrap(); - screen_file.write_all(&screen.contents_formatted()).unwrap(); - let mut diff_file = std::fs::File::create("diff").unwrap(); - diff_file - .write_all(&screen.contents_diff(prev_screen)) - .unwrap(); - } - compare_cells(parser.screen(), &screen); - assert_eq!(parser.screen().hide_cursor(), screen.hide_cursor()); - assert_eq!(parser.screen().cursor_position(), screen.cursor_position()); - assert_eq!( - parser.screen().contents_formatted(), - screen.contents_formatted() - ); -} - -fn compare_cells(screen1: &vt100::Screen, screen2: &vt100::Screen) { - assert_eq!(screen1.size(), screen2.size()); - let (rows, cols) = screen1.size(); - - for row in 0..rows { - for col in 0..cols { - let cell1 = screen1.cell(row, col).unwrap(); - let cell2 = screen2.cell(row, col).unwrap(); - - assert_eq!( - cell1.contents(), - cell2.contents(), - "cell at position ({},{}) had different contents", - row + 1, - col + 1 - ); - assert_eq!( - cell1.fgcolor(), - cell2.fgcolor(), - "cell at position ({},{}) had different fgcolor", - row + 1, - col + 1 - ); - assert_eq!( - cell1.bgcolor(), - cell2.bgcolor(), - "cell at position ({},{}) had different bgcolor", - row + 1, - col + 1 - ); - assert_eq!( - cell1.bold(), - cell2.bold(), - "cell at position ({},{}) had different bold", - row + 1, - col + 1 - ); - assert_eq!( - cell1.italic(), - cell2.italic(), - "cell at position ({},{}) had different italic", - row + 1, - col + 1 - ); - assert_eq!( - cell1.underline(), - cell2.underline(), - "cell at position ({},{}) had different underline", - row + 1, - col + 1 - ); - assert_eq!( - cell1.inverse(), - cell2.inverse(), - "cell at position ({},{}) had different inverse", - row + 1, - col + 1 - ); - } - } -} -- cgit v1.2.3-54-g00ecf