Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10 redoc #11

Merged
merged 7 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: testex
Title: Add Tests to Examples
Version: 0.1.0
Version: 0.2.0
Authors@R:
c(
person(
Expand All @@ -9,18 +9,18 @@ Authors@R:
role = c("aut", "cre")
)
)
Description:
Description:
Add tests in-line in examples. Provides standalone functions for
facilitating easier test writing in Rd files. However, a more familiar
interface is provided using 'roxygen2' tags. Tools are also provided for
facilitating package configuration and use with 'testthat'.
URL: https://github.com/dgkf/testex, https://dgkf.github.io/testex/
License: MIT + file LICENSE
Depends:
Depends:
R (>= 3.2.0)
Imports:
Imports:
utils
Suggests:
Suggests:
testthat,
withr,
callr,
Expand All @@ -33,3 +33,4 @@ Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1
Language: en-US
VignetteBuilder: knitr
Config/testex/options: list(version = "0.2.0")
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ export(fallback_expect_no_error)
export(s3_register)
export(test_examples_as_testthat)
export(testex)
export(testthat_block)
export(use_testex)
export(use_testex_as_testthat)
export(uses_roxygen2)
export(with_attached)
export(with_srcref)
importFrom(utils,getSrcFilename)
Expand Down
45 changes: 45 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# testex (development)

> **Life-cycle Policy Prior to `v1.0.0`**
>
> Be aware that this package produces code that enters into your package's
> R documentation files. Until `testex` reaches `1.0.0`, there are no
> guarantees for a stable interface, which means your package's tests written
> in documentation files may fail if interface changes.
>

# testex 0.2.0

## Breaking Changes

> Documentation syntax changes. Documentation will be need to be
> re-`roxygenize`'d or otherwise updated.

* Changes syntax of tests to minimize reliance on `testex` namespace
consistency across versions. Instead of using `testex(with_srcref(..))` and
`testthat_block(test_that(.., with_srcref(..)))`, both interfaces are now
handled via `testex()` with an added `style` parameter:

```r
testex(style = "testthat", srcref = "fn.R:10:11", { code })
```

This syntax is intended to be more resilient to changes to keep your
tests from relying to heavily on an unchanging `testex` function interface.

## New Features

* Adds configuration (`Config/testex/options`) field `"version"`, which is
automatically updated when a newer version of `testex` is first used.

This field is checked to decide whether the currently loaded version of
`testex` is capable of re-running your tests.

Currently, a conservative approach is taken. If there is a version mismatch,
`testex` will suggest updating when run independently using a testing
framework and will disable `testex` testing during `R CMD check` to avoid
causing downstream test failures as the API changes. However, this means
that `testex` tests will be ineffective if your package is out-of-date
with the released `testex` version on `CRAN`

Past a `v1.0.0` release, this behavior will be relaxed to check for a
compatible major version.

# testex 0.1.0

