diff --git a/src/R/.Rbuildignore b/src/R/.Rbuildignore new file mode 100644 index 0000000..86faf1e --- /dev/null +++ b/src/R/.Rbuildignore @@ -0,0 +1,3 @@ +^R\.Rproj$ +^\.Rproj\.user$ +^LICENSE\.md$ diff --git a/src/R/.gitignore b/src/R/.gitignore new file mode 100644 index 0000000..cd67eac --- /dev/null +++ b/src/R/.gitignore @@ -0,0 +1 @@ +.Rproj.user diff --git a/src/R/DESCRIPTION b/src/R/DESCRIPTION new file mode 100644 index 0000000..ac23b01 --- /dev/null +++ b/src/R/DESCRIPTION @@ -0,0 +1,21 @@ +Package: rtestexplorer +Title: Test Reporters For R Test Explorer +Version: 0.0.0.9000 +Date: 2021-03-23 +Authors@R: + person(given = "Kirill", + family = "Müller", + role = c("aut", "cre"), + email = "krlmlr+r@mailbox.org", + comment = c(ORCID = "0000-0002-1416-3412")) +Description: A test reporter for the R Test Explorer. +License: MIT + file LICENSE +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.1.1.9001 +Imports: + rlang, + testthat (>= 3.0.0), + withr +Config/testthat/edition: 3 diff --git a/src/R/LICENSE b/src/R/LICENSE new file mode 100644 index 0000000..aabf588 --- /dev/null +++ b/src/R/LICENSE @@ -0,0 +1,3 @@ +YEAR: 2021 +COPYRIGHT HOLDER: rtestexplorer authors +COPYRIGHT HOLDER: Hadley Wickham; RStudio diff --git a/src/R/LICENSE.md b/src/R/LICENSE.md new file mode 100644 index 0000000..59f8613 --- /dev/null +++ b/src/R/LICENSE.md @@ -0,0 +1,23 @@ +# MIT License + +Copyright (c) 2021 rtestexplorer authors + +Copyright (c) 2013-2019 Hadley Wickham; RStudio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/R/NAMESPACE b/src/R/NAMESPACE new file mode 100644 index 0000000..a622156 --- /dev/null +++ b/src/R/NAMESPACE @@ -0,0 +1,4 @@ +# Generated by roxygen2: do not edit by hand + +export(VsCodeReporter) +import(testthat) diff --git a/src/R/R/expect.R b/src/R/R/expect.R new file mode 100644 index 0000000..6418b3d --- /dev/null +++ b/src/R/R/expect.R @@ -0,0 +1,20 @@ +expect_snapshot_reporter <- function(reporter, path = test_path("reporters/tests.R")) { + withr::local_rng_version("3.3") + withr::with_seed(1014, { + expect_snapshot_output( + with_reporter( + reporter, + test_one_file(path) + ) + ) + }) +} + +test_one_file <- function(path, env = test_env(), wrap = TRUE) { + reporter <- testthat::get_reporter() + + reporter$start_file(path) + source_file(path, rlang::child_env(env), wrap = wrap) + reporter$end_context_if_started() + reporter$end_file() +} diff --git a/src/R/R/import.R b/src/R/R/import.R new file mode 100644 index 0000000..3886d86 --- /dev/null +++ b/src/R/R/import.R @@ -0,0 +1,2 @@ +#' @import testthat +NULL diff --git a/src/R/R/reporter-vscode.R b/src/R/R/reporter-vscode.R new file mode 100644 index 0000000..3a5c71c --- /dev/null +++ b/src/R/R/reporter-vscode.R @@ -0,0 +1,98 @@ +#' Test reporter: VS Code format. +#' +#' This reporter will output results in a format understood by the +#' [R Test Explorer](https://github.com/meakbiyik/vscode-r-test-adapter). +#' +#' @export +VsCodeReporter <- R6::R6Class("VsCodeReporter", + inherit = Reporter, + private = list( + filename = NULL + ), + public = list( + suite_name = NULL, + + initialize = function(suite_name, ...) { + super$initialize(...) + self$suite_name <- suite_name + private$filename <- NULL + self$capabilities$parallel_support <- TRUE + # FIXME: self$capabilities$parallel_updates <- TRUE + }, + + start_reporter = function() { + self$cat_json(list(type = "start_reporter", tests = list(self$suite_name))) + }, + + start_file = function(filename) { + self$cat_json(list(type = "start_file", filename = filename)) + private$filename <- filename + }, + + start_test = function(context, test) { + self$cat_json(list(type = "start_test", test = test)) + }, + + add_result = function(context, test, result) { + self$cat_json(list( + type = "add_result", + context = context, + test = test, + result = expectation_type(result), + message = exp_message(result), + location = expectation_location(result) + )) + }, + + end_test = function(context, test) { + self$cat_json(list(type = "end_test", test = test)) + }, + + end_file = function() { + self$cat_json(list(type = "end_file", filename = private$filename)) + private$filename <- NULL + }, + + end_reporter = function() { + self$cat_json(list(type = "end_reporter", tests = list(self$suite_name))) + }, + + cat_json = function(x) { + self$cat_line(jsonlite::toJSON(x, auto_unbox = TRUE)) + } + ) +) + +expectation_type <- function(exp) { + stopifnot(is.expectation(exp)) + gsub("^expectation_", "", class(exp)[[1]]) +} + +expectation_success <- function(exp) expectation_type(exp) == "success" +expectation_failure <- function(exp) expectation_type(exp) == "failure" +expectation_error <- function(exp) expectation_type(exp) == "error" +expectation_skip <- function(exp) expectation_type(exp) == "skip" +expectation_warning <- function(exp) expectation_type(exp) == "warning" +expectation_broken <- function(exp) expectation_failure(exp) || expectation_error(exp) +expectation_ok <- function(exp) expectation_type(exp) %in% c("success", "warning") + +exp_message <- function(x) { + if (expectation_error(x)) { + paste0("Error: ", x$message) + } else { + x$message + } +} + +expectation_location <- function(x) { + if (is.null(x$srcref)) { + "???" + } else { + filename <- attr(x$srcref, "srcfile")$filename + if (identical(filename, "")) { + paste0("Line ", x$srcref[1]) + } else { + paste0(basename(filename), ":", x$srcref[1], ":", x$srcref[2]) + } + } +} diff --git a/src/R/man/VsCodeReporter.Rd b/src/R/man/VsCodeReporter.Rd new file mode 100644 index 0000000..3ad9993 --- /dev/null +++ b/src/R/man/VsCodeReporter.Rd @@ -0,0 +1,142 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reporter-vscode.R +\name{VsCodeReporter} +\alias{VsCodeReporter} +\title{Test reporter: VS Code format.} +\description{ +This reporter will output results in a format understood by the +\href{https://github.com/meakbiyik/vscode-r-test-adapter}{R Test Explorer}. +} +\section{Super class}{ +\code{\link[testthat:Reporter]{testthat::Reporter}} -> \code{VsCodeReporter} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-new}{\code{VsCodeReporter$new()}} +\item \href{#method-start_reporter}{\code{VsCodeReporter$start_reporter()}} +\item \href{#method-start_file}{\code{VsCodeReporter$start_file()}} +\item \href{#method-start_test}{\code{VsCodeReporter$start_test()}} +\item \href{#method-add_result}{\code{VsCodeReporter$add_result()}} +\item \href{#method-end_test}{\code{VsCodeReporter$end_test()}} +\item \href{#method-end_file}{\code{VsCodeReporter$end_file()}} +\item \href{#method-end_reporter}{\code{VsCodeReporter$end_reporter()}} +\item \href{#method-cat_json}{\code{VsCodeReporter$cat_json()}} +\item \href{#method-clone}{\code{VsCodeReporter$clone()}} +} +} +\if{html}{ +\out{
Inherited methods} +\itemize{ +\item \out{}\href{../../testthat/html/Reporter.html#method-.start_context}{\code{testthat::Reporter$.start_context()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-cat_line}{\code{testthat::Reporter$cat_line()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-cat_tight}{\code{testthat::Reporter$cat_tight()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-end_context}{\code{testthat::Reporter$end_context()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-end_context_if_started}{\code{testthat::Reporter$end_context_if_started()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-is_full}{\code{testthat::Reporter$is_full()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-local_user_output}{\code{testthat::Reporter$local_user_output()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-rule}{\code{testthat::Reporter$rule()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-start_context}{\code{testthat::Reporter$start_context()}}\out{} +\item \out{}\href{../../testthat/html/Reporter.html#method-update}{\code{testthat::Reporter$update()}}\out{} +} +\out{
} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-new}{}}} +\subsection{Method \code{new()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$new(suite_name, ...)}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-start_reporter}{}}} +\subsection{Method \code{start_reporter()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$start_reporter()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-start_file}{}}} +\subsection{Method \code{start_file()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$start_file(filename)}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-start_test}{}}} +\subsection{Method \code{start_test()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$start_test(context, test)}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-add_result}{}}} +\subsection{Method \code{add_result()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$add_result(context, test, result)}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-end_test}{}}} +\subsection{Method \code{end_test()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$end_test(context, test)}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-end_file}{}}} +\subsection{Method \code{end_file()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$end_file()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-end_reporter}{}}} +\subsection{Method \code{end_reporter()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$end_reporter()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-cat_json}{}}} +\subsection{Method \code{cat_json()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$cat_json(x)}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{VsCodeReporter$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/src/R/tests/testthat.R b/src/R/tests/testthat.R new file mode 100644 index 0000000..c1a36a0 --- /dev/null +++ b/src/R/tests/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library(rtestexplorer) + +test_check("rtestexplorer") diff --git a/src/R/tests/testthat/_snaps/reporter-vscode.md b/src/R/tests/testthat/_snaps/reporter-vscode.md new file mode 100644 index 0000000..7671834 --- /dev/null +++ b/src/R/tests/testthat/_snaps/reporter-vscode.md @@ -0,0 +1,31 @@ +# reporter works + + {"type":"start_reporter","tests":["suite_name"]} + {"type":"start_file","filename":"reporters/tests.R"} + {"type":"start_test","test":"Success"} + {"type":"add_result","context":{},"test":"Success","result":"success","message":"Success has been forced","location":"tests.R:2:3"} + {"type":"end_test","test":"Success"} + {"type":"start_test","test":"Failure:1"} + {"type":"add_result","context":{},"test":"Failure:1","result":"failure","message":"FALSE is not TRUE\n\n`actual`: FALSE\n`expected`: TRUE ","location":"tests.R:6:3"} + {"type":"end_test","test":"Failure:1"} + {"type":"start_test","test":"Failure:2a"} + {"type":"add_result","context":{},"test":"Failure:2a","result":"failure","message":"FALSE is not TRUE\n\n`actual`: FALSE\n`expected`: TRUE ","location":"tests.R:11:3"} + {"type":"end_test","test":"Failure:2a"} + {"type":"start_test","test":"Error:1"} + {"type":"add_result","context":{},"test":"Error:1","result":"error","message":"Error: stop","location":"tests.R:15:3"} + {"type":"end_test","test":"Error:1"} + {"type":"start_test","test":"errors get tracebacks"} + {"type":"add_result","context":{},"test":"errors get tracebacks","result":"error","message":"Error: !","location":"tests.R:23:3"} + {"type":"end_test","test":"errors get tracebacks"} + {"type":"start_test","test":"explicit skips are reported"} + {"type":"add_result","context":{},"test":"explicit skips are reported","result":"skip","message":"Reason: skip","location":"tests.R:27:3"} + {"type":"end_test","test":"explicit skips are reported"} + {"type":"start_test","test":"empty tests are implicitly skipped"} + {"type":"add_result","context":{},"test":"empty tests are implicitly skipped","result":"skip","message":"Reason: empty test","location":"tests.R:30:1"} + {"type":"end_test","test":"empty tests are implicitly skipped"} + {"type":"start_test","test":"warnings get backtraces"} + {"type":"add_result","context":{},"test":"warnings get backtraces","result":"warning","message":"def","location":"tests.R:37:3"} + {"type":"end_test","test":"warnings get backtraces"} + {"type":"end_file","filename":"reporters/tests.R"} + {"type":"end_reporter","tests":["suite_name"]} + diff --git a/src/R/tests/testthat/reporters/tests.R b/src/R/tests/testthat/reporters/tests.R new file mode 100644 index 0000000..5e17fe0 --- /dev/null +++ b/src/R/tests/testthat/reporters/tests.R @@ -0,0 +1,38 @@ +test_that("Success", { + succeed() +}) + +test_that("Failure:1", { + expect_true(FALSE) +}) + +test_that("Failure:2a", { + f <- function() expect_true(FALSE) + f() +}) + +test_that("Error:1", { + stop("stop") +}) + +test_that("errors get tracebacks", { + f <- function() g() + g <- function() h() + h <- function() stop("!") + + f() +}) + +test_that("explicit skips are reported", { + skip("skip") +}) + +test_that("empty tests are implicitly skipped", { +}) + +test_that("warnings get backtraces", { + f <- function() { + warning("def") + } + f() +}) diff --git a/src/R/tests/testthat/test-reporter-vscode.R b/src/R/tests/testthat/test-reporter-vscode.R new file mode 100644 index 0000000..3cf6347 --- /dev/null +++ b/src/R/tests/testthat/test-reporter-vscode.R @@ -0,0 +1,3 @@ +test_that("reporter works", { + expect_snapshot_reporter(VsCodeReporter$new("suite_name")) +}) diff --git a/src/R/testthat_reporter.R b/src/R/testthat_reporter.R deleted file mode 100644 index e69de29..0000000