diff --git a/.gitignore b/.gitignore index d77fcf54..216fc1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /target /scratch-project **/*.rs.bk -/generate-docs.run \ No newline at end of file +/generate-docs.run +/server/failed-snapshots/ \ No newline at end of file diff --git a/server/src/live_session.rs b/server/src/live_session.rs index 1b9db569..b90b6e87 100644 --- a/server/src/live_session.rs +++ b/server/src/live_session.rs @@ -85,6 +85,10 @@ impl LiveSession { Ok(()) } + pub fn root_project(&self) -> &Project { + &self.project + } + pub fn session_id(&self) -> SessionId { self.session_id } diff --git a/server/src/rbx_session.rs b/server/src/rbx_session.rs index 00940dca..7310736a 100644 --- a/server/src/rbx_session.rs +++ b/server/src/rbx_session.rs @@ -251,6 +251,10 @@ impl RbxSession { &self.tree } + pub fn get_all_instance_metadata(&self) -> &HashMap { + &self.metadata_per_instance + } + pub fn get_instance_metadata(&self, id: RbxId) -> Option<&MetadataPerInstance> { self.metadata_per_instance.get(&id) } diff --git a/server/src/visualize.rs b/server/src/visualize.rs index 32ab0786..d0b36604 100644 --- a/server/src/visualize.rs +++ b/server/src/visualize.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, fmt, io::Write, path::Path, @@ -6,12 +7,13 @@ use std::{ }; use log::warn; -use rbx_dom_weak::RbxId; +use rbx_dom_weak::{RbxTree, RbxId}; use crate::{ imfs::{Imfs, ImfsItem}, rbx_session::RbxSession, web::api::PublicInstanceMetadata, + rbx_session::MetadataPerInstance, }; static GRAPHVIZ_HEADER: &str = r#" @@ -53,42 +55,59 @@ pub fn graphviz_to_svg(source: &str) -> Option { Some(String::from_utf8(output.stdout).expect("Failed to parse stdout as UTF-8")) } +pub struct VisualizeRbxTree<'a, 'b> { + pub tree: &'a RbxTree, + pub metadata: &'b HashMap, +} + +impl<'a, 'b> fmt::Display for VisualizeRbxTree<'a, 'b> { + fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { + writeln!(output, "{}", GRAPHVIZ_HEADER)?; + + visualize_instance(&self.tree, self.tree.get_root_id(), &self.metadata, output)?; + + writeln!(output, "}}") + } +} + /// A Display wrapper struct to visualize an RbxSession as SVG. pub struct VisualizeRbxSession<'a>(pub &'a RbxSession); impl<'a> fmt::Display for VisualizeRbxSession<'a> { fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { - writeln!(output, "{}", GRAPHVIZ_HEADER)?; - - visualize_rbx_node(self.0, self.0.get_tree().get_root_id(), output)?; - - writeln!(output, "}}")?; - - Ok(()) + writeln!(output, "{}", VisualizeRbxTree { + tree: self.0.get_tree(), + metadata: self.0.get_all_instance_metadata(), + }) } } -fn visualize_rbx_node(session: &RbxSession, id: RbxId, output: &mut fmt::Formatter) -> fmt::Result { - let node = session.get_tree().get_instance(id).unwrap(); +fn visualize_instance( + tree: &RbxTree, + id: RbxId, + metadata: &HashMap, + output: &mut fmt::Formatter, +) -> fmt::Result { + let instance = tree.get_instance(id).unwrap(); - let mut node_label = format!("{}|{}|{}", node.name, node.class_name, id); + let mut instance_label = format!("{}|{}|{}", instance.name, instance.class_name, id); - if let Some(session_metadata) = session.get_instance_metadata(id) { + if let Some(session_metadata) = metadata.get(&id) { let metadata = PublicInstanceMetadata::from_session_metadata(session_metadata); - node_label.push('|'); - node_label.push_str(&serde_json::to_string(&metadata).unwrap()); + instance_label.push('|'); + instance_label.push_str(&serde_json::to_string(&metadata).unwrap()); } - node_label = node_label + instance_label = instance_label .replace("\"", """) .replace("{", "\\{") .replace("}", "\\}"); - writeln!(output, " \"{}\" [label=\"{}\"]", id, node_label)?; + writeln!(output, " \"{}\" [label=\"{}\"]", id, instance_label)?; - for &child_id in node.get_children_ids() { + for &child_id in instance.get_children_ids() { writeln!(output, " \"{}\" -> \"{}\"", id, child_id)?; - visualize_rbx_node(session, child_id, output)?; + visualize_instance(tree, child_id, metadata, output)?; } Ok(()) diff --git a/server/tests/snapshots.rs b/server/tests/snapshots.rs index 14e4796f..28114a53 100644 --- a/server/tests/snapshots.rs +++ b/server/tests/snapshots.rs @@ -1,6 +1,7 @@ mod test_util; use std::{ + collections::HashMap, fs, path::{Path, PathBuf}, sync::Arc, @@ -17,6 +18,7 @@ use librojo::{ project::Project, live_session::LiveSession, rbx_snapshot::{SnapshotContext, snapshot_project_tree}, + visualize::{VisualizeRbxTree, graphviz_to_svg}, }; use crate::test_util::{ @@ -105,10 +107,56 @@ fn tree_step(step: &str, live_session: &LiveSession, source_path: &Path) { let tree = rbx_session.get_tree(); match read_tree_by_name(source_path, step) { - Some(existing) => match trees_equal(&existing, tree) { + Some(expected) => match trees_equal(&expected, tree) { Ok(_) => {} Err(e) => { error!("Trees at step '{}' were not equal.\n{}", step, e); + + let expected_gv = format!("{}", VisualizeRbxTree { + tree: &expected, + metadata: &HashMap::new(), + }); + + let actual_gv = format!("{}", VisualizeRbxTree { + tree: rbx_session.get_tree(), + metadata: &HashMap::new(), + }); + + let output_dir = PathBuf::from("failed-snapshots"); + fs::create_dir_all(&output_dir) + .expect("Could not create failed-snapshots directory"); + + let expected_basename = format!("{}-{}-expected", live_session.root_project().name, step); + let actual_basename = format!("{}-{}-actual", live_session.root_project().name, step); + + let mut expected_out = output_dir.join(expected_basename); + let mut actual_out = output_dir.join(actual_basename); + + match (graphviz_to_svg(&expected_gv), graphviz_to_svg(&actual_gv)) { + (Some(expected_svg), Some(actual_svg)) => { + expected_out.set_extension("svg"); + actual_out.set_extension("svg"); + + fs::write(&expected_out, expected_svg) + .expect("Couldn't write expected SVG"); + + fs::write(&actual_out, actual_svg) + .expect("Couldn't write actual SVG"); + } + _ => { + expected_out.set_extension("gv"); + actual_out.set_extension("gv"); + + fs::write(&expected_out, expected_gv) + .expect("Couldn't write expected GV"); + + fs::write(&actual_out, actual_gv) + .expect("Couldn't write actual GV"); + } + } + + error!("Output at {} and {}", expected_out.display(), actual_out.display()); + panic!("Tree mismatch at step '{}'", step); } }