VFS in external crate (#297)

* vroom

* Port dir middleware

* Filter rules

* Directory metadata

* Project support

* Enable Lua support

* StringValue support

* CSV

* rbxm, rbxmx, and rbxlx

* JSON models

* Clean up some warnings

* Strip out PathMap

* Unwatch paths when they're reported as removed

* Fix 'rojo upload' behavior

* Upgrade to Insta 0.13.1

* Update dependencies

* Release 0.6.0-alpha.2

* Fix bad merge

* Replace MemoryBackend with InMemoryFs

* Sledgehammer tests into passing for now

* Txt middleware

* Update easy snapshot tests

* Lua tests

* Project middleware tests

* Try to fix test failures by sorting

* Port first set of serve session tests

* Add InMemoryFs::raise_event

* Finish porting serve session tests

* Remove UI code for introspecting VFS for now

* VFS docs
This commit is contained in:
Lucien Greathouse
2020-03-10 17:38:49 -07:00
committed by GitHub
parent a884f693ae
commit 477e0ada32
38 changed files with 846 additions and 2509 deletions

View File

@@ -1,13 +1,13 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use rbx_reflection::try_resolve_value;
use vfs::{IoResultExt, Vfs};
use crate::{
project::{Project, ProjectNode},
snapshot::{
InstanceContext, InstanceMetadata, InstanceSnapshot, InstigatingSource, PathIgnoreRule,
},
vfs::{FsResultExt, Vfs, VfsEntry, VfsFetcher},
};
use super::{
@@ -22,30 +22,28 @@ use super::{
pub struct SnapshotProject;
impl SnapshotMiddleware for SnapshotProject {
fn from_vfs<F: VfsFetcher>(
context: &InstanceContext,
vfs: &Vfs<F>,
entry: &VfsEntry,
) -> SnapshotInstanceResult {
if entry.is_directory() {
let project_path = entry.path().join("default.project.json");
fn from_vfs(context: &InstanceContext, vfs: &Vfs, path: &Path) -> SnapshotInstanceResult {
let meta = vfs.metadata(path)?;
match vfs.get(project_path).with_not_found()? {
if meta.is_dir() {
let project_path = path.join("default.project.json");
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(entry) => return SnapshotProject::from_vfs(context, vfs, &entry),
Some(_meta) => return SnapshotProject::from_vfs(context, vfs, &project_path),
None => return Ok(None),
}
}
if !entry.path().to_string_lossy().ends_with(".project.json") {
if !path.to_string_lossy().ends_with(".project.json") {
// This isn't a project file, so it's not our job.
return Ok(None);
}
let project = Project::load_from_slice(&entry.contents(vfs)?, entry.path())
.map_err(|err| SnapshotError::malformed_project(err, entry.path()))?;
let project = Project::load_from_slice(&vfs.read(path)?, path)
.map_err(|err| SnapshotError::malformed_project(err, path))?;
let mut context = context.clone();
@@ -75,7 +73,7 @@ impl SnapshotMiddleware for SnapshotProject {
// 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(entry.path().to_path_buf().into());
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.
@@ -83,21 +81,18 @@ impl SnapshotMiddleware for SnapshotProject {
// 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(entry.path().to_path_buf());
snapshot.metadata.relevant_paths.push(path.to_path_buf());
Ok(Some(snapshot))
}
}
pub fn snapshot_project_node<F: VfsFetcher>(
pub fn snapshot_project_node(
context: &InstanceContext,
project_folder: &Path,
instance_name: &str,
node: &ProjectNode,
vfs: &Vfs<F>,
vfs: &Vfs,
) -> SnapshotInstanceResult {
let name = Cow::Owned(instance_name.to_owned());
let mut class_name = node
@@ -117,9 +112,7 @@ pub fn snapshot_project_node<F: VfsFetcher>(
Cow::Borrowed(path)
};
let entry = vfs.get(path.as_path())?;
if let Some(snapshot) = snapshot_from_vfs(context, vfs, &entry)? {
if let Some(snapshot) = snapshot_from_vfs(context, vfs, &path)? {
// If a class name was already specified, then it'll override the
// class name of this snapshot ONLY if it's a Folder.
//
@@ -217,259 +210,284 @@ pub fn snapshot_project_node<F: VfsFetcher>(
mod test {
use super::*;
use insta::assert_yaml_snapshot;
use maplit::hashmap;
use crate::vfs::{NoopFetcher, VfsDebug, VfsSnapshot};
use vfs::{InMemoryFs, VfsSnapshot};
#[test]
fn project_from_folder() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "indirect-project",
"tree": {
"$className": "Folder"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "indirect-project",
"tree": {
"$className": "Folder"
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_from_direct_file() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"hello.project.json" => VfsSnapshot::file(r#"
{
"name": "direct-project",
"tree": {
"$className": "Model"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"hello.project.json" => VfsSnapshot::file(r#"
{
"name": "direct-project",
"tree": {
"$className": "Model"
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo/hello.project.json").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
.expect("snapshot error")
.expect("snapshot returned no instances");
let instance_snapshot = SnapshotProject::from_vfs(
&InstanceContext::default(),
&mut vfs,
Path::new("/foo/hello.project.json"),
)
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_with_resolved_properties() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "resolved-properties",
"tree": {
"$className": "StringValue",
"$properties": {
"Value": {
"Type": "String",
"Value": "Hello, world!"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "resolved-properties",
"tree": {
"$className": "StringValue",
"$properties": {
"Value": {
"Type": "String",
"Value": "Hello, world!"
}
}
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_with_unresolved_properties() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "unresolved-properties",
"tree": {
"$className": "StringValue",
"$properties": {
"Value": "Hi!"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "unresolved-properties",
"tree": {
"$className": "StringValue",
"$properties": {
"Value": "Hi!"
}
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_with_children() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "children",
"tree": {
"$className": "Folder",
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "children",
"tree": {
"$className": "Folder",
"Child": {
"$className": "Model"
"Child": {
"$className": "Model"
}
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_with_path_to_txt() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-project",
"tree": {
"$path": "other.txt"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-project",
"tree": {
"$path": "other.txt"
}
}
}
"#),
"other.txt" => VfsSnapshot::file("Hello, world!"),
});
"#),
"other.txt" => VfsSnapshot::file("Hello, world!"),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_with_path_to_project() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-project",
"tree": {
"$path": "other.project.json"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-project",
"tree": {
"$path": "other.project.json"
}
}
}
"#),
"other.project.json" => VfsSnapshot::file(r#"
{
"name": "other-project",
"tree": {
"$className": "Model"
"#),
"other.project.json" => VfsSnapshot::file(r#"
{
"name": "other-project",
"tree": {
"$className": "Model"
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
#[test]
fn project_with_path_to_project_with_children() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-child-project",
"tree": {
"$path": "other.project.json"
}
}
"#),
"other.project.json" => VfsSnapshot::file(r#"
{
"name": "other-project",
"tree": {
"$className": "Folder",
"SomeChild": {
"$className": "Model"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-child-project",
"tree": {
"$path": "other.project.json"
}
}
}
"#),
});
"#),
"other.project.json" => VfsSnapshot::file(r#"
{
"name": "other-project",
"tree": {
"$className": "Folder",
vfs.debug_load_snapshot("/foo", dir);
"SomeChild": {
"$className": "Model"
}
}
}
"#),
}),
)
.unwrap();
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
/// Ensures that if a property is defined both in the resulting instance
@@ -479,40 +497,43 @@ mod test {
fn project_path_property_overrides() {
let _ = env_logger::try_init();
let mut vfs = Vfs::new(NoopFetcher);
let dir = VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-property-override",
"tree": {
"$path": "other.project.json",
"$properties": {
"Value": "Changed"
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/foo",
VfsSnapshot::dir(hashmap! {
"default.project.json" => VfsSnapshot::file(r#"
{
"name": "path-property-override",
"tree": {
"$path": "other.project.json",
"$properties": {
"Value": "Changed"
}
}
}
}
"#),
"other.project.json" => VfsSnapshot::file(r#"
{
"name": "other-project",
"tree": {
"$className": "StringValue",
"$properties": {
"Value": "Original"
"#),
"other.project.json" => VfsSnapshot::file(r#"
{
"name": "other-project",
"tree": {
"$className": "StringValue",
"$properties": {
"Value": "Original"
}
}
}
}
"#),
});
"#),
}),
)
.unwrap();
vfs.debug_load_snapshot("/foo", dir);
let mut vfs = Vfs::new(imfs);
let entry = vfs.get("/foo").unwrap();
let instance_snapshot =
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, &entry)
SnapshotProject::from_vfs(&InstanceContext::default(), &mut vfs, Path::new("/foo"))
.expect("snapshot error")
.expect("snapshot returned no instances");
assert_yaml_snapshot!(instance_snapshot);
insta::assert_yaml_snapshot!(instance_snapshot);
}
}