mirror of
https://github.com/oxalica/nil.git
synced 2024-11-23 03:57:06 +03:00
Impl and unify tests using fixture
This commit is contained in:
parent
e2d1b027dc
commit
4c41f9140c
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.58"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
|
||||
checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
@ -293,6 +293,7 @@ dependencies = [
|
||||
name = "nil"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"either",
|
||||
"expect-test",
|
||||
"indexmap",
|
||||
|
@ -17,6 +17,7 @@ smol_str = "0.1.23"
|
||||
syntax = { path = "./syntax" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.60"
|
||||
expect-test = "1.3.0"
|
||||
|
||||
[workspace]
|
||||
|
14
src/base.rs
14
src/base.rs
@ -62,6 +62,20 @@ impl VfsPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for VfsPath {
|
||||
type Error = ();
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
Self::new(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for VfsPath {
|
||||
type Error = ();
|
||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||
Self::new(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of [`VfsPath`]s identified by [`FileId`]s.
|
||||
#[derive(Default, Clone, PartialEq, Eq)]
|
||||
pub struct FileSet {
|
||||
|
@ -1250,7 +1250,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// FIXME: The location is not quite right currently.
|
||||
#[test]
|
||||
fn attrset_merge_error() {
|
||||
// Value and value.
|
||||
|
@ -120,7 +120,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_path() {
|
||||
let (db, files) = TestDB::file_set(
|
||||
let (db, f) = TestDB::from_fixture(
|
||||
"
|
||||
#- /default.nix
|
||||
./foo/bar.nix
|
||||
@ -128,13 +128,22 @@ mod tests {
|
||||
#- /foo/bar.nix
|
||||
baz/../../bar.nix
|
||||
|
||||
#- bar.nix
|
||||
#- /bar.nix
|
||||
./.
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(db.module_paths(files[0])[0].resolve(&db), Some(files[1]));
|
||||
assert_eq!(db.module_paths(files[1])[0].resolve(&db), Some(files[2]));
|
||||
assert_eq!(db.module_paths(files[2])[0].resolve(&db), Some(files[0]));
|
||||
assert_eq!(
|
||||
db.module_paths(f["/default.nix"])[0].resolve(&db),
|
||||
Some(f["/foo/bar.nix"])
|
||||
);
|
||||
assert_eq!(
|
||||
db.module_paths(f["/foo/bar.nix"])[0].resolve(&db),
|
||||
Some(f["/bar.nix"])
|
||||
);
|
||||
assert_eq!(
|
||||
db.module_paths(f["/bar.nix"])[0].resolve(&db),
|
||||
Some(f["/default.nix"])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -98,13 +98,13 @@ mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
fn check(fixture: &str, expect: Expect) {
|
||||
let (db, file_id, [pos]) = TestDB::single_file(fixture).unwrap();
|
||||
let src = db.file_content(file_id);
|
||||
let targets = super::goto_definition(&db, file_id, pos)
|
||||
let (db, f) = TestDB::from_fixture(fixture).unwrap();
|
||||
let targets = super::goto_definition(&db, f[0].file_id, f[0].pos)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|target| {
|
||||
assert!(target.full_range.contains_range(target.focus_range));
|
||||
let src = db.file_content(target.file_id);
|
||||
let mut full = src[target.full_range].to_owned();
|
||||
let relative_focus = target.focus_range - target.full_range.start();
|
||||
full.insert(relative_focus.end().into(), '>');
|
||||
@ -175,9 +175,18 @@ mod tests {
|
||||
check("let true = 1; in true && $0false", expect![""]);
|
||||
}
|
||||
|
||||
// TODO: Multi-files test.
|
||||
#[test]
|
||||
fn path() {
|
||||
check("1 + $0./.", expect!["<>1 + ./."]);
|
||||
check(
|
||||
"
|
||||
#- /default.nix
|
||||
import $0./bar.nix
|
||||
|
||||
#- /bar.nix
|
||||
hello
|
||||
",
|
||||
expect!["<>hello"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
184
src/tests.rs
184
src/tests.rs
@ -1,6 +1,7 @@
|
||||
use crate::base::SourceDatabaseStorage;
|
||||
use crate::def::DefDatabaseStorage;
|
||||
use crate::{Change, DefDatabase, FileId, FileSet, SourceRoot, VfsPath};
|
||||
use crate::{Change, DefDatabase, FileId, FilePos, FileSet, SourceRoot, VfsPath};
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use indexmap::IndexMap;
|
||||
use rowan::ast::AstNode;
|
||||
use rowan::TextSize;
|
||||
@ -20,33 +21,34 @@ impl salsa::Database for TestDB {}
|
||||
impl TestDB {
|
||||
pub fn single_file<const MARKERS: usize>(
|
||||
fixture: &str,
|
||||
) -> Result<(Self, FileId, [TextSize; MARKERS]), String> {
|
||||
let (f, file, poses) = Fixture::single_file(fixture)?;
|
||||
let db = Self::from_fixture(f);
|
||||
Ok((db, file, poses))
|
||||
) -> Result<(Self, FileId, [TextSize; MARKERS])> {
|
||||
let (db, f) = Self::from_fixture(fixture)?;
|
||||
ensure!(f.files().len() == 1, "Fixture contains multiple files");
|
||||
let file_id = f.files()[0];
|
||||
let poses = f
|
||||
.markers()
|
||||
.iter()
|
||||
.map(|pos| pos.pos)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.ok()
|
||||
.context("Marker count mismatch")?;
|
||||
Ok((db, file_id, poses))
|
||||
}
|
||||
|
||||
pub fn file_set(fixture: &str) -> Result<(Self, Vec<FileId>), String> {
|
||||
let f = Fixture::file_set(fixture)?;
|
||||
let files = (0..f.files.len())
|
||||
.map(|i| FileId(i as u32))
|
||||
.collect::<Vec<_>>();
|
||||
let db = Self::from_fixture(f);
|
||||
Ok((db, files))
|
||||
}
|
||||
|
||||
fn from_fixture(fixture: Fixture) -> Self {
|
||||
pub fn from_fixture(fixture: &str) -> Result<(Self, Fixture)> {
|
||||
let f = Fixture::new(fixture)?;
|
||||
let mut db = Self::default();
|
||||
let mut change = Change::new();
|
||||
let mut file_set = FileSet::default();
|
||||
for (i, (path, text)) in (0u32..).zip(fixture.files) {
|
||||
for (i, (path, text)) in (0u32..).zip(&f.files) {
|
||||
let file = FileId(i);
|
||||
file_set.insert(file, path);
|
||||
change.change_file(file, Some(text.into()));
|
||||
file_set.insert(file, path.clone());
|
||||
change.change_file(file, Some(text.to_owned().into()));
|
||||
}
|
||||
change.set_roots(vec![SourceRoot::new_local(file_set)]);
|
||||
change.apply(&mut db);
|
||||
db
|
||||
Ok((db, f))
|
||||
}
|
||||
|
||||
pub fn find_node<T>(
|
||||
@ -73,88 +75,112 @@ impl TestDB {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct Fixture {
|
||||
pub struct Fixture {
|
||||
files: IndexMap<VfsPath, String>,
|
||||
file_ids: Vec<FileId>,
|
||||
markers: Vec<FilePos>,
|
||||
}
|
||||
|
||||
impl ops::Index<FileId> for Fixture {
|
||||
type Output = str;
|
||||
fn index(&self, index: FileId) -> &Self::Output {
|
||||
&self.files[index.0 as usize]
|
||||
impl ops::Index<usize> for Fixture {
|
||||
type Output = FilePos;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.markers[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ops::Index<&'a str> for Fixture {
|
||||
type Output = FileId;
|
||||
fn index(&self, index: &'a str) -> &Self::Output {
|
||||
let id = self
|
||||
.files
|
||||
.get_index_of(&VfsPath::new(index).unwrap())
|
||||
.unwrap();
|
||||
&self.file_ids[id]
|
||||
}
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn insert_file(&mut self, path: VfsPath, text: String) -> Result<(), String> {
|
||||
if self.files.insert(path, text).is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Duplicated path".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn single_file<const MARKERS: usize>(
|
||||
fixture: &str,
|
||||
) -> Result<(Self, FileId, [TextSize; MARKERS]), String> {
|
||||
if fixture.len() >= u32::MAX as usize {
|
||||
return Err("Size too large".into());
|
||||
}
|
||||
let mut markers = [TextSize::from(!0u32); MARKERS];
|
||||
let mut text = String::new();
|
||||
let mut chars = fixture.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == MARKER_INDICATOR {
|
||||
if let Some(n @ '0'..='9') = chars.peek().copied() {
|
||||
chars.next();
|
||||
let i = n.to_digit(10).unwrap() as usize;
|
||||
let place = markers
|
||||
.get_mut(i)
|
||||
.ok_or_else(|| format!("Marker {} out of bound", i))?;
|
||||
if *place != TextSize::from(!0u32) {
|
||||
return Err(format!("Marker {} redefined", i));
|
||||
}
|
||||
*place = TextSize::from(text.len() as u32);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
text.push(c);
|
||||
}
|
||||
for (i, &pos) in markers.iter().enumerate() {
|
||||
if pos == TextSize::from(!0u32) {
|
||||
return Err(format!("Marker {} not set", i));
|
||||
}
|
||||
}
|
||||
let mut this = Self::default();
|
||||
this.insert_file(VfsPath::new("/default.nix").unwrap(), text)?;
|
||||
Ok((this, FileId(0), markers))
|
||||
}
|
||||
|
||||
fn file_set(fixture: &str) -> Result<Self, String> {
|
||||
if fixture.len() >= u32::MAX as usize {
|
||||
return Err("Size too large".into());
|
||||
}
|
||||
pub fn new(fixture: &str) -> Result<Self> {
|
||||
ensure!(
|
||||
u32::try_from(fixture.len()).is_ok(),
|
||||
"Size too large: {}",
|
||||
fixture.len()
|
||||
);
|
||||
|
||||
let mut this = Self::default();
|
||||
let mut missing_header = false;
|
||||
let mut cur_path = None;
|
||||
let mut cur_text = String::new();
|
||||
let mut cur_file = FileId(0);
|
||||
let mut markers = [None; 10];
|
||||
for line in fixture.lines().skip_while(|line| line.is_empty()) {
|
||||
if let Some(path) = line.strip_prefix("#- ") {
|
||||
let path = VfsPath::new(path).ok_or_else(|| "Invalid path".to_owned())?;
|
||||
ensure!(!missing_header, "Missing path header at the first line");
|
||||
|
||||
let path = VfsPath::new(path).context("Invalid path")?;
|
||||
if let Some(prev_path) = cur_path.replace(path) {
|
||||
this.insert_file(prev_path, mem::take(&mut cur_text))?;
|
||||
cur_file.0 += 1;
|
||||
}
|
||||
} else {
|
||||
if cur_path.is_none() {
|
||||
return Err("No path specified".into());
|
||||
missing_header = true;
|
||||
cur_path = Some("/default.nix".try_into().unwrap());
|
||||
}
|
||||
|
||||
let mut iter = line.chars().peekable();
|
||||
while let Some(ch) = iter.next() {
|
||||
if ch == MARKER_INDICATOR
|
||||
&& matches!(iter.peek(), Some(c) if c.is_ascii_digit())
|
||||
{
|
||||
let n = iter.next().unwrap().to_digit(10).unwrap() as usize;
|
||||
let pos =
|
||||
FilePos::new(cur_file, TextSize::try_from(cur_text.len()).unwrap());
|
||||
ensure!(
|
||||
markers[n].replace(pos).is_none(),
|
||||
"Duplicated marker: {}",
|
||||
n
|
||||
);
|
||||
} else {
|
||||
cur_text.push(ch);
|
||||
}
|
||||
}
|
||||
cur_text += line;
|
||||
cur_text += "\n";
|
||||
}
|
||||
}
|
||||
this.insert_file(
|
||||
cur_path.ok_or_else(|| "Empty fixture".to_owned())?,
|
||||
cur_text,
|
||||
)?;
|
||||
this.insert_file(cur_path.context("Empty fixture")?, cur_text)?;
|
||||
|
||||
let marker_len = markers
|
||||
.iter()
|
||||
.rposition(|p| p.is_some())
|
||||
.map_or(0, |n| n + 1);
|
||||
this.markers = markers
|
||||
.into_iter()
|
||||
.take(marker_len)
|
||||
.enumerate()
|
||||
.map(|(i, p)| p.with_context(|| format!("Discontinuous marker: {}", i)))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
fn insert_file(&mut self, path: VfsPath, mut text: String) -> Result<()> {
|
||||
let file = FileId(self.files.len() as u32);
|
||||
text.truncate(text.trim_end().len());
|
||||
ensure!(
|
||||
self.files.insert(path.clone(), text).is_none(),
|
||||
"Duplicated path: {:?}",
|
||||
path
|
||||
);
|
||||
self.file_ids.push(file);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn files(&self) -> &[FileId] {
|
||||
&self.file_ids
|
||||
}
|
||||
|
||||
pub fn markers(&self) -> &[FilePos] {
|
||||
&self.markers
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user