File: test-files.R

package info (click to toggle)
r-cran-testthat 3.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,452 kB
  • sloc: cpp: 9,261; ansic: 37; sh: 14; makefile: 5
file content (426 lines) | stat: -rw-r--r-- 13,295 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#' Run all tests in a directory
#'
#' @description
#' This function is the low-level workhorse that powers [test_local()] and
#' [test_package()]. Generally, you should not call this function directly.
#' In particular, you are responsible for ensuring that the functions to test
#' are available in the test `env` (e.g. via `load_package`).
#'
#' See `vignette("special-files")` to learn more about the conventions for test,
#' helper, and setup files that testthat uses, and what you might use each for.
#'
#' @section Environments:
#' Each test is run in a clean environment to keep tests as isolated as
#' possible. For package tests, that environment inherits from the package's
#' namespace environment, so that tests can access internal functions
#' and objects.
#'
#' @param path Path to directory containing tests.
#' @param package If these tests belong to a package, the name of the package.
#' @param filter If not `NULL`, only tests with file names matching this
#'   regular expression will be executed. Matching is performed on the file
#'   name after it's stripped of `"test-"` and `".R"`.
#' @param env Environment in which to execute the tests. Expert use only.
#' @param ... Additional arguments passed to [grepl()] to control filtering.
#' @param load_helpers Source helper files before running the tests?
#' @param stop_on_failure If `TRUE`, throw an error if any tests fail.
#' @param stop_on_warning If `TRUE`, throw an error if any tests generate
#'   warnings.
#' @param load_package Strategy to use for load package code:
#'   * "none", the default, doesn't load the package.
#'   * "installed", uses [library()] to load an installed package.
#'   * "source", uses [pkgload::load_all()] to a source package.
#'     To configure the arguments passed to `load_all()`, add this
#'     field in your DESCRIPTION file:
#'
#'     ```
#'     Config/testthat/load-all: list(export_all = FALSE, helpers = FALSE)
#'     ```
#' @param wrap DEPRECATED
#' @return A list (invisibly) containing data about the test results.
#' @inheritParams with_reporter
#' @inheritParams source_file
#' @export
test_dir <- function(path,
                     filter = NULL,
                     reporter = NULL,
                     env = NULL,
                     ...,
                     load_helpers = TRUE,
                     stop_on_failure = TRUE,
                     stop_on_warning = FALSE,
                     wrap = lifecycle::deprecated(),
                     package = NULL,
                     load_package = c("none", "installed", "source")
                     ) {

  load_package <- arg_match(load_package)

  start_first <- find_test_start_first(path, load_package, package)
  test_paths <- find_test_scripts(
    path,
    filter = filter,
    ...,
    full.names = FALSE,
    start_first = start_first
  )
  if (length(test_paths) == 0) {
    abort("No test files found")
  }

  if (!is_missing(wrap)) {
    lifecycle::deprecate_stop("3.0.0", "test_dir(wrap = )")
  }

  want_parallel <- find_parallel(path, load_package, package)

  if (is.null(reporter)) {
    if (want_parallel) {
      reporter <- default_parallel_reporter()
    } else {
      reporter <- default_reporter()
    }
  }
  reporter <- find_reporter(reporter)
  parallel <- want_parallel && reporter$capabilities$parallel_support

  test_files(
    test_dir = path,
    test_paths = test_paths,
    test_package = package,
    reporter = reporter,
    load_helpers = load_helpers,
    env = env,
    stop_on_failure = stop_on_failure,
    stop_on_warning = stop_on_warning,
    load_package = load_package,
    parallel = parallel
  )
}

#' Run tests in a single file
#'
#' Helper, setup, and teardown files located in the same directory as the
#' test will also be run. See `vignette("special-files")` for details.
#'
#' @inherit test_dir return params
#' @inheritSection test_dir Environments
#' @param path Path to file.
#' @param ... Additional parameters passed on to `test_dir()`
#' @param desc Optionally, supply a string here to run only a single
#'   test (`test_that()` or `describe()`) with this `desc`ription.
#' @export
#' @examples
#' path <- testthat_example("success")
#' test_file(path)
#' test_file(path, desc = "some tests have warnings")
#' test_file(path, reporter = "minimal")
test_file <- function(path,
                      reporter = default_compact_reporter(),
                      desc = NULL,
                      package = NULL,
                      ...) {
  if (!file.exists(path)) {
    stop("`path` does not exist", call. = FALSE)
  }

  test_files(
    test_dir = dirname(path),
    test_package = package,
    test_paths = basename(path),
    reporter = reporter,
    desc = desc,
    ...
  )
}

