Nothing
#' Construct the JUnit `</testsuites>` tag
#'
#' Convert the `tinytests2Junit` or `tinytests`-object containing test across
#' possibly multiple files into a JUnit `</testsuites>` tag. More details are reported to the
#' JUnit if a `tinytests2JUnit` object compared to the native `tinytests` object.
#'
#' @details
#' Reference for JUnit XML format: https://llg.cubic.org/docs/junit/
#'
#' See details [runTestDir()] which additional info is recorded.
#'
#' @param testResults `tinytests2Junit | `tinytests` object to convert into a JUnit XML object
#' Usually the result of [runTestDir()] for a `tinytests2JUnit` object or native `tinytests`
#' produced by [tinytest::test_package()] or [tinytest::run_test_dir()].
#'
#' @return `XMLtag`: with tag-name = `</testsuites>`. This is the root of the JUnit XML document.
constructTestsuitesTag <- function(testResults) {
stopifnot(inherits(testResults, "tinytests"))
vctTestFiles <- vapply(
testResults,
function(tinytest) attr(tinytest, "file"),
FUN.VALUE = character(1)
)
isFailure <- vapply(
testResults,
function(tinytest) isFALSE(tinytest) && !inherits(tinytest, "uncaught-error"),
FUN.VALUE = logical(1)
)
isError <- vapply(
testResults,
function(tinytest) isFALSE(tinytest) && inherits(tinytest, "uncaught-error"),
FUN.VALUE = logical(1)
)
attributes <- list(
name = "tinytest results",
tests = length(testResults),
failures = sum(isFailure, na.rm = TRUE),
errors = sum(isError, na.rm = TRUE),
disabled = length(attr(testResults, "disabled"))
)
duration <- attr(testResults, "duration")
if (is.numeric(duration) && !is.na(duration)) attributes$time <- as.character(duration)
uFiles <- unique(vctTestFiles)
testsuiteTags <- Map(
f = constructTestsuiteTag,
testsFile = lapply(uFiles, function(f) testResults[vctTestFiles == f]),
id = seq_along(uFiles) - 1 # JUnit uses 0 based index.
)
tag(
"testsuites",
attributes = attributes,
content = testsuiteTags
)
}
#' Construct JUnit `</testsuite>` tag
#'
#' Construct the `</testsuite>` tag of a `tinytest`, given all the `tinytest` results
#' from a single test file.
#'
#' @details
#' In case a `tinytest2JUnit` is provided following additional info can be reported:
#'
#' * testsuite duration.
#' * timestamp when the testsuite was performed.
#' * hostname where the testsuite was ran.
#'
#' @param testsFile `tinytests2JUnit | tinytests` -object with all test
#' results of a specified test file. At least a single test is expected.
#' @param id `integer(1)` testsuite id.
#'
#' @return `XMLtag`: with tag-name = `</testsuite>`
#' that contains all the test results per test file.
constructTestsuiteTag <- function(testsFile, id) {
stopifnot(inherits(testsFile, "tinytests"))
isFailed <- vapply(
testsFile,
function(tinytest) isFALSE(tinytest) && !inherits(tinytest, "uncaught-error"),
FUN.VALUE = logical(1)
)
isError <- vapply(
testsFile,
function(tinytest) isFALSE(tinytest) && inherits(tinytest, "uncaught-error"),
FUN.VALUE = logical(1)
)
fileName <- attr(testsFile[[1]], "file")
fileNameWithoutExt <- tools::file_path_sans_ext(fileName)
attributes <- list(
name = escapeXml(fileNameWithoutExt),
id = id,
tests = length(testsFile),
failures = sum(isFailed, na.rm = TRUE),
errors = sum(isError, na.rm = TRUE)
)
if (inherits(testsFile, 'tinytests2JUnit')) {
attributes$hostname <- escapeXml(attr(testsFile, "fileHostnames")[fileName])
attributes$time <- as.character(attr(testsFile, "fileDurations")[fileName])
attributes$timestamp <- escapeXml(attr(testsFile, "fileTimestamps")[fileName])
}
tag(
"testsuite",
attributes = attributes,
content = lapply(testsFile, constructTestcaseTag)
)
}
#' Construct JUnit `</testcase>` tag
#'
#' Construct JUnit `</testcase>` tag based on a single `tinytest` result.
#'
#' @param tinytest a `tinytest`-object representing an individual test case.
#' @return `XMLtag`: with tag-name = `tinytest` and contains the test result per test.
constructTestcaseTag <- function(tinytest) {
stopifnot(inherits(tinytest, "tinytest"))
if (isTRUE(tinytest)) {
testcaseTag <- passedTestcaseTag(tinytest)
} else if (isFALSE(tinytest) && !inherits(tinytest, "uncaught-error")) {
testcaseTag <- failureTestcaseTag(tinytest)
} else if (isFALSE(tinytest) && inherits(tinytest, "uncaught-error")) {
testcaseTag <- errorTestcaseTag(tinytest)
} else { # tinytest = NA = side-effect
testcaseTag <- sideeffectTestcaseTag(tinytest)
}
return(testcaseTag)
}
#' Helper function to construct the name of a testcase
#'
#' Helper function to construct the name of a testcase. Note, the charater is already xml escaped.
#'
#' @param tinytest a `tinytest` object. (does not matter what result)
#' @return `character(1)` the testcase name to use for this tinytest object.
nameTestcase <- function(tinytest) {
file <- attr(tinytest, "file")
fst <- attr(tinytest, "fst")
lst <- attr(tinytest, "lst")
info <- attr(tinytest, "info")
# Name = {file}L{fst}-L{lst} : {info}
name <- file
if (!is.na(fst)) {
name <- paste0(name, " L", fst)
}
if (!is.na(lst) && (fst != lst)) {
name <- paste0(name, "-L", lst)
}
if (!is.na(info)) {
infoSplit <- strsplit(info[1], "\n")[[1]]
name <- paste0(name, " : ", infoSplit[1])
}
if (nchar(name) == 0) name <- "unnamed"
name <- escapeXml(name)
return(name)
}
#' Helper function specifying the 'classname' attribute of the testcase tag.
#'
#' Helper function specifying the 'classname' attribute of the testcase tag.
#' Currently equal to the fileName. The classname is already xml escaped.
#'
#' @param tinytest a `tinytest`-object representing an individual test case.
#' @return `character(1)` being the 'classname'
classnameTestcase <- function(tinytest) {
file <- attr(tinytest, "file")
fileName <- tools::file_path_sans_ext(file)
escapeXml(fileName)
}
#' Construct a testcase-tag for a passed test.
#'
#' @param tinytest a `tinytest` object already validated to be a "PASSED" test.
#' @return a `testase` `XMLtag`
passedTestcaseTag <- function(tinytest) {
testcaseTag <- tag(
name = "testcase",
attributes = list(
classname = classnameTestcase(tinytest),
name = nameTestcase(tinytest),
status = "PASSED"
)
)
return(testcaseTag)
}
#' Helper function generating the body of a failure description tag!
#'
#' Helper function generating the body of a failure description tag! Attempts to mimic the print
#' behaviour of a tinytest object.
#'
#' @param tinytest A `tinytest` objec that is considered failed!.
#' @return `character(1)` being the failure tag description body. This string is already propery
#' xml escaped.
constructFailureDescription <- function(tinytest) {
call <- attr(tinytest, "call")
if (is.language(call)) {
callDesc <- paste0('call| ', deparse(call))
} else {
callDesc <- character(0L)
}
diff <- attr(tinytest, "diff")
if (!is.na(diff)) {
diffDesc <- paste0('diff| ', diff)
} else {
diffDesc <- character(0L)
}
info <- attr(tinytest, "info")
if (!is.na(info)) {
infoSplit <- strsplit(info[1], "\n")[[1]]
infoDesc <- paste0('info| ', infoSplit)
} else {
infoDesc <- character(0L)
}
failureDescription <- escapeXmlText(paste0(c(callDesc, diffDesc, infoDesc), collapse = "\n"))
failureDescription
}
#' Construct a testcase-tag for a failed test.
#'
#' @param tinytest a `tinytest` object already validated to be a "FAILURE" test.
#' @return a `testase` `XMLtag`
failureTestcaseTag <- function(tinytest) {
short <- attr(tinytest, "short")
diff <- attr(tinytest, "diff")
failureTag <- tag(
name = "failure",
attributes = list(
message = if (!is.na(diff)) escapeXml(diff[1]) else "",
type = if (!is.na(short)) short else "failure"
),
content = list(
constructFailureDescription(tinytest)
)
)
testcaseTag <- tag(
name = "testcase",
attributes = list(
classname = classnameTestcase(tinytest),
name = nameTestcase(tinytest),
status = "FAILED"
),
content = list(failureTag)
)
return(testcaseTag)
}
#' Construct a testcase-tag for an error test.
#'
#' @param tinytest a `tinytest` object already validated to be a "ERROR" test.
#' @return a `testase` `XMLtag`
errorTestcaseTag <- function(tinytest) {
e <- attr(tinytest, "exception")
if (!is.null(e$call)) {
errorMsg <- paste0("Error in ", deparse(e$call[1]), ": ", e$message)
} else {
errorMsg <- paste0("Error: ", e$message)
}
formattedStacktrace <- attr(tinytest, "formattedStacktrace")
if (!is.na(formattedStacktrace)) {
stacktraceDesc <- paste0("Stacktrace:\n\n", formattedStacktrace)
} else {
stacktraceDesc <- "No Stacktrace available!"
}
errorDescription <- paste(
paste0("Uncaught error in test file: ", basename(attr(tinytest, "file"))),
"No other tests from the test file could be captured duo the error.",
errorMsg,
"\n",
stacktraceDesc,
sep = "\n"
)
errorDescription <- escapeXmlText(errorDescription)
errorTagMessage <- paste0(
"Uncaught error in test file: ", basename(attr(tinytest, "file")), "\n", errorMsg
)
errorTag <- tag(
name = "error",
attributes = list(
message = escapeXml(errorTagMessage),
type = "Error"
),
content = list(errorDescription)
)
testcaseTag <- tag(
name = "testcase",
attributes = list(
classname = classnameTestcase(tinytest),
name = nameTestcase(tinytest),
status = "FAILED"
),
content = list(errorTag)
)
return(testcaseTag)
}
#' Construct a testcase-tag for a side-effect test.
#'
#' @param tinytest a `tinytest` object already validated to be a "SIDE-EFFECT" test.
#' @return a `testase` `XMLtag`
sideeffectTestcaseTag <- function(tinytest) {
name <- "SIDE-EFFECT detected: "
# Not sure if fst and/or lst will be defined for SIDE-EFFECT but in either case just
# add it anyway.
fst <- attr(tinytest, "fst")
lst <- attr(tinytest, "lst")
if (!is.na(fst)) {
name <- paste0(name, ": L", fst)
}
if (!is.na(lst) && (fst != lst)) {
name <- paste0(name, "-L", lst)
}
testcaseTag <- tag(
name = "testcase",
attributes = list(
classname = classnameTestcase(tinytest),
name = escapeXml(name),
status = "SIDE-EFFECT"
),
content = list(
tag(
name = "system-out",
content = list(
constructFailureDescription(tinytest)
)
)
)
)
return(testcaseTag)
}
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.