From d19387f03904361e54c39c8a553be618e367bd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Wed, 26 Jan 2022 16:06:42 +0100 Subject: [PATCH] graphview: implement tests infra - Implement test for graph creation, save and load. - Change xml API - Update public/private api - Add a graphview clear API --- .gitlab-ci.yml | 8 +- Cargo.lock | 2 + Cargo.toml | 6 +- src/graphmanager/graphview.rs | 245 ++++++++++++++++++---------------- src/graphmanager/mod.rs | 137 ++++++++++++++++++- 5 files changed, 282 insertions(+), 116 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6add2ed..19f5fb6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ variables: variables: FDO_DISTRIBUTION_VERSION: "35" # Update this to trigger a container rebuild - FDO_DISTRIBUTION_TAG: "2022-01-07.2" + FDO_DISTRIBUTION_TAG: "2022-01-27.2" build-fedora-container: extends: @@ -42,6 +42,8 @@ build-fedora-container: python3-devel python3-pip python3-setuptools + util-linux + xorg-x11-server-Xvfb FDO_DISTRIBUTION_EXEC: >- pip3 install meson @@ -64,7 +66,9 @@ test-stable: - meson build - rustc --version - cargo build --color=always --all-targets - - cargo test --color=always + - > + xvfb-run -a -s "-screen 0 1024x768x24" + cargo test --color=always rustdoc: extends: diff --git a/Cargo.lock b/Cargo.lock index 505055e..74743e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,8 @@ name = "gst_pipeline_studio" version = "0.1.0" dependencies = [ "anyhow", + "futures-channel", + "futures-executor", "gettext-rs", "gstreamer", "gtk4", diff --git a/Cargo.toml b/Cargo.toml index 485f793..7b6df8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,8 @@ xml-rs = "0.8.4" x11 = { version = "2.18", features = ["xlib"] } serde = "1.0" serde_any = "0.5" -simplelog = "0.11.2" \ No newline at end of file +simplelog = "0.11.2" +futures-channel = "0.3" + +[dev-dependencies] +futures-executor = "0.3" \ No newline at end of file diff --git a/src/graphmanager/graphview.rs b/src/graphmanager/graphview.rs index 5236dc0..926d0f8 100644 --- a/src/graphmanager/graphview.rs +++ b/src/graphmanager/graphview.rs @@ -33,8 +33,7 @@ use super::{ }; use once_cell::sync::Lazy; -use std::fs::File; -use std::io::BufReader; +use std::io::Cursor; use gtk::{ gdk::{BUTTON_PRIMARY, BUTTON_SECONDARY}, @@ -494,11 +493,52 @@ impl GraphView { private.id.set(id) } + /// Retrives the graphview id + /// pub fn id(&self) -> u32 { let private = imp::GraphView::from_instance(self); private.id.get() } + /// Clear the graphview + /// + pub fn clear(&self) { + self.remove_all_nodes(); + } + + // Node + + /// Create a new node with a new id + /// + pub fn create_node(&self, name: &str, node_type: NodeType) -> Node { + let id = self.next_node_id(); + self.create_node_with_id(id, name, node_type) + } + + /// Create a new node and add it to the graphview with input/output port number. + /// + pub fn create_node_with_port( + &self, + name: &str, + node_type: NodeType, + input: u32, + output: u32, + ) -> Node { + let mut node = self.create_node(name, node_type); + + let _i = 0; + for _i in 0..input { + let port = self.create_port("in", PortDirection::Input, PortPresence::Always); + self.add_port_to_node(&mut node, port); + } + let _i = 0; + for _i in 0..output { + let port = self.create_port("out", PortDirection::Output, PortPresence::Always); + self.add_port_to_node(&mut node, port); + } + node + } + /// Add node to the graphview without port /// pub fn add_node(&self, node: Node) { @@ -541,40 +581,6 @@ impl GraphView { self.emit_by_name::<()>("node-added", &[&private.id.get(), &node_id]); self.graph_updated(); } - /// Create a new node with id - /// - pub fn create_node_with_id(&self, id: u32, name: &str, node_type: NodeType) -> Node { - Node::new(id, name, node_type) - } - /// Create a new node with a new id - /// - pub fn create_node(&self, name: &str, node_type: NodeType) -> Node { - let id = self.next_node_id(); - self.create_node_with_id(id, name, node_type) - } - /// Create a new node and add it to the graphview with input/output port number. - /// - pub fn create_node_with_port( - &self, - name: &str, - node_type: NodeType, - input: u32, - output: u32, - ) -> Node { - let mut node = self.create_node(name, node_type); - - let _i = 0; - for _i in 0..input { - let port = self.create_port("in", PortDirection::Input, PortPresence::Always); - self.add_port_to_node(&mut node, port); - } - let _i = 0; - for _i in 0..output { - let port = self.create_port("out", PortDirection::Output, PortPresence::Always); - self.add_port_to_node(&mut node, port); - } - node - } /// Remove node from the graphview /// @@ -644,19 +650,28 @@ impl GraphView { None } + /// Get the position of the specified node inside the graphview. + /// + /// Returns `None` if the node is not in the graphview. + pub(super) fn node_position(&self, node: >k::Widget) -> Option<(f32, f32)> { + let layout_manager = self + .layout_manager() + .expect("Failed to get layout manager") + .dynamic_cast::() + .expect("Failed to cast to FixedLayout"); + + let node = layout_manager + .layout_child(node) + .dynamic_cast::() + .expect("Could not cast to FixedLayoutChild"); + let transform = node + .transform() + .expect("Failed to obtain transform from layout child"); + Some(transform.to_translate()) + } + // Port - /// Create a new port with id - /// - pub fn create_port_with_id( - &self, - id: u32, - name: &str, - direction: PortDirection, - presence: PortPresence, - ) -> Port { - Port::new(id, name, direction, presence) - } /// Create a new port with a new id /// pub fn create_port( @@ -721,26 +736,7 @@ impl GraphView { } // Link - /// Create a new link with id - pub fn create_link_with_id( - &self, - link_id: u32, - node_from_id: u32, - node_to_id: u32, - port_from_id: u32, - port_to_id: u32, - active: bool, - ) -> Link { - Link::new( - link_id, - node_from_id, - node_to_id, - port_from_id, - port_to_id, - active, - false, - ) - } + /// Create a new link with a new id /// pub fn create_link( @@ -760,6 +756,7 @@ impl GraphView { active, ) } + /// Add a link to the graphView /// pub fn add_link(&self, link: Link) { @@ -782,44 +779,18 @@ impl GraphView { } } - /// Get the position of the specified node inside the graphview. + /// Select all nodes according to the NodeType /// - /// Returns `None` if the node is not in the graphview. - pub(super) fn node_position(&self, node: >k::Widget) -> Option<(f32, f32)> { - let layout_manager = self - .layout_manager() - .expect("Failed to get layout manager") - .dynamic_cast::() - .expect("Failed to cast to FixedLayout"); - - let node = layout_manager - .layout_child(node) - .dynamic_cast::() - .expect("Could not cast to FixedLayoutChild"); - let transform = node - .transform() - .expect("Failed to obtain transform from layout child"); - Some(transform.to_translate()) - } - - /// Retrieves the next node unique id. - /// - pub fn next_node_id(&self) -> u32 { + /// Returns a vector of links + pub fn all_links(&self, link_state: bool) -> Vec { let private = imp::GraphView::from_instance(self); - private - .current_node_id - .set(private.current_node_id.get() + 1); - private.current_node_id.get() - } - - /// Retrieves the next port unique id. - /// - pub fn next_port_id(&self) -> u32 { - let private = imp::GraphView::from_instance(self); - private - .current_port_id - .set(private.current_port_id.get() + 1); - private.current_port_id.get() + let links = private.links.borrow(); + let links_list: Vec<_> = links + .iter() + .filter(|(_, link)| link.active == link_state) + .map(|(_, node)| node.clone()) + .collect(); + links_list } /// Retrieves the node/port id connected to the input port id @@ -860,14 +831,15 @@ impl GraphView { self.graph_updated(); } - /// Render the graph in a file with XML format + /// Render the graph with XML format in a buffer /// - pub fn render_xml(&self, filename: &str) -> anyhow::Result<()> { + pub fn render_xml(&self) -> anyhow::Result> { let private = imp::GraphView::from_instance(self); - let mut file = File::create(filename).unwrap(); + + let mut buffer = Vec::new(); let mut writer = EmitterConfig::new() .perform_indent(true) - .create_writer(&mut file); + .create_writer(&mut buffer); writer .write(XMLWEvent::start_element("Graph").attr("id", &private.id.get().to_string()))?; @@ -926,15 +898,14 @@ impl GraphView { writer.write(XMLWEvent::end_element())?; } writer.write(XMLWEvent::end_element())?; - Ok(()) + Ok(buffer) } /// Load the graph from a file with XML format /// - pub fn load_xml(&self, filename: &str) -> anyhow::Result<()> { - let file = File::open(filename)?; - let file = BufReader::new(file); - + pub fn load_from_xml(&self, buffer: Vec) -> anyhow::Result<()> { + self.clear(); + let file = Cursor::new(buffer); let parser = EventReader::new(file); let mut current_node: Option = None; @@ -1113,6 +1084,40 @@ impl GraphView { //Private + fn create_node_with_id(&self, id: u32, name: &str, node_type: NodeType) -> Node { + Node::new(id, name, node_type) + } + + fn create_port_with_id( + &self, + id: u32, + name: &str, + direction: PortDirection, + presence: PortPresence, + ) -> Port { + Port::new(id, name, direction, presence) + } + + fn create_link_with_id( + &self, + link_id: u32, + node_from_id: u32, + node_to_id: u32, + port_from_id: u32, + port_to_id: u32, + active: bool, + ) -> Link { + Link::new( + link_id, + node_from_id, + node_to_id, + port_from_id, + port_to_id, + active, + false, + ) + } + fn remove_link(&self, id: u32) { let private = imp::GraphView::from_instance(self); let mut links = private.links.borrow_mut(); @@ -1226,6 +1231,22 @@ impl GraphView { self.emit_by_name::<()>("graph-updated", &[&private.id.get()]); } + fn next_node_id(&self) -> u32 { + let private = imp::GraphView::from_instance(self); + private + .current_node_id + .set(private.current_node_id.get() + 1); + private.current_node_id.get() + } + + fn next_port_id(&self) -> u32 { + let private = imp::GraphView::from_instance(self); + private + .current_port_id + .set(private.current_port_id.get() + 1); + private.current_port_id.get() + } + fn next_link_id(&self) -> u32 { let private = imp::GraphView::from_instance(self); private diff --git a/src/graphmanager/mod.rs b/src/graphmanager/mod.rs index 865e4d3..5f196fb 100644 --- a/src/graphmanager/mod.rs +++ b/src/graphmanager/mod.rs @@ -4,7 +4,6 @@ mod node; mod port; mod property; mod selection; -mod tests; pub use graphview::GraphView; pub use link::Link; @@ -14,3 +13,139 @@ pub use port::Port; pub use port::{PortDirection, PortPresence}; pub use property::PropertyExt; pub use selection::SelectionExt; + +#[cfg(test)] +fn test_synced(function: F) -> R +where + F: FnOnce() -> R + Send + std::panic::UnwindSafe + 'static, + R: Send + 'static, +{ + /// No-op. + macro_rules! skip_assert_initialized { + () => {}; + } + skip_assert_initialized!(); + + use futures_channel::oneshot; + use std::panic; + + let (tx, rx) = oneshot::channel(); + TEST_THREAD_WORKER + .push(move || { + tx.send(panic::catch_unwind(function)) + .unwrap_or_else(|_| panic!("Failed to return result from thread pool")); + }) + .expect("Failed to schedule a test call"); + futures_executor::block_on(rx) + .expect("Failed to receive result from thread pool") + .unwrap_or_else(|e| std::panic::resume_unwind(e)) +} + +#[cfg(test)] +static TEST_THREAD_WORKER: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| { + let pool = gtk::glib::ThreadPool::exclusive(1).unwrap(); + pool.push(move || { + gtk::init().expect("Tests failed to initialize gtk"); + }) + .expect("Failed to schedule a test call"); + pool + }); + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn graphview_creation() { + test_synced(|| { + let graphview = GraphView::new(); + assert_eq!(graphview.id(), 0); + }); + } + #[test] + fn graphview_lifetime() { + test_synced(|| { + let graphview = GraphView::new(); + assert_eq!(graphview.id(), 0); + let node = graphview.create_node("my_node1", NodeType::Source); + node.add_property("np1", "nv1"); + graphview.add_node(node); + //create a port input on node 1 + let port = graphview.create_port("out", PortDirection::Output, PortPresence::Always); + assert_eq!(port.name(), "out"); + assert_eq!(port.id(), 1); + let mut node: Node = graphview.node(1).unwrap(); + graphview.add_port_to_node(&mut node, port); + + let node = graphview.create_node_with_port("my_node2", NodeType::Transform, 1, 1); + node.add_property("np2", "nv2"); + graphview.add_node(node); + + let node = graphview.create_node("my_node3", NodeType::Sink); + node.add_property("np3", "nv3"); + graphview.add_node(node); + //create a port input on node 3 + let port = graphview.create_port("in", PortDirection::Input, PortPresence::Always); + port.add_property("p1", "v1"); + assert_eq!(port.name(), "in"); + assert_eq!(port.id(), 4); + let mut node: Node = graphview.node(3).unwrap(); + graphview.add_port_to_node(&mut node, port); + + assert_eq!(graphview.all_nodes(NodeType::Source).len(), 1); + assert_eq!(graphview.all_nodes(NodeType::Transform).len(), 1); + assert_eq!(graphview.all_nodes(NodeType::Sink).len(), 1); + assert_eq!(graphview.all_nodes(NodeType::All).len(), 3); + + assert_eq!(graphview.node(1).unwrap().name(), "my_node1"); + assert_eq!(graphview.node(2).unwrap().name(), "my_node2"); + assert_eq!(graphview.node(3).unwrap().name(), "my_node3"); + + // Ports have been created by create_node_with_port + + //Create link between node1 and node 2 + let link = graphview.create_link(1, 2, 1, 2, true); + graphview.add_link(link); + + //Create link between node2 and node 3 + let link = graphview.create_link(2, 3, 3, 4, true); + graphview.add_link(link); + + // Save the graphview in XML into a buffer + let buffer = graphview + .render_xml() + .expect("Should be able to render graph to xml"); + println!("{}", std::str::from_utf8(&buffer).unwrap()); + // Load the graphview from XML buffer + graphview + .load_from_xml(buffer) + .expect("Should be able to load from XML data"); + + // Check that nodes and links are present + assert_eq!(graphview.all_nodes(NodeType::All).len(), 3); + assert_eq!(graphview.all_links(true).len(), 2); + + // Check all nodes are linked + assert!(graphview.node_is_linked(1).is_some()); + assert!(graphview.node_is_linked(2).is_some()); + assert!(graphview.node_is_linked(3).is_some()); + + // Check all ports are linked + assert!(graphview.port_connected_to(1).is_some()); + assert!(graphview.port_connected_to(3).is_some()); + + // Check properties + let node = graphview.node(1).expect("Should be able to get node 1"); + assert_eq!(&node.property("np1").unwrap(), "nv1"); + let node = graphview.node(2).expect("Should be able to get node 1"); + assert_eq!(&node.property("np2").unwrap(), "nv2"); + let node = graphview.node(3).expect("Should be able to get node 1"); + assert_eq!(&node.property("np3").unwrap(), "nv3"); + + // Clear the graph and check that everything has been destroyed properly + graphview.clear(); + assert_eq!(graphview.all_nodes(NodeType::All).len(), 0); + assert_eq!(graphview.all_links(true).len(), 0); + }); + } +}