Refine test infrastructure ✅
- Tests diagnostics - More and better separated image tests
3
.gitignore
vendored
@ -3,6 +3,5 @@
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
bench/target
|
bench/target
|
||||||
tests/png
|
tests/out
|
||||||
tests/pdf
|
|
||||||
_things
|
_things
|
||||||
|
16
Cargo.toml
@ -8,26 +8,22 @@ edition = "2018"
|
|||||||
members = ["bench"]
|
members = ["bench"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["fs", "anyhow"]
|
default = ["cli", "fs"]
|
||||||
|
cli = ["fs", "anyhow"]
|
||||||
fs = ["fontdock/fs"]
|
fs = ["fontdock/fs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fontdock = { path = "../fontdock", default-features = false }
|
|
||||||
pdf-writer = { path = "../pdf-writer" }
|
|
||||||
deflate = { version = "0.8.6" }
|
deflate = { version = "0.8.6" }
|
||||||
|
fontdock = { path = "../fontdock", default-features = false }
|
||||||
image = { version = "0.23", default-features = false, features = ["jpeg", "png"] }
|
image = { version = "0.23", default-features = false, features = ["jpeg", "png"] }
|
||||||
itoa = "0.4"
|
itoa = "0.4"
|
||||||
|
pdf-writer = { path = "../pdf-writer" }
|
||||||
ttf-parser = "0.8.2"
|
ttf-parser = "0.8.2"
|
||||||
unicode-xid = "0.2"
|
unicode-xid = "0.2"
|
||||||
|
anyhow = { version = "1", optional = true }
|
||||||
# feature = "serde"
|
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
# for the CLI
|
|
||||||
anyhow = { version = "1", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
memmap = "0.7"
|
|
||||||
tiny-skia = "0.2"
|
tiny-skia = "0.2"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
@ -38,7 +34,7 @@ lto = true
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "typst"
|
name = "typst"
|
||||||
required-features = ["fs", "anyhow"]
|
required-features = ["cli"]
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "typeset"
|
name = "typeset"
|
||||||
|
@ -13,7 +13,7 @@ use typst::parse::parse;
|
|||||||
use typst::typeset;
|
use typst::typeset;
|
||||||
|
|
||||||
const FONT_DIR: &str = "../fonts";
|
const FONT_DIR: &str = "../fonts";
|
||||||
const COMA: &str = include_str!("../../tests/typ/coma.typ");
|
const COMA: &str = include_str!("../../tests/typ/example-coma.typ");
|
||||||
|
|
||||||
fn benchmarks(c: &mut Criterion) {
|
fn benchmarks(c: &mut Criterion) {
|
||||||
macro_rules! bench {
|
macro_rules! bench {
|
||||||
|
@ -17,6 +17,11 @@ impl Point {
|
|||||||
pub fn new(x: Length, y: Length) -> Self {
|
pub fn new(x: Length, y: Length) -> Self {
|
||||||
Self { x, y }
|
Self { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an instance with two equal components.
|
||||||
|
pub fn uniform(value: Length) -> Self {
|
||||||
|
Self { x: value, y: value }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Get<SpecAxis> for Point {
|
impl Get<SpecAxis> for Point {
|
||||||
|
@ -21,6 +21,11 @@ impl Size {
|
|||||||
Self { width, height }
|
Self { width, height }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an instance with two equal components.
|
||||||
|
pub fn uniform(value: Length) -> Self {
|
||||||
|
Self { width: value, height: value }
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the other size fits into this one (smaller width and height).
|
/// Whether the other size fits into this one (smaller width and height).
|
||||||
pub fn fits(self, other: Self) -> bool {
|
pub fn fits(self, other: Self) -> bool {
|
||||||
self.width >= other.width && self.height >= other.height
|
self.width >= other.width && self.height >= other.height
|
||||||
|
@ -59,10 +59,10 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let map = LineMap::new(&src);
|
let map = LineMap::new(&src);
|
||||||
for diag in diags {
|
for diag in diags {
|
||||||
let span = diag.span;
|
let span = diag.span;
|
||||||
let start = map.location(span.start);
|
let start = map.location(span.start).unwrap();
|
||||||
let end = map.location(span.end);
|
let end = map.location(span.end).unwrap();
|
||||||
println!(
|
println!(
|
||||||
" {}: {}:{}-{}: {}",
|
"{}: {}:{}-{}: {}",
|
||||||
diag.v.level,
|
diag.v.level,
|
||||||
src_path.display(),
|
src_path.display(),
|
||||||
start,
|
start,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Conversion of byte positions to line/column locations.
|
//! Conversion of byte positions to line/column locations.
|
||||||
|
|
||||||
use super::Scanner;
|
use super::Scanner;
|
||||||
use crate::syntax::{Location, Pos};
|
use crate::syntax::{Location, Offset, Pos};
|
||||||
|
|
||||||
/// Enables conversion of byte position to locations.
|
/// Enables conversion of byte position to locations.
|
||||||
pub struct LineMap<'s> {
|
pub struct LineMap<'s> {
|
||||||
@ -25,23 +25,48 @@ impl<'s> LineMap<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a byte position to a location.
|
/// Convert a byte position to a location.
|
||||||
///
|
pub fn location(&self, pos: Pos) -> Option<Location> {
|
||||||
/// # Panics
|
// Find the line which contains the position.
|
||||||
/// This panics if the position is out of bounds.
|
|
||||||
pub fn location(&self, pos: Pos) -> Location {
|
|
||||||
let line_index = match self.line_starts.binary_search(&pos) {
|
let line_index = match self.line_starts.binary_search(&pos) {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
Err(i) => i - 1,
|
Err(i) => i - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let line_start = self.line_starts[line_index];
|
let start = self.line_starts.get(line_index)?;
|
||||||
let head = &self.src[line_start.to_usize() .. pos.to_usize()];
|
let head = self.src.get(start.to_usize() .. pos.to_usize())?;
|
||||||
let column_index = head.chars().count();
|
let column_index = head.chars().count();
|
||||||
|
|
||||||
Location {
|
Some(Location {
|
||||||
line: 1 + line_index as u32,
|
line: 1 + line_index as u32,
|
||||||
column: 1 + column_index as u32,
|
column: 1 + column_index as u32,
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a location to a byte position.
|
||||||
|
pub fn pos(&self, location: Location) -> Option<Pos> {
|
||||||
|
// Determine the boundaries of the line.
|
||||||
|
let line_idx = location.line.checked_sub(1)? as usize;
|
||||||
|
let line_start = self.line_starts.get(line_idx)?;
|
||||||
|
let line_end = self
|
||||||
|
.line_starts
|
||||||
|
.get(location.line as usize)
|
||||||
|
.map_or(self.src.len(), |pos| pos.to_usize());
|
||||||
|
|
||||||
|
let line = self.src.get(line_start.to_usize() .. line_end)?;
|
||||||
|
|
||||||
|
// Find the index in the line. For the first column, the index is always zero. For
|
||||||
|
// other columns, we have to look at which byte the char directly before the
|
||||||
|
// column in question ends. We can't do `nth(column_idx)` directly since the
|
||||||
|
// column may be behind the last char.
|
||||||
|
let column_idx = location.column.checked_sub(1)? as usize;
|
||||||
|
let line_offset = if let Some(prev_idx) = column_idx.checked_sub(1) {
|
||||||
|
let (idx, prev) = line.char_indices().nth(prev_idx)?;
|
||||||
|
idx + prev.len_utf8()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(line_start.offset(Pos(line_offset as u32)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,18 +96,26 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_line_map_location() {
|
fn test_line_map_location() {
|
||||||
let map = LineMap::new(TEST);
|
let map = LineMap::new(TEST);
|
||||||
assert_eq!(map.location(Pos(0)), Location::new(1, 1));
|
assert_eq!(map.location(Pos(0)), Some(Location::new(1, 1)));
|
||||||
assert_eq!(map.location(Pos(2)), Location::new(1, 2));
|
assert_eq!(map.location(Pos(2)), Some(Location::new(1, 2)));
|
||||||
assert_eq!(map.location(Pos(6)), Location::new(1, 6));
|
assert_eq!(map.location(Pos(6)), Some(Location::new(1, 6)));
|
||||||
assert_eq!(map.location(Pos(7)), Location::new(2, 1));
|
assert_eq!(map.location(Pos(7)), Some(Location::new(2, 1)));
|
||||||
assert_eq!(map.location(Pos(8)), Location::new(2, 2));
|
assert_eq!(map.location(Pos(8)), Some(Location::new(2, 2)));
|
||||||
assert_eq!(map.location(Pos(12)), Location::new(2, 3));
|
assert_eq!(map.location(Pos(12)), Some(Location::new(2, 3)));
|
||||||
assert_eq!(map.location(Pos(21)), Location::new(4, 4));
|
assert_eq!(map.location(Pos(21)), Some(Location::new(4, 4)));
|
||||||
|
assert_eq!(map.location(Pos(22)), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
fn test_line_map_pos() {
|
||||||
fn test_line_map_panics_out_of_bounds() {
|
fn assert_round_trip(map: &LineMap, pos: Pos) {
|
||||||
LineMap::new(TEST).location(Pos(22));
|
assert_eq!(map.location(pos).and_then(|loc| map.pos(loc)), Some(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = LineMap::new(TEST);
|
||||||
|
assert_round_trip(&map, Pos(0));
|
||||||
|
assert_round_trip(&map, Pos(7));
|
||||||
|
assert_round_trip(&map, Pos(12));
|
||||||
|
assert_round_trip(&map, Pos(21));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,24 +56,31 @@ use Unit::*;
|
|||||||
fn Id(ident: &str) -> Expr {
|
fn Id(ident: &str) -> Expr {
|
||||||
Expr::Lit(Lit::Ident(Ident(ident.to_string())))
|
Expr::Lit(Lit::Ident(Ident(ident.to_string())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Bool(b: bool) -> Expr {
|
fn Bool(b: bool) -> Expr {
|
||||||
Expr::Lit(Lit::Bool(b))
|
Expr::Lit(Lit::Bool(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Int(int: i64) -> Expr {
|
fn Int(int: i64) -> Expr {
|
||||||
Expr::Lit(Lit::Int(int))
|
Expr::Lit(Lit::Int(int))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Float(float: f64) -> Expr {
|
fn Float(float: f64) -> Expr {
|
||||||
Expr::Lit(Lit::Float(float))
|
Expr::Lit(Lit::Float(float))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Percent(percent: f64) -> Expr {
|
fn Percent(percent: f64) -> Expr {
|
||||||
Expr::Lit(Lit::Percent(percent))
|
Expr::Lit(Lit::Percent(percent))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Length(val: f64, unit: Unit) -> Expr {
|
fn Length(val: f64, unit: Unit) -> Expr {
|
||||||
Expr::Lit(Lit::Length(val, unit))
|
Expr::Lit(Lit::Length(val, unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Color(color: RgbaColor) -> Expr {
|
fn Color(color: RgbaColor) -> Expr {
|
||||||
Expr::Lit(Lit::Color(color))
|
Expr::Lit(Lit::Color(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Str(string: &str) -> Expr {
|
fn Str(string: &str) -> Expr {
|
||||||
Expr::Lit(Lit::Str(string.to_string()))
|
Expr::Lit(Lit::Str(string.to_string()))
|
||||||
}
|
}
|
||||||
@ -98,6 +105,7 @@ fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
|
|||||||
expr: expr.into().map(Box::new),
|
expr: expr.into().map(Box::new),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Binary(
|
fn Binary(
|
||||||
op: impl Into<Spanned<BinOp>>,
|
op: impl Into<Spanned<BinOp>>,
|
||||||
lhs: impl Into<Spanned<Expr>>,
|
lhs: impl Into<Spanned<Expr>>,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
# Tests
|
# Tests
|
||||||
|
|
||||||
- `typ`: Input files
|
- `typ`: Input files
|
||||||
- `pdf`: PDF files produced by tests
|
- `ref`: Reference images which the output is compared with to determine
|
||||||
- `png`: PNG files produced by tests
|
whether a test passed or failed
|
||||||
- `cmp`: Reference images which the PNGs are compared to byte-wise to determine
|
|
||||||
whether the test passed or failed
|
|
||||||
- `res`: Resource files used by tests
|
- `res`: Resource files used by tests
|
||||||
|
- `out`: PNG and PDF files produced by tests
|
||||||
|
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 275 KiB |
BIN
tests/ref/empty.png
Normal file
After Width: | Height: | Size: 120 B |
BIN
tests/ref/example-coma.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
tests/ref/image-error.png
Normal file
After Width: | Height: | Size: 120 B |
BIN
tests/ref/image-fit.png
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
tests/ref/image-jpeg.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
tests/ref/image-png.png
Normal file
After Width: | Height: | Size: 37 KiB |
0
tests/typ/empty.typ
Normal file
@ -1,3 +1,5 @@
|
|||||||
|
// Small integration test of syntax, page setup, box layout and alignment.
|
||||||
|
|
||||||
[page: width=450pt, height=300pt, margins=1cm]
|
[page: width=450pt, height=300pt, margins=1cm]
|
||||||
|
|
||||||
[box][
|
[box][
|
8
tests/typ/image-error.typ
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// error: 5:9-5:30 failed to load image
|
||||||
|
// error: 8:9-8:30 failed to load image
|
||||||
|
|
||||||
|
// File does not exist.
|
||||||
|
[image: "path/does/not/exist"]
|
||||||
|
|
||||||
|
// File exists, but is no image.
|
||||||
|
[image: "typ/image-error.typ"]
|
21
tests/typ/image-fit.typ
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Fit to width of page.
|
||||||
|
[image: "res/rhino.png"]
|
||||||
|
|
||||||
|
// Fit to height of page.
|
||||||
|
[page: width=270pt][
|
||||||
|
[image: "res/rhino.png"]
|
||||||
|
]
|
||||||
|
|
||||||
|
// Set width explicitly.
|
||||||
|
[image: "res/rhino.png", width=50pt]
|
||||||
|
|
||||||
|
// Set height explicitly.
|
||||||
|
[image: "res/rhino.png", height=50pt]
|
||||||
|
|
||||||
|
// Set width and height explicitly and force stretching.
|
||||||
|
[image: "res/rhino.png", width=25pt, height=50pt]
|
||||||
|
|
||||||
|
// Make sure the bounding-box of the image is correct.
|
||||||
|
[align: bottom, right][
|
||||||
|
[image: "res/tiger.jpg"]
|
||||||
|
]
|
2
tests/typ/image-jpeg.typ
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Load an RGB JPEG image.
|
||||||
|
[image: "res/tiger.jpg"]
|
2
tests/typ/image-png.typ
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Load an RGBA PNG image.
|
||||||
|
[image: "res/rhino.png"]
|
@ -1,15 +0,0 @@
|
|||||||
[page: width=5cm, height=5cm, margins=0.25cm]
|
|
||||||
|
|
||||||
[image: "res/tiger.jpg"]
|
|
||||||
|
|
||||||
[pagebreak]
|
|
||||||
|
|
||||||
# Tiger
|
|
||||||
[image: "res/tiger.jpg", width=2cm]
|
|
||||||
[image: "res/rhino.png", width=1cm]
|
|
||||||
[image: "res/rhino.png", height=2cm]
|
|
||||||
|
|
||||||
[pagebreak]
|
|
||||||
|
|
||||||
[align: center, bottom]
|
|
||||||
[image: "res/tiger.jpg", width=2cm, height=3.5cm]
|
|
193
tests/typeset.rs
@ -1,35 +1,35 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, File};
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fontdock::fs::{FsIndex, FsSource};
|
use fontdock::fs::{FsIndex, FsSource};
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
use memmap::Mmap;
|
|
||||||
use tiny_skia::{
|
use tiny_skia::{
|
||||||
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
||||||
Rect, SpreadMode, Transform,
|
Rect, SpreadMode, Transform,
|
||||||
};
|
};
|
||||||
use ttf_parser::OutlineBuilder;
|
use ttf_parser::OutlineBuilder;
|
||||||
|
|
||||||
use typst::diag::{Feedback, Pass};
|
use typst::diag::{Diag, Feedback, Level, Pass};
|
||||||
use typst::env::{Env, ImageResource, ResourceLoader, SharedEnv};
|
use typst::env::{Env, ImageResource, ResourceLoader, SharedEnv};
|
||||||
use typst::eval::State;
|
use typst::eval::State;
|
||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::FontLoader;
|
use typst::font::FontLoader;
|
||||||
use typst::geom::{Length, Point};
|
use typst::geom::{Length, Point, Sides, Size};
|
||||||
use typst::layout::{BoxLayout, ImageElement, LayoutElement};
|
use typst::layout::{BoxLayout, ImageElement, LayoutElement};
|
||||||
use typst::parse::LineMap;
|
use typst::parse::{LineMap, Scanner};
|
||||||
use typst::shaping::Shaped;
|
use typst::shaping::Shaped;
|
||||||
|
use typst::syntax::{Location, Pos, SpanVec, SpanWith, Spanned};
|
||||||
use typst::typeset;
|
use typst::typeset;
|
||||||
|
|
||||||
const FONT_DIR: &str = "../fonts";
|
|
||||||
const TYP_DIR: &str = "typ";
|
const TYP_DIR: &str = "typ";
|
||||||
const PDF_DIR: &str = "pdf";
|
const REF_DIR: &str = "ref";
|
||||||
const PNG_DIR: &str = "png";
|
const PNG_DIR: &str = "out/png";
|
||||||
const CMP_DIR: &str = "cmp";
|
const PDF_DIR: &str = "out/pdf";
|
||||||
|
const FONT_DIR: &str = "../fonts";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env::set_current_dir(env::current_dir().unwrap().join("tests")).unwrap();
|
env::set_current_dir(env::current_dir().unwrap().join("tests")).unwrap();
|
||||||
@ -44,12 +44,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = src_path.file_stem().unwrap().to_string_lossy().to_string();
|
let name = src_path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
let pdf_path = Path::new(PDF_DIR).join(&name).with_extension("pdf");
|
|
||||||
let png_path = Path::new(PNG_DIR).join(&name).with_extension("png");
|
|
||||||
let ref_path = Path::new(CMP_DIR).join(&name).with_extension("png");
|
|
||||||
|
|
||||||
if filter.matches(&name) {
|
if filter.matches(&name) {
|
||||||
filtered.push((name, src_path, pdf_path, png_path, ref_path));
|
filtered.push((name, src_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +58,8 @@ fn main() {
|
|||||||
println!("Running {} tests", len);
|
println!("Running {} tests", len);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::create_dir_all(PDF_DIR).unwrap();
|
|
||||||
fs::create_dir_all(PNG_DIR).unwrap();
|
fs::create_dir_all(PNG_DIR).unwrap();
|
||||||
|
fs::create_dir_all(PDF_DIR).unwrap();
|
||||||
|
|
||||||
let mut index = FsIndex::new();
|
let mut index = FsIndex::new();
|
||||||
index.search_dir(FONT_DIR);
|
index.search_dir(FONT_DIR);
|
||||||
@ -76,29 +72,12 @@ fn main() {
|
|||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
for (name, src_path, pdf_path, png_path, ref_path) in filtered {
|
for (name, src_path) in filtered {
|
||||||
print!("Testing {}.", name);
|
let png_path = Path::new(PNG_DIR).join(&name).with_extension("png");
|
||||||
test(&src_path, &pdf_path, &png_path, &env);
|
let pdf_path = Path::new(PDF_DIR).join(&name).with_extension("pdf");
|
||||||
|
let ref_path = Path::new(REF_DIR).join(&name).with_extension("png");
|
||||||
|
|
||||||
let png_file = File::open(&png_path).unwrap();
|
ok &= test(&name, &src_path, &pdf_path, &png_path, &ref_path, &env);
|
||||||
let ref_file = match File::open(&ref_path) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(_) => {
|
|
||||||
println!(" Failed to open reference image. ❌");
|
|
||||||
ok = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let a = unsafe { Mmap::map(&png_file).unwrap() };
|
|
||||||
let b = unsafe { Mmap::map(&ref_file).unwrap() };
|
|
||||||
|
|
||||||
if *a != *b {
|
|
||||||
println!(" Does not match reference image. ❌");
|
|
||||||
ok = false;
|
|
||||||
} else {
|
|
||||||
println!(" Okay. ✔");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -106,41 +85,6 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test(src_path: &Path, pdf_path: &Path, png_path: &Path, env: &SharedEnv) {
|
|
||||||
let src = fs::read_to_string(src_path).unwrap();
|
|
||||||
let state = State::default();
|
|
||||||
let Pass {
|
|
||||||
output: layouts,
|
|
||||||
feedback: Feedback { mut diags, .. },
|
|
||||||
} = typeset(&src, Rc::clone(env), state);
|
|
||||||
|
|
||||||
if !diags.is_empty() {
|
|
||||||
diags.sort();
|
|
||||||
|
|
||||||
let map = LineMap::new(&src);
|
|
||||||
for diag in diags {
|
|
||||||
let span = diag.span;
|
|
||||||
let start = map.location(span.start);
|
|
||||||
let end = map.location(span.end);
|
|
||||||
println!(
|
|
||||||
" {}: {}:{}-{}: {}",
|
|
||||||
diag.v.level,
|
|
||||||
src_path.display(),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
diag.v.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let env = env.borrow();
|
|
||||||
let canvas = draw(&layouts, &env, 2.0);
|
|
||||||
canvas.pixmap.save_png(png_path).unwrap();
|
|
||||||
|
|
||||||
let pdf_data = pdf::export(&layouts, &env);
|
|
||||||
fs::write(pdf_path, pdf_data).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestFilter {
|
struct TestFilter {
|
||||||
filter: Vec<String>,
|
filter: Vec<String>,
|
||||||
perfect: bool,
|
perfect: bool,
|
||||||
@ -171,6 +115,111 @@ impl TestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test(
|
||||||
|
name: &str,
|
||||||
|
src_path: &Path,
|
||||||
|
pdf_path: &Path,
|
||||||
|
png_path: &Path,
|
||||||
|
ref_path: &Path,
|
||||||
|
env: &SharedEnv,
|
||||||
|
) -> bool {
|
||||||
|
println!("Testing {}.", name);
|
||||||
|
|
||||||
|
let src = fs::read_to_string(src_path).unwrap();
|
||||||
|
let map = LineMap::new(&src);
|
||||||
|
let ref_diags = parse_diags(&src, &map);
|
||||||
|
|
||||||
|
let mut state = State::default();
|
||||||
|
state.page.size = Size::uniform(Length::pt(120.0));
|
||||||
|
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
|
||||||
|
|
||||||
|
let Pass {
|
||||||
|
output: layouts,
|
||||||
|
feedback: Feedback { mut diags, .. },
|
||||||
|
} = typeset(&src, Rc::clone(env), state);
|
||||||
|
diags.sort();
|
||||||
|
|
||||||
|
let env = env.borrow();
|
||||||
|
let canvas = draw(&layouts, &env, 2.0);
|
||||||
|
canvas.pixmap.save_png(png_path).unwrap();
|
||||||
|
|
||||||
|
let pdf_data = pdf::export(&layouts, &env);
|
||||||
|
fs::write(pdf_path, pdf_data).unwrap();
|
||||||
|
|
||||||
|
let mut ok = true;
|
||||||
|
|
||||||
|
if diags != ref_diags {
|
||||||
|
println!(" Does not match expected diagnostics. ❌");
|
||||||
|
ok = false;
|
||||||
|
|
||||||
|
for diag in &diags {
|
||||||
|
if ref_diags.binary_search(diag).is_err() {
|
||||||
|
print!(" Unexpected | ");
|
||||||
|
print_diag(diag, &map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for diag in &ref_diags {
|
||||||
|
if diags.binary_search(diag).is_err() {
|
||||||
|
print!(" Missing | ");
|
||||||
|
print_diag(diag, &map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(ref_pixmap) = Pixmap::load_png(&ref_path) {
|
||||||
|
if canvas.pixmap != ref_pixmap {
|
||||||
|
println!(" Does not match reference image. ❌");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!(" Failed to open reference image. ❌");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
println!("\x1b[1ATesting {}. ✔", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_diags(src: &str, map: &LineMap) -> SpanVec<Diag> {
|
||||||
|
let mut diags = vec![];
|
||||||
|
|
||||||
|
for line in src.lines() {
|
||||||
|
let (level, rest) = if let Some(rest) = line.strip_prefix("// error: ") {
|
||||||
|
(Level::Error, rest)
|
||||||
|
} else if let Some(rest) = line.strip_prefix("// warning: ") {
|
||||||
|
(Level::Warning, rest)
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn pos(s: &mut Scanner, map: &LineMap) -> Pos {
|
||||||
|
let (line, _, column) = (num(s), s.eat_assert(':'), num(s));
|
||||||
|
map.pos(Location { line, column }).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num(s: &mut Scanner) -> u32 {
|
||||||
|
s.eat_while(|c| c.is_numeric()).parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = Scanner::new(rest);
|
||||||
|
let (start, _, end) = (pos(&mut s, map), s.eat_assert('-'), pos(&mut s, map));
|
||||||
|
diags.push(Diag::new(level, s.rest().trim()).span_with(start .. end));
|
||||||
|
}
|
||||||
|
|
||||||
|
diags.sort();
|
||||||
|
diags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_diag(diag: &Spanned<Diag>, map: &LineMap) {
|
||||||
|
let start = map.location(diag.span.start).unwrap();
|
||||||
|
let end = map.location(diag.span.end).unwrap();
|
||||||
|
println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message,);
|
||||||
|
}
|
||||||
|
|
||||||
fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
|
fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
|
||||||
let pad = Length::pt(5.0);
|
let pad = Length::pt(5.0);
|
||||||
|
|
||||||
|