test_files <- function(test_dir,
                       test_package,
                       test_paths,
                       load_helpers = TRUE,
                       reporter = default_reporter(),
                       env = NULL,
                       stop_on_failure = FALSE,
                       stop_on_warning = FALSE,
                       desc = NULL,
                       wrap = TRUE,
                       load_package = c("none", "installed", "source"),
                       parallel = FALSE,
                       error_call = caller_env()) {

  if (!isTRUE(wrap)) {
    lifecycle::deprecate_stop("3.0.0", "test_dir(wrap = )")
  }

  # Must keep these two blocks in sync
  if (parallel) {
    test_files_parallel(
      test_dir = test_dir,
      test_package = test_package,
      test_paths = test_paths,
      load_helpers = load_helpers,
      reporter = reporter,
      env = env,
      stop_on_failure = stop_on_failure,
      stop_on_warning = stop_on_warning,
      load_package = load_package
    )
  } else {
    test_files_serial(
      test_dir = test_dir,
      test_package = test_package,
      test_paths = test_paths,
      load_helpers = load_helpers,
      reporter = reporter,
      env = env,
      stop_on_failure = stop_on_failure,
      stop_on_warning = stop_on_warning,
      desc = desc,
      load_package = load_package,
      error_call = error_call
    )
  }

}

test_files_serial <- function(test_dir,
                       test_package,
                       test_paths,
                       load_helpers = TRUE,
                       reporter = default_reporter(),
                       env = NULL,
                       stop_on_failure = FALSE,
                       stop_on_warning = FALSE,
                       desc = NULL,
                       wrap = TRUE,
                       load_package = c("none", "installed", "source"),
                       error_call = caller_env()) {

  # Because load_all() called by test_files_setup_env() will have already
  # loaded them. We don't want to rely on testthat's loading since that
  # only affects the test environment and we want to keep the helpers
  # loaded in the user's session.
  load_package <- arg_match(load_package)
  if (load_package == "source") {
    load_helpers <- FALSE
  }

  env <- test_files_setup_env(test_package, test_dir, load_package, env)
  # record testing env for mocks
  local_testing_env(env)

  test_files_setup_state(test_dir, test_package, load_helpers, env)
  reporters <- test_files_reporter(reporter)

  with_reporter(reporters$multi,
    lapply(
      test_paths,
      test_one_file,
      env = env,
      desc = desc,
      error_call = error_call
    )
  )

  test_files_check(reporters$list$get_results(),
    stop_on_failure = stop_on_failure,
    stop_on_warning = stop_on_warning
  )
}

test_files_setup_env <- function(test_package,
                                 test_dir,
                                 load_package = c("none", "installed", "source"),
                                 env = NULL) {
  library(testthat)

  load_package <- arg_match(load_package)
  if (load_package == "installed") {
    library(test_package, character.only = TRUE)
  } else if (load_package == "source") {
    # Allow configuring what we export to the search path (#1636)
    args <- find_load_all_args(test_dir)
    pkgload::load_all(
      test_dir,
      export_all = args[["export_all"]],
      helpers = args[["helpers"]],
      quiet = TRUE
    )
  }

  env %||% test_env(test_package)
}

find_load_all_args <- function(path) {
  default <- list(export_all = TRUE, helpers = TRUE)

  desc <- find_description(path)
  if (is.null(desc)) {
    return(default)
  }

  args <- desc$get_field("Config/testthat/load-all", default = NULL)
  if (is.null(args)) {
    return(default)
  }

  args <- parse_expr(args)
  if (!is_call(args, "list")) {
    abort("`Config/testthat/load-all` must be a list.", call = NULL)
  }

  args <- as.list(args[-1])
  list(
    export_all = args[["export_all"]] %||% default[["export_all"]],
    helpers = args[["helpers"]] %||% default[["helpers"]]
  )
}

