Expand visualization for snapshots when they fail

This commit is contained in:
Lucien Greathouse 2019-03-12 18:06:42 -07:00
parent 007ab077eb
commit 80a8e0e985
5 changed files with 96 additions and 20 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/scratch-project /scratch-project
**/*.rs.bk **/*.rs.bk
/generate-docs.run /generate-docs.run
/server/failed-snapshots/

View file

@ -85,6 +85,10 @@ impl LiveSession {
Ok(()) Ok(())
} }
pub fn root_project(&self) -> &Project {
&self.project
}
pub fn session_id(&self) -> SessionId { pub fn session_id(&self) -> SessionId {
self.session_id self.session_id
} }

View file

@ -251,6 +251,10 @@ impl RbxSession {
&self.tree &self.tree
} }
pub fn get_all_instance_metadata(&self) -> &HashMap<RbxId, MetadataPerInstance> {
&self.metadata_per_instance
}
pub fn get_instance_metadata(&self, id: RbxId) -> Option<&MetadataPerInstance> { pub fn get_instance_metadata(&self, id: RbxId) -> Option<&MetadataPerInstance> {
self.metadata_per_instance.get(&id) self.metadata_per_instance.get(&id)
} }

View file

@ -1,4 +1,5 @@
use std::{ use std::{
collections::HashMap,
fmt, fmt,
io::Write, io::Write,
path::Path, path::Path,
@ -6,12 +7,13 @@ use std::{
}; };
use log::warn; use log::warn;
use rbx_dom_weak::RbxId; use rbx_dom_weak::{RbxTree, RbxId};
use crate::{ use crate::{
imfs::{Imfs, ImfsItem}, imfs::{Imfs, ImfsItem},
rbx_session::RbxSession, rbx_session::RbxSession,
web::api::PublicInstanceMetadata, web::api::PublicInstanceMetadata,
rbx_session::MetadataPerInstance,
}; };
static GRAPHVIZ_HEADER: &str = r#" static GRAPHVIZ_HEADER: &str = r#"
@ -53,42 +55,59 @@ pub fn graphviz_to_svg(source: &str) -> Option<String> {
Some(String::from_utf8(output.stdout).expect("Failed to parse stdout as UTF-8")) 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<RbxId, MetadataPerInstance>,
}
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. /// A Display wrapper struct to visualize an RbxSession as SVG.
pub struct VisualizeRbxSession<'a>(pub &'a RbxSession); pub struct VisualizeRbxSession<'a>(pub &'a RbxSession);
impl<'a> fmt::Display for VisualizeRbxSession<'a> { impl<'a> fmt::Display for VisualizeRbxSession<'a> {
fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result {
writeln!(output, "{}", GRAPHVIZ_HEADER)?; writeln!(output, "{}", VisualizeRbxTree {
tree: self.0.get_tree(),
visualize_rbx_node(self.0, self.0.get_tree().get_root_id(), output)?; metadata: self.0.get_all_instance_metadata(),
})
writeln!(output, "}}")?;
Ok(())
} }
} }
fn visualize_rbx_node(session: &RbxSession, id: RbxId, output: &mut fmt::Formatter) -> fmt::Result { fn visualize_instance(
let node = session.get_tree().get_instance(id).unwrap(); tree: &RbxTree,
id: RbxId,
metadata: &HashMap<RbxId, MetadataPerInstance>,
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); let metadata = PublicInstanceMetadata::from_session_metadata(session_metadata);
node_label.push('|'); instance_label.push('|');
node_label.push_str(&serde_json::to_string(&metadata).unwrap()); instance_label.push_str(&serde_json::to_string(&metadata).unwrap());
} }
node_label = node_label instance_label = instance_label
.replace("\"", "&quot;") .replace("\"", "&quot;")
.replace("{", "\\{") .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)?; writeln!(output, " \"{}\" -> \"{}\"", id, child_id)?;
visualize_rbx_node(session, child_id, output)?; visualize_instance(tree, child_id, metadata, output)?;
} }
Ok(()) Ok(())

View file

@ -1,6 +1,7 @@
mod test_util; mod test_util;
use std::{ use std::{
collections::HashMap,
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
@ -17,6 +18,7 @@ use librojo::{
project::Project, project::Project,
live_session::LiveSession, live_session::LiveSession,
rbx_snapshot::{SnapshotContext, snapshot_project_tree}, rbx_snapshot::{SnapshotContext, snapshot_project_tree},
visualize::{VisualizeRbxTree, graphviz_to_svg},
}; };
use crate::test_util::{ 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(); let tree = rbx_session.get_tree();
match read_tree_by_name(source_path, step) { match read_tree_by_name(source_path, step) {
Some(existing) => match trees_equal(&existing, tree) { Some(expected) => match trees_equal(&expected, tree) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("Trees at step '{}' were not equal.\n{}", step, 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); panic!("Tree mismatch at step '{}'", step);
} }
} }