Flatten snapshot middleware to be much simpler (#324)

* First take at flattening middleware for simpler code and better perf

* Undo debug prints

* Fix using wrong path in snapshot_from_vfs

* Disable some broken tests

* Re-enable (mistakenly?) disabled CSV test

* Fix some tests

* Update project file tests

* Fix benchmark
This commit is contained in:
Lucien Greathouse
2020-06-17 13:47:09 -07:00
committed by GitHub
parent bdd1afea57
commit 486b067567
18 changed files with 489 additions and 713 deletions

View File

@@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use memofs::{IoResultExt, Vfs};
use memofs::Vfs;
use rbx_reflection::{get_class_descriptor, try_resolve_value};
use crate::{
@@ -10,92 +10,61 @@ use crate::{
},
};
use super::{
error::SnapshotError,
middleware::{SnapshotInstanceResult, SnapshotMiddleware},
snapshot_from_vfs,
};
use super::{error::SnapshotError, middleware::SnapshotInstanceResult, snapshot_from_vfs};
/// Handles snapshots for:
/// * Files ending in `.project.json`
/// * Folders containing a file named `default.project.json`
pub struct SnapshotProject;
pub fn snapshot_project(
context: &InstanceContext,
vfs: &Vfs,
path: &Path,
) -> SnapshotInstanceResult {
let project = Project::load_from_slice(&vfs.read(path)?, path)
.map_err(|err| SnapshotError::malformed_project(err, path))?;
impl SnapshotMiddleware for SnapshotProject {
fn from_vfs(context: &InstanceContext, vfs: &Vfs, path: &Path) -> SnapshotInstanceResult {
let meta = vfs.metadata(path)?;
let mut context = context.clone();
if meta.is_dir() {
let project_path = path.join("default.project.json");
let rules = project.glob_ignore_paths.iter().map(|glob| PathIgnoreRule {
glob: glob.clone(),
base_path: project.folder_location().to_path_buf(),
});
match vfs.metadata(&project_path).with_not_found()? {
// TODO: Do we need to muck with the relevant paths if we're a
// project file within a folder? Should the folder path be the
// relevant path instead of the project file path?
Some(_meta) => return SnapshotProject::from_vfs(context, vfs, &project_path),
None => return Ok(None),
}
}
context.add_path_ignore_rules(rules);
if !path.to_string_lossy().ends_with(".project.json") {
// This isn't a project file, so it's not our job.
return Ok(None);
}
// TODO: If this project node is a path to an instance that Rojo doesn't
// understand, this may panic!
let mut snapshot =
snapshot_project_node(&context, path, &project.name, &project.tree, vfs, None)?.unwrap();
let project = Project::load_from_slice(&vfs.read(path)?, path)
.map_err(|err| SnapshotError::malformed_project(err, path))?;
// Setting the instigating source to the project file path is a little
// coarse.
//
// Ideally, we'd only snapshot the project file if the project file
// actually changed. Because Rojo only has the concept of one
// relevant path -> snapshot path mapping per instance, we pick the more
// conservative approach of snapshotting the project file if any
// relevant paths changed.
snapshot.metadata.instigating_source = Some(path.to_path_buf().into());
let mut context = context.clone();
// Mark this snapshot (the root node of the project file) as being
// related to the project file.
//
// We SHOULD NOT mark the project file as a relevant path for any
// nodes that aren't roots. They'll be updated as part of the project
// file being updated.
snapshot.metadata.relevant_paths.push(path.to_path_buf());
let rules = project.glob_ignore_paths.iter().map(|glob| PathIgnoreRule {
glob: glob.clone(),
base_path: project.folder_location().to_path_buf(),
});
context.add_path_ignore_rules(rules);
// Snapshotting a project should always return an instance, so this
// unwrap is safe.
let mut snapshot = snapshot_project_node(
&context,
project.folder_location(),
&project.name,
&project.tree,
vfs,
None,
)?
.unwrap();
// Setting the instigating source to the project file path is a little
// coarse.
//
// Ideally, we'd only snapshot the project file if the project file
// actually changed. Because Rojo only has the concept of one
// relevant path -> snapshot path mapping per instance, we pick the more
// conservative approach of snapshotting the project file if any
// relevant paths changed.
snapshot.metadata.instigating_source = Some(path.to_path_buf().into());
// Mark this snapshot (the root node of the project file) as being
// related to the project file.
//
// We SHOULD NOT mark the project file as a relevant path for any
// nodes that aren't roots. They'll be updated as part of the project
// file being updated.
snapshot.metadata.relevant_paths.push(path.to_path_buf());
Ok(Some(snapshot))
}
Ok(Some(snapshot))
}
pub fn snapshot_project_node(
context: &InstanceContext,
project_folder: &Path,
project_path: &Path,
instance_name: &str,
node: &ProjectNode,
vfs: &Vfs,
parent_class: Option<&str>,
) -> SnapshotInstanceResult {
let project_folder = project_path.parent().unwrap();
let name = Cow::Owned(instance_name.to_owned());
let mut class_name = node
.class_name
@@ -191,7 +160,7 @@ pub fn snapshot_project_node(
for (child_name, child_project_node) in &node.children {
if let Some(child) = snapshot_project_node(
context,
project_folder,
project_path,
child_name,
child_project_node,
vfs,
@@ -223,7 +192,7 @@ pub fn snapshot_project_node(
}
metadata.instigating_source = Some(InstigatingSource::ProjectNode(
project_folder.to_path_buf(),
project_path.to_path_buf(),
instance_name.to_string(),
node.clone(),
parent_class.map(|name| name.to_owned()),
@@ -239,6 +208,7 @@ pub fn snapshot_project_node(
}))
}
// #[cfg(feature = "broken-tests")]
#[cfg(test)]
mod test {
use super::*;
@@ -246,6 +216,7 @@ mod test {
use maplit::hashmap;
use memofs::{InMemoryFs, VfsSnapshot};
#[ignore = "Functionality moved to root snapshot middleware"]
#[test]
fn project_from_folder() {
let _ = env_logger::try_init();
@@ -269,7 +240,7 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
snapshot_project(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
@@ -298,7 +269,7 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot = SnapshotProject::from_vfs(
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo/hello.project.json"),
@@ -315,9 +286,9 @@ mod test {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
"/foo.project.json",
VfsSnapshot::file(
r#"
{
"name": "resolved-properties",
"tree": {
@@ -330,17 +301,20 @@ mod test {
}
}
}
"#),
}),
"#,
),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}
@@ -351,9 +325,9 @@ mod test {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
"/foo.project.json",
VfsSnapshot::file(
r#"
{
"name": "unresolved-properties",
"tree": {
@@ -363,17 +337,20 @@ mod test {
}
}
}
"#),
}),
"#,
),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}
@@ -384,9 +361,9 @@ mod test {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
"/foo.project.json",
VfsSnapshot::file(
r#"
{
"name": "children",
"tree": {
@@ -397,17 +374,20 @@ mod test {
}
}
}
"#),
}),
"#,
),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}
@@ -435,10 +415,13 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo/default.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}
@@ -473,10 +456,13 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo/default.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}
@@ -515,10 +501,13 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo/default.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}
@@ -562,10 +551,13 @@ mod test {
let mut vfs = Vfs::new(imfs);
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = snapshot_project(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo/default.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
insta::assert_yaml_snapshot!(instance_snapshot);
}