refactor(tests): move test utilities to TestBackend

* Remove custom Debug implementation of Buffer
* Add `TestBackend::assert_buffer` to compare buffers in integration tests. When
the assertion fails, the output now show the list of differences in addition
of the views of the computed and expected buffers. This effectively replaces
the table of debug code for colors and modifiers as it is easier to read.
pull/305/head
Florian Dehau 4 years ago
parent 18714caa60
commit 96c6b4efcb

@ -1,8 +1,12 @@
use crate::backend::Backend;
use crate::buffer::{Buffer, Cell};
use crate::layout::Rect;
use std::io;
use crate::{
backend::Backend,
buffer::{Buffer, Cell},
layout::Rect,
};
use std::{fmt::Write, io};
use unicode_width::UnicodeWidthStr;
/// A backend used for the integration tests.
#[derive(Debug)]
pub struct TestBackend {
width: u16,
@ -12,6 +16,35 @@ pub struct TestBackend {
pos: (u16, u16),
}
/// Returns a string representation of the given buffer for debugging purpose.
fn buffer_view(buffer: &Buffer) -> String {
let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3);
for cells in buffer.content.chunks(buffer.area.width as usize) {
let mut overwritten = vec![];
let mut skip: usize = 0;
view.push('"');
for (x, c) in cells.iter().enumerate() {
if skip == 0 {
view.push_str(&c.symbol);
} else {
overwritten.push((x, &c.symbol))
}
skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1);
}
view.push('"');
if !overwritten.is_empty() {
write!(
&mut view,
" Hidden by multi-width symbols: {:?}",
overwritten
)
.unwrap();
}
view.push_str("\n");
}
view
}
impl TestBackend {
pub fn new(width: u16, height: u16) -> TestBackend {
TestBackend {
@ -26,6 +59,44 @@ impl TestBackend {
pub fn buffer(&self) -> &Buffer {
&self.buffer
}
pub fn assert_buffer(&self, expected: &Buffer) {
assert_eq!(expected.area, self.buffer.area);
let diff = expected.diff(&self.buffer);
if diff.is_empty() {
return;
}
let mut debug_info = String::from("Buffers are not equal");
debug_info.push_str("\n");
debug_info.push_str("Expected:");
debug_info.push_str("\n");
let expected_view = buffer_view(expected);
debug_info.push_str(&expected_view);
debug_info.push_str("\n");
debug_info.push_str("Got:");
debug_info.push_str("\n");
let view = buffer_view(&self.buffer);
debug_info.push_str(&view);
debug_info.push_str("\n");
debug_info.push_str("Diff:");
debug_info.push_str("\n");
let nice_diff = diff
.iter()
.enumerate()
.map(|(i, (x, y, cell))| {
let expected_cell = expected.get(*x, *y);
format!(
"{}: at ({}, {}) expected {:?} got {:?}",
i, x, y, expected_cell, cell
)
})
.collect::<Vec<String>>()
.join("\n");
debug_info.push_str(&nice_diff);
panic!(debug_info);
}
}
impl Backend for TestBackend {
@ -40,27 +111,34 @@ impl Backend for TestBackend {
}
Ok(())
}
fn hide_cursor(&mut self) -> Result<(), io::Error> {
self.cursor = false;
Ok(())
}
fn show_cursor(&mut self) -> Result<(), io::Error> {
self.cursor = true;
Ok(())
}
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error> {
Ok(self.pos)
}
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error> {
self.pos = (x, y);
Ok(())
}
fn clear(&mut self) -> Result<(), io::Error> {
Ok(())
}
fn size(&self) -> Result<Rect, io::Error> {
Ok(Rect::new(0, 0, self.width, self.height))
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}

@ -1,13 +1,11 @@
use crate::{
layout::Rect,
style::{Color, Modifier, Style},
};
use std::cmp::min;
use std::fmt;
use std::usize;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::layout::Rect;
use crate::style::{Color, Modifier, Style};
/// A buffer cell
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
@ -92,7 +90,7 @@ impl Default for Cell {
/// buf.get_mut(5, 0).set_char('x');
/// assert_eq!(buf.get(5, 0).symbol, "x");
/// ```
#[derive(Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct Buffer {
/// The area represented by this buffer
pub area: Rect,
@ -110,49 +108,6 @@ impl Default for Buffer {
}
}
impl fmt::Debug for Buffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Buffer: {:?}", self.area)?;
f.write_str("Content (quoted lines):\n")?;
for cells in self.content.chunks(self.area.width as usize) {
let mut line = String::new();
let mut overwritten = vec![];
let mut skip: usize = 0;
for (x, c) in cells.iter().enumerate() {
if skip == 0 {
line.push_str(&c.symbol);
} else {
overwritten.push((x, &c.symbol))
}
skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1);
}
f.write_fmt(format_args!("{:?},", line))?;
if !overwritten.is_empty() {
f.write_fmt(format_args!(
" Hidden by multi-width symbols: {:?}",
overwritten
))?;
}
f.write_str("\n")?;
}
f.write_str("Style:\n")?;
for cells in self.content.chunks(self.area.width as usize) {
f.write_str("|")?;
for cell in cells {
write!(
f,
"{} {} {}|",
cell.style.fg.code(),
cell.style.bg.code(),
cell.style.modifier.code()
)?;
}
f.write_str("\n")?;
}
Ok(())
}
}
impl Buffer {
/// Returns a Buffer with all cells set to the default one
pub fn empty(area: Rect) -> Buffer {

@ -23,34 +23,6 @@ pub enum Color {
Indexed(u8),
}
impl Color {
/// Returns a short code associated with the color, used for debug purpose
/// only
pub(crate) fn code(self) -> &'static str {
match self {
Color::Reset => "X",
Color::Black => "b",
Color::Red => "r",
Color::Green => "c",
Color::Yellow => "y",
Color::Blue => "b",
Color::Magenta => "m",
Color::Cyan => "c",
Color::Gray => "w",
Color::DarkGray => "B",
Color::LightRed => "R",
Color::LightGreen => "G",
Color::LightYellow => "Y",
Color::LightBlue => "B",
Color::LightMagenta => "M",
Color::LightCyan => "C",
Color::White => "W",
Color::Indexed(_) => "i",
Color::Rgb(_, _, _) => "o",
}
}
}
bitflags! {
pub struct Modifier: u16 {
const BOLD = 0b0000_0000_0001;
@ -65,46 +37,6 @@ bitflags! {
}
}
impl Modifier {
/// Returns a short code associated with the color, used for debug purpose
/// only
pub(crate) fn code(self) -> String {
use std::fmt::Write;
let mut result = String::new();
if self.contains(Modifier::BOLD) {
write!(result, "BO").unwrap();
}
if self.contains(Modifier::DIM) {
write!(result, "DI").unwrap();
}
if self.contains(Modifier::ITALIC) {
write!(result, "IT").unwrap();
}
if self.contains(Modifier::UNDERLINED) {
write!(result, "UN").unwrap();
}
if self.contains(Modifier::SLOW_BLINK) {
write!(result, "SL").unwrap();
}
if self.contains(Modifier::RAPID_BLINK) {
write!(result, "RA").unwrap();
}
if self.contains(Modifier::REVERSED) {
write!(result, "RE").unwrap();
}
if self.contains(Modifier::HIDDEN) {
write!(result, "HI").unwrap();
}
if self.contains(Modifier::CROSSED_OUT) {
write!(result, "CR").unwrap();
}
result
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
pub fg: Color,

Loading…
Cancel
Save