Skip to content

Commit 00c6de0

Browse files
authored
Merge pull request #463 from dgkf/dev/linked_testrefs
2 parents eb9beda + 356d927 commit 00c6de0

23 files changed

+798
-24
lines changed

DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Encoding: UTF-8
22
Package: covr
33
Title: Test Coverage for Packages
4-
Version: 3.5.1.9000
4+
Version: 3.5.1.9001
55
Authors@R: c(
66
person("Jim", "Hester", email = "[email protected]", role = c("aut", "cre")),
77
person("Willem", "Ligtenberg", role = "ctb"),
@@ -25,6 +25,7 @@ Authors@R: c(
2525
person("Chris", "Campbell", role = "ctb"),
2626
person("David", "Hugh-Jones", role = "ctb"),
2727
person("Qin", "Wang", role = "ctb"),
28+
person("Doug", "Kelkhoff", role = "ctb"),
2829
person("Ivan", "Sagalaev", role = c("ctb", "cph"), comment = "highlight.js library"),
2930
person("Mark", "Otto", role = "ctb", comment = "Bootstrap library"),
3031
person("Jacob", "Thornton", role = "ctb", comment = "Bootstrap library"),

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ importFrom(stats,na.pass)
3636
importFrom(stats,setNames)
3737
importFrom(utils,capture.output)
3838
importFrom(utils,getParseData)
39+
importFrom(utils,getSrcDirectory)
3940
importFrom(utils,getSrcFilename)
4041
importFrom(utils,getSrcref)
4142
importFrom(utils,head)

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# covr (development version)
22

3+
* Added `covr.record_tests` option. When `TRUE`, this enables the recording of
4+
the trace of the tests being executed and adds an itemization of which tests
5+
result in the exeuction of each trace. For more details see
6+
`?covr.record_tests`
7+
`?covr.record_tests` (@dgkf, #463)
8+
39
* `package_coverage()` now sets the environment variable `R_TESTS` to the tests-startup.R file like R CMD check does (#420)
410

511
* `codecov()` is now more robust when `coverage` is not the output from `package_coverage()` and `token` is not provided (#456)

R/covr.R

Lines changed: 122 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,66 @@
1515
#' # If run with no arguments `report()` implicitly calls `package_coverage()`
1616
#' report()
1717
#' ```
18+
#'
19+
#' @section Package options:
20+
#'
21+
#' `covr` uses the following [options()] to configure behaviour:
22+
#'
23+
#' \itemize{
24+
#' \item `covr.covrignore`: A filename to use as an ignore file,
25+
#' listing glob-style wildcarded paths of files to ignore for coverage
26+
#' calculations. Defaults to the value of environment variable
27+
#' `COVR_COVRIGNORE`, or `".covrignore"` if the neither the option nor the
28+
#' environment variable are set.
29+
#'
30+
#' \item `covr.exclude_end`: Used along with `covr.exclude_start`, an optional
31+
#' regular expression which ends a line-exclusion region. For more
32+
#' details, see `?exclusions`.
33+
#'
34+
#' \item `covr.exclude_pattern`: An optional line-exclusion pattern. Lines
35+
#' which match the pattern will be excluded from coverage. For more details,
36+
#' see `?exclusions`.
37+
#'
38+
#' \item `covr.exclude_start`: Used along with `covr.exclude_end`, an optional
39+
#' regular expression which starts a line-exclusion region. For more
40+
#' details, see `?exclusions`.
41+
#'
42+
#' \item `covr.filter_non_package`: If `TRUE` (the default behavior), coverage
43+
#' of files outside the target package are filtered from coverage output.
44+
#'
45+
#' \item `covr.fix_parallel_mcexit`:
46+
#'
47+
#' \item `covr.flags`:
48+
#'
49+
#' \item `covr.gcov`: If the appropriate gcov version is not on your path you
50+
#' can use this option to set the appropriate location. If set to "" it will
51+
#' turn off coverage of compiled code.
52+
#'
53+
#' \item `covr.gcov_additional_paths`:
54+
#'
55+
#' \item `covr.gcov_args`:
56+
#'
57+
#' \item `covr.icov`:
58+
#'
59+
#' \item `covr.icov_args`:
60+
#'
61+
#' \item `covr.icov_flags`:
62+
#'
63+
#' \item `covr.icov_prof`:
64+
#'
65+
#' \item `covr.rstudio_source_markers`: A logical value. If `TRUE` (the
66+
#' default behavior), source markers are displayed within the RStudio IDE
67+
#' when using `zero_coverage`.
68+
#'
69+
#' \item `covr.record_tests`: If `TRUE` (default `NULL`), record a listing of
70+
#' top level test expressions and associate tests with `covr` traces
71+
#' evaluated during the test's execution. For more details, see
72+
#' `?covr.record_tests`.
73+
#'
74+
#' \item `covr.showCfunctions`:
75+
#' }
76+
#'
77+
#'
1878
"_PACKAGE"
1979

2080
#' @import methods
@@ -47,6 +107,26 @@ save_trace <- function(directory) {
47107
saveRDS(.counters, file = tmp_file)
48108
}
49109

110+
#' Convert a counters object to a coverage object
111+
#'
112+
#' @param counters An environment of covr trace results to convert to a coverage
113+
#' object. If `counters` is not provided, the `covr` namespace value
114+
#' `.counters` is used.
115+
#' @param ... Additional attributes to include with the coverage object.
116+
#'
117+
as_coverage <- function(counters = NULL, ...) {
118+
if (missing(counters))
119+
counters <- .counters
120+
121+
counters <- as.list(counters)
122+
123+
# extract optional tests
124+
tests <- counters$tests
125+
counters$tests <- NULL
126+
127+
structure(counters, tests = tests, ..., class = "coverage")
128+
}
129+
50130
#' Calculate test coverage for a specific function.
51131
#'
52132
#' @param fun name of the function.
@@ -81,7 +161,7 @@ function_coverage <- function(fun, code = NULL, env = NULL, enc = parent.frame()
81161
eval(code, enc)
82162
)
83163

84-
structure(as.list(.counters), class = "coverage")
164+
as_coverage(as.list(.counters))
85165
}
86166

87167
#' Calculate test coverage for sets of files
@@ -121,7 +201,7 @@ file_coverage <- function(
121201
sys.source, keep.source = TRUE, envir = env)
122202
)
123203

124-
coverage <- structure(as.list(.counters), class = "coverage")
204+
coverage <- as_coverage(.counters)
125205

126206
exclude(coverage,
127207
line_exclusions = line_exclusions,
@@ -178,7 +258,7 @@ environment_coverage <- function(
178258
sys.source, keep.source = TRUE, envir = exec_env)
179259
)
180260

181-
coverage <- structure(as.list(.counters), class = "coverage")
261+
coverage <- as_coverage(.counters)
182262

183263
exclude(coverage,
184264
line_exclusions = line_exclusions,
@@ -382,10 +462,11 @@ package_coverage <- function(path = ".",
382462
res <- run_icov(pkg$path, quiet = quiet)
383463
}
384464

385-
coverage <- structure(c(coverage, res),
386-
class = "coverage",
387-
package = pkg,
388-
relative = relative_path)
465+
coverage <- as_coverage(
466+
c(coverage, res),
467+
package = pkg,
468+
relative = relative_path
469+
)
389470

390471
if (!clean) {
391472
attr(coverage, "library") <- install_path
@@ -454,29 +535,46 @@ show_failures <- function(dir) {
454535
# merge multiple coverage files together. Assumes the order of coverage lines
455536
# is the same in each object, this should always be the case if the objects are
456537
# from the same initial library.
457-
merge_coverage <- function(files) {
458-
nfiles <- length(files)
459-
if (nfiles == 0) {
460-
return()
461-
}
538+
merge_coverage <- function(x) {
539+
UseMethod("merge_coverage")
540+
}
462541

463-
x <- suppressWarnings(readRDS(files[1]))
464-
x <- as.list(x)
465-
if (nfiles == 1) {
466-
return(x)
542+
merge_coverage.character <- function(files) {
543+
coverage_objs <- lapply(files, function(f) {
544+
as.list(suppressWarnings(readRDS(f)))
545+
})
546+
merge_coverage(coverage_objs)
547+
}
548+
549+
merge_coverage.list <- function(coverage_objs) {
550+
if (length(coverage_objs) == 0) {
551+
return()
467552
}
468553

554+
x <- coverage_objs[[1]]
469555
names <- names(x)
470-
for (i in 2:nfiles) {
471-
y <- suppressWarnings(readRDS(files[i]))
556+
557+
for (y in tail(coverage_objs, -1L)) {
558+
# align tests from coverage objects
559+
test_idx <- match(names(y$tests), Filter(nchar, names(x$tests)))
560+
new_test_idx <- if (!length(test_idx)) seq_along(y$tests) else which(is.na(test_idx))
561+
test_idx[new_test_idx] <- length(x$tests) + seq_along(new_test_idx)
562+
563+
# append any tests that we haven't encountered in previous objects
564+
x$tests <- append(x$tests, y$tests[new_test_idx])
565+
y$tests <- NULL
566+
472567
for (name in intersect(names, names(y))) {
473568
x[[name]]$value <- x[[name]]$value + y[[name]]$value
569+
y[[name]]$tests[,1] <- test_idx[y[[name]]$tests[,1]]
570+
x[[name]]$tests <- rbind(x[[name]]$tests, y[[name]]$tests)
474571
}
572+
475573
for (name in setdiff(names(y), names)) {
476574
x[[name]] <- y[[name]]
477575
}
576+
478577
names <- union(names, names(y))
479-
y <- NULL
480578
}
481579

482580
x
@@ -550,13 +648,16 @@ run_commands <- function(pkg, lib, commands) {
550648
# @param pkg_name name of the package to add hooks to
551649
# @param lib the library path to look in
552650
# @param fix_mcexit whether to add the fix for mcparallel:::mcexit
553-
add_hooks <- function(pkg_name, lib, fix_mcexit = FALSE) {
651+
add_hooks <- function(pkg_name, lib, fix_mcexit = FALSE,
652+
record_tests = isTRUE(getOption("covr.record_tests", FALSE))) {
653+
554654
trace_dir <- paste0("Sys.getenv(\"COVERAGE_DIR\", \"", lib, "\")")
555655

556656
load_script <- file.path(lib, pkg_name, "R", pkg_name)
557657
lines <- readLines(file.path(lib, pkg_name, "R", pkg_name))
558658
lines <- append(lines,
559-
c("setHook(packageEvent(pkg, \"onLoad\"), function(...) covr:::trace_environment(ns))",
659+
c(paste0("setHook(packageEvent(pkg, \"onLoad\"), function(...) options(covr.record_tests = ", record_tests, "))"),
660+
"setHook(packageEvent(pkg, \"onLoad\"), function(...) covr:::trace_environment(ns))",
560661
paste0("reg.finalizer(ns, function(...) { covr:::save_trace(", trace_dir, ") }, onexit = TRUE)")),
561662
length(lines) - 1L)
562663

R/trace_calls.R

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ trace_calls <- function (x, parent_functions = NULL, parent_ref = NULL) {
101101
}
102102

103103
.counters <- new.env(parent = emptyenv())
104+
.current_test <- new.env(parent = emptyenv())
104105

105106
#' initialize a new counter
106107
#'
@@ -112,6 +113,14 @@ new_counter <- function(src_ref, parent_functions) {
112113
.counters[[key]]$value <- 0
113114
.counters[[key]]$srcref <- src_ref
114115
.counters[[key]]$functions <- parent_functions
116+
117+
if (isTRUE(getOption("covr.record_tests", FALSE))) {
118+
.counters[[key]]$tests <- matrix(
119+
numeric(0L),
120+
ncol = 2,
121+
dimnames = list(c(), c("test", "depth")))
122+
}
123+
115124
key
116125
}
117126

@@ -120,14 +129,16 @@ new_counter <- function(src_ref, parent_functions) {
120129
#' @param key generated with [key()]
121130
#' @keywords internal
122131
count <- function(key) {
123-
.counters[[key]]$value <- .counters[[key]]$value + 1
132+
.counters[[key]]$value <- .counters[[key]]$value + 1L
133+
if (isTRUE(getOption("covr.record_tests"))) count_test(key)
124134
}
125135

126136
#' clear all previous counters
127137
#'
128138
#' @keywords internal
129139
clear_counters <- function() {
130140
rm(envir = .counters, list = ls(envir = .counters))
141+
rm(envir = .current_test, list = ls(envir = .current_test))
131142
}
132143

133144
#' Generate a key for a call

0 commit comments

Comments
 (0)