diff --git a/DESCRIPTION b/DESCRIPTION index 4cd0b49..75ea4a2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: orbweaver Title: Fast and Efficient Graph Data Structures -Version: 0.6.1 +Version: 0.10.1 Authors@R: c(person(given = "ixpantia, SRL", role = "cph", @@ -18,7 +18,7 @@ Description: Empower your data analysis with 'orbweaver', an R package package, 'orbweaver' ensures that mutations and changes to the graph are performed in place, streamlining your workflow for optimal productivity. -URL: https://github.com/ixpantia/orbweaver +URL: https://github.com/ixpantia/orbweaver-r BugReports: https://github.com/ixpantia/orbweaver-r/issues License: MIT + file LICENSE Encoding: UTF-8 diff --git a/Makefile b/Makefile index 2f625af..37f8120 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: vendor document +.PHONY: vendor document covr vendor: $(MAKE) -C src/rust vendor @@ -12,3 +12,5 @@ install: document test: Rscript -e "devtools::test()" +covr: + Rscript -e "covr::report()" diff --git a/NAMESPACE b/NAMESPACE index 39bbf9e..0729e93 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,7 +24,7 @@ S3method(has_children,DirectedAcyclicGraph) S3method(has_children,DirectedGraph) S3method(has_parents,DirectedAcyclicGraph) S3method(has_parents,DirectedGraph) -S3method(least_common_parents,AcyclicDirectedGraph) +S3method(least_common_parents,DirectedAcyclicGraph) S3method(least_common_parents,DirectedGraph) S3method(length,DirectedAcyclicGraph) S3method(length,DirectedGraph) @@ -52,7 +52,7 @@ export(graph_to_bin) export(has_children) export(has_parents) export(least_common_parents) -export(new_directed_graph) +export(nodes) export(parents) export(populate_edges) useDynLib(orbweaver, .registration = TRUE) diff --git a/R/bin.R b/R/bin.R index 7d491f1..6a79643 100644 --- a/R/bin.R +++ b/R/bin.R @@ -11,7 +11,8 @@ graph_to_bin <- function(graph, path) { } #' @title Read the graph from a binary blob -#' @param path Path to a file containing a graph binary +#' @param path (Optional) Path to a file containing a graph binary +#' @param bin (Optional) The raw binary of the graph #' @param type The type of graph the JSON represents #' @return A graph object #' @export @@ -20,10 +21,12 @@ graph_from_bin <- function(path, bin, type = c("directed", "dag")) { rlang::abort("Invalid argument `type`") } if (!missing(bin)) { - switch( - type[1], - "directed" = DirectedGraph$from_bin_mem(bin), - "dag" = DirectedAcyclicGraph$from_bin_mem(bin) + return( + switch( + type[1], + "directed" = DirectedGraph$from_bin_mem(bin), + "dag" = DirectedAcyclicGraph$from_bin_mem(bin) + ) ) } if (missing(path)) { diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 79d4e46..04c7042 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -39,8 +39,6 @@ DirectedGraph$subset <- function(node_id) .Call(wrap__DirectedGraph__subset, sel DirectedGraph$print <- function() invisible(.Call(wrap__DirectedGraph__print, self)) -DirectedGraph$find_all_paths <- function(`_from`, `_to`) .Call(wrap__DirectedGraph__find_all_paths, self, `_from`, `_to`) - DirectedGraph$to_bin_disk <- function(path) .Call(wrap__DirectedGraph__to_bin_disk, self, path) DirectedGraph$to_bin_mem <- function() .Call(wrap__DirectedGraph__to_bin_mem, self) @@ -49,6 +47,12 @@ DirectedGraph$from_bin_disk <- function(path) .Call(wrap__DirectedGraph__from_bi DirectedGraph$from_bin_mem <- function(bin) .Call(wrap__DirectedGraph__from_bin_mem, bin) +DirectedGraph$nodes <- function() .Call(wrap__DirectedGraph__nodes, self) + +DirectedGraph$length <- function() .Call(wrap__DirectedGraph__length, self) + +DirectedGraph$find_all_paths <- function(`_from`, `_to`) .Call(wrap__DirectedGraph__find_all_paths, self, `_from`, `_to`) + #' @export `$.DirectedGraph` <- function (self, name) { func <- DirectedGraph[[name]]; environment(func) <- environment(); func } @@ -81,8 +85,6 @@ DirectedAcyclicGraph$subset <- function(node_id) .Call(wrap__DirectedAcyclicGrap DirectedAcyclicGraph$print <- function() invisible(.Call(wrap__DirectedAcyclicGraph__print, self)) -DirectedAcyclicGraph$find_all_paths <- function(from, to) .Call(wrap__DirectedAcyclicGraph__find_all_paths, self, from, to) - DirectedAcyclicGraph$to_bin_disk <- function(path) .Call(wrap__DirectedAcyclicGraph__to_bin_disk, self, path) DirectedAcyclicGraph$to_bin_mem <- function() .Call(wrap__DirectedAcyclicGraph__to_bin_mem, self) @@ -91,6 +93,12 @@ DirectedAcyclicGraph$from_bin_disk <- function(path) .Call(wrap__DirectedAcyclic DirectedAcyclicGraph$from_bin_mem <- function(bin) .Call(wrap__DirectedAcyclicGraph__from_bin_mem, bin) +DirectedAcyclicGraph$nodes <- function() .Call(wrap__DirectedAcyclicGraph__nodes, self) + +DirectedAcyclicGraph$length <- function() .Call(wrap__DirectedAcyclicGraph__length, self) + +DirectedAcyclicGraph$find_all_paths <- function(from, to) .Call(wrap__DirectedAcyclicGraph__find_all_paths, self, from, to) + #' @export `$.DirectedAcyclicGraph` <- function (self, name) { func <- DirectedAcyclicGraph[[name]]; environment(func) <- environment(); func } diff --git a/R/get_leaves.R b/R/get_leaves.R index c50e8cd..0e76f07 100644 --- a/R/get_leaves.R +++ b/R/get_leaves.R @@ -44,4 +44,3 @@ get_all_leaves.DirectedGraph <- function(graph, ...) { get_all_leaves.DirectedAcyclicGraph <- function(graph, ...) { graph$get_all_leaves() } - diff --git a/R/get_roots.R b/R/get_roots.R index 7012349..22ecb9a 100644 --- a/R/get_roots.R +++ b/R/get_roots.R @@ -14,17 +14,11 @@ get_roots_over <- function(graph, nodes) { #' @export get_roots_over.DirectedGraph <- function(graph, nodes) { - if (missing(nodes)) { - return(graph$get_roots_over()) - } graph$get_roots_over(nodes) } #' @export get_roots_over.DirectedAcyclicGraph <- function(graph, nodes) { - if (missing(nodes)) { - return(graph$get_roots_over()) - } graph$get_roots_over(nodes) } diff --git a/R/graph.R b/R/graph.R deleted file mode 100644 index 41db634..0000000 --- a/R/graph.R +++ /dev/null @@ -1,9 +0,0 @@ -#' @title Initialize a New Directed Graph -#' -#' @description -#' Initializes a new and empty Directed Graph. -#' @return A new Directed Graph object. -#' @export -new_directed_graph <- function() { - DirectedGraph$new() -} diff --git a/R/least_common_parents.R b/R/least_common_parents.R index 5d163a7..2a3a3bb 100644 --- a/R/least_common_parents.R +++ b/R/least_common_parents.R @@ -16,6 +16,6 @@ least_common_parents.DirectedGraph <- function(graph, selected) { } #' @export -least_common_parents.AcyclicDirectedGraph <- function(graph, selected) { +least_common_parents.DirectedAcyclicGraph <- function(graph, selected) { graph$least_common_parents(selected) } diff --git a/R/length.R b/R/length.R index 9951dfa..828053c 100644 --- a/R/length.R +++ b/R/length.R @@ -1,14 +1,15 @@ #' @export length.DirectedGraph <- function(x) { - 1 + x$length() } #' @export length.DirectedAcyclicGraph <- function(x) { - 1 + x$length() } #' @export length.DirectedGraphBuilder <- function(x) { + rlang::warn("Length of a graph builder is always 1") 1 } diff --git a/R/nodes.R b/R/nodes.R new file mode 100644 index 0000000..f2aabb6 --- /dev/null +++ b/R/nodes.R @@ -0,0 +1,9 @@ +#' @title Get the nodes in the graph +#' @description Returns the unique nodes in the graph +#' @param graph A directed or directed acyclic graph +#' @param ... Reserved for later use +#' @return A character vector with the nodes +#' @export +nodes <- function(graph, ...) { + graph$nodes() +} diff --git a/man/graph_from_bin.Rd b/man/graph_from_bin.Rd index d905d20..61e7163 100644 --- a/man/graph_from_bin.Rd +++ b/man/graph_from_bin.Rd @@ -7,7 +7,9 @@ graph_from_bin(path, bin, type = c("directed", "dag")) } \arguments{ -\item{path}{Path to a file containing a graph binary} +\item{path}{(Optional) Path to a file containing a graph binary} + +\item{bin}{(Optional) The raw binary of the graph} \item{type}{The type of graph the JSON represents} } diff --git a/man/new_directed_graph.Rd b/man/new_directed_graph.Rd deleted file mode 100644 index 0716274..0000000 --- a/man/new_directed_graph.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/graph.R -\name{new_directed_graph} -\alias{new_directed_graph} -\title{Initialize a New Directed Graph} -\usage{ -new_directed_graph() -} -\value{ -A new Directed Graph object. -} -\description{ -Initializes a new and empty Directed Graph. -} diff --git a/man/nodes.Rd b/man/nodes.Rd new file mode 100644 index 0000000..177a645 --- /dev/null +++ b/man/nodes.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/nodes.R +\name{nodes} +\alias{nodes} +\title{Get the nodes in the graph} +\usage{ +nodes(graph, ...) +} +\arguments{ +\item{graph}{A directed or directed acyclic graph} + +\item{...}{Reserved for later use} +} +\value{ +A character vector with the nodes +} +\description{ +Returns the unique nodes in the graph +} diff --git a/man/orbweaver-package.Rd b/man/orbweaver-package.Rd index 22c73e5..b9d6af0 100644 --- a/man/orbweaver-package.Rd +++ b/man/orbweaver-package.Rd @@ -11,7 +11,7 @@ Empower your data analysis with 'orbweaver', an R package designed for effortles \seealso{ Useful links: \itemize{ - \item \url{https://github.com/ixpantia/orbweaver} + \item \url{https://github.com/ixpantia/orbweaver-r} \item Report bugs at \url{https://github.com/ixpantia/orbweaver-r/issues} } diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index aa5043c..6b135bc 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -153,9 +153,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "orbweaver" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f334810e600abd2e53d728234a65b43d99387d85e866627b13a7aa302826e80b" +checksum = "cd2766c9d5a3f3ec0e06dd606245e3fec8a9b631e292bae23601588628acafac" dependencies = [ "flate2", "fxhash", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index cc1af47..d088229 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -9,4 +9,4 @@ name = 'orbweaver' [dependencies] extendr-api = { version = "0.6", features = ["serde"] } -orbweaver = { version = "0.9.0" } +orbweaver = { version = "0.10.1" } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 644c50e..7015c4c 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -1,6 +1,6 @@ use extendr_api::prelude::*; use orbweaver::prelude as ow; -use std::io::{BufReader, BufWriter, Cursor}; +use std::io::{BufReader, BufWriter}; pub mod from_dataframe; mod macros; @@ -41,16 +41,16 @@ impl DirectedGraphBuilder { } trait ImplDirectedGraph: Sized { - fn find_path(&self, from: &str, to: &str) -> Result>; - fn children(&self, nodes: Strings) -> Vec; - fn parents(&self, nodes: Strings) -> Vec; + fn find_path(&self, from: &str, to: &str) -> Result>; + fn children(&self, nodes: Strings) -> Vec<&str>; + fn parents(&self, nodes: Strings) -> Vec<&str>; fn has_parents(&self, nodes: Strings) -> Result>; fn has_children(&self, nodes: Strings) -> Result>; - fn least_common_parents(&self, selected: Strings) -> Result>; - fn get_all_leaves(&self) -> Vec; - fn get_leaves_under(&self, nodes: Strings) -> Result>; + fn least_common_parents(&self, selected: Strings) -> Result>; + fn get_all_leaves(&self) -> Vec<&str>; + fn get_leaves_under(&self, nodes: Strings) -> Result>; fn get_all_roots(&self) -> Vec; - fn get_roots_over(&self, node_ids: Vec) -> Result>; + fn get_roots_over(&self, node_ids: Vec) -> Result>; fn subset(&self, node_id: &str) -> Result; fn print(&self); fn find_all_paths(&self, from: &str, to: &str) -> Result; @@ -58,30 +58,20 @@ trait ImplDirectedGraph: Sized { fn to_bin_mem(&self) -> Result>; fn from_bin_disk(path: &str) -> Result; fn from_bin_mem(bin: &[u8]) -> Result; + fn nodes(&self) -> Vec<&str>; + fn length(&self) -> i32; } #[extendr] impl ImplDirectedGraph for DirectedGraph { - fn find_path(&self, from: &str, to: &str) -> Result> { - Ok(self - .0 - .find_path(from, to) - .map_err(to_r_error)? - .into_iter() - .map(String::from) - .collect()) + fn find_path(&self, from: &str, to: &str) -> Result> { + self.0.find_path(from, to).map_err(to_r_error) } - fn children(&self, nodes: Strings) -> Vec { - self.0 - .children(nodes.iter()) - .map(|children| children.into_iter().map(String::from).collect()) - .unwrap_or_default() + fn children(&self, nodes: Strings) -> Vec<&str> { + self.0.children(nodes.iter()).unwrap_or_default() } - fn parents(&self, nodes: Strings) -> Vec { - self.0 - .parents(nodes.iter()) - .map(|children| children.into_iter().map(String::from).collect()) - .unwrap_or_default() + fn parents(&self, nodes: Strings) -> Vec<&str> { + self.0.parents(nodes.iter()).unwrap_or_default() } fn has_parents(&self, nodes: Strings) -> Result> { self.0.has_parents(nodes.iter()).map_err(to_r_error) @@ -89,29 +79,20 @@ impl ImplDirectedGraph for DirectedGraph { fn has_children(&self, nodes: Strings) -> Result> { self.0.has_children(nodes.iter()).map_err(to_r_error) } - fn least_common_parents(&self, selected: Strings) -> Result> { - Ok(self - .0 + fn least_common_parents(&self, selected: Strings) -> Result> { + self.0 .least_common_parents(selected.iter()) - .map_err(to_r_error)? - .into_iter() - .map(String::from) - .collect()) + .map_err(to_r_error) } - fn get_all_leaves(&self) -> Vec { - self.0 - .get_all_leaves() - .into_iter() - .map(String::from) - .collect() + fn get_all_leaves(&self) -> Vec<&str> { + self.0.get_all_leaves().into_iter().collect() } - fn get_leaves_under(&self, nodes: Strings) -> Result> { + fn get_leaves_under(&self, nodes: Strings) -> Result> { Ok(self .0 .get_leaves_under(nodes.iter()) .map_err(to_r_error)? .into_iter() - .map(String::from) .collect()) } fn get_all_roots(&self) -> Vec { @@ -121,14 +102,8 @@ impl ImplDirectedGraph for DirectedGraph { .map(String::from) .collect() } - fn get_roots_over(&self, node_ids: Vec) -> Result> { - Ok(self - .0 - .get_roots_over(&node_ids) - .map_err(to_r_error)? - .into_iter() - .map(String::from) - .collect()) + fn get_roots_over(&self, node_ids: Vec) -> Result> { + self.0.get_roots_over(&node_ids).map_err(to_r_error) } fn subset(&self, node_id: &str) -> Result { Ok(Self(self.0.subset(node_id).map_err(to_r_error)?)) @@ -136,9 +111,6 @@ impl ImplDirectedGraph for DirectedGraph { fn print(&self) { println!("{:?}", self.0) } - fn find_all_paths(&self, _from: &str, _to: &str) -> Result { - todo!("Find all paths is not implemented for DirectedGraph") - } fn to_bin_disk(&self, path: &str) -> Result<()> { let writer = BufWriter::new( @@ -170,30 +142,30 @@ impl ImplDirectedGraph for DirectedGraph { .map(DirectedGraph) .map_err(to_r_error) } + + fn nodes(&self) -> Vec<&str> { + self.0.nodes() + } + + fn length(&self) -> i32 { + self.0.len() as i32 + } + + fn find_all_paths(&self, _from: &str, _to: &str) -> Result { + todo!("Find all paths is not implemented for DirectedGraph") + } } #[extendr] impl ImplDirectedGraph for DirectedAcyclicGraph { - fn find_path(&self, from: &str, to: &str) -> Result> { - Ok(self - .0 - .find_path(from, to) - .map_err(to_r_error)? - .into_iter() - .map(String::from) - .collect()) + fn find_path(&self, from: &str, to: &str) -> Result> { + self.0.find_path(from, to).map_err(to_r_error) } - fn children(&self, nodes: Strings) -> Vec { - self.0 - .children(nodes.iter()) - .map(|children| children.into_iter().map(String::from).collect()) - .unwrap_or_default() + fn children(&self, nodes: Strings) -> Vec<&str> { + self.0.children(nodes.iter()).unwrap_or_default() } - fn parents(&self, nodes: Strings) -> Vec { - self.0 - .parents(nodes.iter()) - .map(|children| children.into_iter().map(String::from).collect()) - .unwrap_or_default() + fn parents(&self, nodes: Strings) -> Vec<&str> { + self.0.parents(nodes.iter()).unwrap_or_default() } fn has_parents(&self, nodes: Strings) -> Result> { self.0.has_parents(nodes.iter()).map_err(to_r_error) @@ -201,29 +173,20 @@ impl ImplDirectedGraph for DirectedAcyclicGraph { fn has_children(&self, nodes: Strings) -> Result> { self.0.has_children(nodes.iter()).map_err(to_r_error) } - fn least_common_parents(&self, selected: Strings) -> Result> { - Ok(self - .0 + fn least_common_parents(&self, selected: Strings) -> Result> { + self.0 .least_common_parents(selected.iter()) - .map_err(to_r_error)? - .into_iter() - .map(String::from) - .collect()) + .map_err(to_r_error) } - fn get_all_leaves(&self) -> Vec { - self.0 - .get_all_leaves() - .into_iter() - .map(String::from) - .collect() + fn get_all_leaves(&self) -> Vec<&str> { + self.0.get_all_leaves().into_iter().collect() } - fn get_leaves_under(&self, nodes: Strings) -> Result> { + fn get_leaves_under(&self, nodes: Strings) -> Result> { Ok(self .0 .get_leaves_under(nodes.iter()) .map_err(to_r_error)? .into_iter() - .map(String::from) .collect()) } fn get_all_roots(&self) -> Vec { @@ -233,14 +196,8 @@ impl ImplDirectedGraph for DirectedAcyclicGraph { .map(String::from) .collect() } - fn get_roots_over(&self, node_ids: Vec) -> Result> { - Ok(self - .0 - .get_roots_over(&node_ids) - .map_err(to_r_error)? - .into_iter() - .map(String::from) - .collect()) + fn get_roots_over(&self, node_ids: Vec) -> Result> { + self.0.get_roots_over(&node_ids).map_err(to_r_error) } fn subset(&self, node_id: &str) -> Result { Ok(Self(self.0.subset(node_id).map_err(to_r_error)?)) @@ -249,15 +206,6 @@ impl ImplDirectedGraph for DirectedAcyclicGraph { println!("{:?}", self.0) } - fn find_all_paths(&self, from: &str, to: &str) -> Result { - Ok(self - .0 - .find_all_paths(from, to) - .map_err(to_r_error)? - .into_iter() - .collect()) - } - fn to_bin_disk(&self, path: &str) -> Result<()> { let writer = BufWriter::new( std::fs::File::options() @@ -288,6 +236,23 @@ impl ImplDirectedGraph for DirectedAcyclicGraph { .map(DirectedAcyclicGraph) .map_err(to_r_error) } + + fn nodes(&self) -> Vec<&str> { + self.0.nodes() + } + + fn length(&self) -> i32 { + self.0.len() as i32 + } + + fn find_all_paths(&self, from: &str, to: &str) -> Result { + Ok(self + .0 + .find_all_paths(from, to) + .map_err(to_r_error)? + .into_iter() + .collect()) + } } // Macro to generate exports. diff --git a/src/rust/vendor.tar.xz b/src/rust/vendor.tar.xz index a10312e..a44becd 100644 Binary files a/src/rust/vendor.tar.xz and b/src/rust/vendor.tar.xz differ diff --git a/tests/testthat/test-bin.R b/tests/testthat/test-bin.R new file mode 100644 index 0000000..3b5ee4b --- /dev/null +++ b/tests/testthat/test-bin.R @@ -0,0 +1,68 @@ +test_that("graph_to_bin in memory directed", { + graph <- graph_builder() |> + add_edge("A", "B") |> + build_directed() + + graph_bin <- graph_to_bin(graph) + + graph_read <- graph_from_bin(bin = graph_bin) + + expect_equal(children(graph, "A"), children(graph_read, "A")) +}) + +test_that("graph_to_bin in memory acyclic", { + graph <- graph_builder() |> + add_edge("A", "B") |> + build_acyclic() + + graph_bin <- graph_to_bin(graph) + + graph_read <- graph_from_bin(bin = graph_bin, type = "dag") + + expect_equal(children(graph, "A"), children(graph_read, "A")) +}) + +test_that("graph_from_bin no args should error", { + expect_error(graph_from_bin()) +}) + +test_that("graph_to_bin on disk directed", { + + temp_file <- tempfile() + + graph <- graph_builder() |> + add_edge("A", "B") |> + build_directed() + + graph_to_bin(graph, temp_file) + + graph_read <- graph_from_bin(path = temp_file) + + expect_equal(children(graph, "A"), children(graph_read, "A")) +}) + +test_that("graph_to_bin on disk acyclic", { + + temp_file <- tempfile() + + graph <- graph_builder() |> + add_edge("A", "B") |> + build_acyclic() + + graph_to_bin(graph, temp_file) + + graph_read <- graph_from_bin(path = temp_file, type = "dag") + + expect_equal(children(graph, "A"), children(graph_read, "A")) + +}) + +test_that("graph_from_bin should error on invalid type", { + graph <- graph_builder() |> + add_edge("A", "B") |> + build_directed() + + graph_bin <- graph_to_bin(graph) + + expect_error(graph_from_bin(bin = graph_bin, type = "invalid")) +}) diff --git a/tests/testthat/test-builder.R b/tests/testthat/test-builder.R new file mode 100644 index 0000000..15fd44b --- /dev/null +++ b/tests/testthat/test-builder.R @@ -0,0 +1,99 @@ +test_that("can initialize a builder", { + + expect_no_error(graph_builder()) + +}) + +test_that("can add edge to a builder", { + expect_no_error({ + graph_builder() |> + add_edge("Hello", "World") + }) +}) + +test_that("can add edge to a builder piped", { + expect_no_error({ + graph_builder() |> + add_edge("Hello", "World") |> + add_edge("Hello", "Mundo") + }) +}) + +test_that("can add path to a builder", { + expect_no_error({ + graph_builder() |> + add_path(c("1", "2", "3")) + }) +}) + +test_that("can add path to a builder piped", { + expect_no_error({ + graph_builder() |> + add_path(c("1", "2", "3")) |> + add_path(c("1", "2", "3", "4")) + }) +}) + +test_that("can build into a directed graph", { + graph <- graph_builder() |> + add_path(c("1", "2", "3", "4")) |> + build_directed() + + expect_s3_class(graph, "DirectedGraph") + expect_equal(children(graph, "1"), "2") + expect_equal(parents(graph, "2"), "1") +}) + +test_that("can build into a directed acyclic graph", { + graph <- graph_builder() |> + add_path(c("1", "2", "3", "4")) |> + build_acyclic() + + expect_s3_class(graph, "DirectedAcyclicGraph") + expect_equal(children(graph, "1"), "2") + expect_equal(parents(graph, "2"), "1") +}) + +test_that("can populate edges", { + graph_edges <- data.frame( + parent = c("A", "B", "C", "C", "F"), + child = c("B", "C", "D", "E", "D") + ) + + graph <- graph_builder() |> + populate_edges(graph_edges, "parent", "child") |> + build_directed() + + expect_s3_class(graph, "DirectedGraph") + expect_equal(children(graph, "A"), "B") +}) + +test_that("can populate edges error when not char col1", { + graph_edges <- data.frame( + parent = 1:5, + child = c("B", "C", "D", "E", "D") + ) + + graph <- graph_builder() + + expect_error({ + graph |> + populate_edges(graph_edges, "parent", "child") + }) + +}) + +test_that("can populate edges error when not char col2", { + graph_edges <- data.frame( + child = 1:5, + parent = c("B", "C", "D", "E", "D") + ) + + graph <- graph_builder() + + expect_error({ + graph |> + populate_edges(graph_edges, "parent", "child") + }) + +}) diff --git a/tests/testthat/test-find_path.R b/tests/testthat/test-find_path.R new file mode 100644 index 0000000..175890c --- /dev/null +++ b/tests/testthat/test-find_path.R @@ -0,0 +1,23 @@ +test_that("find all paths on directed graph should error", { + + graph <- graph_builder() |> + add_edge("A", "B") |> + build_directed() + + expect_error(find_all_paths(graph, "A", "B")) + +}) + +test_that("find all paths on directed acyclic graph", { + + graph <- graph_builder() |> + add_path(c("A", "B", "C")) |> + add_path(c("A", "Z", "C")) |> + build_acyclic() + + expect_equal( + find_all_paths(graph, "A", "C"), + list(c("A", "B", "C"), c("A", "Z", "C")) + ) + +}) diff --git a/tests/testthat/test-get-roots-leaves.R b/tests/testthat/test-get-roots-leaves.R new file mode 100644 index 0000000..8c49367 --- /dev/null +++ b/tests/testthat/test-get-roots-leaves.R @@ -0,0 +1,47 @@ +test_that("get roots directed", { + graph <- graph_builder() |> + add_path(c("A", "B", "C")) |> + add_path(c("A", "D", "C")) |> + add_path(c("Z", "B", "C")) |> + build_directed() + + + expect_equal(get_all_roots(graph), c("A", "Z")) + expect_equal(get_roots_over(graph, "D"), c("A")) +}) + +test_that("get leaves directed", { + graph <- graph_builder() |> + add_path(c("A", "B", "C")) |> + add_path(c("A", "D", "C")) |> + add_path(c("Z", "B", "C")) |> + add_path(c("Z", "B", "H")) |> + build_directed() + + expect_equal(get_all_leaves(graph), c("C", "H")) + expect_equal(get_leaves_under(graph, "D"), c("C")) +}) + +test_that("get roots acyclic", { + graph <- graph_builder() |> + add_path(c("A", "B", "C")) |> + add_path(c("A", "D", "C")) |> + add_path(c("Z", "B", "C")) |> + build_acyclic() + + + expect_equal(get_all_roots(graph), c("A", "Z")) + expect_equal(get_roots_over(graph, "D"), c("A")) +}) + +test_that("get leaves acyclic", { + graph <- graph_builder() |> + add_path(c("A", "B", "C")) |> + add_path(c("A", "D", "C")) |> + add_path(c("Z", "B", "C")) |> + add_path(c("Z", "B", "H")) |> + build_acyclic() + + expect_equal(get_all_leaves(graph), c("C", "H")) + expect_equal(get_leaves_under(graph, "D"), c("C")) +}) diff --git a/tests/testthat/test-least-common-parents.R b/tests/testthat/test-least-common-parents.R index 31596f7..a2f3905 100644 --- a/tests/testthat/test-least-common-parents.R +++ b/tests/testthat/test-least-common-parents.R @@ -25,3 +25,31 @@ test_that("can find least common parents between selected nodes", { expect_equal(c("A", "F")) }) + +test_that("can find least common parents between selected nodes on acyclic", { + + + graph_edges <- data.frame( + parent = c("A", "B", "C", "C", "F"), + child = c("B", "C", "D", "E", "D") + ) + + graph <- graph_builder() |> + populate_edges(graph_edges, parent, child) |> + build_acyclic() + + graph |> + least_common_parents(c("D", "E")) |> + sort() |> + expect_equal(c("D", "E")) + + graph |> + least_common_parents(c("C", "D", "E")) |> + expect_equal("C") + + graph |> + least_common_parents(c("A", "B", "C", "D", "E", "F")) |> + sort() |> + expect_equal(c("A", "F")) + +}) diff --git a/tests/testthat/test-length.R b/tests/testthat/test-length.R new file mode 100644 index 0000000..efab398 --- /dev/null +++ b/tests/testthat/test-length.R @@ -0,0 +1,22 @@ +test_that("length of directed graph", { + graph <- graph_builder() |> + add_path(c("1", "2", "3")) |> + build_directed() + + expect_equal(length(graph), 3) +}) + +test_that("length of directed acyclic graph", { + graph <- graph_builder() |> + add_path(c("1", "2", "3")) |> + build_acyclic() + + expect_equal(length(graph), 3) +}) + +test_that("length of graph builder is always 1", { + expect_warning(length(graph_builder())) + suppressWarnings({ + expect_equal(length(graph_builder()), 1) + }) +}) diff --git a/tests/testthat/test-subset.R b/tests/testthat/test-subset.R index 3b1f952..1e13d43 100644 --- a/tests/testthat/test-subset.R +++ b/tests/testthat/test-subset.R @@ -1,4 +1,4 @@ -test_that("can find least common parents between selected nodes", { +test_that("can find subset graph", { graph_edges <- data.frame( parent = c("A", "B", "C", "C", "F"), @@ -24,5 +24,63 @@ test_that("can find least common parents between selected nodes", { subset("B") expect_equal(find_path(graph_under_b, "B", "E"), c("B", "C", "E")) + expect_equal(nodes(graph_under_b), c("B", "C", "D", "E")) + +}) + +test_that("can find subset acyclic graph", { + + graph_edges <- data.frame( + parent = c("A", "B", "C", "C", "F"), + child = c("B", "C", "D", "E", "D") + ) + + graph <- graph_builder() |> + populate_edges(graph_edges, "parent", "child") |> + build_acyclic() + + graph_under_d <- graph |> + subset("D") + + expect_s3_class(graph_under_d, "DirectedAcyclicGraph") + expect_equal(parents(graph_under_d, "D"), character(0)) + + graph_under_c <- graph |> + subset("C") + + expect_equal(parents(graph_under_d, "C"), character(0)) + expect_equal(find_path(graph_under_c, "C", "E"), c("C", "E")) + + graph_under_b <- graph |> + subset("B") + + expect_equal(find_path(graph_under_b, "B", "E"), c("B", "C", "E")) + +}) + +test_that("subset shout error with multiple arguments", { + + graph_edges <- data.frame( + parent = c("A", "B", "C", "C", "F"), + child = c("B", "C", "D", "E", "D") + ) + + graph <- graph_builder() |> + populate_edges(graph_edges, "parent", "child") |> + build_acyclic() + + expect_error({ + graph |> + subset("A", "B") + }) + + graph <- graph_builder() |> + populate_edges(graph_edges, "parent", "child") |> + build_directed() + + expect_error({ + graph |> + subset("A", "B") + }) })