Skip to content

Commit

Permalink
Merge pull request #272 from baszalmstra/feature/fixtures
Browse files Browse the repository at this point in the history
feature: adds fixtures to support multiple files from string
  • Loading branch information
baszalmstra authored Sep 28, 2020
2 parents 575a3ba + ab46466 commit 720bdae
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 27 deletions.
1 change: 1 addition & 0 deletions crates/mun_hir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ either = "1.5.3"
[dev-dependencies]
insta = "0.16"
parking_lot = "0.10"
mun_test = { version = "=0.1.0", path = "../mun_test" }
1 change: 1 addition & 0 deletions crates/mun_hir/src/expr/validator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
db::DefDatabase,
diagnostics::DiagnosticSink,
expr::validator::{ExprValidator, TypeAliasValidator},
fixture::WithFixture,
mock::MockDatabase,
ModuleDef,
};
Expand Down
48 changes: 48 additions & 0 deletions crates/mun_hir/src/fixture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#![cfg(test)]

use crate::{FileId, SourceDatabase, SourceRoot, SourceRootId};
pub use mun_test::Fixture;
use std::convert::TryInto;
use std::sync::Arc;

impl<DB: SourceDatabase + Default + 'static> WithFixture for DB {}

/// Enables the creation of an instance from a [`Fixture`]
pub trait WithFixture: Default + SourceDatabase + 'static {
/// Constructs an instance from a fixture
fn with_files(fixture: impl AsRef<str>) -> Self {
let mut db = Self::default();
with_files(&mut db, fixture.as_ref());
db
}

/// Constructs an instance from a fixture
fn with_single_file(text: impl AsRef<str>) -> (Self, FileId) {
let mut db = Self::default();
let files = with_files(&mut db, text.as_ref());
assert_eq!(files.len(), 1);
(db, files[0])
}
}

/// Fills the specified database with all the files from the specified `fixture`
fn with_files(db: &mut dyn SourceDatabase, fixture: &str) -> Vec<FileId> {
let fixture = Fixture::parse(fixture);

let mut source_root = SourceRoot::default();
let source_root_id = SourceRootId(0);
let mut files = Vec::new();

for (idx, entry) in fixture.into_iter().enumerate() {
let file_id = FileId(idx.try_into().expect("too many files"));
db.set_file_relative_path(file_id, entry.relative_path);
db.set_file_text(file_id, Arc::new(entry.text));
db.set_file_source_root(file_id, source_root_id);
source_root.insert_file(file_id);
files.push(file_id);
}

db.set_source_root(source_root_id, Arc::new(source_root));

return files;
}
3 changes: 2 additions & 1 deletion crates/mun_hir/src/item_tree/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::item_tree::Fields;
use crate::{
fixture::WithFixture,
item_tree::Fields,
item_tree::{ItemTree, ModItem},
mock::MockDatabase,
DefDatabase,
Expand Down
1 change: 1 addition & 0 deletions crates/mun_hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod db;
pub mod diagnostics;
mod display;
mod expr;
mod fixture;
mod ids;
mod in_file;
mod input;
Expand Down
38 changes: 14 additions & 24 deletions crates/mun_hir/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::db::{AstDatabase, SourceDatabase};
use crate::db::{HirDatabase, Upcast};
use crate::input::{SourceRoot, SourceRootId};
use crate::{DefDatabase, FileId, RelativePathBuf};
#![cfg(test)]

use crate::{
db::{AstDatabase, SourceDatabase},
db::{HirDatabase, Upcast},
DefDatabase,
};
use mun_target::spec::Target;
use parking_lot::Mutex;
use std::sync::Arc;

/// A mock implementation of the IR database. It can be used to set up a simple test case.
#[salsa::database(
Expand All @@ -14,7 +16,6 @@ use std::sync::Arc;
crate::DefDatabaseStorage,
crate::HirDatabaseStorage
)]
#[derive(Default)]
pub(crate) struct MockDatabase {
storage: salsa::Storage<Self>,
events: Mutex<Option<Vec<salsa::Event>>>,
Expand Down Expand Up @@ -47,25 +48,14 @@ impl Upcast<dyn SourceDatabase> for MockDatabase {
}
}

