Skip to content

Commit

Permalink
Added additional documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
TimKDJ committed Dec 15, 2017
1 parent ddcf876 commit 635f3f1
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 32 deletions.
29 changes: 29 additions & 0 deletions Developers-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Note to developers of jasptools
These are some short musings on the internal workings of jasptools.

jasptools calls the function `run` in `common.R` instead of directly calling the analysis itself.
The advantage of this method is that changes to `run` in `common.R` are immediately incorporated in jasptools.
The downside of this approach is that it needs to convert the results from JSON (and thereby loses attributes) as well as use an alternative method to obtain the state file (see the next section).

#### Globals
jasptools uses three different mechanisms to share variables between inner functions and to the environment outside jasptools.
1. `.internal`: environment that holds internal variables; these are used to pass information between functions.
The state entry is special, because it interfaces directly with the JASP code. Rather than storing and then loading the state, it is passed to jasptools, which is much faster.
2. `.pkgOptions`: environment that holds variables to be changed by users; mainly paths to resources.
3. global variables: the rbridge in JASP defines certain global variables that JASP assumes are always globally accessible and these need to be matched in jasptools. They include `perform`, `.ppi` and `.baseCitation` (the global functions on the other hand are defined in the jasptools file rbridge.R). Global variables are set every time `run` is called through `.setRCPPMasks`.

#### Handling of S3 methods
Currently it is necessary to export the S3 methods used for generic functions in JASP (e.g., those defined in `common.R`).
The reason for this is that they are not found on the search path when defined in an environment within jasptools (see `utils::methods`) and consequently are not registered.
The S3 methods are briefly exported during runtime to the .GlobalEnv with `.exportS3Methods` before they are again destroyed.

#### Changing package resources
jasp-desktop follows a fixed structure, meaning that in every development environment the resources are in the same place.
Once jasptools verifies that it is located within /Tools/ it converts the relative paths to the resources to absolute paths.
Now, if these resources are changed, jasptools will need to be adjusted. Firstly, `zzz.R` needs to be modified where it states `# set locations of all required resources (json, analyses, html, packages)` and secondly `.pkgOptions` in `pkg-settings.R` must reflect the same changes.

#### Changing to TOML
At the moment jasptools only supports JSON format for the description files.
Should we decide to change this in the future, then the functions that interface with the description file must be adapted.
These functions can be found in `options.R` (`.analysisOptionsFromFile`), `main.R` (`run`) and in `utils.R` (`.getJSON`).
It is not a problem that the code that is send to `view` still uses JSON as this is unlikely to change.
3 changes: 3 additions & 0 deletions R/dataset.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
}