test_files_setup_state <- function(
    test_dir,
    test_package,
    load_helpers,
    env,
    frame = parent.frame()
) {
  # Define testing environment
  local_test_directory(test_dir, test_package, .env = frame)
  withr::local_options(
    topLevelEnvironment = env_parent(env),
    .local_envir = frame
  )

  # Load helpers, setup, and teardown (on exit)
  local_teardown_env(frame)
  if (load_helpers) {
    source_test_helpers(".", env)
  }
  source_test_setup(".", env)
  withr::defer(source_test_teardown(".", env), frame)      # old school
}

test_files_reporter <- function(reporter, .env = parent.frame()) {
  lister <- ListReporter$new()
  reporters <- list(
    find_reporter(reporter),
    lister, # track data
    local_snapshotter("_snaps", fail_on_new = FALSE, .env = .env)
  )
  list(
    multi = MultiReporter$new(reporters = compact(reporters)),
    list = lister
  )
}

test_files_check <- function(results, stop_on_failure = TRUE, stop_on_warning = FALSE) {
  if (stop_on_failure && !all_passed(results)) {
    stop("Test failures", call. = FALSE)
  }
  if (stop_on_warning && any_warnings(results)) {
    stop("Tests generated warnings", call. = FALSE)
  }

  invisible(results)
}

test_one_file <- function(path,
                          env = test_env(),
                          desc = NULL,
                          error_call = caller_env()) {
  reporter <- get_reporter()
  on.exit(teardown_run(), add = TRUE)

  reporter$start_file(path)
  source_file(
    path,
    env = env(env),
    desc = desc,
    error_call = error_call
  )
  reporter$end_context_if_started()
  reporter$end_file()
}

# Helpers -----------------------------------------------------------------

#' Run code after all test files
#'
#' This environment has no purpose other than as a handle for [withr::defer()]:
#' use it when you want to run code after all tests have been run.
#' Typically, you'll use `withr::defer(cleanup(), teardown_env())`
#' immediately after you've made a mess in a `setup-*.R` file.
#'
#' @export
teardown_env <- function() {
  if (is.null(the$teardown_env)) {
    abort("`teardown_env()` has not been initialized", .internal = TRUE)
  }

  the$teardown_env
}

local_teardown_env <- function(frame = parent.frame()) {
  local_bindings(teardown_env = frame, .env = the, .frame = frame)
}

#' Find test files
#'
#' @param path path to tests
#' @param invert If `TRUE` return files which **don't** match.
#' @param ... Additional arguments passed to [grepl()] to control filtering.
#' @param start_first A character vector of file patterns (globs, see
#'   [utils::glob2rx()]). The patterns are for the file names (base names),
#'   not for the whole paths. testthat starts the files matching the
#'   first pattern first,  then the ones matching the second, etc. and then
#'   the rest of the files, alphabetically. Parallel tests tend to finish
#'   quicker if you start the slowest files first. `NULL` means alphabetical
#'   order.
#' @inheritParams test_dir
#' @return A character vector of paths
#' @keywords internal
#' @export
find_test_scripts <- function(path, filter = NULL, invert = FALSE, ..., full.names = TRUE, start_first = NULL) {
  files <- dir(path, "^test.*\\.[rR]$", full.names = full.names)
  files <- filter_test_scripts(files, filter, invert, ...)
  order_test_scripts(files, start_first)
}

filter_test_scripts <- function(files, filter = NULL, invert = FALSE, ...) {
  if (is.null(filter)) {
    return(files)
  }

  which_files <- grepl(filter, context_name(files), ...)
  if (isTRUE(invert)) {
    which_files <- !which_files
  }
  files[which_files]
}

find_test_start_first <- function(path, load_package, package) {
  # Make sure we get the local package package if not "installed"
  if (load_package != "installed") package <- NULL
  desc <- find_description(path, package)
  if (is.null(desc)) {
    return(NULL)
  }

  conf <- desc$get_field("Config/testthat/start-first", NULL)
  if (is.null(conf)) {
    return(NULL)
  }

  trimws(strsplit(conf, ",")[[1]])
}

order_test_scripts <- function(paths, start_first) {
  if (is.null(start_first)) return(paths)
  filemap <- data.frame(
    stringsAsFactors = FALSE,
    base = sub("\\.[rR]$", "", sub("^test[-_\\.]?", "", basename(paths))),
    orig = paths
  )
  rxs <- utils::glob2rx(start_first)
  mch <- lapply(rxs, function(rx) filemap$orig[grep(rx, filemap$base)])
  unique(c(unlist(mch), paths))
}