impl MockDatabase {
/// Creates a database from the given text.
pub fn with_single_file(text: &str) -> (MockDatabase, FileId) {
let mut db: MockDatabase = Default::default();

let mut source_root = SourceRoot::default();
let source_root_id = SourceRootId(0);

let text = Arc::new(text.to_owned());
let rel_path = RelativePathBuf::from("main.mun");
let file_id = FileId(0);
impl Default for MockDatabase {
fn default() -> Self {
let mut db: MockDatabase = MockDatabase {
storage: Default::default(),
events: Default::default(),
};
db.set_target(Target::host_target().unwrap());
db.set_file_relative_path(file_id, rel_path.clone());
db.set_file_text(file_id, Arc::new(text.to_string()));
db.set_file_source_root(file_id, source_root_id);
source_root.insert_file(file_id);

db.set_source_root(source_root_id, Arc::new(source_root));
(db, file_id)
db
}
}

Expand Down
7 changes: 5 additions & 2 deletions crates/mun_hir/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::db::{DefDatabase, SourceDatabase};
use crate::mock::MockDatabase;
use crate::{
db::{DefDatabase, SourceDatabase},
fixture::WithFixture,
mock::MockDatabase,
};
use std::sync::Arc;

/// This function tests that the ModuleData of a module does not change if the contents of a function
Expand Down
1 change: 1 addition & 0 deletions crates/mun_hir/src/ty/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::fixture::WithFixture;
use crate::{
db::DefDatabase, diagnostics::DiagnosticSink, expr::BodySourceMap, mock::MockDatabase,
HirDisplay, InferenceResult, ModuleDef,
Expand Down
1 change: 1 addition & 0 deletions crates/mun_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ anyhow = "1.0"
compiler = { path = "../mun_compiler", package = "mun_compiler" }
runtime = { path = "../mun_runtime", package = "mun_runtime" }
tempfile = "3"
itertools = "0.9.0"
221 changes: 221 additions & 0 deletions crates/mun_test/src/fixture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use compiler::RelativePathBuf;
use itertools::Itertools;

const DEFAULT_FILE_NAME: &str = "main.mun";
const META_LINE: &str = "//-";

/// A `Fixture` describes an single file in a project workspace. `Fixture`s can be parsed from a
/// single string with the `parse` function. Using that function enables users to conveniently
/// describe an entire workspace in a single string.
#[derive(Debug, Eq, PartialEq)]
pub struct Fixture {
/// The relative path of this file
pub relative_path: RelativePathBuf,

/// The text of the file
pub text: String,
}

impl Fixture {
/// Parses text which looks like this:
///
/// ```not_rust
/// //- /foo.mun
/// fn hello_world() {
/// }
///
/// //- /bar.mun
/// fn baz() {
/// }
/// ```
///
/// into two separate `Fixture`s one with `relative_path` 'foo.mun' and one with 'bar.mun'.
pub fn parse(text: impl AsRef<str>) -> Vec<Fixture> {
let text = trim_raw_string_literal(text);
let mut result: Vec<Fixture> = Vec::new();

// If the text does not contain any meta tags, insert a default meta tag at the start.
let default_start = if text.contains(META_LINE) {
None
} else {
Some(format!("{} /{}", META_LINE, DEFAULT_FILE_NAME))
};

for (idx, line) in default_start
.as_deref()
.into_iter()
.chain(text.lines())
.enumerate()
{
if line.contains(META_LINE) {
assert!(
line.starts_with(META_LINE),
"Metadata line {} has invalid indentation. \
All metadata lines need to have the same indentation \n\
The offending line: {:?}",
idx,
line
);
}

if line.starts_with(META_LINE) {
let meta = Fixture::parse_meta_line(line);
result.push(meta);
} else if let Some(entry) = result.last_mut() {
entry.text.push_str(line);
entry.text.push_str("\n");
}
}

result
}

/// Parses a fixture meta line like:
/// ```
/// //- /main.mun
/// ```
fn parse_meta_line(line: impl AsRef<str>) -> Fixture {
let line = line.as_ref();
assert!(line.starts_with(META_LINE));

let line = line[META_LINE.len()..].trim();
let components = line.split_ascii_whitespace().collect::<Vec<_>>();

let path = components[0].to_string();
assert!(path.starts_with('/'));
let relative_path = RelativePathBuf::from(&path[1..]);

Fixture {
relative_path,
text: String::new(),
}
}
}

/// Turns a string that is likely to come from a raw string literal into something that is
/// probably intended.
///
/// * Strips the first newline if there is one
/// * Removes any initial indentation
///
/// Example usecase:
///
/// ```
/// # fn do_something(s: &str) {}
/// do_something(r#"
/// fn func() {
/// // code
/// }
/// "#)
/// ```
///
/// Results in the string (with no leading newline):
/// ```not_rust
/// fn func() {
/// // code
/// }
/// ```
pub fn trim_raw_string_literal(text: impl AsRef<str>) -> String {
let mut text = text.as_ref();
if text.starts_with('\n') {
text = &text[1..];
}

let minimum_indentation = text
.lines()
.filter(|it| !it.trim().is_empty())
.map(|it| it.len() - it.trim_start().len())
.min()
.unwrap_or(0);

text.lines()
.map(|line| {
if line.len() <= minimum_indentation {
line.trim_start_matches(' ')
} else {
&line[minimum_indentation..]
}
})
.join("\n")
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn trim_raw_string_literal() {
assert_eq!(
&super::trim_raw_string_literal(
r#"
fn hello_world() {
// code
}
"#
),
"fn hello_world() {\n // code\n}\n"
);
}

#[test]
fn empty_fixture() {
assert_eq!(
Fixture::parse(""),
vec![Fixture {
relative_path: RelativePathBuf::from(DEFAULT_FILE_NAME),
text: "".to_owned()
}]
);
}

#[test]
fn single_fixture() {
assert_eq!(
Fixture::parse(format!("{} /foo.mun\nfn hello_world() {{}}", META_LINE)),
vec![Fixture {
relative_path: RelativePathBuf::from("foo.mun"),
text: "fn hello_world() {}\n".to_owned()
}]
);
}

#[test]
fn multiple_fixtures() {
assert_eq!(
Fixture::parse(
r#"
//- /foo.mun
fn hello_world() {
}
//- /bar.mun
fn baz() {
}
"#
),
vec![
Fixture {
relative_path: RelativePathBuf::from("foo.mun"),
text: "fn hello_world() {\n}\n\n".to_owned()
},
Fixture {
relative_path: RelativePathBuf::from("bar.mun"),
text: "fn baz() {\n}\n".to_owned()
}
]
);
}

#[test]
#[should_panic]
fn incorrectly_indented_fixture() {
Fixture::parse(
r"
//- /foo.mun
fn foo() {}
//- /bar.mun
pub fn baz() {}
",
);
}
}
2 changes: 2 additions & 0 deletions crates/mun_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
#![warn(missing_docs)]

mod driver;
mod fixture;

pub use driver::*;
pub use fixture::{trim_raw_string_literal, Fixture};

0 comments on commit 720bdae

Please sign in to comment.