From 7b92bd7c340d9f9c094ed2fa57912049317d9b20 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 26 Jun 2023 13:57:21 +0200 Subject: Basic package management --- tests/Cargo.toml | 1 - tests/packages/adder-0.1.0/lib.typ | 1 + tests/packages/adder-0.1.0/typst.toml | 4 + tests/src/benches.rs | 31 ++++---- tests/src/tests.rs | 143 ++++++++++++++-------------------- tests/typ/compiler/hint.typ | 10 ++- tests/typ/compiler/packages.typ | 64 +++++++++++++++ tests/typ/visualize/image.typ | 4 +- 8 files changed, 151 insertions(+), 107 deletions(-) create mode 100644 tests/packages/adder-0.1.0/lib.typ create mode 100644 tests/packages/adder-0.1.0/typst.toml create mode 100644 tests/typ/compiler/packages.typ (limited to 'tests') diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 1912f50a..c87ecc79 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -10,7 +10,6 @@ publish = false typst = { path = ".." } typst-library = { path = "../library" } comemo = "0.3" -elsa = "1.8" iai = { git = "https://github.com/reknih/iai" } once_cell = "1" oxipng = "8.0.0" diff --git a/tests/packages/adder-0.1.0/lib.typ b/tests/packages/adder-0.1.0/lib.typ new file mode 100644 index 00000000..217e7930 --- /dev/null +++ b/tests/packages/adder-0.1.0/lib.typ @@ -0,0 +1 @@ +#let add(x, y) = x + y diff --git a/tests/packages/adder-0.1.0/typst.toml b/tests/packages/adder-0.1.0/typst.toml new file mode 100644 index 00000000..b8d62f99 --- /dev/null +++ b/tests/packages/adder-0.1.0/typst.toml @@ -0,0 +1,4 @@ +[package] +name = "adder" +version = "0.1.0" +entrypoint = "lib.typ" diff --git a/tests/src/benches.rs b/tests/src/benches.rs index aeddcaf9..9ee7a2f3 100644 --- a/tests/src/benches.rs +++ b/tests/src/benches.rs @@ -1,13 +1,12 @@ -use std::path::Path; - use comemo::{Prehashed, Track, Tracked}; use iai::{black_box, main, Iai}; -use typst::diag::{FileError, FileResult}; +use typst::diag::FileResult; use typst::eval::{Datetime, Library}; +use typst::file::FileId; use typst::font::{Font, FontBook}; use typst::geom::Color; -use typst::syntax::{Source, SourceId}; -use typst::util::Buffer; +use typst::syntax::Source; +use typst::util::Bytes; use typst::World; use unscanny::Scanner; @@ -124,31 +123,27 @@ impl World for BenchWorld { &self.library } - fn main(&self) -> &Source { - &self.source + fn book(&self) -> &Prehashed { + &self.book } - fn resolve(&self, path: &Path) -> FileResult { - Err(FileError::NotFound(path.into())) + fn main(&self) -> Source { + self.source.clone() } - fn source(&self, _: SourceId) -> &Source { - &self.source + fn source(&self, _: FileId) -> FileResult { + unimplemented!() } - fn book(&self) -> &Prehashed { - &self.book + fn file(&self, _: FileId) -> FileResult { + unimplemented!() } fn font(&self, _: usize) -> Option { Some(self.font.clone()) } - fn file(&self, path: &Path) -> FileResult { - Err(FileError::NotFound(path.into())) - } - fn today(&self, _: Option) -> Option { - Some(Datetime::from_ymd(1970, 1, 1).unwrap()) + unimplemented!() } } diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 4aa459af..a2b6e985 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -13,11 +13,11 @@ use std::path::{Path, PathBuf}; use clap::Parser; use comemo::{Prehashed, Track}; -use elsa::FrozenVec; use oxipng::{InFile, Options, OutFile}; use rayon::iter::{ParallelBridge, ParallelIterator}; use std::cell::OnceCell; use tiny_skia as sk; +use typst::file::FileId; use unscanny::Scanner; use walkdir::WalkDir; @@ -26,8 +26,8 @@ use typst::doc::{Document, Frame, FrameItem, Meta}; use typst::eval::{eco_format, func, Datetime, Library, NoneValue, Value}; use typst::font::{Font, FontBook}; use typst::geom::{Abs, Color, RgbaColor, Smart}; -use typst::syntax::{Source, SourceId, Span, SyntaxNode}; -use typst::util::{Buffer, PathExt}; +use typst::syntax::{Source, Span, SyntaxNode}; +use typst::util::{Bytes, PathExt}; use typst::World; use typst_library::layout::{Margin, PageElem}; use typst_library::text::{TextElem, TextSize}; @@ -197,34 +197,21 @@ fn library() -> Library { } /// A world that provides access to the tests environment. +#[derive(Clone)] struct TestWorld { print: PrintConfig, + main: FileId, library: Prehashed, book: Prehashed, fonts: Vec, paths: RefCell>, - sources: FrozenVec>, - main: SourceId, } -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)] +#[derive(Clone)] struct PathSlot { - source: OnceCell>, - buffer: OnceCell>, + system_path: PathBuf, + source: OnceCell>, + buffer: OnceCell>, } impl TestWorld { @@ -243,92 +230,81 @@ impl TestWorld { Self { print, + main: FileId::detached(), library: Prehashed::new(library()), book: Prehashed::new(FontBook::from_fonts(&fonts)), fonts, paths: RefCell::default(), - sources: FrozenVec::new(), - main: SourceId::detached(), } } } impl World for TestWorld { - fn root(&self) -> &Path { - Path::new(FILE_DIR) - } - fn library(&self) -> &Prehashed { &self.library } - fn main(&self) -> &Source { - self.source(self.main) + fn book(&self) -> &Prehashed { + &self.book + } + + fn main(&self) -> Source { + self.source(self.main).unwrap() } - fn resolve(&self, path: &Path) -> FileResult { - self.slot(path) - .source + fn source(&self, id: FileId) -> FileResult { + let slot = self.slot(id)?; + slot.source .get_or_init(|| { - let buf = read(path)?; + let buf = read(&slot.system_path)?; let text = String::from_utf8(buf)?; - Ok(self.insert(path, text)) + Ok(Source::new(id, text)) }) .clone() } - fn source(&self, id: SourceId) -> &Source { - &self.sources[id.as_u16() as usize] - } - - fn book(&self) -> &Prehashed { - &self.book + fn file(&self, id: FileId) -> FileResult { + let slot = self.slot(id)?; + slot.buffer + .get_or_init(|| read(&slot.system_path).map(Bytes::from)) + .clone() } fn font(&self, id: usize) -> Option { Some(self.fonts[id].clone()) } - fn file(&self, path: &Path) -> FileResult { - self.slot(path) - .buffer - .get_or_init(|| read(path).map(Buffer::from)) - .clone() - } - fn today(&self, _: Option) -> Option { Some(Datetime::from_ymd(1970, 1, 1).unwrap()) } } impl TestWorld { - fn set(&mut self, path: &Path, text: String) -> SourceId { - let slot = self.slot(path); - let id = if let Some(&Ok(id)) = slot.source.get() { - drop(slot); - self.sources.as_mut()[id.as_u16() as usize].replace(text); - id - } else { - let id = self.insert(path, text); - slot.source.set(Ok(id)).unwrap(); - drop(slot); - id + fn set(&mut self, path: &Path, text: String) -> Source { + self.main = FileId::new(None, path); + let mut slot = self.slot(self.main).unwrap(); + let source = Source::new(self.main, text); + slot.source = OnceCell::from(Ok(source.clone())); + source + } + + fn slot(&self, id: FileId) -> FileResult> { + let path = id.path(); + let root: PathBuf = match id.package() { + Some(spec) => format!("packages/{}-{}", spec.name, spec.version).into(), + None if path.is_relative() => PathBuf::new(), + None => FILE_DIR.into(), }; - self.main = id; - id - } - fn slot(&self, path: &Path) -> RefMut { - RefMut::map(self.paths.borrow_mut(), |paths| { - paths.entry(path.normalize()).or_default() - }) - } + let system_path = root.join_rooted(id.path()).ok_or(FileError::AccessDenied)?; - fn insert(&self, path: &Path, text: String) -> SourceId { - let id = SourceId::from_u16(self.sources.len() as u16); - let source = Source::new(id, path, text); - self.sources.push(Box::new(source)); - id + Ok(RefMut::map(self.paths.borrow_mut(), |paths| { + paths.entry(system_path.clone()).or_insert_with(|| PathSlot { + system_path, + source: OnceCell::new(), + buffer: OnceCell::new(), + }) + })) } } @@ -522,26 +498,25 @@ fn test_part( ) -> (bool, bool, Vec) { let mut ok = true; - let id = world.set(src_path, text); - let source = world.source(id); + let source = world.set(src_path, text); if world.print.syntax { writeln!(output, "Syntax Tree:\n{:#?}\n", source.root()).unwrap(); } - let metadata = parse_part_metadata(source); + let metadata = parse_part_metadata(&source); let compare_ref = metadata.part_configuration.compare_ref.unwrap_or(compare_ref); let validate_hints = metadata.part_configuration.validate_hints.unwrap_or(validate_hints); ok &= test_spans(output, source.root()); - ok &= test_reparse(output, world.source(id).text(), i, rng); + ok &= test_reparse(output, source.text(), i, rng); if world.print.model { let world = (world as &dyn World).track(); let route = typst::eval::Route::default(); let mut tracer = typst::eval::Tracer::default(); let module = - typst::eval::eval(world, route.track(), tracer.track_mut(), source).unwrap(); + typst::eval::eval(world, route.track(), tracer.track_mut(), &source).unwrap(); writeln!(output, "Model:\n{:#?}\n", module.content()).unwrap(); } @@ -563,15 +538,17 @@ fn test_part( // however, as the line of the hint is still verified. let actual_errors_and_hints: HashSet = errors .into_iter() - .filter(|error| error.span.source() == id) + .inspect(|error| assert!(!error.span.is_detached())) + .filter(|error| error.span.id() == source.id()) .flat_map(|error| { + let range = error.span.range(world); let output_error = - UserOutput::Error(error.range(world), error.message.replace('\\', "/")); + UserOutput::Error(range.clone(), error.message.replace('\\', "/")); let hints = error .hints .iter() .filter(|_| validate_hints) // No unexpected hints should be verified if disabled. - .map(|hint| UserOutput::Hint(error.range(world), hint.to_string())); + .map(|hint| UserOutput::Hint(range.clone(), hint.to_string())); iter::once(output_error).chain(hints).collect::>() }) .collect(); @@ -596,12 +573,12 @@ fn test_part( for unexpected in unexpected_outputs { write!(output, " Not annotated | ").unwrap(); - print_user_output(output, source, line, unexpected) + print_user_output(output, &source, line, unexpected) } for missing in missing_outputs { write!(output, " Not emitted | ").unwrap(); - print_user_output(output, source, line, missing) + print_user_output(output, &source, line, missing) } } @@ -820,7 +797,7 @@ fn test_reparse( let source = Source::detached(text); let leafs = leafs(source.root()); - let start = source.range(leafs[pick(0..leafs.len())].span()).start; + let start = source.find(leafs[pick(0..leafs.len())].span()).unwrap().offset(); let supplement = supplements[pick(0..supplements.len())]; ok &= apply(start..start, supplement); diff --git a/tests/typ/compiler/hint.typ b/tests/typ/compiler/hint.typ index 19d233d0..fdd5f59b 100644 --- a/tests/typ/compiler/hint.typ +++ b/tests/typ/compiler/hint.typ @@ -1,4 +1,4 @@ -// Test diagnostics. +// Test hints on diagnostics. // Ref: false --- @@ -23,13 +23,17 @@ --- = Heading + // Error: 1:20-1:26 cannot reference heading without numbering -// Hint: 1:20-1:26 did you mean to use `#set heading(numbering: "1.")`? +// Hint: 1:20-1:26 you can enable heading numbering with `#set heading(numbering: "1.")`? Can not be used as @intro --- +// This test is more of a tooling test. It checks if hint annotation validation +// can be turned off. // Hints: false -// This test is more of a tooling test. It checks if hint annotation validation can be turned off. + = Heading + // Error: 1:20-1:26 cannot reference heading without numbering Can not be used as @intro diff --git a/tests/typ/compiler/packages.typ b/tests/typ/compiler/packages.typ new file mode 100644 index 00000000..066a31de --- /dev/null +++ b/tests/typ/compiler/packages.typ @@ -0,0 +1,64 @@ +// Test package imports +// Ref: false + +--- +// Test import without items. +#import "@test/adder:0.1.0" +#test(adder.add(2, 8), 10) + +--- +// Test import with items. +#import "@test/adder:0.1.0": add +#test(add(2, 8), 10) + +--- +// Error: 9-13 `@` is not a valid package namespace +#import "@@": * + +--- +// Error: 9-16 package specification is missing name +#import "@heya": * + +--- +// Error: 9-15 `123` is not a valid package namespace +#import "@123": * + +--- +// Error: 9-17 package specification is missing name +#import "@test/": * + +--- +// Error: 9-22 package specification is missing version +#import "@test/mypkg": * + +--- +// Error: 9-20 `$$$` is not a valid package name +#import "@test/$$$": * + +--- +// Error: 9-23 package specification is missing version +#import "@test/mypkg:": * + +--- +// Error: 9-24 version number is missing minor version +#import "@test/mypkg:0": * + +--- +// Error: 9-29 `latest` is not a valid major version +#import "@test/mypkg:latest": * + +--- +// Error: 9-29 `-3` is not a valid major version +#import "@test/mypkg:-3.0.0": * + +--- +// Error: 9-26 version number is missing patch version +#import "@test/mypkg:0.3": * + +--- +// Error: 9-27 version number is missing patch version +#import "@test/mypkg:0.3.": * + +--- +// Error: 9-28 file not found (searched at typ/compiler/#test/mypkg:1.0.0) +#import "#test/mypkg:1.0.0": * diff --git a/tests/typ/visualize/image.typ b/tests/typ/visualize/image.typ index dc5b2ef6..7891e7e2 100644 --- a/tests/typ/visualize/image.typ +++ b/tests/typ/visualize/image.typ @@ -54,9 +54,9 @@ A #box(image("/tiger.jpg", height: 1cm, width: 80%)) B #image("path/does/not/exist") --- -// Error: 8-21 unknown image format +// Error: 2-22 unknown image format #image("./image.typ") --- -// Error: 8-18 failed to parse svg: found closing tag 'g' instead of 'style' in line 4 +// Error: 2-19 failed to parse svg: found closing tag 'g' instead of 'style' in line 4 #image("/bad.svg") -- cgit v1.2.3