xee-testrunner 0.1.6

Conformance testing for Xee's XPath and XSLT implementations
Documentation
use std::io::{IsTerminal, Stdout, Write};

use crossterm::{
    execute,
    style::{self, Stylize},
};

use crate::{
    catalog::Catalog,
    language::Language,
    testcase::{TestCase, TestOutcome, UnexpectedError},
    testset::TestSet,
};

pub(crate) trait Renderer<L: Language> {
    fn render_test_set(
        &self,
        stdout: &mut Stdout,
        catalog: &Catalog<L>,
        test_set: &TestSet<L>,
    ) -> std::io::Result<()>;
    fn render_test_case(&self, stdout: &mut Stdout, test_case: &TestCase<L>)
        -> std::io::Result<()>;
    fn render_test_outcome(
        &self,
        stdout: &mut Stdout,
        test_result: &TestOutcome,
    ) -> std::io::Result<()>;
    fn render_test_set_summary(
        &self,
        stdout: &mut Stdout,
        test_set: &TestSet<L>,
    ) -> std::io::Result<()>;
}

pub(crate) struct VerboseRenderer {}

impl VerboseRenderer {
    pub(crate) fn new() -> Self {
        Self {}
    }
}

impl<L: Language> Renderer<L> for VerboseRenderer {
    fn render_test_set(
        &self,
        _stdout: &mut Stdout,
        catalog: &Catalog<L>,
        test_set: &TestSet<L>,
    ) -> std::io::Result<()> {
        println!("{}", test_set.file_path(catalog).display());
        println!("{}", test_set.name);
        for description in &test_set.descriptions {
            println!("{} ", description);
        }
        Ok(())
    }

    fn render_test_case(
        &self,
        _stdout: &mut Stdout,
        test_case: &TestCase<L>,
    ) -> std::io::Result<()> {
        print!("{} ... ", test_case.name);
        Ok(())
    }

    fn render_test_outcome(
        &self,
        stdout: &mut Stdout,
        test_result: &TestOutcome,
    ) -> std::io::Result<()> {
        match test_result {
            TestOutcome::Passed => {
                writeln!(
                    stdout,
                    "{}",
                    with_color(stdout, "PASS", crossterm::style::Color::Green)
                )
            }
            TestOutcome::UnexpectedError(error) => match error {
                UnexpectedError(s) => writeln!(
                    stdout,
                    "{} code: {}",
                    with_color(stdout, "WRONG ERROR", crossterm::style::Color::Yellow),
                    s
                ),
            },
            TestOutcome::Failed(failure) => {
                writeln!(
                    stdout,
                    "{} {}",
                    with_color(stdout, "FAIL", crossterm::style::Color::Red),
                    failure
                )
            }
            TestOutcome::RuntimeError(error) => {
                writeln!(
                    stdout,
                    "{} {} {:?}",
                    with_color(stdout, "RUNTIME ERROR", crossterm::style::Color::Red),
                    error,
                    error
                )
            }
            TestOutcome::CompilationError(error) => {
                writeln!(
                    stdout,
                    "{} {} {:?}",
                    with_color(stdout, "COMPILATION ERROR", crossterm::style::Color::Red),
                    error,
                    error
                )
            }
            TestOutcome::UnsupportedExpression(error) => {
                writeln!(
                    stdout,
                    "{} {}",
                    with_color(
                        stdout,
                        "UNSUPPORTED EXPRESSION ERROR",
                        crossterm::style::Color::Red
                    ),
                    error
                )
            }
            TestOutcome::Unsupported => {
                writeln!(
                    stdout,
                    "{}",
                    with_color(stdout, "UNSUPPORTED", crossterm::style::Color::Red)
                )
            }
            TestOutcome::EnvironmentError(error) => {
                writeln!(
                    stdout,
                    "{} {}",
                    with_color(stdout, "CONTEXT ITEM ERROR", crossterm::style::Color::Red),
                    error
                )
            }
            TestOutcome::Panic => {
                writeln!(
                    stdout,
                    "{}",
                    with_color(stdout, "PANIC", crossterm::style::Color::Red)
                )
            }
        }
    }

    fn render_test_set_summary(
        &self,
        _stdout: &mut Stdout,
        _test_set: &TestSet<L>,
    ) -> std::io::Result<()> {
        println!();
        Ok(())
    }
}

pub(crate) struct CharacterRenderer {}

impl CharacterRenderer {
    pub(crate) fn new() -> Self {
        Self {}
    }
}

impl<L: Language> Renderer<L> for CharacterRenderer {
    fn render_test_set(
        &self,
        _stdout: &mut Stdout,
        catalog: &Catalog<L>,
        test_set: &TestSet<L>,
    ) -> std::io::Result<()> {
        print!("{} ", test_set.file_path(catalog).display());
        Ok(())
    }

    fn render_test_case(
        &self,
        _stdout: &mut Stdout,
        _test_case: &TestCase<L>,
    ) -> std::io::Result<()> {
        Ok(())
    }

    fn render_test_outcome(
        &self,
        stdout: &mut Stdout,
        outcome: &TestOutcome,
    ) -> std::io::Result<()> {
        match outcome {
            TestOutcome::Passed => render_error_code(stdout, ".", crossterm::style::Color::Green),
            TestOutcome::UnexpectedError(_) => {
                render_error_code(stdout, "F", crossterm::style::Color::Red)
            }
            TestOutcome::Failed(_) => render_error_code(stdout, "F", crossterm::style::Color::Red),
            TestOutcome::RuntimeError(_) => {
                render_error_code(stdout, "E", crossterm::style::Color::Red)
            }
            TestOutcome::CompilationError(_) => {
                render_error_code(stdout, "E", crossterm::style::Color::Red)
            }
            TestOutcome::UnsupportedExpression(_) => {
                render_error_code(stdout, "E", crossterm::style::Color::Red)
            }
            TestOutcome::Unsupported => {
                render_error_code(stdout, "E", crossterm::style::Color::Red)
            }
            TestOutcome::EnvironmentError(_) => {
                render_error_code(stdout, "E", crossterm::style::Color::Red)
            }
            TestOutcome::Panic => {
                render_error_code(stdout, "E", crossterm::style::Color::Red)
            }
        }
    }

    fn render_test_set_summary(
        &self,
        _stdout: &mut Stdout,
        _test_set: &TestSet<L>,
    ) -> std::io::Result<()> {
        println!();
        Ok(())
    }
}

fn render_error_code(
    stdout: &mut Stdout,
    error_code: &str,
    color: crossterm::style::Color,
) -> std::io::Result<()> {
    if stdout.is_terminal() {
        execute!(stdout, style::PrintStyledContent(error_code.with(color)))?;
    } else {
        stdout.write_all(error_code.as_bytes())?;
    }
    Ok(())
}

fn with_color<'a>(
    stdout: &Stdout,
    text: &'a str,
    color: crossterm::style::Color,
) -> crossterm::style::StyledContent<&'a str> {
    if stdout.is_terminal() {
        text.with(color)
    } else {
        text.stylize()
    }
}