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

Feature Enhancement: Minimize duplicated recorded tests #562

Merged
merged 6 commits into from
Apr 7, 2024

Conversation

dgkf
Copy link
Contributor

@dgkf dgkf commented Mar 12, 2024

Supersedes #530, Closes #528

The previous PR never got picked up and the repo has moved forward since then. This PR just rebases the changes onto the latest work in covr. Copying the original description below:


Refer to #528 for a detailed description of the issue, but in short the heuristic that was used for quickly determining whether we're evaluating a new test ended up being too specific, resulting in the same test being logged many times.

Generally this isn't an issue. Even in situations where a test suite might loop over a test, the heuristic would usually treat those as the same test. Even if it was logged with each iteration, those iteration counts are usually low enough that it doesn't impact the coverage object substantially. However, when testing fastmap, there were a couple cases where an expectation is made in a loop with 10s of thousands of iterations and logging the test with each evaluation, blowing up the coverage object memory needs.

To emphasize, these changes will only affect behaviors when options(covr.record_tests = TRUE), which must be opted into.

Technical details annotated as in-line comments below:

Comment on lines -71 to +73
#' # test depth i
#' # [1,] 1 2 4
#' # test call depth i
#' # [1,] 1 1 2 4
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A change to the structure of the <coverage>[[<key>]]$tests elements.

Adds a call column, which is the number of times the test expression was evaluated before hitting this trace. It's not used within covr, but since the same test now represents multiple calls it is useful for distinguishing test evaluations useful for downstream tooling.

Comment on lines -219 to +224
.current_test$src_env <- sys.frame(which = .current_test$last_frame)
.current_test$src_env <- sys.frame(which = .current_test$last_frame - 1L)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the calling frame instead of the evaluation frame is one way that this PR cuts down on unnecessary duplication of tests.

Comment on lines +279 to +297
current_test_index <- function() {
# check if test has already been encountered and reuse test index
if (inherits(.current_test$src, "srcref")) {
# when tests have srcrefs, we can quickly compare test keys
match(
.current_test$key,
names(.counters$tests),
nomatch = length(.counters$tests) + 1L
)
} else {
# otherwise we compare call stacks
Position(
function(t) identical(t[], .current_test$trace), # t[] to ignore attr
.counters$tests,
right = TRUE,
nomatch = length(.counters$tests) + 1L
)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the current test matches an already logged test, then we reuse that index. Otherwise it's added to a growing list of recorded test expressions.

New tests are distinguished by comparing the srcref "key" to any previous test keys if the test code has known srcrefs, or otherwise looking for identical call stacks.

I was expecting call stack comparisons like this to come with a big performance hit, but it ended up being pretty minor.

@dgkf
Copy link
Contributor Author

dgkf commented Apr 4, 2024

@jimhester - Any chance I could get your eyes on this?

We've been using this patch for a year and have tested against ~1000 packages at this point without the memory issues we were seeing before.

Briefly, this patch further minimizes duplicated recording of test call stacks. In extreme cases (namely fastmap & rlang, which have tests that hit the same line of code millions of times), this can easily save GBs of memory.

@jimhester
Copy link
Member

Thanks @dgkf, sorry for the delay in merging!

@jimhester jimhester merged commit dd5286d into r-lib:main Apr 7, 2024
12 checks passed
@dgkf
Copy link
Contributor Author

dgkf commented Apr 7, 2024

Thanks, @jimhester! (and no worries - I would have nudged more frequently if it was a major blocker 😉). Thanks as always for covr's maintenance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

options(covr.record_tests = TRUE): tests being captured multiple times in for loops
2 participants