1
1
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:
oxalica 2022-08-10 19:24:11 +08:00
parent e2d1b027dc
commit 4c41f9140c
7 changed files with 150 additions and 91 deletions

5
Cargo.lock generated
View File

@ -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",

View File

@ -17,6 +17,7 @@ smol_str = "0.1.23"
syntax = { path = "./syntax" }
[dev-dependencies]
anyhow = "1.0.60"
expect-test = "1.3.0"
[workspace]

View File

@ -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 {

View File

@ -1250,7 +1250,6 @@ mod tests {
);
}
// FIXME: The location is not quite right currently.
#[test]
fn attrset_merge_error() {
// Value and value.

View File

@ -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"])
);
}
}

View File

@ -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"],
);
}
}

View File

@ -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
}
}