Nothing
###################################################################################################
# RTest #
###################################################################################################
# #
# RTest Testing Uitility Functions #
# #
# This file defines a set of utility functions to executes general testthat test, which can be #
# defined for several different packages. #
# #
# Date: 25 - Jan - 2016 #
# Author: Matthias Pfeifer (matthias.pfeifer@roche.com) #
# #
###################################################################################################
# test_execution_silent ###########################################################################
#' Tests Silent Execution of an Function
#'
#' @param what,args Parameters for execution of the test function
#' (see \code{\link{do.call}}).
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type 'RTestTest_variable'.
#' @param ... Additional parameters passed to \code{\link{do.call}}.
#'
#' @return \code{ANY} result of test functin
#'
#' @seealso \code{\link{do.call}}
#'
#' @examples
#' value <- test_execution(
#' "sum",
#' list(x=2,y=3),
#' xmlTestSpec=XML::xmlNode(
#' name="execution",
#' attrs=list('execution-type'="silent"))
#' )
#' stopifnot(value==5)
#'
#' # Create a function that always produces warnings
#'
#' sum_test <- function(...){
#' warning("test")
#' sum(...)
#' }
#'
#' # Let this function run and crash, if it crashes check if the error contains "produced warnings"
#'
#' tryCatch(
#' test_execution(
#' "sum_test",
#' list(x=2,y=3),
#' xmlTestSpec=XML::xmlNode(name="execution",attrs=list("execution-type"="silent"))
#' ),error=function(e){
#' stopifnot(grepl("produced warnings",e))
#' })
#' @importFrom utils packageVersion
#' @author Matthias Pfeifer \email{matthias.pfeifer@@roche.com}
test_execution <- function(what, args, xmlTestSpec=NULL, ...) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- XML::xmlNode("execution",attrs=c("execution-type"="silent"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
# Global settings of the test -------------------------------------------------------------------
test.type <-
ifelse(!is.null(test.attrs[["execution-type"]]),
test.attrs[["execution-type"]], "silent")
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]],
switch(test.type,
"silent" = paste("Execute function silently.",paste0("(",what,")")),
"output" = paste("Execute function with output.",paste0("(",what,")")),
"message" = paste("Execute function with message(s).",paste0("(",what,")")),
"warning" = paste("Execute function with warning(s).",paste0("(",what,")")),
"error" = paste("Execute function with error(s).",paste0("(",what,")")),
stop("Test type '",test.type,"' not implemented.")
))
# message(test.name)
# Perform test ----------------------------------------------------------------------------------
# Initialize variable to store result of computation
result <- NULL
force_implementation <- if(!is.null(options("force_implementation")[[1]])){
as.logical(options("force_implementation"))
}else{
FALSE
}
test_that(test.name, {
# Check different execution types....
if(test.type == "silent") {
# ... without any message / warning / error
if(as.numeric(
stringr::str_extract(
as.character(packageVersion("testthat")),"[0-9]{1,2}\\.[0-9]{1,2}")) >=
2 && !force_implementation){
expect_silent_RTest(
result <<- do.call(what = what, args = args, ...)
)
}else{
expect_silent(
result <<- do.call(what = what, args = args, ...)
)
}
} else if(test.type == "output") {
# ... with message(s)
expect_output(
result <<- do.call(what = what, args = args, ...)
)
} else if(test.type == "message") {
# ... with message(s)
expect_message(
result <<- do.call(what = what, args = args, ...)
)
} else if(test.type == "warning") {
# ... with warning(s)
expect_warning(
result <<- do.call(what = what, args = args, ...)
)
} else if(test.type == "error") {
# ... with error
expect_error(
result <<- do.call(what = what, args = args, ...)
)
}
})
# Return result of function ---------------------------------------------------------------------
return(result)
}
# test_returnValue_variable #######################################################################
#' Tests a Standard R 'variable' ('RTestTest_vector_variable')
#'
#' @param result (\code{object}) The result object to be tested.
#' @param reference (\code{object}) The reference object.
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type 'RTestTest_variable'.
#' @param add.desc (\code{character}) Additional description added to the XML
#' definition.
#'
#' @seealso \code{\link[XML]{XMLNode-class}}
#'
#' @examples
#'
#' # Cleaning up
#'
#' tryCatch(unloadNamespace("RTest"))
#' tryCatch(unloadNamespace("testthat"))
#' library(RTest)
#'
#' data <- '<test_variable desc="Compare a value"
#' diff-type="absolute" compare-type="equal" tolerance="1E-3"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#'
#' test_returnValue_variable(5,5,xmlTestSpec)
#'
#' test_returnValue_variable(5.0001,5,xmlTestSpec)
#'
#' # Compare variable with a stricter tolerance
#'
#' data <- '<test_variable desc="Compare a value"
#' diff-type="relative" compare-type="equal" tolerance="1E-6"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#'
#' tryCatch(unloadNamespace("RTest"))
#' tryCatch(unloadNamespace("testthat"))
#' library(RTest)
#'
#' tryCatch(
#' test_returnValue_variable(5.0001,5,xmlTestSpec),error=function(e){
#' stopifnot(grepl("5.0001 not equal to 5.",e))
#' })
#'
#' @author Matthias Pfeifer \email{matthias.pfeifer@@roche.com}
test_returnValue_variable <- function(result, reference, xmlTestSpec, add.desc = NULL) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- xmlNode("return-value",attrs=list("compare-type"="equal"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
# Global settings of the test -------------------------------------------------------------------
# Get the global settings of the test from the current XML definition.
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]], "Check return value (variable).")
if(!is.null(add.desc))
test.name <- paste0(add.desc," ",test.name)
test.diffType <-
ifelse("diff-type" %in% names(test.attrs),
test.attrs[["diff-type"]], "absolute")
test.compareType <-
ifelse("compare-type" %in% names(test.attrs),
test.attrs[["compare-type"]], "equal")
test.tolerance <-
ifelse("tolerance" %in% names(test.attrs),
as.numeric(test.attrs[["tolerance"]]), 1.5e-8)
# Perform test ----------------------------------------------------------------------------------
test_that(test.name, {
# Level 1: Test value - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test.info <- paste0(
"{",
"\"Test\":\"Equal Value\", ",
"\"Name\":\"%s\", ",
"\"Received\":\"%s\", \"Data Type\":\"%s\", ",
"\"Expected\":\"%s\", \"Data Type\":\"%s\", ",
"\"Diff Type\":\"%s\", \"Compare Type\":\"%s\", ",
"\"Tolerance\":\"%s\" ",
"}")
# Get data
rec <- unname(result)
exp <- unname(reference)
# Get data types
rec.type <- typeof(rec)
exp.type <- typeof(exp)
# Handle factors as strings for comparison
if(is.factor(rec)) rec <- levels(rec)[rec]
if(is.factor(exp)) exp <- levels(exp)[exp]
# Tolerance set to very small number, like in all.equal (which is used by testthat)
# https://stat.ethz.ch/R-manual/R-devel/library/base/html/all.equal.html
if(test.tolerance == 0)
test.tolerance <- 1.5e-8
switch(test.compareType,
"equal" = {
do.call(
"expect_equal",
list(
object = rec,
expected = exp,
tolerance = test.tolerance,
scale = if(test.diffType == "absolute") 1 else NULL,
info = sprintf(
test.info,
test.name,
htmlify_string(rec), rec.type,
htmlify_string(exp), exp.type,
test.diffType,
test.compareType,
test.tolerance)
)
)
},
"less_than" = {
do.call(
"expect_lt",
list(
object = rec,
expected = exp,
info = sprintf(
test.info,
test.name,
rec, rec.type,
exp, exp.type,
"absolute",
test.compareType,
0)
)
)
},
"more_than" = {
do.call(
"expect_gt",
list(
object = rec,
expected = exp,
info = sprintf(
test.info,
test.name,
rec, rec.type,
exp, exp.type,
"absolute",
test.compareType,
0)
)
)
},
"regex"={
test.info[["Diff Type"]] <- "regex"
test.info[["Tolerance"]] <- 0
do.call(
"expect_match",
list(
object = rec,
regexp = htmlify_string(exp),
info = test.info
)
)
},
stop("Compare type '", test.compareType,"' currently not implemented.")
)
})
}
# test_returnValue_vector_elementbyelement ########################################################
#' Tests a Standard R 'vector' Element-By-Element ('RTestTest_vector_elementbyelement')
#'
#' @param result (\code{vector}) The result vector to be tested
#' @param reference (\code{vector}) The reference vector
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type
#' 'RTestTest_vector_elementbyelement'
#' @param add.desc (\code{character}) Additional description added to the XML
#' definition.
#' @examples
#'
#' # Cleaning up
#'
#' tryCatch(unloadNamespace("RTest"))
#' tryCatch(unloadNamespace("testthat"))
#' library(RTest)
#'
#' data <- '<test_variable desc="Compare a value" diff-type="absolute" compare-type="equal"
#' tolerance="1E-3"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#'
#' test_returnValue_vector_elementbyelement(c(5,5),c(5,5),xmlTestSpec)
#' test_returnValue_vector_elementbyelement(c(5,5),c(5,5.000001),xmlTestSpec)
#'
#' data <- '<test_variable desc="Compare a value" diff-type="relative" compare-type="equal"
#' tolerance="1E-6"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#'
#' tryCatch(unloadNamespace("RTest"))
#' tryCatch(unloadNamespace("testthat"))
#' library(RTest)
#'
#' tryCatch(
#' test_returnValue_vector_elementbyelement(c(5,5),c(5,5.0001),xmlTestSpec),
#' error=function(e){
#' stopifnot(grepl("5 not equal to 5.0001.",e))
#' })
#'
#' @seealso \code{\link[XML]{XMLNode-class}}
#'
#' @author Matthias Pfeifer \email{matthias.pfeifer@@roche.com}
test_returnValue_vector_elementbyelement <- function(result, reference, xmlTestSpec,
add.desc = NULL) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- xmlNode("return-value",attrs=list("compare-type"="equal"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
# Global settings of the test -------------------------------------------------------------------
# Get the global settings of the test from the current XML definition. These are mendatory and
# will be used for the subsequent element-by-element tests if not overwritten by other
# specifications on the element level
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]], "Check return value (variable).")
if(!is.null(add.desc)) test.name <- paste0(add.desc," ",test.name)
test.diffType <-
ifelse("diff-type" %in% names(test.attrs),
test.attrs[["diff-type"]], "absolute")
test.compareType <-
ifelse("compare-type" %in% names(test.attrs),
test.attrs[["compare-type"]], "equal")
test.tolerance <-
ifelse("tolerance" %in% names(test.attrs),
as.numeric(test.attrs[["tolerance"]]), 1.5e-8)
# Get the entries and settings from the reference vector ----------------------------------------
elems <- lapply(1:length(reference),
function(i) {
elem <- list()
elem$name <- if(is.null(names(reference))) i else names(reference)[i]
elem$diffType <- test.diffType
elem$compareType <- test.compareType
elem$tolerance <- test.tolerance
return(elem)
})
names(elems) <- sapply(elems, function(e) e$name)
# Check if specific specifications for single elements are done ---------------------------------
if(length(xmlChildren(xmlTestSpec)) > 0) {
# If any children (i.e. single elements) are defined, use the settings of them for the test
xmlApply(xmlTestSpec,
function(xmlElemItem) {
attrs <- xmlAttrs(xmlElemItem)
name <- attrs[["name"]]
if("diff-type" %in% names(attrs)) elems[[name]][["diffType"]] <<- attrs[["diff-type"]]
if("compare-type" %in% names(attrs)) elems[[name]][["compareType"]] <<- attrs[["compare-type"]]
if("tolerance" %in% names(attrs)) elems[[name]][["tolerance"]] <<- as.numeric(attrs[["tolerance"]])
})
}
# Perform test ----------------------------------------------------------------------------------
test_that(test.name, {
# Level 1: Test vector length - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
rec.n <- length(result)
exp.n <- length(reference)
test.info <- paste0(
"{",
"\"Test\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
expect_equal(
object = rec.n,
expected = exp.n,
info = sprintf(test.info, "Equal Vector Length", rec.n, exp.n))
if(rec.n == exp.n) {
# Level 2: Test vector names (if specified) - - - - - - - - - - - - - - - - - - - - - - - -
if(!is.null(names(reference))) {
for(i in 1:length(reference)) {
rec <- names(result)[i]
exp <- names(reference)[i]
test.info <- jsonlite::toJSON(
list(
Test = "Equal Vector Names",
Received = rec,
Expected = exp),
digits = NA
)
expect_equal(
object = rec,
expected = exp,
info = test.info)
}
}
# Level 3: Test element by element - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if(length(reference) > 0) {
sapply(1:length(reference), function(i) {
elem <- elems[[i]]
if(!is.na(unname(result[elem$name]))){
# Get data
rec <- unname(result[elem$name])
exp <- unname(reference[elem$name])
}else{
# Get data
rec <- unname(result[i])
exp <- unname(reference[i])
}
# Get data types
rec.type <- typeof(rec)
exp.type <- typeof(exp)
# Handle factors as strings for comparison
if(is.factor(rec)) rec <- levels(rec)[rec]
if(is.factor(exp)) exp <- levels(exp)[exp]
# Tolerance set to very small number, like in all.equal (which is used by testthat)
# https://stat.ethz.ch/R-manual/R-devel/library/base/html/all.equal.html
if(elem$tolerance == 0) elem$tolerance <- 1.5e-8
test.info <- list(
Test = "Equal Value",
i = i,
Name = elem$name,
Received = rec,
"Data Type (Rec.)" = rec.type,
Expected = exp,
"Data Type (Exp.)" = exp.type,
"Diff Type" = NA,
"Compare Type" = elem$compareType,
Tolerance = NA
)
switch(test.compareType,
"equal" = {
test.info[["Diff Type"]] <- elem$diffType
test.info[["Tolerance"]] <- elem$tolerance
do.call(
"expect_equal",
list(
object = rec,
expected = exp,
tolerance = elem$tolerance,
scale = if(elem$diffType == "absolute") 1 else exp,
info = jsonlite::toJSON(test.info,
digits = NA)
)
)
},
"less_than" = {
test.info[["Diff Type"]] <- "absolute"
test.info[["Tolerance"]] <- 0
do.call(
"expect_less_than",
list(
object = rec,
expected = exp,
info = jsonlite::toJSON(test.info,
digits = NA)
)
)
},
"more_than" = {
test.info[["Diff Type"]] <- "absolute"
test.info[["Tolerance"]] <- 0
do.call(
"expect_more_than",
list(
object = rec,
expected = exp,
info = jsonlite::toJSON(test.info,
digits = NA)
)
)
},
"regex"={
test.info[["Diff Type"]] <- "regex"
test.info[["Tolerance"]] <- 0
do.call(
"expect_match",
list(
object = rec,
regexp = exp,
info = jsonlite::toJSON(test.info,
digits = NA)
)
)
},
stop("Compare type '",test.compareType,"' currently not implemented.")
)
})
}
}
})
}
# test_returnValue_data.frame_cellbycell ##########################################################
#' Tests a Standard R 'data.frame' Cell-By-Cell ('RTestTest_data.frame_cellbycell')
#'
#' @param result (\code{data.frame}) The result data.frame to be tested
#' @param reference (\code{data.frame}) The reference data.frame
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type
#' 'RTestTest_data.frame_cellbycell'
#' @param add.desc (\code{character}) Additional description added to the XML
#' definition.
#'
#' @seealso \code{\link[XML]{XMLNode-class}}
#'
#' @author Matthias Pfeifer \email{matthias.pfeifer@@roche.com}
test_returnValue_data.frame_cellbycell <- function(result, reference, xmlTestSpec, add.desc = NULL) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- xmlNode("return-value",attrs=list("compare-type"="equal"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
RTest.cat(" data.frame: ",dim(result)[1]," x ",dim(result)[2]," ... ")
# Global settings of the test -------------------------------------------------------------------
# Get the global settings of the test from the current XML definition. These are mendatory and
# will be used for the subsequent element-by-element (i.e. each cell between all column and rows)
# tests if not overwritten by other specifications on the element level
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]], "Check return value (data.frame).")
if(!is.null(add.desc))
test.name <- paste0(add.desc," ",test.name)
test.diffType <-
ifelse("diff-type" %in% names(test.attrs),
test.attrs[["diff-type"]], "absolute")
test.compareType <-
ifelse("compare-type" %in% names(test.attrs),
test.attrs[["compare-type"]], "equal")
test.tolerance <-
ifelse("tolerance" %in% names(test.attrs),
as.numeric(test.attrs[["tolerance"]]), 1.5e-8)
# Get the entries and settings from the reference vector ----------------------------------------
elems <- lapply(1:dim(reference)[2],
function(i) {
elem <- list()
elem$name <- if(is.null(colnames(reference))) i else colnames(reference)[i]
elem$diffType <- test.diffType
elem$compareType <- test.compareType
elem$tolerance <- test.tolerance
return(elem)
})
names(elems) <- sapply(elems, function(e) e$name)
# Check if specific specifications for single columns are done ----------------------------------
if(length(xmlChildren(xmlTestSpec)) > 0) {
# If any children (i.e. single elements) are defined, use the settings of them for the test
xmlApply(xmlTestSpec,
function(xmlElemItem) {
attrs <- xmlAttrs(xmlElemItem)
name <- attrs[["name"]]
if("diff-type" %in% names(attrs))
elems[[name]][["diffType"]] <<- attrs[["diff-type"]]
if("compare-type" %in% names(attrs))
elems[[name]][["compareType"]] <<- attrs[["compare-type"]]
if("tolerance" %in% names(attrs))
elems[[name]][["tolerance"]] <<- as.numeric(attrs[["tolerance"]])
# Tolerance set to very small number, like in all.equal (which is used by testthat)
# https://stat.ethz.ch/R-manual/R-devel/library/base/html/all.equal.html
if(elems[[name]][["tolerance"]] == 0)
elems[[name]][["tolerance"]] <<- 1.5e-8
})
}
# Perform test ----------------------------------------------------------------------------------
test_that(test.name, {
# Level 1: Test number of rows and columns - - - - - - - - - - - - - - - - - - - - - - - - -
# Get dimensions
exp.nrows <- dim(reference)[1]
rec.nrows <- dim(result)[1]
exp.ncols <- dim(reference)[2]
rec.ncols <- dim(result)[2]
# Get column/row names
exp.colnames <- colnames(reference)
rec.colnames <- colnames(result)
exp.rownames <- rownames(reference)
rec.rownames <- rownames(result)
# Get data types
exp.colTypes <- sapply(1:exp.ncols, function(i) {
type <- typeof(reference[[i]])
if(type=="integer"){
if(grepl("Factor",capture.output(str(reference[[i]])))){
"factor"
}else{
type
}
}else{
type
}
}
)
rec.colTypes <- sapply(1:rec.ncols, function(i) {
type <- typeof(result[[i]])
if(type=="integer"){
if(grepl("Factor",capture.output(str(result[[i]])))){
"factor"
}else{
type
}
}else{
type
}
}
)
test.info.dims <- paste0(
"{",
"\"Test\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
expect_equal(
object = rec.nrows,
expected = exp.nrows,
info = sprintf(test.info.dims, "Equal Row Number", rec.nrows, exp.nrows))
expect_equal(
object = rec.ncols,
expected = exp.ncols,
info = sprintf(test.info.dims, "Equal Column Number", rec.ncols, exp.ncols))
if(exp.nrows == rec.nrows && exp.ncols == rec.ncols && rec.nrows > 0) {
# Level 2: Test column and row names - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Test column names
if(!is.null(colnames(result))) {
test.info.names <- paste0(
"{",
"\"Test\":\"Equal Column Name\",",
"\"Column\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
#for(i in 1:dim(result)[2]) {
tmp <- lapply(1:dim(result)[2], function(i) {
rec.name <- rec.colnames[i]
exp.name <- exp.colnames[i]
expect_equal(
object = rec.name,
expected = exp.name,
info = sprintf(test.info.names, i, rec.name, exp.name))
})
}
# Test row names
if(!is.null(rownames(result))) {
test.info.names <- paste0(
"{",
"\"Test\":\"Equal Row Name\",",
"\"Row\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
#for(i in 1:dim(result)[1]) {
tmp <- lapply(1:dim(result)[1], function(i) {
rec.name <- rec.rownames[i]
exp.name <- exp.rownames[i]
expect_equal(
object = rec.name,
expected = exp.name,
tolerance = 0,
info = sprintf(test.info.names, i, rec.name, exp.name))
})
}
# Level 3: Test cell by cell - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test.info.data <- paste0(
"{",
"\"Test\":\"Equal Value\", ",
"\"Row\":\"%s\", \"Column\":\"%s\", ",
"\"Received\":\"%s\", \"Data Type\":\"%s\", ",
"\"Expected\":\"%s\", \"Data Type\":\"%s\", ",
"\"Diff Type\":\"%s\", \"Compare Type\":\"%s\", ",
"\"Tolerance\":\"%s\"",
"}")
#for(elem.r in 1:dim(reference)[1]) {
#lapply(1:exp.nrows, function(elem.r) {
#for(elem.c in 1:dim(reference)[2]) {
#lapply(1:exp.ncols, function(elem.c) {
lapply(1:exp.nrows, function(elem.r) {
# Show rowname in Output
elem.r.name <-
if(!is.null(exp.rownames)) paste0(elem.r," (",exp.rownames[elem.r],")")
else elem.r
lapply(1:exp.ncols, function(elem.c) {
col <- elems[[elem.c]]
# Get test specifications
col.name <- col$name
col.diffType <- col$diffType
col.compareType <- col$compareType
col.tolerance <- col$tolerance
# Show colname in Output
elem.c.name <-
if(!is.null(exp.colnames)) paste0(elem.c," (",exp.colnames[elem.c],")")
else elem.c
# Get received and expected value
#rec <- result[elem.r, elem.c]
#exp <- reference[elem.r, elem.c]
rec <- result[[elem.c]][elem.r]
exp <- reference[[elem.c]][elem.r]
# rec <- rec.c[elem.r]
# exp <- exp.c[elem.r]
# Datatypes
rec.type <- rec.colTypes[elem.c]
exp.type <- exp.colTypes[elem.c]
# Handle factors as strings for comparison
if(is.factor(exp)) {
rec <- levels(rec)[rec]
exp <- levels(exp)[exp]
}
if(col.diffType == "absolute_as_numeric"){
if(!is.na(suppressWarnings(as.numeric(rec))) && !is.na(suppressWarnings(as.numeric(exp)))){
rec.type <- "numeric"
exp.type <- "numeric"
rec <- as.numeric(rec)
exp <- as.numeric(exp)
}
col.diffType <- "absolute"
}
# Get testthat function for testing the expection
switch(test.compareType,
"equal" = {
do.call(
"expect_equal",
list(
object = rec,
expected = exp,
tolerance = col.tolerance,
scale = if(col.diffType == "absolute") 1 else NULL,
info = sprintf(
test.info.data,
elem.r.name, elem.c.name,
htmlify_string(rec), rec.type,
htmlify_string(exp), exp.type,
col.diffType,
col.compareType,
col.tolerance)
)
)
},
"less_than" = {
do.call(
"expect_less_than",
list(
object = rec,
expected = exp,
info = sprintf(
test.info.data,
elem.r.name, elem.c.name,
rec, rec.type,
exp, exp.type,
"absolute",
col.compareType,
0)
)
)
},
"more_than" = {
do.call(
"expect_more_than",
list(
object = rec,
expected = exp,
info = sprintf(
test.info.data,
elem.r.name, elem.c.name,
rec, rec.type,
exp, exp.type,
"absolute",
col.compareType,
0)
)
)
},
"regex"={
do.call(
"expect_match",
list(
object = rec,
regexp = exp,
info = sprintf(
test.info.data,
elem.r.name,
elem.c.name,
rec, rec.type,
htmlify_string(exp), exp.type,
"absolute",
col.compareType,
0)
)
)
},
stop("Compare type '", test.compareType,"' currently not implemented.")
)
})
})
}
})
}
#' Tests a Standard R 'data.frame' by shape, rownames and colnames
#' ('RTestTest_data.frame_shape')
#'
#' @param result (\code{data.frame}) The result data.frame to be tested
#' @param reference (\code{data.frame}) The reference data.frame
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type
#' 'RTestTest_data.frame_cellbycell'
#' @param add.desc (\code{character}) Additional description added to the XML
#' definition.
#' @examples
#' # Cleaning up
#'
#' tryCatch(unloadNamespace("RTest"))
#' tryCatch(unloadNamespace("testthat"))
#' library(RTest)
#'
#' # create some definition of tests
#'
#' data <- '<test_df desc="Compare a value" diff-type="relative"
#' compare-type="equal" tolerance="1E-6"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#'
#' # Create data frames
#'
#' x <- data.frame(x=c(1,2,3,4),y=c(1,2,3,4))
#' y <- data.frame(x=c(1,2,3,4),y=c(1,2,3,4))
#' y_wrong_shape <- data.frame(x=c(1,2,3,4,5),y=c(1,2,3,4,5))
#' y_wrong_names <- data.frame(x=c(1,2,3,4),y1=c(1,2,3,4))
#'
#' test_returnValue_data.frame_shape(x,y,xmlTestSpec)
#'
#' # Test for shape
#'
#' tryCatch(
#' {test_returnValue_data.frame_shape(x,y_wrong_shape,xmlTestSpec)
#' stop("test did not find difference")},
#' error=function(e){
#' stopifnot(grepl("rec.nrows",e))
#' stopifnot(grepl("exp.nrows",e))
#' stopifnot(grepl("not equal",e))
#' })
#'
#' # Test for column names
#'
#' data <- '<test_df check_colnames="TRUE"
#' desc="Compare a value" diff-type="relative"
#' compare-type="equal" tolerance="1E-6"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#'
#' tryCatch({
#' test_returnValue_data.frame_shape(x,y_wrong_names,xmlTestSpec)
#' stop("test did not find difference")},
#' error=function(e){
#' stopifnot(grepl("rec.name",e))
#' stopifnot(grepl("exp.name",e))
#' stopifnot(grepl("not equal",e))
#' })
#'
#' @seealso \code{\link[XML]{XMLNode-class}}
#'
#' @author Sebastian Wolf \email{sebastian.wolf.sw1@@roche.com}
test_returnValue_data.frame_shape <- function(result, reference, xmlTestSpec, add.desc = NULL) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- xmlNode("return-value",attrs=list("compare-type"="equal"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
RTest.cat(" data.frame: ",dim(result)[1]," x ",dim(result)[2]," ... ")
# Global settings of the test -------------------------------------------------------------------
# Get the global settings of the test from the current XML definition. These are mendatory and
# will be used for the subsequent element-by-element (i.e. each cell between all column and rows)
# tests if not overwritten by other specifications on the element level
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]], "Check return value (variable).")
if(!is.null(add.desc))
test.name <- paste0(add.desc," ",test.name)
test.diffType <-
ifelse("diff-type" %in% names(test.attrs),
test.attrs[["diff-type"]], "absolute")
test.compareType <-
ifelse("compare-type" %in% names(test.attrs),
test.attrs[["compare-type"]], "equal")
test.tolerance <-
ifelse("tolerance" %in% names(test.attrs),
as.numeric(test.attrs[["tolerance"]]), 1.5e-8)
test.colnames <- ifelse("check_colnames" %in% names(test.attrs),
as.logical(test.attrs[["check_colnames"]]), FALSE)
test.rownames <- ifelse("check_rownames" %in% names(test.attrs),
as.logical(test.attrs[["check_rownames"]]), FALSE)
# Get the entries and settings from the reference vector ----------------------------------------
elems <- lapply(1:dim(reference)[2],
function(i) {
elem <- list()
elem$name <- if(is.null(colnames(reference))) i else colnames(reference)[i]
elem$diffType <- test.diffType
elem$compareType <- test.compareType
elem$tolerance <- test.tolerance
return(elem)
})
names(elems) <- sapply(elems, function(e) e$name)
# Perform test ----------------------------------------------------------------------------------
test_that(test.name, {
# Level 1: Test number of rows and columns - - - - - - - - - - - - - - - - - - - - - - - - -
# Get dimensions
exp.nrows <- dim(reference)[1]
rec.nrows <- dim(result)[1]
exp.ncols <- dim(reference)[2]
rec.ncols <- dim(result)[2]
# Get column/row names
exp.colnames <- colnames(reference)
rec.colnames <- colnames(result)
exp.rownames <- rownames(reference)
rec.rownames <- rownames(result)
test.info.dims <- paste0(
"{",
"\"Test\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
expect_equal(
object = rec.nrows,
expected = exp.nrows,
info = sprintf(test.info.dims, "Equal Row Number", rec.nrows, exp.nrows))
expect_equal(
object = rec.ncols,
expected = exp.ncols,
info = sprintf(test.info.dims, "Equal Column Number", rec.ncols, exp.ncols))
if(exp.nrows == rec.nrows && exp.ncols == rec.ncols && rec.nrows > 0) {
# Level 2: Test column and row names - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Test column names
if(!is.null(colnames(result))) {
if(test.colnames){
test.info.names <- paste0(
"{",
"\"Test\":\"Equal Column Name\",",
"\"Column\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
#for(i in 1:dim(result)[2]) {
tmp <- lapply(1:dim(result)[2], function(i) {
rec.name <- rec.colnames[i]
exp.name <- exp.colnames[i]
expect_equal(
object = rec.name,
expected = exp.name,
info = sprintf(test.info.names, i, rec.name, exp.name))
})
}
}
# Test row names
if(!is.null(rownames(result))) {
if(test.rownames){
test.info.names <- paste0(
"{",
"\"Test\":\"Equal Row Name\",",
"\"Row\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
#for(i in 1:dim(result)[1]) {
tmp <- lapply(1:dim(result)[1], function(i) {
rec.name <- rec.rownames[i]
exp.name <- exp.rownames[i]
expect_equal(
object = rec.name,
expected = exp.name,
tolerance = 0,
info = sprintf(test.info.names, i, rec.name, exp.name))
})
}
}
}
})
}
# test_returnValue_list_nodebynode ##########################################################
#' Tests a Standard R 'list' Node-By-Node ('RTestTest_list_nodebynode')
#'
#' @param result (\code{list}) The result list to be tested
#' @param reference (\code{list}) The reference list
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type
#' 'RTestTest_list_nodebynode'
#' @param add.desc (\code{character}) Additional description added to the XML
#' definition.
#'
#' @seealso \code{\link[XML]{XMLNode-class}}
#'
#' @author Sergej Potapov \email{sergej.potapov@@roche.com}
test_returnValue_list_nodebynode <- function(result, reference, xmlTestSpec, add.desc = NULL) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- xmlNode("return-value",attrs=list("compare-type"="equal"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
# Global settings of the test -------------------------------------------------------------------
# Get the global settings of the test from the current XML definition. These are mendatory and
# will be used for the subsequent element-by-element (i.e. each cell between all column and rows)
# tests if not overwritten by other specifications on the element level
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]], "Check return value (list).")
if(!is.null(add.desc))
test.name <- paste0(add.desc," ",test.name)
test.diffType <-
ifelse("diff-type" %in% names(test.attrs),
test.attrs[["diff-type"]], "absolute")
test.compareType <-
ifelse("compare-type" %in% names(test.attrs),
test.attrs[["compare-type"]], "equal")
test.tolerance <-
ifelse("tolerance" %in% names(test.attrs),
as.numeric(test.attrs[["tolerance"]]), 1.5e-8)
# Get the entries and settings from the reference vector ----------------------------------------
elems <- lapply(1:length(reference),
function(i) {
elem <- list()
elem$name <- if(is.null(names(reference))) i else names(reference)[i]
elem$diffType <- test.diffType
elem$compareType <- test.compareType
elem$tolerance <- test.tolerance
return(elem)
})
names(elems) <- sapply(elems, function(e) e$name)
# Check if specific specifications for single columns are done ----------------------------------
if(length(xmlChildren(xmlTestSpec)) > 0) {
# If any children (i.e. single elements) are defined, use the settings of them for the test
xmlApply(xmlTestSpec,
function(xmlElemItem) {
attrs <- xmlAttrs(xmlElemItem)
name <- attrs[["name"]]
if("diff-type" %in% names(attrs))
elems[[name]][["diff-type"]] <<- attrs[["diff-type"]]
if("compare-type" %in% names(attrs))
elems[[name]][["compare-type"]] <<- attrs[["compare-type"]]
if("tolerance" %in% names(attrs))
elems[[name]][["tolerance"]] <<- as.numeric(attrs[["tolerance"]])
})
}
# Perform test ----------------------------------------------------------------------------------
test_that(test.name, {
# Level 1: Test number of nodes - - - - - - - - - - - - - - - - - - - - - - - - -
exp.nnodes <- length(reference)
rec.nnodes <- length(result)
test.info.dims <- paste0(
"{",
"\"Test\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
expect_equal(
object = rec.nnodes,
expected = exp.nnodes,
info = sprintf(test.info.dims, "Equal Node Number", rec.nnodes, exp.nnodes))
if(exp.nnodes == rec.nnodes) {
# Level 2: Test node names - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Test node names
if(!is.null(names(result))) {
test.info.names <- paste0(
"{",
"\"Test\":\"Equal Node Name\",",
"\"Column\":\"%s\", ",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
for(i in 1:length(result)) {
rec.name <- names(result)[i]
exp.name <- names(reference)[i]
expect_equal(
object = rec.name,
expected = exp.name,
info = sprintf(test.info.names, i, rec.name, exp.name))
}
}
}
})
# Level 3: Test node by node - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if(length(reference) > 0) {
for(elem in 1:length(reference)){
if(!is.null(tryCatch(result[[elem]],error=function(e)NULL))){
if(class(reference[[elem]]) %in% c("data.frame","matrix"))
{
test_returnValue_data.frame_cellbycell(
result[[elem]], reference[[elem]], xmlTestSpec,
add.desc = paste0(" -- List entry '",elems[[elem]]$name,"' (data.frame):")
)
} else if(class(reference[[elem]]) %in%
c("numeric", "character", "logical", "integer", "factor"))
{
if(length(reference[[elem]])==1){
names(result[[elem]]) <- names(reference[[elem]])
test_returnValue_variable(
result[[elem]],
reference[[elem]],
xmlTestSpec,
add.desc = paste0(" -- List entry '",elems[[elem]]$name,"' (variable):"))
}else{
test_returnValue_vector_elementbyelement(
result[[elem]], reference[[elem]], xmlTestSpec,
add.desc = paste0(" -- List entry '",elems[[elem]]$name,"' (vector):")
)
}
} else if(class(reference[[elem]]) %in% c("list","XMLNodeList","XMLNode"))
{
# Check if the list is an image
if(!is.null(reference[[elem]]$image) && reference[[elem]]$image){
test_returnValue_image(
result[[elem]]$address,
reference[[elem]]$address,
xmlTestSpec,
add.desc = paste0(" -- List entry '",elems[[elem]]$name,"' (image):"))
}else{
test_returnValue_list_nodebynode(
result[[elem]], reference[[elem]], xmlTestSpec,
add.desc = paste0(" -- List entry '",elems[[elem]]$name,"' (list):")
)
}
} else {
stop(paste0("Mode with class: '",class(reference[[elem]]),"' not supported"))
}
}else{
# In case the list element was not found, return that result
test.info.names <- paste0(
"{",
"\"Test\":\"Equal Node Name\",",
"\"Received\":\"%s\", \"Expected\":\"%s\"",
"}")
test_returnValue_variable(
"no list element",
elems[[elem]]$name,
xmlTestSpec,
add.desc = paste0(" -- List entry '",elems[[elem]]$name,"' (ANY):"))
}
}
}
}
# test_returnValue_variable #######################################################################
#' Tests an image file with ImageMagick ('RTestTest_image')
#'
#' @param result (\code{object}) The result object to be tested.
#' @param reference (\code{object}) The reference object.
#' @param xmlTestSpec (\code{XMLNode}) The XML definition of type 'RTestTest_variable'.
#' @param add.desc (\code{character}) Additional description added to the XML
#' definition.
#'
#' @seealso \code{\link[XML]{XMLNode-class}}
#' @examples
#'
#' # Cleaning up
#'
#' tryCatch(unloadNamespace("RTest"))
#' tryCatch(unloadNamespace("testthat"))
#' library(RTest)
#'
#' # create some definition of tests
#'
#' data <- '<test_image desc="Compare a value" diff-type="relative"
#' compare-type="equal" tolerance="0"/>'
#' xmlTestSpec <- XML::xmlRoot(XML::xmlParse(data,asText=TRUE))
#' location <- find.package("RTest")
#'
#' # Create a test with equal images
#'
#' result <- paste0(location,"/images/Roche_Logo.png")
#' reference <- paste0(location,"/images/Roche_Logo.png")
#'
#' test_returnValue_image(result,reference,xmlTestSpec)
#'
#' # Create a test with images that are not equal
#'
#' reference <- paste0(location,"/images/Roche_Logo_defect.png")
#' tryCatch(
#' test_returnValue_image(result,reference,xmlTestSpec),
#' error=function(e){
#' if(!grepl("not equal to",e)){
#' stop("image omparison defect, please check code")
#' }
#' })
#'
#' @importFrom magick image_compare image_read image_write
#' @author Sebastian Wolf \email{sebastian.wolf.sw1@@roche.com}
test_returnValue_image <- function(result, reference, xmlTestSpec, add.desc = NULL) {
if(is.null(xmlTestSpec)){
xmlTestSpec <- xmlNode("return-value",attrs=list("compare-type"="equal"))
}
test.attrs <- xmlAttrs(xmlTestSpec)
# Global settings of the test -------------------------------------------------------------------
# Get the global settings of the test from the current XML definition.
test.name <-
ifelse("desc" %in% names(test.attrs),
test.attrs[["desc"]], "Check return image.")
if(!is.null(add.desc))
test.name <- paste0(add.desc," ",test.name)
test.diffType <-
ifelse("diff-type" %in% names(test.attrs),
test.attrs[["diff-type"]], "absolute")
test.compareType <-
ifelse("compare-type" %in% names(test.attrs),
test.attrs[["compare-type"]], "equal")
test.tolerance <-
ifelse("tolerance" %in% names(test.attrs),
as.numeric(test.attrs[["tolerance"]]), 1.5e-8)
# Perform test ----------------------------------------------------------------------------------
test_that(test.name, {
# Level 1: Test value - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test.info <- paste0(
"{",
"\"Test\":\"Equal Value\", ",
"\"Name\":\"%s\", ",
"\"Received\":\"%s\", \"Data Type\":\"%s\", ",
"\"Expected\":\"%s\", \"Data Type\":\"%s\", ",
"\"Diff in percent\":\"%s\", ",
"\"Diff Image\":\"%s\", ",
"\"Tolerance\":\"%s\" ",
"}")
# Get data
rec <- unname(result)
exp <- unname(reference)
# Get data types
rec.type <- typeof(rec)
exp.type <- typeof(exp)
# Handle factors as strings for comparison
if(is.factor(rec)) rec <- levels(rec)[rec]
if(is.factor(exp)) exp <- levels(exp)[exp]
# Tolerance set to very small number, like in all.equal (which is used by testthat)
# https://stat.ethz.ch/R-manual/R-devel/library/base/html/all.equal.html
if(test.tolerance == 0)
test.tolerance <- 1.5e-8
difference_png_name <- tempfile( fileext = ".png")
# ImageMagick <- if(Sys.which("magick")!=""){
# "magick "
# }else{
# if(Sys.which("compare")==""){
# stop("No ImageMagick installed. Please use \n
# sudo apt-get install imagemagick libmagickcore-dev libmagickwand-dev libmagic-dev \n
# on Linux or download ImageMagick for Windows.
# ")
# }else{
# ""
# }
# }
#
# if(Sys.info()["sysname"]=="Windows"){
# compare_result <- suppressWarnings(shell(
# paste(ImageMagick,"compare -metric RMSE \"",
# gsub("\\\\", "/", result),"\" \"",
# gsub("\\\\", "/",reference),"\" ",
# paste0("\"",difference_png_name,"\""," 2>&1"),sep=""),
# intern=T))
# }else{
# compare_result <- suppressWarnings(system(
# paste(ImageMagick,"compare -metric RMSE \"",
# gsub("\\\\", "/", result),"\" \"",
# gsub("\\\\", "/",reference),"\" ",
# paste0("\"",difference_png_name,"\""," 2>&1"),sep=""),
# intern=T))
# }
# difference_in_percent <- as.numeric(
# sub("\\(","",
# stringr::str_extract(compare_result[1],"\\([^\\)]*")
# )
# )
image_compared <- magick::image_compare(
image=magick::image_read(rec),
reference_image = magick::image_read(exp),
metric = "RMSE")
difference_in_percent <- attributes(image_compared)$distortion
magick::image_write(
image_compared,
path = difference_png_name
)
difference_png_name_text <- tempfile()
base64::encode(difference_png_name, difference_png_name_text)
src <- sprintf("data:image/png;base64,%s",
paste(readLines(difference_png_name_text), collapse = ""))
image_for_info <-
sprintf("<img width=200 src='%s' alt='%s' />",
src,
paste0(gsub(":","_",gsub(c(" "),"_",date()))))
switch(test.compareType,
"equal" = {
do.call(
"expect_equal",
list(
object = difference_in_percent,
expected = 0,
tolerance = test.tolerance,
scale = if(test.diffType == "absolute") 1 else NULL,
info = sprintf(
test.info,
test.name,
htmlify_string(rec), "Image",
htmlify_string(exp), "Image",
difference_in_percent,
image_for_info,
test.tolerance
)
)
)
},
stop("Compare type '", test.compareType,"' currently not implemented.")
)
})
}
#' Generically compare two values with RTest
#'
#' This function compares two value by a \code{test_returnValue_...} function
#' that fits the class of the \code{reference} input parameter.
#'
#' @param result (\code{any}) Any value of type character, numeric, data.frame or list
#' (image links do not work!)
#' @param reference (\code{any}) Any value of type character, numeric, data.frame or list
#' (image links do not work!)
#' @param xmlTestSpec (\code{XMLNode}) An XMLNode of type \code{RTest_test_returnValue_...}
#'
#' @return The function will not return anything but call \code{testthat} functions
#' creating outputs in the reporter
#'
#' @export
#'
#' @author Sebastian Wolf \email{sebastian@@mail-wolf.de}
test_returnValue_any <- function(result,reference,xmlTestSpec){
### ------ Check class of values (result, reference) ------ ######
test_returnValue_variable(
class(result),
class(reference),
NULL,
add.desc="Checking output class and reference class.")
### ------ Compare values ------ ######
if(class(reference)=="data.frame"){
test_returnValue_data.frame_cellbycell(
result,
reference,
xmlTestSpec = xmlTestSpec)
}else if(class(reference)=="list"){
test_returnValue_list_nodebynode(
result,
reference,
xmlTestSpec = xmlTestSpec)
}else if(length(reference)>1){
test_returnValue_vector_elementbyelement(
result = result,
reference = reference,
xmlTestSpec = xmlTestSpec)
}else{
test_returnValue_variable(
result,
reference,
xmlTestSpec = xmlTestSpec)
}
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.