r-lib / covr

@@ -15,6 +15,66 @@
Loading
15 15
#' # If run with no arguments `report()` implicitly calls `package_coverage()`
16 16
#' report()
17 17
#' ```
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 +
#'
18 78
"_PACKAGE"
19 79
20 80
#' @import methods
@@ -47,12 +107,36 @@
Loading
47 107
  saveRDS(.counters, file = tmp_file)
48 108
}
49 109
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 +
50 130
#' Calculate test coverage for a specific function.
51 131
#'
52 132
#' @param fun name of the function.
53 133
#' @param code expressions to run.
54 134
#' @param env environment the function is defined in.
55 135
#' @param enc the enclosing environment which to run the expressions.
136 +
#' @examples
137 +
#' add <- function(x, y) { x + y }
138 +
#' function_coverage(fun = add, code = NULL) # 0% coverage
139 +
#' function_coverage(fun = add, code = add(1, 2) == 3) # 100% coverage
56 140
#' @export
57 141
function_coverage <- function(fun, code = NULL, env = NULL, enc = parent.frame()) {
58 142
  if (is.function(fun)) {
@@ -81,7 +165,7 @@
Loading
81 165
    eval(code, enc)
82 166
  )
83 167
84 -
  structure(as.list(.counters), class = "coverage")
168 +
  as_coverage(as.list(.counters))
85 169
}
86 170
87 171
#' Calculate test coverage for sets of files
@@ -95,6 +179,12 @@
Loading
95 179
#'   functions
96 180
#' @param parent_env The parent environment to use when sourcing the files.
97 181
#' @inheritParams package_coverage
182 +
#' @examples
183 +
#' # For the purpose of this example, save code containing code and tests to files
184 +
#' cat("add <- function(x, y) { x + y }", file="add.R")
185 +
#' cat("add(1, 2) == 3", file="add_test.R")
186 +
#' # Use file_coverage() to calculate test coverage
187 +
#' file_coverage(source_files = "add.R", test_files = "add_test.R")
98 188
#' @export
99 189
file_coverage <- function(
100 190
  source_files,
@@ -121,7 +211,7 @@
Loading
121 211
      sys.source, keep.source = TRUE, envir = env)
122 212
  )
123 213
124 -
  coverage <- structure(as.list(.counters), class = "coverage")
214 +
  coverage <- as_coverage(.counters)
125 215
126 216
  exclude(coverage,
127 217
    line_exclusions = line_exclusions,
@@ -138,6 +228,10 @@
Loading
138 228
#' @param test_code A character vector of test code
139 229
#' @inheritParams file_coverage
140 230
#' @param ... Additional arguments passed to [file_coverage()]
231 +
#' @examples
232 +
#' source <- "add <- function(x, y) { x + y }"
233 +
#' test <- "add(1, 2) == 3"
234 +
#' code_coverage(source, test)
141 235
#' @export
142 236
code_coverage <- function(
143 237
   source_code,
@@ -178,7 +272,7 @@
Loading
178 272
      sys.source, keep.source = TRUE, envir = exec_env)
179 273
  )
180 274
181 -
  coverage <- structure(as.list(.counters), class = "coverage")
275 +
  coverage <- as_coverage(.counters)
182 276
183 277
  exclude(coverage,
184 278
    line_exclusions = line_exclusions,
@@ -331,6 +425,11 @@
Loading
331 425
332 426
  libs <- env_path(install_path, .libPaths())
333 427
428 +
  # We need to set the libpaths in the current R session for examples with
429 +
  # install or runtime Sexpr blocks, which may implicitly load the package in
430 +
  # the current R session.
431 +
  withr::with_libpaths(install_path, action = "prefix", {
432 +
334 433
  withr::with_envvar(
335 434
    c(R_DEFAULT_PACKAGES = "datasets,utils,grDevices,graphics,stats,methods",
336 435
      R_LIBS = libs,
@@ -372,6 +471,7 @@
Loading
372 471
    message = function(e) if (quiet) invokeRestart("muffleMessage") else e,
373 472
    warning = function(e) if (quiet) invokeRestart("muffleWarning") else e)
374 473
    })
474 +
    })
