---
title: "Plotting Test Paths"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Plotting Test Paths}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

options(width = 120L)
```

```{css, echo=FALSE}
img {
  border: none;
  margin: 0;
}
```

## Setup

As with all covtracer analysis, we need to start by collecting coverage traces
of a package. Below is an example where a package is installed with the
necessary flags such that the coverage traces can be collected.

```{r setup}
library(covtracer)

library(withr)
library(covr)
```

```{r libpaths, include = FALSE}
init_libs <- .libPaths()
dir.create(lib <- tempfile("covtracer_pkgs_"))
.libPaths(c(lib, .libPaths()))
```

```{r calc_cov, message = FALSE, warning = FALSE}
options(keep.source = TRUE, keep.source.pkg = TRUE, covr.record_tests = TRUE)
examplepkg_source_path <- system.file("examplepkg", package = "covtracer")

install.packages(
  examplepkg_source_path,
  type = "source",
  repos = NULL,
  quiet = TRUE,
  INSTALL_opts = c("--with-keep.source", "--install-tests")
)

examplepkg_cov <- covr::package_coverage(examplepkg_source_path)
examplepkg_ns <- getNamespace("examplepkg")

ttdf <- covtracer::test_trace_df(examplepkg_cov, aggregate_by = NULL)
```

As well, for this analysis we will use a few supporting packages.

```{r more_setup}
library(dplyr)
library(igraph)
```

## Preparing Graph Data

Before we use the test data, we will clean our incoming data, removing untested
records and filtering out untestable objects like S4 class definitions.

```{r}
ttdf <- ttdf %>%
  filter(!is.na(test_name)) %>%
  filter(is.na(doctype) | !doctype %in% "class") %>%
  select(test_name, alias, is_exported, i) %>%
  arrange(test_name, i) %>%
  mutate(test_id = cumsum(!duplicated(test_name)))

head(ttdf)
```

## Create Edges of Our Test Path 

Our test-trace dataframe has an index of test expressions, each linked to the
traces that they evaluate, with added order of evaluation, `i`. To prepare this
for visualization, we want to convert this to a dataframe where each record
describes a step of this process. Instead of a test linking to a trace with an
index, each jump in the test path should link from the calling expression to the
evaluated expression.

```{r}
edges_df <- ttdf %>%
  split(.$test_name) %>%
  lapply(function(sdf) {
    unique(data.frame(
      from = c(sdf$test_name[[1L]], head(sdf$alias, -1L)),
      to = sdf$alias
    ))
  }) %>%
  bind_rows() %>%
  distinct()

head(edges_df)
```

Likewise, we want to capture some metadata about each vertex. Since a vertex in
this context can be either a test or a trace, we have some data that is captured
differently for each class of vertex.

```{r}
test_names <- Filter(Negate(is.na), unique(ttdf$test_name))
obj_names <- Filter(Negate(is.na), unique(ttdf$alias))

n_tests <- length(test_names)
n_objs <- length(obj_names)

vertices_df <- data.frame(
  name = c(test_names, obj_names),
  color = rep(c("cornflowerblue", "darkgoldenrod"), times = c(n_tests, n_objs)),
  label = c(sprintf("Test #%d", seq_along(test_names)), obj_names),
  test_id = c(seq_along(test_names), rep_len(NA, n_objs)),
  is_test = rep(c(TRUE, FALSE), times = c(n_tests, n_objs)),
  is_exported = c(rep_len(NA, n_tests), ttdf$is_exported[match(obj_names, ttdf$alias)])
)

vertices_df <- vertices_df %>%
  mutate(color = ifelse(is_exported, "goldenrod", color))

vertices_df %>%
  select(name, label) %>%
  head()
```

```{r, include = FALSE}
# for whatever reason... this fixes errors when building vignettes on R-devel
edges_df
vertices_df
```

## Plotting Our Test Paths

Finally, we can plot this network of test executions:

```{r, fig.asp = 1, fig.width = 8L, out.width = "100%", error = TRUE}
g <- igraph::graph_from_data_frame(edges_df, vertices = vertices_df)

par(mai = rep(0, 4), omi = rep(0, 4L))
plot.igraph(g,
  vertex.size = 8,
  vertex.label = V(g)$label,
  vertex.color = V(g)$color,
  vertex.label.family = "sans",
  vertex.label.color = "black",
  vertex.label.dist = 1,
  vertex.label.degree = -pi / 2,
  vertex.label.cex = 0.8,
  mark.border = NA,
  margin = c(0, 0.2, 0, 0.2)
)

legend(
  "bottomleft",
  inset = c(0.05, 0),
  legend = c("test", "exported function", "unexported function"),
  col = c("cornflowerblue", "goldenrod", "darkgoldenrod"),
  pch = 16,
  bty = "n"
)
```

Naturally, there are a plethora of wonderful visualization packages available
that accept igraph data as input. This graph could just as well be plotted with
the `visNetwork` package, though it is omitted to keep this example analysis
minimal.
