dev: split test functions for multiple crates (#1695)

* dev: split test functions for multiple crates

* build: update cargo.lock

* fix: compile error
This commit is contained in:
Myriad-Dreamin 2025-04-30 19:01:29 +08:00 committed by GitHub
parent 1108b39e3f
commit 647cda29a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 212 additions and 165 deletions

19
Cargo.lock generated
View file

@ -3218,9 +3218,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rpds"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0e15515d3ce3313324d842629ea4905c25a13f81953eadb88f85516f59290a4"
checksum = "a7f89f654d51fffdd6026289d07d1fd523244d46ae0a8bc22caa6dd7f9e8cb0b"
dependencies = [
"archery",
]
@ -4294,7 +4294,6 @@ dependencies = [
"hex",
"if_chain",
"indexmap 2.9.0",
"insta",
"itertools 0.13.0",
"log",
"lsp-types",
@ -4318,6 +4317,7 @@ dependencies = [
"tinymist-lint",
"tinymist-project",
"tinymist-std",
"tinymist-tests",
"tinymist-world",
"toml",
"ttf-parser",
@ -4413,6 +4413,19 @@ dependencies = [
"typst-svg",
]
[[package]]
name = "tinymist-tests"
version = "0.13.12"
dependencies = [
"comemo",
"insta",
"rayon",
"tinymist-analysis",
"tinymist-project",
"tinymist-std",
"typst",
]
[[package]]
name = "tinymist-vfs"
version = "0.13.12"

View file

@ -186,6 +186,8 @@ tinymist-project = { path = "./crates/tinymist-project/", version = "0.13.12" }
tinymist-task = { path = "./crates/tinymist-task/", version = "0.13.12" }
typst-shim = { path = "./crates/typst-shim", version = "0.13.12" }
tinymist-tests = { path = "./crates/tinymist-tests/" }
tinymist = { path = "./crates/tinymist/", version = "0.13.12" }
tinymist-analysis = { path = "./crates/tinymist-analysis/", version = "0.13.12" }
tinymist-core = { path = "./crates/tinymist-core/", version = "0.13.12", default-features = false }

View file

@ -6,9 +6,9 @@ use std::ops::Range;
use typst::syntax::Source;
/// An LSP Position encoded by [`PositionEncoding`].
type LspPosition = tinymist_world::debug_loc::LspPosition;
pub type LspPosition = tinymist_world::debug_loc::LspPosition;
/// An LSP range encoded by [`PositionEncoding`].
type LspRange = tinymist_world::debug_loc::LspRange;
pub type LspRange = tinymist_world::debug_loc::LspRange;
/// What counts as "1 character" for string indexing. We should always prefer
/// UTF-8, but support UTF-16 as long as it is standard. For more background on

View file

@ -57,10 +57,10 @@ walkdir.workspace = true
yaml-rust2.workspace = true
[dev-dependencies]
insta.workspace = true
serde.workspace = true
serde_json.workspace = true
typst-assets = { workspace = true, features = ["fonts"] }
tinymist-tests = { workspace = true }
sha2 = { version = "0.10" }
hex = { version = "0.4" }

View file

@ -33,19 +33,16 @@ mod prelude;
mod global;
pub use global::*;
use std::path::Path;
use std::sync::Arc;
use ecow::{eco_format, EcoVec};
use ecow::eco_format;
use lsp_types::Url;
use tinymist_project::LspComputeGraph;
use tinymist_std::{bail, ImmutPath, Result};
use tinymist_world::vfs::WorkspaceResolver;
use tinymist_world::{EntryReader, TaskInputs, WorldDeps};
use tinymist_std::{bail, Result};
use tinymist_world::{EntryReader, TaskInputs};
use typst::diag::{FileError, FileResult};
use typst::foundations::{Func, Value};
use typst::syntax::{FileId, Source};
use typst::World;
use typst::syntax::FileId;
use crate::{path_res_to_url, CompilerQueryResponse, SemanticRequest, StatefulRequest};
@ -65,65 +62,17 @@ impl ToFunc for Value {
/// Extension trait for `typst::World`.
pub trait LspWorldExt {
/// Get file's id by its path
fn file_id_by_path(&self, path: &Path) -> FileResult<FileId>;
/// Get the source of a file by file path.
fn source_by_path(&self, path: &Path) -> FileResult<Source>;
/// Resolve the uri for a file id.
fn uri_for_id(&self, fid: FileId) -> FileResult<Url>;
/// Get all depended file ids of a compilation, inclusively.
/// Note: must be called after compilation.
fn depended_files(&self) -> EcoVec<FileId>;
/// Get all depended paths in file system of a compilation, inclusively.
/// Note: must be called after compilation.
fn depended_fs_paths(&self) -> EcoVec<ImmutPath>;
}
impl LspWorldExt for tinymist_project::LspWorld {
fn file_id_by_path(&self, path: &Path) -> FileResult<FileId> {
// todo: source in packages
match self.id_for_path(path) {
Some(id) => Ok(id),
None => WorkspaceResolver::file_with_parent_root(path).ok_or_else(|| {
let reason = eco_format!("invalid path: {path:?}");
FileError::Other(Some(reason))
}),
}
}
fn source_by_path(&self, path: &Path) -> FileResult<Source> {
// todo: source cache
self.source(self.file_id_by_path(path)?)
}
fn uri_for_id(&self, fid: FileId) -> Result<Url, FileError> {
let res = path_res_to_url(self.path_for_id(fid)?);
crate::log_debug_ct!("uri_for_id: {fid:?} -> {res:?}");
res.map_err(|err| FileError::Other(Some(eco_format!("convert to url: {err:?}"))))
}
fn depended_files(&self) -> EcoVec<FileId> {
let mut deps = EcoVec::new();
self.iter_dependencies(&mut |file_id| {
deps.push(file_id);
});
deps
}
fn depended_fs_paths(&self) -> EcoVec<ImmutPath> {
let mut deps = EcoVec::new();
self.iter_dependencies(&mut |file_id| {
if let Ok(path) = self.path_for_id(file_id) {
deps.push(path.as_path().into());
}
});
deps
}
}
/// A snapshot for LSP queries.
@ -448,7 +397,6 @@ mod type_check_tests {
#[cfg(test)]
mod post_type_check_tests {
use insta::with_settings;
use typst::syntax::LinkedNode;
use typst_shim::syntax::LinkedNodeExt;
@ -484,7 +432,6 @@ mod post_type_check_tests {
#[cfg(test)]
mod type_describe_tests {
use insta::with_settings;
use typst::syntax::LinkedNode;
use typst_shim::syntax::LinkedNodeExt;

View file

@ -213,6 +213,8 @@ mod tests {
use typst::syntax::{FileId, VirtualPath};
use crate::tests::*;
// This is a workaround for slashes in the path on Windows and Linux
// are different
fn bib_snap(snap: &impl fmt::Debug) -> String {
@ -234,8 +236,8 @@ Euclid2:
FileId::new_fake(VirtualPath::new(Path::new("test.yml"))),
);
assert_eq!(bib.entries.len(), 2);
insta::assert_snapshot!(bib_snap(&bib.entries[0]), @r###"("Euclid", BibEntry { file_id: /test.yml, name_range: 1..7, range: 1..63, raw_entry: None })"###);
insta::assert_snapshot!(bib_snap(&bib.entries[1]), @r###"("Euclid2", BibEntry { file_id: /test.yml, name_range: 63..70, range: 63..126, raw_entry: None })"###);
assert_snapshot!(bib_snap(&bib.entries[0]), @r###"("Euclid", BibEntry { file_id: /test.yml, name_range: 1..7, range: 1..63, raw_entry: None })"###);
assert_snapshot!(bib_snap(&bib.entries[1]), @r###"("Euclid2", BibEntry { file_id: /test.yml, name_range: 63..70, range: 63..126, raw_entry: None })"###);
}
#[test]

View file

@ -98,7 +98,7 @@ mod tests {
let result = request.request(ctx);
insta::with_settings!({
with_settings!({
description => format!("Code Action on {})", make_range_annoation(&source)),
}, {
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));

View file

@ -100,8 +100,6 @@ impl StatefulRequest for CompletionRequest {
mod tests {
use std::collections::HashSet;
use insta::with_settings;
use super::*;
use crate::{completion::proto::CompletionItem, syntax::find_module_level_docs, tests::*};

View file

@ -4,7 +4,7 @@ use tinymist_project::LspWorld;
use tinymist_world::vfs::WorkspaceResolver;
use typst::syntax::Span;
use crate::{analysis::Analysis, prelude::*, LspWorldExt};
use crate::{analysis::Analysis, prelude::*};
use regex::RegexSet;

View file

@ -249,7 +249,7 @@ mod tests {
})
.join("\n");
insta::with_settings!({
with_settings!({
description => format!("Jump cursor on {})", make_range_annoation(&source)),
}, {
assert_snapshot!(results);

View file

@ -6,9 +6,9 @@ use tinymist_world::vfs::PathResolution;
use crate::prelude::*;
/// An LSP Position encoded by [`PositionEncoding`].
pub type LspPosition = lsp_types::Position;
pub use tinymist_analysis::location::LspPosition;
/// An LSP range encoded by [`PositionEncoding`].
pub type LspRange = lsp_types::Range;
pub use tinymist_analysis::location::LspRange;
pub use tinymist_analysis::location::*;

View file

@ -218,7 +218,7 @@ mod tests {
format!("{window_before}|{window_line}|{window_after}")
};
insta::with_settings!({
with_settings!({
description => format!("On Enter on {annotated})"),
}, {
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));

View file

@ -9,45 +9,30 @@ use std::{
};
use serde_json::{ser::PrettyFormatter, Serializer, Value};
use tinymist_project::{CompileFontArgs, ExportTarget, LspCompileSnapshot, LspComputeGraph};
use tinymist_project::{LspCompileSnapshot, LspComputeGraph};
use tinymist_std::path::unix_slash;
use tinymist_std::typst::TypstDocument;
use tinymist_world::debug_loc::LspRange;
use tinymist_world::package::PackageSpec;
use tinymist_world::vfs::WorkspaceResolver;
use tinymist_world::{EntryManager, EntryReader, EntryState, ShadowApi, TaskInputs};
use typst::foundations::Bytes;
use tinymist_world::{EntryReader, ShadowApi, TaskInputs};
use typst::syntax::ast::{self, AstNode};
use typst::syntax::{LinkedNode, Source, SyntaxKind, VirtualPath};
use typst_shim::syntax::LinkedNodeExt;
pub use crate::syntax::find_module_level_docs;
pub use insta::assert_snapshot;
pub use serde::Serialize;
pub use serde_json::json;
pub use tinymist_project::{LspUniverse, LspUniverseBuilder};
pub use tinymist_project::LspUniverse;
pub use tinymist_tests::{assert_snapshot, run_with_sources, with_settings};
pub use tinymist_world::WorldComputeGraph;
pub use crate::syntax::find_module_level_docs;
use crate::{analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding};
use crate::{to_lsp_position, CompletionFeat, LspWorldExt};
use crate::{to_lsp_position, CompletionFeat};
pub fn snapshot_testing(name: &str, f: &impl Fn(&mut LocalContext, PathBuf)) {
let name = if name.is_empty() { "playground" } else { name };
let mut settings = insta::Settings::new();
settings.set_prepend_module_to_snapshot(false);
settings.set_snapshot_path(format!("fixtures/{name}/snaps"));
settings.bind(|| {
let glob_path = format!("fixtures/{name}/*.typ");
insta::glob!(&glob_path, |path| {
let contents = std::fs::read_to_string(path).unwrap();
#[cfg(windows)]
let contents = contents.replace("\r\n", "\n");
run_with_sources(&contents, |verse, path| {
run_with_ctx(verse, path, f);
});
});
tinymist_tests::snapshot_testing!(name, |verse, path| {
run_with_ctx(verse, path, f);
});
}
@ -143,64 +128,6 @@ pub fn compile_doc_for_test(
WorldComputeGraph::new(snap)
}
pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBuf) -> T) -> T {
let root = if cfg!(windows) {
PathBuf::from("C:\\root")
} else {
PathBuf::from("/root")
};
let mut verse = LspUniverseBuilder::build(
EntryState::new_rooted(root.as_path().into(), None),
ExportTarget::Paged,
Default::default(),
Default::default(),
LspUniverseBuilder::resolve_package(None, None),
Arc::new(
LspUniverseBuilder::resolve_fonts(CompileFontArgs {
ignore_system_fonts: true,
..Default::default()
})
.unwrap(),
),
);
let sources = source.split("-----");
let mut last_pw = None;
for (idx, source) in sources.enumerate() {
// find prelude
let mut source = source.trim_start();
let mut path = None;
if source.starts_with("//") {
let first_line = source.lines().next().unwrap();
let content = first_line.trim_start_matches("/").trim();
if let Some(path_attr) = content.strip_prefix("path:") {
source = source.strip_prefix(first_line).unwrap().trim();
path = Some(path_attr.trim().to_owned())
}
};
let path = path.unwrap_or_else(|| format!("/s{idx}.typ"));
let path = path.strip_prefix("/").unwrap_or(path.as_str());
let pw = root.join(Path::new(&path));
verse
.map_shadow(&pw, Bytes::from_string(source.to_owned()))
.unwrap();
last_pw = Some(pw);
}
let pw = last_pw.unwrap();
verse
.mutate_entry(EntryState::new_rooted(
root.as_path().into(),
Some(VirtualPath::new(pw.strip_prefix(root).unwrap())),
))
.unwrap();
f(&mut verse, pw)
}
pub fn find_test_range(s: &Source) -> LspRange {
let range = find_test_range_(s);
crate::to_lsp_range(range, s, PositionEncoding::Utf16)

View file

@ -0,0 +1,22 @@
[package]
name = "tinymist-tests"
description = "Test support for Tinymist."
authors.workspace = true
version.workspace = true
license.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
typst.workspace = true
tinymist-analysis.workspace = true
tinymist-project = { workspace = true, features = ["lsp"] }
tinymist-std.workspace = true
comemo.workspace = true
rayon.workspace = true
insta.workspace = true
[lints]
workspace = true

View file

@ -0,0 +1,103 @@
//! Tests support for tinymist crates.
use std::{
path::{Path, PathBuf},
sync::{Arc, LazyLock},
};
use tinymist_project::{
base::ShadowApi, font::FontResolverImpl, CompileFontArgs, EntryManager, EntryState,
ExportTarget, LspUniverse, LspUniverseBuilder,
};
use typst::{foundations::Bytes, syntax::VirtualPath};
pub use insta::{assert_debug_snapshot, assert_snapshot, glob, with_settings, Settings};
/// Runs snapshot tests.
#[macro_export]
macro_rules! snapshot_testing {
($name:expr, $f:expr) => {
let name = $name;
let name = if name.is_empty() { "playground" } else { name };
let mut settings = $crate::Settings::new();
settings.set_prepend_module_to_snapshot(false);
settings.set_snapshot_path(format!("fixtures/{name}/snaps"));
settings.bind(|| {
let glob_path = format!("fixtures/{name}/*.typ");
$crate::glob!(&glob_path, |path| {
let contents = std::fs::read_to_string(path).unwrap();
#[cfg(windows)]
let contents = contents.replace("\r\n", "\n");
$crate::run_with_sources(&contents, $f);
});
});
};
}
/// A test that runs a function with a given source string and returns the
/// result.
///
/// Multiple sources can be provided, separated by `-----`. The last source
/// is used as the entry point.
pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBuf) -> T) -> T {
static FONT_RESOLVER: LazyLock<Arc<FontResolverImpl>> = LazyLock::new(|| {
Arc::new(
LspUniverseBuilder::resolve_fonts(CompileFontArgs {
ignore_system_fonts: true,
..Default::default()
})
.unwrap(),
)
});
let root = if cfg!(windows) {
PathBuf::from("C:\\root")
} else {
PathBuf::from("/root")
};
let mut verse = LspUniverseBuilder::build(
EntryState::new_rooted(root.as_path().into(), None),
ExportTarget::Paged,
Default::default(),
Default::default(),
LspUniverseBuilder::resolve_package(None, None),
FONT_RESOLVER.clone(),
);
let sources = source.split("-----");
let mut last_pw = None;
for (idx, source) in sources.enumerate() {
// find prelude
let mut source = source.trim_start();
let mut path = None;
if source.starts_with("//") {
let first_line = source.lines().next().unwrap();
let content = first_line.trim_start_matches("/").trim();
if let Some(path_attr) = content.strip_prefix("path:") {
source = source.strip_prefix(first_line).unwrap().trim();
path = Some(path_attr.trim().to_owned())
}
};
let path = path.unwrap_or_else(|| format!("/s{idx}.typ"));
let path = path.strip_prefix("/").unwrap_or(path.as_str());
let pw = root.join(Path::new(&path));
verse
.map_shadow(&pw, Bytes::from_string(source.to_owned()))
.unwrap();
last_pw = Some(pw);
}
let pw = last_pw.unwrap();
verse
.mutate_entry(EntryState::new_rooted(
root.as_path().into(),
Some(VirtualPath::new(pw.strip_prefix(root).unwrap())),
))
.unwrap();
f(&mut verse, pw)
}

View file

@ -6,7 +6,8 @@ use std::{
sync::{Arc, LazyLock, OnceLock},
};
use tinymist_std::error::prelude::*;
use ecow::EcoVec;
use tinymist_std::{error::prelude::*, ImmutPath};
use tinymist_vfs::{
FileId, FsProvider, PathResolution, RevisingVfs, SourceCache, Vfs, WorkspaceResolver,
};
@ -545,6 +546,20 @@ impl<F: CompilerFeat> CompilerWorld<F> {
self.inputs.clone()
}
pub fn revision(&self) -> NonZeroUsize {
self.revision
}
pub fn evict_vfs(&mut self, threshold: usize) {
self.vfs.evict(threshold);
}
pub fn evict_source_cache(&mut self, threshold: usize) {
self.vfs
.clone_source_cache()
.evict(self.vfs.revision(), threshold);
}
/// Resolve the real path for a file id.
pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
self.vfs.file_path(id)
@ -559,18 +574,37 @@ impl<F: CompilerFeat> CompilerWorld<F> {
))
}
pub fn revision(&self) -> NonZeroUsize {
self.revision
pub fn file_id_by_path(&self, path: &Path) -> FileResult<FileId> {
// todo: source in packages
match self.id_for_path(path) {
Some(id) => Ok(id),
None => WorkspaceResolver::file_with_parent_root(path).ok_or_else(|| {
let reason = eco_format!("invalid path: {path:?}");
FileError::Other(Some(reason))
}),
}
}
pub fn evict_vfs(&mut self, threshold: usize) {
self.vfs.evict(threshold);
pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
self.source(self.file_id_by_path(path)?)
}
pub fn evict_source_cache(&mut self, threshold: usize) {
self.vfs
.clone_source_cache()
.evict(self.vfs.revision(), threshold);
pub fn depended_files(&self) -> EcoVec<FileId> {
let mut deps = EcoVec::new();
self.iter_dependencies(&mut |file_id| {
deps.push(file_id);
});
deps
}
pub fn depended_fs_paths(&self) -> EcoVec<ImmutPath> {
let mut deps = EcoVec::new();
self.iter_dependencies(&mut |file_id| {
if let Ok(path) = self.path_for_id(file_id) {
deps.push(path.as_path().into());
}
});
deps
}
/// A list of all available packages and optionally descriptions for them.

View file

@ -2,7 +2,6 @@ use std::{path::Path, sync::Arc};
use reflexo_typst::{path::unix_slash, typst::prelude::EcoVec, LazyHash};
use rpds::RedBlackTreeMapSync;
use tinymist_query::LspWorldExt;
use tinymist_std::{hash::FxHashMap, ImmutPath};
use typst::diag::EcoString;

View file

@ -7,7 +7,7 @@ use lsp_types::request::ShowMessageRequest;
use lsp_types::*;
use reflexo::debug_loc::LspPosition;
use sync_ls::*;
use tinymist_query::{LspWorldExt, OnExportRequest, ServerInfoResponse};
use tinymist_query::{OnExportRequest, ServerInfoResponse};
use tinymist_std::error::prelude::*;
use tinymist_std::ImmutPath;
use tinymist_task::ProjectTask;