375 475
376 476
  # read tracing files
377 477
  trace_files <- list.files(path = install_path, pattern = "^covr_trace_[^/]+$", full.names = TRUE)
@@ -382,10 +482,11 @@
Loading
382 482
    res <- run_icov(pkg$path, quiet = quiet)
383 483
  }
384 484
385 -
  coverage <- structure(c(coverage, res),
386 -
      class = "coverage",
387 -
      package = pkg,
388 -
      relative = relative_path)
485 +
  coverage <- as_coverage(
486 +
    c(coverage, res),
487 +
    package = pkg,
488 +
    relative = relative_path
489 +
  )
389 490
390 491
  if (!clean) {
391 492
    attr(coverage, "library") <- install_path
@@ -454,29 +555,46 @@
Loading
454 555
# merge multiple coverage files together. Assumes the order of coverage lines
455 556
# is the same in each object, this should always be the case if the objects are
456 557
# from the same initial library.
457 -
merge_coverage <- function(files) {
458 -
  nfiles <- length(files)
459 -
  if (nfiles == 0) {
460 -
    return()
461 -
  }
558 +
merge_coverage <- function(x) {
559 +
  UseMethod("merge_coverage")
560 +
}
561 +
562 +
merge_coverage.character <- function(files) {
563 +
  coverage_objs <- lapply(files, function(f) {
564 +
    as.list(suppressWarnings(readRDS(f)))
565 +
  })
566 +
  merge_coverage(coverage_objs)
567 +
}
462 568
463 -
  x <- suppressWarnings(readRDS(files[1]))
464 -
  x <- as.list(x)
465 -
  if (nfiles == 1) {
466 -
    return(x)
569 +
merge_coverage.list <- function(coverage_objs) {
570 +
  if (length(coverage_objs) == 0) {
571 +
    return()
467 572
  }
468 573
574 +
  x <- coverage_objs[[1]]
469 575
  names <- names(x)
470 -
  for (i in 2:nfiles) {
471 -
    y <- suppressWarnings(readRDS(files[i]))
576 +
577 +
  for (y in tail(coverage_objs, -1L)) {
578 +
    # align tests from coverage objects
579 +
    test_idx <- match(names(y$tests), Filter(nchar, names(x$tests)))
580 +
    new_test_idx <- if (!length(test_idx)) seq_along(y$tests) else which(is.na(test_idx))
581 +
    test_idx[new_test_idx] <- length(x$tests) + seq_along(new_test_idx)
582 +
583 +
    # append any tests that we haven't encountered in previous objects
584 +
    x$tests <- append(x$tests, y$tests[new_test_idx])
585 +
    y$tests <- NULL
586 +
472 587
    for (name in intersect(names, names(y))) {
473 588
      x[[name]]$value <- x[[name]]$value + y[[name]]$value
589 +
      y[[name]]$tests[,1] <- test_idx[y[[name]]$tests[,1]]
590 +
      x[[name]]$tests <- rbind(x[[name]]$tests, y[[name]]$tests)
474 591
    }
592 +
475 593
    for (name in setdiff(names(y), names)) {
476 594
      x[[name]] <- y[[name]]
477 595
    }
596 +
478 597
    names <- union(names, names(y))
479 -
    y <- NULL
480 598
  }
481 599
482 600
  x
@@ -550,13 +668,16 @@
Loading
550 668
# @param pkg_name name of the package to add hooks to
551 669
# @param lib the library path to look in
552 670
# @param fix_mcexit whether to add the fix for mcparallel:::mcexit
553 -
add_hooks <- function(pkg_name, lib, fix_mcexit = FALSE) {
671 +
add_hooks <- function(pkg_name, lib, fix_mcexit = FALSE,
672 +
  record_tests = isTRUE(getOption("covr.record_tests", FALSE))) {
673 +
554 674
  trace_dir <- paste0("Sys.getenv(\"COVERAGE_DIR\", \"", lib, "\")")
555 675
556 676
  load_script <- file.path(lib, pkg_name, "R", pkg_name)
557 677
  lines <- readLines(file.path(lib, pkg_name, "R", pkg_name))
558 678
  lines <- append(lines,
559 -
    c("setHook(packageEvent(pkg, \"onLoad\"), function(...) covr:::trace_environment(ns))",
679 +
    c(paste0("setHook(packageEvent(pkg, \"onLoad\"), function(...) options(covr.record_tests = ", record_tests, "))"),
680 +
      "setHook(packageEvent(pkg, \"onLoad\"), function(...) covr:::trace_environment(ns))",
560 681
      paste0("reg.finalizer(ns, function(...) { covr:::save_trace(", trace_dir, ") }, onexit = TRUE)")),
561 682
    length(lines) - 1L)
562 683

@@ -101,6 +101,7 @@
Loading
101 101
}
102 102
103 103
.counters <- new.env(parent = emptyenv())
104 +
.current_test <- new.env(parent = emptyenv())
104 105
105 106
#' initialize a new counter
106 107
#'
@@ -112,6 +113,15 @@
Loading
112 113
  .counters[[key]]$value <- 0
113 114
  .counters[[key]]$srcref <- src_ref
114 115
  .counters[[key]]$functions <- parent_functions
116 +
117 +
  if (isTRUE(getOption("covr.record_tests", FALSE))) {
118 +
    .counters[[key]]$tests <- matrix(
119 +
      numeric(0L),
120 +
      ncol = 3L,
121 +
      # test index; call stack depth of covr:::count; execution order index
122 +
      dimnames = list(c(), c("test", "depth", "i")))
123 +
  }
124 +
115 125
  key
116 126
}
117 127
@@ -120,7 +130,8 @@
Loading
120 130
#' @param key generated with [key()]
121 131
#' @keywords internal
122 132
count <- function(key) {
123 -
  .counters[[key]]$value <- .counters[[key]]$value + 1
133 +
  .counters[[key]]$value <- .counters[[key]]$value + 1L
134 +
  if (isTRUE(getOption("covr.record_tests"))) count_test(key)
124 135
}
125 136
126 137
#' clear all previous counters
@@ -128,6 +139,7 @@
Loading
128 139
#' @keywords internal
129 140
clear_counters <- function() {
130 141
  rm(envir = .counters, list = ls(envir = .counters))
142 +
  rm(envir = .current_test, list = ls(envir = .current_test))
131 143
}
132 144
133 145
#' Generate a key for a  call

@@ -0,0 +1,231 @@
Loading
1 +
#' Record Test Traces During Coverage Execution
2 +
#'
3 +
#' By setting `options(covr.record_tests = TRUE)`, the result of covr coverage
4 +
#' collection functions will include additional data pertaining to the tests
5 +
#' which are executed and an index of which tests, at what stack depth, trigger
6 +
#' the execution of each trace.
7 +
#'
8 +
#' This functionality requires that the package code and tests are installed and
9 +
#' sourced with the source. For more details, refer to R options, `keep.source`,
10 +
#' `keep.source.pkgs` and `keep.parse.data.pkgs`.
11 +
#'
12 +
#' @section Additional fields:
13 +
#'
14 +
#' Within the `covr` result, you can explore this information in two places:
15 +
#'
16 +
#' \itemize{
17 +
#'   \item `attr(,"tests")`: A list of call stacks, which results in target code
18 +
#'     execution.
19 +
#'
20 +
#'   \item `$<srcref>$tests`: For each srcref count in the coverage object, a
21 +
#'     `$tests` field is now included which contains a matrix with three columns,
22 +
#'     "test", "depth" and "i" which specify the test number (corresponding to the
23 +
#'     index of the test in `attr(,"tests")`, the stack depth into the target
24 +
#'     code where the trace was executed, and the order of execution for each
25 +
#'     test.
26 +
#' }
27 +
#'
28 +
#' @section Test traces:
29 +
#'
30 +
#' The content of test traces are dependent on the unit testing framework that
31 +
#' is used by the target package. The behavior is contingent on the available
32 +
#' information in the sources kept for the testing files.
33 +
#'
34 +
#' Test traces are extracted by the following criteria:
35 +
#'
36 +
#' 1. If any `srcref` files are are provided by a file within [covr]'s temporary
37 +
#'    library, all calls from those files are kept as a test trace. This will
38 +
#'    collect traces from tests run with common testing frameworks such as
39 +
#'    `testthat` and `RUnit`.
40 +
#' 1. Otherwise, as a conservative fallback in situations where no source
41 +
#'    references are found, or when none are from within the temporary
42 +
#'    directory, the entire call stack is collected.
43 +
#'
44 +
#' These calls are subsequently subset for only those up until the call to
45 +
#' [covr]'s internal `count` function, and will always include the last call in
46 +
#' the call stack prior to a call to `count`. 
47 +
#'
48 +
#' @examples
49 +
#' fcode <- '
50 +
#' f <- function(x) {
51 +
#'   if (x)
52 +
#'     f(!x)
53 +
#'   else
54 +
#'     FALSE
55 +
#' }'
56 +
#'
57 +
#' options(covr.record_tests = TRUE)
58 +
#' cov <- code_coverage(fcode, "f(TRUE)")
59 +
#'
60 +
#' # extract executed test code for the first test
61 +
#' tail(attr(cov, "tests")[[1L]], 1L)
62 +
#' # [[1]]
63 +
#' # f(TRUE)
64 +
#'
65 +
#' # extract test itemization per trace
66 +
#' cov[[3]][c("srcref", "tests")]
67 +
#' # $srcref
68 +
#' # f(!x)
69 +
#' #
70 +
#' # $tests
71 +
#' #      test depth i
72 +
#' # [1,]    1     2 4
73 +
#'
74 +
#' # reconstruct the code path of a test by ordering test traces by [,"i"]
75 +
#' lapply(cov, `[[`, "tests")
76 +
#' # $`source.Ref2326138c55:4:6:4:10:6:10:4:4`
77 +
#' #      test depth i
78 +
#' # [1,]    1     1 2
79 +
#' # 
80 +
#' # $`source.Ref2326138c55:3:8:3:8:8:8:3:3`
81 +
#' #      test depth i
82 +
#' # [1,]    1     1 1
83 +
#' # [2,]    1     2 3
84 +
#' # 
85 +
#' # $`source.Ref2326138c55:6:6:6:10:6:10:6:6`
86 +
#' #      test depth i
87 +
#' # [1,]    1     2 4
88 +
#'
89 +
#' @name covr.record_tests
90 +
NULL
91 +
92 +
#' Append a test trace to a counter, updating global current test
93 +
#'
94 +
#' @param key generated with [key()]
95 +
#' @keywords internal
96 +
#'
97 +
count_test <- function(key) {
98 +
  n_calls_into_covr <- 2L
99 +
100 +
  if (is_current_test_finished())
101 +
    update_current_test(key)
102 +
103 +
  # ignore if .counter was not created with record_tests (nested coverage calls)
104 +
  if (is.null(.counters[[key]]$tests)) return()
105 +
106 +
  depth_into_pkg <- length(sys.calls()) - length(.current_test$frames) - n_calls_into_covr + 1L
107 +
  .current_test$i <- .current_test$i + 1L
108 +
  .counters[[key]]$tests <- rbind(
109 +
    .counters[[key]]$tests,
110 +
    c(length(.counters$tests), depth_into_pkg, .current_test$i)
111 +
  )
112 +
}
113 +
114 +
#' Update current test if unit test expression has progressed
115 +
#'
116 +
#' Updating a test logs some metadata regarding the current call stack, noteably
117 +
#' trying to capture information about the call stack prior to the covr::count
118 +
#' call being traced.
119 +
#'
120 +
#' There are a couple patterns of behavior, which try to accommodate a variety
121 +
#' of testing suites:
122 +
#'
123 +
#' \itemize{
124 +
#'   \item `testthat`: During execution of `testthat`'s `test_*` functions,
125 +
#'     files are sourced and the working directory is temporarily changed to the
126 +
#'     package `/tests` directory. Knowing this, calls in the call stack which
127 +
#'     are relative to this directory are extracted and recorded.
128 +
#'   \item `RUnit`:
129 +
#'   \item `custom`: Any other custom test suites may not have source kept with
130 +
#'     their execution, in which case the entire test call stack is kept.
131 +
#' }
132 +
#'
133 +
#' checks to see if the current call stack has the same
134 +
#' `srcref` (or expression, if no source is available) at the same frame prior
135 +
#' to entering into a package where `covr:::count` is called.
136 +
#'
137 +
#' @param key generated with [key()]
138 +
#' @keywords internal
139 +
#'
140 +
#' @importFrom utils getSrcDirectory
141 +
#'
142 +
update_current_test <- function(key) {
143 +
  syscalls <- sys.calls()
144 +
  syscall_first_count <- Position(is_covr_count_call, syscalls, nomatch = -1L)
145 +
  if (syscall_first_count < 2L) return()  # skip if nothing before covr::count
146 +
  syscall_srcfile <- vcapply(syscalls, get_source_filename, normalize = TRUE)
147 +
  
148 +
  has_srcfile <- viapply(syscall_srcfile, length) > 0L
149 +
  srcfile_tmp <- logical(length(has_srcfile))
150 +
  srcfile_tmp[has_srcfile] <- startsWith(syscall_srcfile[has_srcfile], normalizePath(.libPaths()[[1]]))
151 +
152 +
  test_frames <- if (any(srcfile_tmp)) {
153 +
    # if possible, try to take any frames within the temporary library
154 +
    which(srcfile_tmp)
155 +
  } else {
156 +
    # otherwise, default to taking all syscalls up until covr:::count
157 +
    seq_len(syscall_first_count - 1L)
158 +
  }
159 +
160 +
  # add in outer frame, which may call intermediate .Internal or .External
161 +
  exec_frames <- unique(c(test_frames, syscall_first_count - 1L))
162 +
163 +
  # build updated current test data, isolating relevant frames
164 +
  .current_test$trace <- syscalls[exec_frames]
165 +
  .current_test$i <- 0L
166 +
  .current_test$frames <- exec_frames
167 +
  .current_test$last_frame <- exec_frames[[Position(
168 +
    has_srcref,
169 +
    .current_test$trace,
170 +
    right = TRUE,
171 +
    nomatch = length(exec_frames))]]
172 +
173 +
  # might be NULL if srcrefs aren't kept during building / sourcing
174 +
  .current_test$src <- getSrcref(syscalls[[.current_test$last_frame]]) %||%
175 +
    syscalls[[.current_test$last_frame]]
176 +
177 +
  # build test data to store within .counters
178 +
  test <- list(.current_test$trace)
179 +
180 +
  # only name if srcrefs can be determined
181 +
  if (inherits(.current_test$src, "srcref")) {
182 +
    names(test) <- file.path(
183 +
      dirname(get_source_filename(.current_test$src, normalize = TRUE)),
184 +
      key(.current_test$src))
185 +
  }
186 +
187 +
  .counters$tests <- append(.counters$tests, test)
188 +
}
189 +
190 +
#' Returns TRUE if we've moved on from test reflected in .current_test
191 +
#'
192 +
#' Quickly dismiss the need to update the current test if we can. To test if
193 +
#' we're still in the last test, check if the same srcref (or call, if source is
194 +
#' not kept) exists at the last recorded calling frame prior to entering a covr
195 +
#' trace. If this has changed, do a more comprehensive test to see if any of the
196 +
#' test call stack has changed, in which case we are onto a new test.
197 +
#'
198 +
is_current_test_finished <- function() {
199 +
  syscalls <- sys.calls()
200 +
201 +
  is.null(.current_test$src) ||
202 +
  .current_test$last_frame > length(syscalls) ||
203 +
  !identical(
204 +
    .current_test$src,
205 +
    getSrcref(syscalls[[.current_test$last_frame]]) %||% syscalls[[.current_test$last_frame]]
206 +
  ) ||
207 +
  !identical(
208 +
    .current_test$trace,
209 +
    syscalls[.current_test$frames]
210 +
  )
211 +
}
212 +
213 +
#' Is the source bound to the expression
214 +
#'
215 +
#' @param expr A language object which may have a `srcref` attribute
216 +
#' @return A logical value indicating whether the language object has source
217 +
#'
218 +
has_srcref <- function(expr) {
219 +
  !is.null(getSrcref(expr))
220 +
}
221 +
222 +
#' Is the expression a call to covr:::count
223 +
#'
224 +
#' @param expr A language object
225 +
#' @return A logical value indicating whether the object is a call to
226 +
#'   `covr:::count`.
227 +
#'
228 +
is_covr_count_call <- function(expr) {
229 +
  count_call <- call(":::", as.symbol("covr"), as.symbol("count"))
230 +
  identical(expr[[1]], count_call)
231 +
}

@@ -103,32 +103,34 @@
Loading
103 103
  for (i in seq_along(x)) {
104 104
    src_file <- attr(x[[i]]$srcref, "srcfile")
105 105
    filename <- filenames[[i]]
106 -
    if (is.null(res[[filename]])) {
107 -
      lines <- getSrcLines(src_file, 1, Inf)
108 -
      matches <- rex::re_matches(lines,
109 -
        rex::rex(start, any_spaces, "#line", spaces,
110 -
          capture(name = "line_number", digit), spaces,
111 -
          quotes, capture(name = "filename", anything), quotes))
112 -
113 -
      matches <- na.omit(matches)
114 -
115 -
      filename_match <- which(matches$filename == src_file$filename)
116 -
117 -
      if (length(filename_match) == 1) {
118 -
        start <- as.numeric(rownames(matches)[filename_match]) + 1
119 -
        end <- if (!is.na(rownames(matches)[filename_match + 1])) {
120 -
          as.numeric(rownames(matches)[filename_match + 1]) - 1
121 -
        } else {
122 -
          length(lines)
123 -
        }
106 +
107 +
    if (filename == "") next
108 +
    if (!is.null(res[[filename]])) next
109 +
110 +
    lines <- getSrcLines(src_file, 1, Inf)
111 +
    matches <- rex::re_matches(lines,
112 +
      rex::rex(start, any_spaces, "#line", spaces,
113 +
        capture(name = "line_number", digit), spaces,
114 +
        quotes, capture(name = "filename", anything), quotes))
115 +
116 +
    matches <- na.omit(matches)
117 +
118 +
    filename_match <- which(matches$filename == src_file$filename)
119 +
120 +
    if (length(filename_match) == 1) {
121 +
      start <- as.numeric(rownames(matches)[filename_match]) + 1
122 +
      end <- if (!is.na(rownames(matches)[filename_match + 1])) {
123 +
        as.numeric(rownames(matches)[filename_match + 1]) - 1
124 124
      } else {
125 -
        start <- 1
126 -
        end <- length(lines)
125 +
        length(lines)
127 126
      }
128 -
      src_file$file_lines <- lines[seq(start, end)]
129 -
130 -
      res[[filename]] <- src_file
127 +
    } else {
128 +
      start <- 1
129 +
      end <- length(lines)
131 130
    }
131 +
    src_file$file_lines <- lines[seq(start, end)]
132 +
133 +
    res[[filename]] <- src_file
132 134
  }
133 135
  res
134 136
}
@@ -327,11 +329,14 @@
Loading
327 329
   attr(x, "package")$package %||% "coverage"
328 330
}
329 331
330 -
get_source_filename <- function(x, full.names = FALSE, unique = TRUE) {
332 +
get_source_filename <- function(x, full.names = FALSE, unique = TRUE, normalize = FALSE) {
331 333
  res <- getSrcFilename(x, full.names, unique)
332 334
  if (length(res) == 0) {
333 335
    return("")
334 336
  }
337 +
  if (normalize) {
338 +
    return(normalize_path(res))
339 +
  }
335 340
  res
336 341
}
337 342
Files Coverage
R 77.47%
src/reassign.c 100.00%
Project Totals (28 files) 77.66%
1
comment: false
2

3
coverage:
4
  status:
5
    project:
6
      default:
7
        target: auto
8
        threshold: 1%
9
        informational: true
10
    patch:
11
      default:
12
        target: auto
13
        threshold: 1%
14
        informational: true
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading