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 --- tests/helpers/fixtures.rs | 320 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 tests/helpers/fixtures.rs (limited to 'tests/helpers/fixtures.rs') 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"); +} -- cgit v1.2.3-54-g00ecf