From b0a5265ad3369e2f162470a63d1c69eef1b163a1 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 29 Apr 2022 09:19:53 -0400 Subject: [PATCH] tamer: diagnose::report::test: Extract into separate file Tests are large and will be getting larger. The source will also grow as it's better documented and cleaned up. It's getting more difficult to navigate efficiently and concurrently modify implementation and tests, and parsing via LSP is getting slower with certain types of changes. DEV-12151 --- tamer/src/diagnose/report.rs | 339 +--------------------------- tamer/src/diagnose/report/test.rs | 356 ++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+), 338 deletions(-) create mode 100644 tamer/src/diagnose/report/test.rs diff --git a/tamer/src/diagnose/report.rs b/tamer/src/diagnose/report.rs index 89c05ce3..6e2a79f1 100644 --- a/tamer/src/diagnose/report.rs +++ b/tamer/src/diagnose/report.rs @@ -689,341 +689,4 @@ impl MarkChar for Level { } #[cfg(test)] -mod test { - use super::*; - use crate::{ - convert::ExpectInto, - diagnose::resolver::Column, - span::{DUMMY_CONTEXT, DUMMY_SPAN}, - }; - use std::{io, num::NonZeroU32}; - - mod integration; - - #[derive(Default)] - struct StubResolvedSpan { - span: Option, - first_line_span: Option, - line_num: Option, - col_num: Option, - context: Option, - src_lines: Option>, - } - - impl ResolvedSpanData for StubResolvedSpan { - fn line_num(&self) -> NonZeroU32 { - self.line_num.expect("missing stub line_num") - } - - fn col_num(&self) -> Option { - self.col_num - } - - fn first_line_span(&self) -> Span { - self.first_line_span.expect("missing stub first_line_span") - } - - fn context(&self) -> Context { - self.context.expect("missing stub ctx") - } - - fn unresolved_span(&self) -> Span { - self.span.expect("missing stub unresolved span") - } - - fn into_lines(self) -> Vec { - self.src_lines.unwrap_or_default() - } - } - - #[test] - fn header_col_with_available_col() { - let sut = HeadingColNum::Resolved(Column::Endpoints( - 5.unwrap_into(), - // Second endpoint is ignored. - 6.unwrap_into(), - )); - - assert_eq!(":5", format!("{}", sut)); - } - - #[test] - fn header_col_without_available_col() { - let sut = HeadingColNum::Unresolved { - unresolved_span: DUMMY_CONTEXT.span(5, 2), - first_line_span: DUMMY_CONTEXT.span(3, 7), - }; - - assert_eq!(" bytes 2--4", format!("{}", sut)); - } - - // Note that line is coupled with `HeadingColNum`, - // tested above. - // The coupling is not ideal, - // but it keeps it simple and we don't concretely benefit from the - // decoupling for now. - #[test] - fn line_with_resolved_span() { - let sut = HeadingLineNum::Resolved( - 5.unwrap_into(), - HeadingColNum::Resolved(Column::Endpoints( - 3.unwrap_into(), - 3.unwrap_into(), - )), - ); - - assert_eq!(":5:3", format!("{}", sut)); - } - - // Does _not_ use `HeadingColNum`, - // unlike the above, - // because the line was not resolved. - #[test] - fn line_with_unresolved_span_without_resolved_col() { - let sut = HeadingLineNum::Unresolved(DUMMY_CONTEXT.span(3, 4)); - - assert_eq!(" offset 3--7", format!("{}", sut)); - } - - // Whether you call this a unit or integration test depends on your - // perspective---it's - // either an integration test, - // or we're testing privates. - // Neither are ideal, - // but decoupling isn't worth the type burden that results. - #[test] - fn span_heading() { - let ctx = "header".unwrap_into(); - let sut = SpanHeading( - ctx, - HeadingLineNum::Resolved( - 2.unwrap_into(), - HeadingColNum::Resolved(Column::Endpoints( - 6.unwrap_into(), - 6.unwrap_into(), - )), - ), - ); - - assert_eq!("--> header:2:6", format!("{}", sut)); - } - - #[test] - fn section_from_mspan_resolved() { - let ctx = Context::from("mspan/sec"); - let span = ctx.span(2, 3); - - let col_1 = Column::Endpoints(2.unwrap_into(), 3.unwrap_into()); - let col_2 = Column::Endpoints(1.unwrap_into(), 4.unwrap_into()); - - let src_lines = vec![ - SourceLine::new_stub( - 1.unwrap_into(), - Some(col_1), - span, - "line 1".into(), - ), - SourceLine::new_stub( - 2.unwrap_into(), - Some(col_2), - span, - "line 2".into(), - ), - ]; - - assert_eq!( - Section::from(MaybeResolvedSpan::Resolved( - StubResolvedSpan { - context: Some(ctx), - line_num: Some(1.unwrap_into()), - col_num: Some(col_1), - first_line_span: Some(DUMMY_SPAN), - span: Some(span), - src_lines: Some(src_lines.clone()), - }, - Some(SpanLabel(Level::Note, "test label".into())), - )), - Section { - heading: SpanHeading( - ctx, - HeadingLineNum::Resolved( - 1.unwrap_into(), - HeadingColNum::Resolved(Column::Endpoints( - 2.unwrap_into(), - 3.unwrap_into() - )) - ) - ), - span, - // Derived from label. - level: Level::Note, - body: vec![ - SectionLine::SourceLinePadding, - SectionLine::SourceLine(src_lines[0].clone().into()), - SectionLine::SourceLineMark(LineMark { - level: Level::Note, - col: col_1, - // Label goes on the last source line. - label: None, - }), - SectionLine::SourceLinePadding, - SectionLine::SourceLine(src_lines[1].clone().into()), - SectionLine::SourceLineMark(LineMark { - level: Level::Note, - col: col_2, - // Label at last source line - label: Some("test label".into()), - }), - ], - line_max: 2.unwrap_into(), - } - ); - } - - #[test] - fn section_from_mspan_resolved_no_label() { - let ctx = Context::from("mspan/sec-no-label"); - let span = ctx.span(3, 4); - - assert_eq!( - Section::from(MaybeResolvedSpan::Resolved( - StubResolvedSpan { - context: Some(ctx), - line_num: Some(2.unwrap_into()), - col_num: Some(Column::Endpoints( - 1.unwrap_into(), - 2.unwrap_into() - )), - first_line_span: Some(DUMMY_SPAN), - span: Some(span), - src_lines: None, - }, - None, - )), - Section { - heading: SpanHeading( - ctx, - HeadingLineNum::Resolved( - 2.unwrap_into(), - HeadingColNum::Resolved(Column::Endpoints( - 1.unwrap_into(), - 2.unwrap_into() - )) - ) - ), - span, - // Level is normally derived from the label, - // so in this case it gets defaulted. - level: Level::default(), - body: vec![], - // Line comes from `src_lines`. - line_max: 1.unwrap_into(), - } - ); - } - - #[test] - fn section_from_mspan_unresolved() { - let ctx = Context::from("mspan/sec-unresolved"); - let span = ctx.span(2, 3); - - let mspan = MaybeResolvedSpan::Unresolved::( - span, - Some(SpanLabel(Level::Note, "test label".into())), - SpanResolverError::Io(io::ErrorKind::NotFound), - ); - - assert_eq!( - Section::from(mspan), - Section { - heading: SpanHeading(ctx, HeadingLineNum::Unresolved(span),), - span, - level: Level::Note, - body: vec![ - SectionLine::Footnote(SpanLabel( - Level::Note, - "test label".into() - )), - SectionLine::Footnote(SpanLabel( - Level::Help, - // This hard-coding is not ideal, - // as it makes the test fragile. - format!( - "an error occurred while trying to look up \ - information about this span: {}", - io::ErrorKind::NotFound - ) - .into() - )), - ], - line_max: 1.unwrap_into(), - } - ); - } - - #[test] - fn section_footnote_into_footnote() { - assert_eq!( - SectionLine::Footnote(SpanLabel( - Level::Note, - "test footnote".into() - )) - .into_footnote(), - Some(SectionLine::Footnote(SpanLabel( - Level::Note, - "test footnote".into() - ))), - ); - } - - #[test] - fn section_src_line_into_footnote() { - assert_eq!( - SectionLine::SourceLine( - SourceLine::new_stub( - 1.unwrap_into(), - None, - DUMMY_SPAN, - "discarded".into() - ) - .into() - ) - .into_footnote(), - None - ); - } - - #[test] - fn section_mark_with_label_into_footnote() { - assert_eq!( - SectionLine::SourceLineMark(LineMark { - level: Level::Help, - col: Column::Before(1.unwrap_into()), - label: Some("kept label".into()) - }) - .into_footnote(), - Some(SectionLine::Footnote(SpanLabel( - Level::Help, - "kept label".into() - ))), - ); - } - - #[test] - fn section_mark_without_label_into_footnote() { - assert_eq!( - SectionLine::SourceLineMark(LineMark { - level: Level::Help, - col: Column::Before(1.unwrap_into()), - label: None, - }) - .into_footnote(), - None - ); - } - - // TODO: Section squashing is currently only covered by integration - // tests! - - // TODO: Most `Display::fmt` only covered by integration tests! -} +mod test; diff --git a/tamer/src/diagnose/report/test.rs b/tamer/src/diagnose/report/test.rs new file mode 100644 index 00000000..42b0945c --- /dev/null +++ b/tamer/src/diagnose/report/test.rs @@ -0,0 +1,356 @@ +// Diagnostic system rendering tests +// +// Copyright (C) 2014-2021 Ryan Specialty Group, LLC. +// +// This file is part of TAME. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use crate::{ + convert::ExpectInto, + diagnose::resolver::Column, + span::{DUMMY_CONTEXT, DUMMY_SPAN}, +}; +use std::{io, num::NonZeroU32}; + +// NB: Integration tests exist in this module, +// and test against exact output of this system; +// you should use them for prototyping changes to be sure you understand +// how output will be considered in various circumstances. +mod integration; + +#[derive(Default)] +struct StubResolvedSpan { + span: Option, + first_line_span: Option, + line_num: Option, + col_num: Option, + context: Option, + src_lines: Option>, +} + +impl ResolvedSpanData for StubResolvedSpan { + fn line_num(&self) -> NonZeroU32 { + self.line_num.expect("missing stub line_num") + } + + fn col_num(&self) -> Option { + self.col_num + } + + fn first_line_span(&self) -> Span { + self.first_line_span.expect("missing stub first_line_span") + } + + fn context(&self) -> Context { + self.context.expect("missing stub ctx") + } + + fn unresolved_span(&self) -> Span { + self.span.expect("missing stub unresolved span") + } + + fn into_lines(self) -> Vec { + self.src_lines.unwrap_or_default() + } +} + +#[test] +fn header_col_with_available_col() { + let sut = HeadingColNum::Resolved(Column::Endpoints( + 5.unwrap_into(), + // Second endpoint is ignored. + 6.unwrap_into(), + )); + + assert_eq!(":5", format!("{}", sut)); +} + +#[test] +fn header_col_without_available_col() { + let sut = HeadingColNum::Unresolved { + unresolved_span: DUMMY_CONTEXT.span(5, 2), + first_line_span: DUMMY_CONTEXT.span(3, 7), + }; + + assert_eq!(" bytes 2--4", format!("{}", sut)); +} + +// Note that line is coupled with `HeadingColNum`, +// tested above. +// The coupling is not ideal, +// but it keeps it simple and we don't concretely benefit from the +// decoupling for now. +#[test] +fn line_with_resolved_span() { + let sut = HeadingLineNum::Resolved( + 5.unwrap_into(), + HeadingColNum::Resolved(Column::Endpoints( + 3.unwrap_into(), + 3.unwrap_into(), + )), + ); + + assert_eq!(":5:3", format!("{}", sut)); +} + +// Does _not_ use `HeadingColNum`, +// unlike the above, +// because the line was not resolved. +#[test] +fn line_with_unresolved_span_without_resolved_col() { + let sut = HeadingLineNum::Unresolved(DUMMY_CONTEXT.span(3, 4)); + + assert_eq!(" offset 3--7", format!("{}", sut)); +} + +// Whether you call this a unit or integration test depends on your +// perspective---it's +// either an integration test, +// or we're testing privates. +// Neither are ideal, +// but decoupling isn't worth the type burden that results. +#[test] +fn span_heading() { + let ctx = "header".unwrap_into(); + let sut = SpanHeading( + ctx, + HeadingLineNum::Resolved( + 2.unwrap_into(), + HeadingColNum::Resolved(Column::Endpoints( + 6.unwrap_into(), + 6.unwrap_into(), + )), + ), + ); + + assert_eq!("--> header:2:6", format!("{}", sut)); +} + +#[test] +fn section_from_mspan_resolved() { + let ctx = Context::from("mspan/sec"); + let span = ctx.span(2, 3); + + let col_1 = Column::Endpoints(2.unwrap_into(), 3.unwrap_into()); + let col_2 = Column::Endpoints(1.unwrap_into(), 4.unwrap_into()); + + let src_lines = vec![ + SourceLine::new_stub( + 1.unwrap_into(), + Some(col_1), + span, + "line 1".into(), + ), + SourceLine::new_stub( + 2.unwrap_into(), + Some(col_2), + span, + "line 2".into(), + ), + ]; + + assert_eq!( + Section::from(MaybeResolvedSpan::Resolved( + StubResolvedSpan { + context: Some(ctx), + line_num: Some(1.unwrap_into()), + col_num: Some(col_1), + first_line_span: Some(DUMMY_SPAN), + span: Some(span), + src_lines: Some(src_lines.clone()), + }, + Some(SpanLabel(Level::Note, "test label".into())), + )), + Section { + heading: SpanHeading( + ctx, + HeadingLineNum::Resolved( + 1.unwrap_into(), + HeadingColNum::Resolved(Column::Endpoints( + 2.unwrap_into(), + 3.unwrap_into() + )) + ) + ), + span, + // Derived from label. + level: Level::Note, + body: vec![ + SectionLine::SourceLinePadding, + SectionLine::SourceLine(src_lines[0].clone().into()), + SectionLine::SourceLineMark(LineMark { + level: Level::Note, + col: col_1, + // Label goes on the last source line. + label: None, + }), + SectionLine::SourceLinePadding, + SectionLine::SourceLine(src_lines[1].clone().into()), + SectionLine::SourceLineMark(LineMark { + level: Level::Note, + col: col_2, + // Label at last source line + label: Some("test label".into()), + }), + ], + line_max: 2.unwrap_into(), + } + ); +} + +#[test] +fn section_from_mspan_resolved_no_label() { + let ctx = Context::from("mspan/sec-no-label"); + let span = ctx.span(3, 4); + + assert_eq!( + Section::from(MaybeResolvedSpan::Resolved( + StubResolvedSpan { + context: Some(ctx), + line_num: Some(2.unwrap_into()), + col_num: Some(Column::Endpoints( + 1.unwrap_into(), + 2.unwrap_into() + )), + first_line_span: Some(DUMMY_SPAN), + span: Some(span), + src_lines: None, + }, + None, + )), + Section { + heading: SpanHeading( + ctx, + HeadingLineNum::Resolved( + 2.unwrap_into(), + HeadingColNum::Resolved(Column::Endpoints( + 1.unwrap_into(), + 2.unwrap_into() + )) + ) + ), + span, + // Level is normally derived from the label, + // so in this case it gets defaulted. + level: Level::default(), + body: vec![], + // Line comes from `src_lines`. + line_max: 1.unwrap_into(), + } + ); +} + +#[test] +fn section_from_mspan_unresolved() { + let ctx = Context::from("mspan/sec-unresolved"); + let span = ctx.span(2, 3); + + let mspan = MaybeResolvedSpan::Unresolved::( + span, + Some(SpanLabel(Level::Note, "test label".into())), + SpanResolverError::Io(io::ErrorKind::NotFound), + ); + + assert_eq!( + Section::from(mspan), + Section { + heading: SpanHeading(ctx, HeadingLineNum::Unresolved(span),), + span, + level: Level::Note, + body: vec![ + SectionLine::Footnote(SpanLabel( + Level::Note, + "test label".into() + )), + SectionLine::Footnote(SpanLabel( + Level::Help, + // This hard-coding is not ideal, + // as it makes the test fragile. + format!( + "an error occurred while trying to look up \ + information about this span: {}", + io::ErrorKind::NotFound + ) + .into() + )), + ], + line_max: 1.unwrap_into(), + } + ); +} + +#[test] +fn section_footnote_into_footnote() { + assert_eq!( + SectionLine::Footnote(SpanLabel(Level::Note, "test footnote".into())) + .into_footnote(), + Some(SectionLine::Footnote(SpanLabel( + Level::Note, + "test footnote".into() + ))), + ); +} + +#[test] +fn section_src_line_into_footnote() { + assert_eq!( + SectionLine::SourceLine( + SourceLine::new_stub( + 1.unwrap_into(), + None, + DUMMY_SPAN, + "discarded".into() + ) + .into() + ) + .into_footnote(), + None + ); +} + +#[test] +fn section_mark_with_label_into_footnote() { + assert_eq!( + SectionLine::SourceLineMark(LineMark { + level: Level::Help, + col: Column::Before(1.unwrap_into()), + label: Some("kept label".into()) + }) + .into_footnote(), + Some(SectionLine::Footnote(SpanLabel( + Level::Help, + "kept label".into() + ))), + ); +} + +#[test] +fn section_mark_without_label_into_footnote() { + assert_eq!( + SectionLine::SourceLineMark(LineMark { + level: Level::Help, + col: Column::Before(1.unwrap_into()), + label: None, + }) + .into_footnote(), + None + ); +} + +// TODO: Section squashing is currently only covered by integration +// tests! + +// TODO: Most `Display::fmt` only covered by integration tests!