* Initial CRAN submission
88 changes: 72 additions & 16 deletions R/opts.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#' As long as the `fingerprint` has not changed, the package `DESCRIPTION` will
#' be read only once to parse and retrieve configuration options. If the
#' `DESCRIPTION` file is modified or if run from a separate process, the
#' configured settings will be refreshed based on the most recent version of
#' configured settings will be refreshed based on the most recent version of
#' the file.
#'
#' @param path A path in which to search for a package `DESCRIPTION`
Expand All @@ -18,48 +18,104 @@
#'
#' @name testex-options
#' @keywords internal
update_testex_desc <- function(path, fingerprint) {
memoise_testex_desc <- function(path, fingerprint, ...) {
if (identical(fingerprint, .testex_options$.fingerprint)) {
return(invisible(.testex_options))
}

field <- "Config/testex/options"
desc_opts <- read.dcf(file = path, fields = field, keep.white = field)[[1L]]

# the field name is erroneously parsed with the contents on R <4.1 in CMD check
desc_opts <- gsub(paste0(field, ": "), "", desc_opts, fixed = TRUE)
desc_opts <- read_testex_options(path, ...)

desc_opts <- eval(parse(text = desc_opts), envir = baseenv())
# clean and re-load memoised options
rm(list = names(.testex_options), envir = .testex_options)
for (n in names(desc_opts)) .testex_options[[n]] <- desc_opts[[n]]

.testex_options$.fingerprint <- fingerprint
invisible(.testex_options)
}



read_testex_options <- function(path, warn = TRUE, update = FALSE) {
desc <- read.dcf(file = path, all = TRUE)
desc <- read.dcf(file = path, keep.white = colnames(desc))

field <- "Config/testex/options"
desc_opts <- if (field %in% colnames(desc)) desc[, field][[1]] else ""

# the field name is erroneously parsed with the contents on R <4.1 in CMD check
desc_opts <- gsub(paste0(field, ": "), "", desc_opts, fixed = TRUE)
pkg_opts <- pkg_opts_orig <- eval(parse(text = desc_opts), envir = baseenv())
loaded_version <- packageVersion(packageName())
loaded_version_str <- as.character(loaded_version)

warn_mismatch_msg <- cliless(
"{.pkg testex} {.code version} in {.file DESCRIPTION} does not match ",
"currently loaded version. Consider updating to avoid unexpected test ",
"failures. Execution during {.code R CMD check} disabled."
)

if (update) {
# update registered version if necessary
if (is.null(pkg_opts$version) || pkg_opts$version < loaded_version) {
pkg_opts$version <- loaded_version_str
}

# only write if field was modified
if (!identical(pkg_opts, pkg_opts_orig)) {
if (!field %in% colnames(desc)) {
field_col <- matrix(nrow = nrow(desc), dimnames = list(c(), field))
desc <- cbind(desc, field_col)
}

desc[, field] <- deparse(pkg_opts)

write.dcf(
desc,
file = path,
keep.white = colnames(desc),
width = 80L,
indent = 2L
)
}
}

if (!identical(pkg_opts$version, loaded_version_str)) {
if (warn) warning(warn_mismatch_msg)
pkg_opts$check <- FALSE
}

pkg_opts
}



#' @describeIn testex-options
#'
#' @return The test options environment as a list
#'
testex_options <- function(path = package_desc()) {
testex_options <- function(path = package_desc(), ...) {
path <- package_desc(path)

if (is_r_cmd_check()) {
fingerprint <- list(
rcmdcheck = TRUE,
pid = Sys.getpid()
)
fingerprint <- list(rcmdcheck = TRUE, pid = Sys.getpid())

return(as.list(update_testex_desc(path, fingerprint)))
# don't warn or update description during checking
return(as.list(memoise_testex_desc(
path,
fingerprint,
warn = FALSE,
update = FALSE
)))
}

if (file.exists(path)) {
if (!is.null(path) && file.exists(path)) {
fingerprint <- list(
desc = TRUE,
path = path,
mtime = file.info(path)[["mtime"]]
)

return(as.list(update_testex_desc(path, fingerprint)))
return(as.list(memoise_testex_desc(path, fingerprint, ...)))
}

return(as.list(.testex_options))
Expand Down
31 changes: 17 additions & 14 deletions R/roxygen2.R
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ NULL
#' @importFrom utils head tail
#' @exportS3Method roxygen2::roxy_tag_parse roxy_tag_test
roxy_tag_parse.roxy_tag_test <- function(x) {
testex_options(path = x$file, warn = TRUE, update = TRUE)
x$raw <- x$val <- format_tag_expect_test(x)
as_example(x)
}

#' @importFrom utils head tail
#' @exportS3Method roxygen2::roxy_tag_parse roxy_tag_test
roxy_tag_parse.roxy_tag_testthat <- function(x) {
testex_options(path = x$file, warn = TRUE, update = TRUE)
x$raw <- x$val <- format_tag_testthat_test(x)
as_example(x)
}
Expand Down Expand Up @@ -98,14 +100,22 @@ as_example <- function(tag) {
#'
#' @noRd
#' @keywords internal
format_tag_expect_test <- function(tag) { # nolint
format_tag_expect_test <- function(tag) { # nolint
parsed_test <- parse(text = tag$raw, n = 1, keep.source = TRUE)
test <- populate_test_dot(parsed_test)
n <- first_expr_end(parsed_test)

test_str <- trimws(substring(tag$raw, 0, n), "right")
n_newlines <- nchar(gsub("[^\n]", "", test_str))

srcref_str <- paste0(
basename(tag$file),
":", tag$line, ":", tag$line + n_newlines
)

paste0(
"\\testonly{",
"testex::testex(",
"\\testonly{\n",
"testex::testex(srcref = ", deparse(srcref_str), ", \n",
deparse_pretty(test),
")}",
trimws(substring(tag$raw, n + 1L), "right")
Expand Down Expand Up @@ -139,7 +149,7 @@ populate_test_dot <- function(expr) {
#'
#' @noRd
#' @keywords internal
format_tag_testthat_test <- function(tag) { # nolint
format_tag_testthat_test <- function(tag) { # nolint
parsed_test <- parse(text = tag$raw, n = 1, keep.source = TRUE)
test <- populate_testthat_dot(parsed_test)

Expand All @@ -148,20 +158,13 @@ format_tag_testthat_test <- function(tag) { # nolint

nlines <- string_newline_count(trimws(test_str, "right"))
lines <- tag$line + c(0L, nlines)

src <- paste0(basename(tag$file), ":", lines[[1]], ":", lines[[2]])
desc <- sprintf("example tests at `%s`", src)

paste0(
"\\testonly{\n",
paste0("testex::testthat_block(test_that(", deparse(desc), ", {\n"),
paste0(
"testex::with_srcref(",
"\"", src, "\", ", deparse_pretty(test),
")\n"
),
"}))\n",
"}",
"testex::testex(style = \"testthat\", srcref = ", deparse(src), ", \n",
deparse_pretty(test),
")}",
trimws(substring(tag$raw, n + 1L), "right")
)
}
Expand Down
Loading
Loading