mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Parallelize tests (#900)
This commit is contained in:
parent
561ff979d5
commit
b75cad2d3b
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -1089,9 +1089,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.141"
|
version = "0.2.142"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflate-sys"
|
name = "libdeflate-sys"
|
||||||
@ -1704,9 +1704,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.37.12"
|
version = "0.37.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659"
|
checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
@ -2373,6 +2373,7 @@ dependencies = [
|
|||||||
"iai",
|
"iai",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"oxipng",
|
"oxipng",
|
||||||
|
"rayon",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
"ttf-parser 0.17.1",
|
"ttf-parser 0.17.1",
|
||||||
"typst",
|
"typst",
|
||||||
|
@ -18,6 +18,7 @@ use crate::util::{PathExt, StrExt};
|
|||||||
///
|
///
|
||||||
/// All line and column indices start at zero, just like byte indices. Only for
|
/// All line and column indices start at zero, just like byte indices. Only for
|
||||||
/// user-facing display, you should add 1 to them.
|
/// user-facing display, you should add 1 to them.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Source {
|
pub struct Source {
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -13,6 +13,7 @@ elsa = "1.7"
|
|||||||
iai = { git = "https://github.com/reknih/iai" }
|
iai = { git = "https://github.com/reknih/iai" }
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
oxipng = "8.0.0"
|
oxipng = "8.0.0"
|
||||||
|
rayon = "1.7.0"
|
||||||
tiny-skia = "0.6.2"
|
tiny-skia = "0.6.2"
|
||||||
ttf-parser = "0.17"
|
ttf-parser = "0.17"
|
||||||
unscanny = "0.1"
|
unscanny = "0.1"
|
||||||
|
@ -3,15 +3,24 @@
|
|||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt::Write as FmtWrite;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{env, io};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use comemo::{Prehashed, Track};
|
use comemo::{Prehashed, Track};
|
||||||
use elsa::FrozenVec;
|
use elsa::FrozenVec;
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
use oxipng::{InFile, Options, OutFile};
|
use oxipng::{InFile, Options, OutFile};
|
||||||
|
use rayon::iter::ParallelBridge;
|
||||||
|
use rayon::iter::ParallelIterator;
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
|
use unscanny::Scanner;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use typst::diag::{bail, FileError, FileResult};
|
use typst::diag::{bail, FileError, FileResult};
|
||||||
use typst::doc::{Document, Frame, FrameItem, Meta};
|
use typst::doc::{Document, Frame, FrameItem, Meta};
|
||||||
use typst::eval::{func, Library, Value};
|
use typst::eval::{func, Library, Value};
|
||||||
@ -22,8 +31,6 @@ use typst::util::{Buffer, PathExt};
|
|||||||
use typst::World;
|
use typst::World;
|
||||||
use typst_library::layout::PageElem;
|
use typst_library::layout::PageElem;
|
||||||
use typst_library::text::{TextElem, TextSize};
|
use typst_library::text::{TextElem, TextSize};
|
||||||
use unscanny::Scanner;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
const TYP_DIR: &str = "typ";
|
const TYP_DIR: &str = "typ";
|
||||||
const REF_DIR: &str = "ref";
|
const REF_DIR: &str = "ref";
|
||||||
@ -32,8 +39,6 @@ const PDF_DIR: &str = "pdf";
|
|||||||
const FONT_DIR: &str = "../assets/fonts";
|
const FONT_DIR: &str = "../assets/fonts";
|
||||||
const FILE_DIR: &str = "../assets/files";
|
const FILE_DIR: &str = "../assets/files";
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
#[clap(name = "typst-test", author)]
|
#[clap(name = "typst-test", author)]
|
||||||
struct Args {
|
struct Args {
|
||||||
@ -43,7 +48,7 @@ struct Args {
|
|||||||
subtest: Option<usize>,
|
subtest: Option<usize>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
exact: bool,
|
exact: bool,
|
||||||
#[arg(long)]
|
#[arg(long, default_value_t = env::var_os("UPDATE_EXPECT").is_some())]
|
||||||
update: bool,
|
update: bool,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pdf: bool,
|
pdf: bool,
|
||||||
@ -78,53 +83,49 @@ impl Args {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let mut filtered = Vec::new();
|
|
||||||
// Since different tests can affect each other through the memoization
|
// Create loader and context.
|
||||||
// cache, a deterministic order is important for reproducibility.
|
let world = TestWorld::new(args.print);
|
||||||
for entry in WalkDir::new("typ").sort_by_file_name() {
|
|
||||||
|
println!("Running tests...");
|
||||||
|
let results = WalkDir::new("typ")
|
||||||
|
.into_iter()
|
||||||
|
.par_bridge()
|
||||||
|
.filter_map(|entry| {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
if entry.depth() == 0 {
|
if entry.depth() == 0 {
|
||||||
continue;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.path().starts_with("typ/benches") {
|
if entry.path().starts_with("typ/benches") {
|
||||||
continue;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let src_path = entry.into_path();
|
let src_path = entry.into_path();
|
||||||
if src_path.extension() != Some(OsStr::new("typ")) {
|
if src_path.extension() != Some(OsStr::new("typ")) {
|
||||||
continue;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.matches(&src_path) {
|
if args.matches(&src_path) {
|
||||||
filtered.push(src_path);
|
Some(src_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.map_with(world, |world, src_path| {
|
||||||
let len = filtered.len();
|
|
||||||
if len == 1 {
|
|
||||||
println!("Running test ...");
|
|
||||||
} else if len > 1 {
|
|
||||||
println!("Running {len} tests");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create loader and context.
|
|
||||||
let mut world = TestWorld::new(args.print);
|
|
||||||
|
|
||||||
// Run all the tests.
|
|
||||||
let mut ok = 0;
|
|
||||||
for src_path in filtered {
|
|
||||||
let path = src_path.strip_prefix(TYP_DIR).unwrap();
|
let path = src_path.strip_prefix(TYP_DIR).unwrap();
|
||||||
let png_path = Path::new(PNG_DIR).join(path).with_extension("png");
|
let png_path = Path::new(PNG_DIR).join(path).with_extension("png");
|
||||||
let ref_path = Path::new(REF_DIR).join(path).with_extension("png");
|
let ref_path = Path::new(REF_DIR).join(path).with_extension("png");
|
||||||
let pdf_path =
|
let pdf_path =
|
||||||
args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf"));
|
args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf"));
|
||||||
|
|
||||||
ok +=
|
test(world, &src_path, &png_path, &ref_path, pdf_path.as_deref(), &args)
|
||||||
test(&mut world, &src_path, &png_path, &ref_path, pdf_path.as_deref(), &args)
|
as usize
|
||||||
as usize;
|
})
|
||||||
}
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let len = results.len();
|
||||||
|
let ok = results.iter().sum::<usize>();
|
||||||
if len > 1 {
|
if len > 1 {
|
||||||
println!("{ok} / {len} tests passed.");
|
println!("{ok} / {len} tests passed.");
|
||||||
}
|
}
|
||||||
@ -158,14 +159,15 @@ fn library() -> Library {
|
|||||||
/// Returns:
|
/// Returns:
|
||||||
#[func]
|
#[func]
|
||||||
fn print(#[variadic] values: Vec<Value>) -> Value {
|
fn print(#[variadic] values: Vec<Value>) -> Value {
|
||||||
print!("> ");
|
let mut stdout = io::stdout().lock();
|
||||||
|
write!(stdout, "> ").unwrap();
|
||||||
for (i, value) in values.into_iter().enumerate() {
|
for (i, value) in values.into_iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
print!(", ")
|
write!(stdout, ", ").unwrap();
|
||||||
}
|
}
|
||||||
print!("{value:?}");
|
write!(stdout, "{value:?}").unwrap();
|
||||||
}
|
}
|
||||||
println!();
|
writeln!(stdout).unwrap();
|
||||||
Value::None
|
Value::None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +208,21 @@ struct TestWorld {
|
|||||||
main: SourceId,
|
main: SourceId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl Clone for TestWorld {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
print: self.print,
|
||||||
|
library: self.library.clone(),
|
||||||
|
book: self.book.clone(),
|
||||||
|
fonts: self.fonts.clone(),
|
||||||
|
paths: self.paths.clone(),
|
||||||
|
sources: FrozenVec::from_iter(self.sources.iter().cloned().map(Box::new)),
|
||||||
|
main: self.main,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
struct PathSlot {
|
struct PathSlot {
|
||||||
source: OnceCell<FileResult<SourceId>>,
|
source: OnceCell<FileResult<SourceId>>,
|
||||||
buffer: OnceCell<FileResult<Buffer>>,
|
buffer: OnceCell<FileResult<Buffer>>,
|
||||||
@ -222,7 +238,7 @@ impl TestWorld {
|
|||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|entry| entry.file_type().is_file())
|
.filter(|entry| entry.file_type().is_file())
|
||||||
{
|
{
|
||||||
let data = std::fs::read(entry.path()).unwrap();
|
let data = fs::read(entry.path()).unwrap();
|
||||||
fonts.extend(Font::iter(data.into()));
|
fonts.extend(Font::iter(data.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,10 +353,10 @@ fn test(
|
|||||||
args: &Args,
|
args: &Args,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
||||||
println!("Testing {}", name.display());
|
|
||||||
|
|
||||||
let text = fs::read_to_string(src_path).unwrap();
|
let text = fs::read_to_string(src_path).unwrap();
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
@ -353,7 +369,7 @@ fn test(
|
|||||||
for (i, &part) in parts.iter().enumerate() {
|
for (i, &part) in parts.iter().enumerate() {
|
||||||
if let Some(x) = args.subtest {
|
if let Some(x) = args.subtest {
|
||||||
if x != i {
|
if x != i {
|
||||||
println!("skipped subtest {i}");
|
writeln!(output, " Skipped subtest {i}.").unwrap();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,8 +386,16 @@ fn test(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let (part_ok, compare_here, part_frames) =
|
let (part_ok, compare_here, part_frames) = test_part(
|
||||||
test_part(world, src_path, part.into(), i, compare_ref, line, &mut rng);
|
&mut output,
|
||||||
|
world,
|
||||||
|
src_path,
|
||||||
|
part.into(),
|
||||||
|
i,
|
||||||
|
compare_ref,
|
||||||
|
line,
|
||||||
|
&mut rng,
|
||||||
|
);
|
||||||
ok &= part_ok;
|
ok &= part_ok;
|
||||||
compare_ever |= compare_here;
|
compare_ever |= compare_here;
|
||||||
frames.extend(part_frames);
|
frames.extend(part_frames);
|
||||||
@ -390,7 +414,7 @@ fn test(
|
|||||||
|
|
||||||
if world.print.frames {
|
if world.print.frames {
|
||||||
for frame in &document.pages {
|
for frame in &document.pages {
|
||||||
println!("Frame:\n{:#?}\n", frame);
|
writeln!(output, "{:#?}\n", frame).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +435,7 @@ fn test(
|
|||||||
update_image(png_path, ref_path);
|
update_image(png_path, ref_path);
|
||||||
updated = true;
|
updated = true;
|
||||||
} else {
|
} else {
|
||||||
println!(" Does not match reference image. ❌");
|
writeln!(output, " Does not match reference image.").unwrap();
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,24 +444,32 @@ fn test(
|
|||||||
update_image(png_path, ref_path);
|
update_image(png_path, ref_path);
|
||||||
updated = true;
|
updated = true;
|
||||||
} else {
|
} else {
|
||||||
println!(" Failed to open reference image. ❌");
|
writeln!(output, " Failed to open reference image.").unwrap();
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok && !updated {
|
{
|
||||||
if world.print == PrintConfig::default() {
|
let mut stdout = io::stdout().lock();
|
||||||
print!("\x1b[1A");
|
stdout.write_all(name.to_string_lossy().as_bytes()).unwrap();
|
||||||
|
if ok {
|
||||||
|
writeln!(stdout, " ✔").unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(stdout, " ❌").unwrap();
|
||||||
|
}
|
||||||
|
if updated {
|
||||||
|
writeln!(stdout, " Updated reference image.").unwrap();
|
||||||
|
}
|
||||||
|
if !output.is_empty() {
|
||||||
|
stdout.write_all(output.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
println!("Testing {} ✔", name.display());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ok
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_image(png_path: &Path, ref_path: &Path) {
|
fn update_image(png_path: &Path, ref_path: &Path) {
|
||||||
println!(" Updated reference image. ✔");
|
|
||||||
oxipng::optimize(
|
oxipng::optimize(
|
||||||
&InFile::Path(png_path.to_owned()),
|
&InFile::Path(png_path.to_owned()),
|
||||||
&OutFile::Path(Some(ref_path.to_owned())),
|
&OutFile::Path(Some(ref_path.to_owned())),
|
||||||
@ -446,7 +478,9 @@ fn update_image(png_path: &Path, ref_path: &Path) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn test_part(
|
fn test_part(
|
||||||
|
output: &mut String,
|
||||||
world: &mut TestWorld,
|
world: &mut TestWorld,
|
||||||
src_path: &Path,
|
src_path: &Path,
|
||||||
text: String,
|
text: String,
|
||||||
@ -460,14 +494,14 @@ fn test_part(
|
|||||||
let id = world.set(src_path, text);
|
let id = world.set(src_path, text);
|
||||||
let source = world.source(id);
|
let source = world.source(id);
|
||||||
if world.print.syntax {
|
if world.print.syntax {
|
||||||
println!("Syntax Tree:\n{:#?}\n", source.root())
|
writeln!(output, "Syntax Tree:\n{:#?}\n", source.root()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (local_compare_ref, mut ref_errors) = parse_metadata(source);
|
let (local_compare_ref, mut ref_errors) = parse_metadata(source);
|
||||||
let compare_ref = local_compare_ref.unwrap_or(compare_ref);
|
let compare_ref = local_compare_ref.unwrap_or(compare_ref);
|
||||||
|
|
||||||
ok &= test_spans(source.root());
|
ok &= test_spans(output, source.root());
|
||||||
ok &= test_reparse(world.source(id).text(), i, rng);
|
ok &= test_reparse(output, world.source(id).text(), i, rng);
|
||||||
|
|
||||||
if world.print.model {
|
if world.print.model {
|
||||||
let world = (world as &dyn World).track();
|
let world = (world as &dyn World).track();
|
||||||
@ -475,7 +509,7 @@ fn test_part(
|
|||||||
let mut tracer = typst::eval::Tracer::default();
|
let mut tracer = typst::eval::Tracer::default();
|
||||||
let module =
|
let module =
|
||||||
typst::eval::eval(world, route.track(), tracer.track_mut(), source).unwrap();
|
typst::eval::eval(world, route.track(), tracer.track_mut(), source).unwrap();
|
||||||
println!("Model:\n{:#?}\n", module.content());
|
writeln!(output, "Model:\n{:#?}\n", module.content()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut frames, errors) = match typst::compile(world) {
|
let (mut frames, errors) = match typst::compile(world) {
|
||||||
@ -500,21 +534,21 @@ fn test_part(
|
|||||||
ref_errors.sort_by_key(|error| error.0.start);
|
ref_errors.sort_by_key(|error| error.0.start);
|
||||||
|
|
||||||
if errors != ref_errors {
|
if errors != ref_errors {
|
||||||
println!(" Subtest {i} does not match expected errors. ❌");
|
writeln!(output, " Subtest {i} does not match expected errors.").unwrap();
|
||||||
ok = false;
|
ok = false;
|
||||||
|
|
||||||
let source = world.source(id);
|
let source = world.source(id);
|
||||||
for error in errors.iter() {
|
for error in errors.iter() {
|
||||||
if !ref_errors.contains(error) {
|
if !ref_errors.contains(error) {
|
||||||
print!(" Not annotated | ");
|
write!(output, " Not annotated | ").unwrap();
|
||||||
print_error(source, line, error);
|
print_error(output, source, line, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for error in ref_errors.iter() {
|
for error in ref_errors.iter() {
|
||||||
if !errors.contains(error) {
|
if !errors.contains(error) {
|
||||||
print!(" Not emitted | ");
|
write!(output, " Not emitted | ").unwrap();
|
||||||
print_error(source, line, error);
|
print_error(output, source, line, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -551,7 +585,7 @@ fn parse_metadata(source: &Source) -> (Option<bool>, Vec<(Range<usize>, String)>
|
|||||||
source.line_column_to_byte(line, column).unwrap()
|
source.line_column_to_byte(line, column).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(rest) = line.strip_prefix("// Error: ") else { continue };
|
let Some(rest) = line.strip_prefix("// Error: ") else { continue; };
|
||||||
let mut s = Scanner::new(rest);
|
let mut s = Scanner::new(rest);
|
||||||
let start = pos(&mut s);
|
let start = pos(&mut s);
|
||||||
let end = if s.eat_if('-') { pos(&mut s) } else { start };
|
let end = if s.eat_if('-') { pos(&mut s) } else { start };
|
||||||
@ -563,12 +597,18 @@ fn parse_metadata(source: &Source) -> (Option<bool>, Vec<(Range<usize>, String)>
|
|||||||
(compare_ref, errors)
|
(compare_ref, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_error(source: &Source, line: usize, (range, message): &(Range<usize>, String)) {
|
fn print_error(
|
||||||
|
output: &mut String,
|
||||||
|
source: &Source,
|
||||||
|
line: usize,
|
||||||
|
(range, message): &(Range<usize>, String),
|
||||||
|
) {
|
||||||
let start_line = 1 + line + source.byte_to_line(range.start).unwrap();
|
let start_line = 1 + line + source.byte_to_line(range.start).unwrap();
|
||||||
let start_col = 1 + source.byte_to_column(range.start).unwrap();
|
let start_col = 1 + source.byte_to_column(range.start).unwrap();
|
||||||
let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
|
let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
|
||||||
let end_col = 1 + source.byte_to_column(range.end).unwrap();
|
let end_col = 1 + source.byte_to_column(range.end).unwrap();
|
||||||
println!("Error: {start_line}:{start_col}-{end_line}:{end_col}: {message}");
|
writeln!(output, "Error: {start_line}:{start_col}-{end_line}:{end_col}: {message}")
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pseudorandomly edit the source file and test whether a reparse produces the
|
/// Pseudorandomly edit the source file and test whether a reparse produces the
|
||||||
@ -577,7 +617,12 @@ fn print_error(source: &Source, line: usize, (range, message): &(Range<usize>, S
|
|||||||
/// The method will first inject 10 strings once every 400 source characters
|
/// The method will first inject 10 strings once every 400 source characters
|
||||||
/// and then select 5 leaf node boundaries to inject an additional, randomly
|
/// and then select 5 leaf node boundaries to inject an additional, randomly
|
||||||
/// chosen string from the injection list.
|
/// chosen string from the injection list.
|
||||||
fn test_reparse(text: &str, i: usize, rng: &mut LinearShift) -> bool {
|
fn test_reparse(
|
||||||
|
output: &mut String,
|
||||||
|
text: &str,
|
||||||
|
i: usize,
|
||||||
|
rng: &mut LinearShift,
|
||||||
|
) -> bool {
|
||||||
let supplements = [
|
let supplements = [
|
||||||
"[",
|
"[",
|
||||||
"]",
|
"]",
|
||||||
@ -608,7 +653,7 @@ fn test_reparse(text: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let apply = |replace: std::ops::Range<usize>, with| {
|
let mut apply = |replace: Range<usize>, with| {
|
||||||
let mut incr_source = Source::detached(text);
|
let mut incr_source = Source::detached(text);
|
||||||
if incr_source.root().len() != text.len() {
|
if incr_source.root().len() != text.len() {
|
||||||
println!(
|
println!(
|
||||||
@ -627,7 +672,7 @@ fn test_reparse(text: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
let mut incr_root = incr_source.root().clone();
|
let mut incr_root = incr_source.root().clone();
|
||||||
|
|
||||||
// Ensures that the span numbering invariants hold.
|
// Ensures that the span numbering invariants hold.
|
||||||
let spans_ok = test_spans(&ref_root) && test_spans(&incr_root);
|
let spans_ok = test_spans(output, &ref_root) && test_spans(output, &incr_root);
|
||||||
|
|
||||||
// Remove all spans so that the comparison works out.
|
// Remove all spans so that the comparison works out.
|
||||||
let tree_ok = {
|
let tree_ok = {
|
||||||
@ -637,13 +682,19 @@ fn test_reparse(text: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !tree_ok {
|
if !tree_ok {
|
||||||
println!(
|
writeln!(
|
||||||
|
output,
|
||||||
" Subtest {i} reparse differs from clean parse when inserting '{with}' at {}-{} ❌\n",
|
" Subtest {i} reparse differs from clean parse when inserting '{with}' at {}-{} ❌\n",
|
||||||
replace.start, replace.end,
|
replace.start, replace.end,
|
||||||
);
|
).unwrap();
|
||||||
println!(" Expected reference tree:\n{ref_root:#?}\n");
|
writeln!(output, " Expected reference tree:\n{ref_root:#?}\n").unwrap();
|
||||||
println!(" Found incremental tree:\n{incr_root:#?}");
|
writeln!(output, " Found incremental tree:\n{incr_root:#?}").unwrap();
|
||||||
println!(" Full source ({}):\n\"{edited_src:?}\"", edited_src.len());
|
writeln!(
|
||||||
|
output,
|
||||||
|
" Full source ({}):\n\"{edited_src:?}\"",
|
||||||
|
edited_src.len()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
spans_ok && tree_ok
|
spans_ok && tree_ok
|
||||||
@ -687,22 +738,27 @@ fn leafs(node: &SyntaxNode) -> Vec<SyntaxNode> {
|
|||||||
|
|
||||||
/// Ensure that all spans are properly ordered (and therefore unique).
|
/// Ensure that all spans are properly ordered (and therefore unique).
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_spans(root: &SyntaxNode) -> bool {
|
fn test_spans(output: &mut String, root: &SyntaxNode) -> bool {
|
||||||
test_spans_impl(root, 0..u64::MAX)
|
test_spans_impl(output, root, 0..u64::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_spans_impl(node: &SyntaxNode, within: Range<u64>) -> bool {
|
fn test_spans_impl(output: &mut String, node: &SyntaxNode, within: Range<u64>) -> bool {
|
||||||
if !within.contains(&node.span().number()) {
|
if !within.contains(&node.span().number()) {
|
||||||
eprintln!(" Node: {node:#?}");
|
writeln!(output, " Node: {node:#?}").unwrap();
|
||||||
eprintln!(" Wrong span order: {} not in {within:?} ❌", node.span().number(),);
|
writeln!(
|
||||||
|
output,
|
||||||
|
" Wrong span order: {} not in {within:?} ❌",
|
||||||
|
node.span().number()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = node.span().number() + 1;
|
let start = node.span().number() + 1;
|
||||||
let mut children = node.children().peekable();
|
let mut children = node.children().peekable();
|
||||||
while let Some(child) = children.next() {
|
while let Some(child) = children.next() {
|
||||||
let end = children.peek().map_or(within.end, |next| next.span().number());
|
let end = children.peek().map_or(within.end, |next| next.span().number());
|
||||||
if !test_spans_impl(child, start..end) {
|
if !test_spans_impl(output, child, start..end) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user