Mutates all .R files under a package's R/ directory, runs the package's
tests against each mutant in parallel, and summarizes mutation outcomes.
Usage
mutate_package(
pkg_dir,
cores = max(1, parallel::detectCores() - 2),
isFullLog = FALSE,
detectEqMutants = FALSE,
mutation_dir = NULL,
max_mutants = NULL,
timeout_seconds = NULL,
config_dir = getwd(),
max_line_deletions = 5,
cran = TRUE
)Arguments
- pkg_dir
Path to the package directory.
- cores
Number of parallel workers used for mutant test execution.
- isFullLog
Logical; if
TRUE, prints per-mutant logs and timeout info.- detectEqMutants
Logical; if
TRUE, survived mutants are analyzed for equivalence using the OpenAI-based workflow.- mutation_dir
Optional directory to store generated mutant files. If
NULL, a temporary directory is used.- max_mutants
Optional cap on the number of mutants tested.
- timeout_seconds
Optional timeout in seconds for each mutant run. If
NULL, timeout is derived from baseline runtime with a small minimum floor. Each mutant's tests run in a separate subprocess, so the limit is enforced as a hard wall-clock kill even when a mutant loops inside compiled code (via callr for thetestthatstrategy andsystem2(timeout=)for the installed-tests strategy).- config_dir
Directory searched for a
.openai_configfile whendetectEqMutants = TRUE(seeget_openai_config()). Defaults to the current working directory.- max_line_deletions
Maximum number of line-deletion mutants per
.Rfile (passed tomutate_file());0disables them. Defaults to5.- cran
Logical; if
TRUE(the default), tests run in "CRAN mode": theNOT_CRANenvironment variable is set to"false"in the test subprocess sotestthat::skip_on_cran()/skip_if_offline()guards take effect and the same tests CRAN would run are used (skipping network/slow tests the package marks). Set toFALSEto run the full suite (NOT_CRAN = "true"), asdevtools::test()does. Note this only affects tests the package actually guards; unguarded network tests still run.
Value
An invisible list with three components:
package_mutantsNamed list with mutant path, mutation info, status, and optional equivalence flags.
test_resultsNamed list mapping mutant IDs to statuses:
"KILLED","SURVIVED", or"HANG".timingNamed list of phase durations in seconds:
baseline,generation,test_execution, andequivalence_detection.
Details
Test strategy is detected automatically:
If
tests/testthat/exists,testthat::test_dir()is used.Otherwise, if
tests/exists, mutator installs the mutant package with--install-testsand runstools::testInstalledPackage().
Examples
# Wrapped in \donttest{}: it loads and test-runs a throwaway package, which
# is too slow/heavy for routine automated checks.
# \donttest{
pkg <- file.path(tempdir(), "examplepkg")
dir.create(file.path(pkg, "R"), recursive = TRUE, showWarnings = FALSE)
dir.create(file.path(pkg, "tests", "testthat"), recursive = TRUE, showWarnings = FALSE)
writeLines(c(
"Package: examplepkg",
"Title: Example Package",
"Version: 0.0.1",
"Description: Minimal package for a mutator example.",
"License: GPL-3",
"Encoding: UTF-8"
), file.path(pkg, "DESCRIPTION"))
writeLines("export(add)", file.path(pkg, "NAMESPACE"))
writeLines("add <- function(x, y) x + y", file.path(pkg, "R", "add.R"))
writeLines(
"testthat::expect_equal(add(1, 2), 3)",
file.path(pkg, "tests", "testthat", "test-add.R")
)
result <- mutate_package(pkg, cores = 1, max_mutants = 1, timeout_seconds = 10)
#> ✔ | F W S OK | Context
#>
#> ⠏ | 0 | add
#> ✔ | 1 | add
#>
#> ══ Results ═════════════════════════════════════════════════════════════════════
#> [ FAIL 0 | WARN 0 | SKIP 0 | PASS 1 ]
#> Generated 1 AST-based mutants for add.R
#> ✔ | F W S OK | Context
#>
#> ⠏ | 0 | add
#> ⠋ | 1 0 | add
#> ✖ | 1 0 | add
#> ────────────────────────────────────────────────────────────────────────────────
#> Failure ('test-add.R:1:1'): (code run outside of `test_that()`)
#> Expected `add(1, 2)` to equal 3.
#> Differences:
#> 1/1 mismatches
#> [1] -1 - 3 == -4
#> ────────────────────────────────────────────────────────────────────────────────
#>
#> ══ Results ═════════════════════════════════════════════════════════════════════
#> ── Failed tests ────────────────────────────────────────────────────────────────
#> Failure ('test-add.R:1:1'): (code run outside of `test_that()`)
#> Expected `add(1, 2)` to equal 3.
#> Differences:
#> 1/1 mismatches
#> [1] -1 - 3 == -4
#>
#> [ FAIL 1 | WARN 0 | SKIP 0 | PASS 0 ]
#> Error : Test failures.
#> Test error: ! in callr subprocess.
#> Caused by error:
#> ! Test failures.
#> Mutation Testing Summary:
#> Total mutants: 1
#> Killed: 1
#> Hanged: 0
#> Survived: 0
#> Mutation Score: 100.00%
#> Timing (seconds):
#> Baseline run: 0.8
#> Mutant generation: 0.0
#> Test execution: 1.1
#> Equivalence detection: 0.0
names(result)
#> [1] "package_mutants" "test_results" "timing"
# }