.datasetLocations <- function() {
# datasets in different locations may share the same name, we try to be a little smart here
# and first look in the most likely location;
# this means tests.data.dir if we're in a testthat context and otherwise data.dir
locOrder <- c(.getPkgOption("data.dir"), .getPkgOption("tests.data.dir"))
testthat <- vapply(sys.frames(), function(frame) getPackageName(frame) == "testthat", logical(1))
if (any(testthat)) {
Expand Down
9 changes: 5 additions & 4 deletions R/main.R
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ view <- function(results) {
content <- paste0("{ \"status\" : \"error\", \"results\" : { \"error\" : 1, \"errorMessage\" : \"Unable to jsonify\" } }")
}
content <- .parseUnicode(content)
content <- gsub("<div class=stack-trace>", "<div>", content, fixed=TRUE)
content <- gsub("<div class=stack-trace>", "<div>", content, fixed=TRUE) # this makes sure the stacktrace is not hidden

html <- readChar(file.path(.getPkgOption("html.dir"), "index.html"), 1000000)
insertedJS <- paste0(
Expand Down Expand Up @@ -233,7 +233,7 @@ view <- function(results) {
run <- function(name, dataset, options, perform = "run", view = TRUE, quiet = FALSE, sideEffects = FALSE) {
envir <- .GlobalEnv
if (! isTRUE(sideEffects)) {
if (! is.logical(sideEffects))
if (! is.logical(sideEffects)) # users can supply a character vector
sideEffects <- tolower(sideEffects)
if (! "globalenv" %in% sideEffects || identical(sideEffects, FALSE))
envir <- new.env()
Expand Down Expand Up @@ -264,12 +264,13 @@ run <- function(name, dataset, options, perform = "run", view = TRUE, quiet = FA

.initRunEnvironment(envir = envir, dataset = dataset, perform = perform)

config <- .getJSON(name, "title", "dataset", "results", "state", "init") # use => for nested objects
config <- .getJSON(name, "title", "dataset", "results", "state", "init") # use '=>' for nested objects
title <- jsonlite::fromJSON(config[["title"]])
options <- jsonlite::toJSON(options)
requiresInit <- jsonlite::fromJSON(config[["init"]])
possibleArgs <- list(
name = name,
title = jsonlite::fromJSON(config[["title"]]),
title = title,
requiresInit = ifelse(is.null(requiresInit) || requiresInit, TRUE, FALSE),
options.as.json.string = options, # backwards compatibility
options = options,
Expand Down
4 changes: 2 additions & 2 deletions R/pkg-settings.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# externally accessible options
# first the externally accessible options
.pkgOptions <- list2env(list(
r.dir = file.path("..", "JASP-Engine", "JASP", "R"),
html.dir = file.path("..", "JASP-Desktop", "html"),
Expand Down Expand Up @@ -69,7 +69,7 @@ setPkgOption <- function(name, value) {
return(get(name, envir = .pkgOptions))
}

# internally accessible options
# ... and the internally accessible options
.setInternal <- function(name, value) {
.internal <- get(".internal", envir = as.environment("package:jasptools"))
.internal[[name]] <- value
Expand Down
38 changes: 19 additions & 19 deletions R/test.R
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#' Test a specific JASP analysis.
#'
#'
#' Tests a specific R analysis found under JASP-Tests. Useful to perform before
#' making a pull request, to prevent failing builds.
#'
#'
#'
#'
#' @param analysis String name of the analysis to test.
#' @examples
#'
#'
#' jasptools::testAnalysis("AnovaBayesian")
#'
#'
#' @export testAnalysis
testAnalysis <- function(analysis) {
analysis <- .validateAnalysis(analysis)
Expand All @@ -20,11 +20,11 @@ testAnalysis <- function(analysis) {


#' Test all JASP analyses.
#'
#'
#' Tests all R analyses found under JASP-Tests. Useful to perform before making
#' a pull request, to prevent failing builds.
#'
#'
#'
#'
#' @export testAll
testAll <- function() {
testDir <- .getPkgOption("tests.dir")
Expand All @@ -34,53 +34,53 @@ testAll <- function() {


#' Visually inspect new/failed test plots.
#'
#'
#' This function is a wrapper around \code{vdiffr::manage_cases()}. It allows
#' visual inspection of the plots in the unit tests that were newly added or
#' produced an error. If no analysis is specified it will iterate over all test
#' cases.
#'
#'
#'
#'
#' @param analysis Optional string name of the analysis whose plots should be
#' tested.
#' @return A Shiny app that shows all new/failed/orphaned cases. The app allows
#' test plots to be validated, at which point they are placed in the figs
#' folder and used as a reference for future tests.
#' @examples
#'
#'
#' jasptools::inspectTestPlots("Anova")
#'
#'
#' @export inspectTestPlots
inspectTestPlots <- function(analysis = NULL) {
if (! is.null(analysis)) {
analysis <- .validateAnalysis(analysis)
analysis <- paste0("^", analysis, "$")
}
testDir <- .getPkgOption("tests.dir")
on.exit(unloadNamespace("SomePkg")) # unload fake pkg needed to run vdiffr
on.exit(unloadNamespace("SomePkg")) # unload fake pkg in JASP unit tests, which is needed to run vdiffr
vdiffr::manage_cases(testDir, analysis)
}



#' Aids in the creation of tests for tables.
#'
#'
#' This function is designed to make it easier to create unit tests for tables.
#' It strips off attributes and flattens the structure until a list remains
#' with dimension 1. Output is then produced which can be immediately placed in
#' the test file.
#'
#'
#'
#'
#' @param rows A list with lists of rows (i.e., a JASP table).
#' @return Copy-paste ready output which may serve as the reference to test
#' tables against.
#' @examples
#'
#'
#' options <- jasptools::analysisOptions("BinomialTest")
#' options[["variables"]] <- "contBinom"
#' results <- jasptools::run("BinomialTest", "debug", options, view=FALSE)
#' jasptools::makeTestTable(results[["results"]][["binomial"]][["data"]])
#'
#'
#' @export makeTestTable
makeTestTable <- function(rows) {
x <- collapseTable(rows)
Expand Down
15 changes: 8 additions & 7 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
}

.initRunEnvironment <- function(envir, dataset, ...) {
.setInternal("envir", envir)
.setInternal("dataset", dataset)
.libPaths(c(.getPkgOption("pkgs.dir"), .libPaths()))
.sourceDir(.getPkgOption("r.dir"), envir)
.exportS3Methods(envir)
.setRCPPMasks(...)
.setInternal("envir", envir) # envir in which the analysis is executed
.setInternal("dataset", dataset) # dataset to be found later when it needs to be read
.libPaths(c(.getPkgOption("pkgs.dir"), .libPaths())) # location of JASP's R packages
.sourceDir(.getPkgOption("r.dir"), envir) # source all the R analysis files
.exportS3Methods(envir) # ensure S3 methods can be registered to the associated generic functions
.setRCPPMasks(...) # set the rbridge globals to the value run is called with
}

# it is necessary to export custom S3 methods to the global envir as otherwise they are not registered
.exportS3Methods <- function(env) {
if (identical(env, .GlobalEnv))
return(invisible(NULL))
Expand All @@ -51,7 +52,7 @@

.setRCPPMasks <- function(...) {
setFromRun <- list(...)
for (mask in .masks) {
for (mask in .masks) { # .masks is a global
unlockBinding(mask, env = as.environment("package:jasptools"))
if (mask %in% names(setFromRun)) {
value <- setFromRun[[mask]]
Expand Down

0 comments on commit 635f3f1

Please sign in to comment.