| Title: | Mutation Testing |
|---|---|
| Description: | Performs mutation testing for 'R' packages to assess the effectiveness of a test suite. It mutates source files, runs package tests against each mutant, and reports which mutants were killed, survived, or timed out. Parallel execution is supported for larger code bases. The optional 'imputesrcref' package, available from <https://github.com/PRL-PRG/imputesrcref>, improves source locations when installed. |
| Authors: | Pierre Donat-Bouillud [aut, cre, cph] (ORCID: <https://orcid.org/0000-0003-4455-1130>), Assanali Amandykov [aut, cph] |
| Maintainer: | Pierre Donat-Bouillud <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.1.1.9000 |
| Built: | 2026-07-15 20:44:57 UTC |
| Source: | https://github.com/PRL-PRG/mutator |
Resolves the API key, model and base URL used for equivalent-mutant detection. Each field is resolved independently, with this precedence (highest first):
get_openai_config(dir = getwd())get_openai_config(dir = getwd())
dir |
Directory to search for a |
values set with set_openai_config();
a .openai_config file in dir (a human-readable "field: value" file
that is parsed, never executed);
the environment variables OPENAI_API_KEY, OPENAI_MODEL,
OPENAI_BASE_URL and OPENAI_MAX_PARALLEL_REQUESTS;
built-in defaults (model "gpt-4", the public OpenAI base URL).
A list with elements api_key, model, base_url and
max_parallel_requests (an integer cap on concurrent API requests, or
NA for no cap).
config <- get_openai_config() names(config)config <- get_openai_config() names(config)
Analyzes survived mutants to determine if they are functionally equivalent to the original code using OpenAI's language models.
identify_equivalent_mutants( src_file, survived_mutants, api_config = NULL, batch_size = 25, workers = 1, report = TRUE )identify_equivalent_mutants( src_file, survived_mutants, api_config = NULL, batch_size = 25, workers = 1, report = TRUE )
src_file |
Path to the original source file |
survived_mutants |
List of mutants that survived test execution |
api_config |
Optional API configuration (will be loaded if NULL) |
batch_size |
Maximum number of mutants sent in a single API request. Smaller batches keep each response short enough to avoid truncation (which silently drops verdicts) and let batches run concurrently. Defaults to 25. |
workers |
Number of API requests to run concurrently (requires a forking platform). Defaults to 1 (sequential). |
report |
Whether to print per-file progress and an equivalence summary
to the console. Defaults to |
Updated list of survived mutants with equivalence information
src <- tempfile(fileext = ".R") writeLines("add <- function(x, y) x + y", src) survived <- list(mutant_001 = list(mutation_info = "x + y -> x - y")) suppressWarnings(identify_equivalent_mutants( src, survived, api_config = list(api_key = "", model = "gpt-4") ))src <- tempfile(fileext = ".R") writeLines("add <- function(x, y) x + y", src) survived <- list(mutant_001 = list(mutation_info = "x + y -> x - y")) suppressWarnings(identify_equivalent_mutants( src, survived, api_config = list(api_key = "", model = "gpt-4") ))
Creates mutants for a single R source file by combining AST-based mutations from the C++ mutation engine with fallback line-deletion mutants.
mutate_file( src_file, out_dir = "mutations", max_mutants = NULL, max_line_deletions = 5 )mutate_file( src_file, out_dir = "mutations", max_mutants = NULL, max_line_deletions = 5 )
src_file |
Path to an R source file. |
out_dir |
Directory where mutant files are written. |
max_mutants |
Optional cap on the number of returned mutants. If set, a random subset of generated mutants is returned. |
max_line_deletions |
Maximum number of line-deletion mutants generated
per file (a random subset of deletable lines). These complement the
AST-based statement deletions by also covering top-level / non-block lines.
Use |
A list of mutants. Each element contains:
pathPath to the mutant file.
infoFormatted mutation metadata (file, source range, and details).
locMachine-readable location: a list with file_path,
start_line, and end_line (the latter two NA when unavailable).
src <- tempfile(fileext = ".R") writeLines("add <- function(x, y) x + y", src) mutants <- mutate_file(src, out_dir = tempfile("mutations_"), max_mutants = 1) length(mutants)src <- tempfile(fileext = ".R") writeLines("add <- function(x, y) x + y", src) mutants <- mutate_file(src, out_dir = tempfile("mutations_"), max_mutants = 1) length(mutants)
Mutates all .R files under a package's R/ directory, runs the package's
tests against each mutant in parallel, and summarizes mutation outcomes.
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 = 0, cran = TRUE, fail_fast = TRUE, isolate = FALSE, exclude_files = NULL, strategy = c("auto", "testthat", "installed"), coverage_guided = TRUE, coverage_backend = c("record_tests", "per_file"), target_margin = NULL, confidence = 0.95, max_show = 50L )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 = 0, cran = TRUE, fail_fast = TRUE, isolate = FALSE, exclude_files = NULL, strategy = c("auto", "testthat", "installed"), coverage_guided = TRUE, coverage_backend = c("record_tests", "per_file"), target_margin = NULL, confidence = 0.95, max_show = 50L )
pkg_dir |
Path to the package directory. |
cores |
Number of parallel workers used for mutant test execution. |
isFullLog |
Logical; if |
detectEqMutants |
Logical; if |
mutation_dir |
Optional directory to store generated mutant files.
If |
max_mutants |
Sample that number of mutants for testing. If |
timeout_seconds |
Optional timeout in seconds for each mutant run.
If |
config_dir |
Directory searched for a |
max_line_deletions |
Maximum number of line-deletion mutants per |
cran |
Logical; if |
fail_fast |
Logical; if |
isolate |
Logical; if |
exclude_files |
Optional character vector of shell-style glob patterns
(e.g. |
strategy |
Test strategy to use. |
coverage_guided |
Logical; if |
coverage_backend |
How |
target_margin |
Optional desired half-width of the confidence interval on
the mutation score, as a proportion (e.g. |
confidence |
Confidence level for |
max_show |
Maximum number of surviving mutants to print to the console;
the remainder are summarised as "... and N more" but always remain in the
returned |
The example is not run during routine automated checks because it creates and mutation-tests a throwaway package, which is too slow for that context.
Test strategy is, by default, detected automatically:
If tests/testthat/ exists, the mutant is loaded in-process with
pkgload::load_all() (no installation) and its tests are run the way the
package's own tests/testthat.R harness runs them, i.e. with the same
arguments (notably any filter) that the harness passes to
testthat::test_check(), via testthat::test_dir().
Otherwise, if tests/ exists, mutator installs the mutant package
with --install-tests and runs tools::testInstalledPackage().
Pass strategy to override this (for example to run a testthat package
through the slower installed-tests path for comparison).
An invisible list with four 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, and equivalence_detection.
summaryNamed list with generated, tested, killed, hanged,
survived, mutation_score, mutation_score_ci (a length-2 percentage
vector, or NULL when no sampling occurred), and confidence.
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) names(result)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) names(result)
This help page lists the mutation operators that the mutator package supports for mutation testing in R.
The following mutation operators can be applied to R code:
Arithmetic operators: + <-> -, and * <-> /
Comparison operators: == <-> !=, < <-> >, and <= <-> >=
Logical operators: \& <-> |, && <-> ||, removes !, and negates if / while conditions
Assignment and call values: replaces assignment right-hand sides and ordinary function calls with 42
Scalar constants: replaces numeric zero with 42, numeric non-zero values with 0, constants with a typed NA, and constants with NULL
Returns: replaces non-constant direct return() values with NULL, for example return(x) -> return(NULL)
Deletions: removes statements inside { ... } blocks and, as a fallback, valid source lines
Direct literal return values are not rewritten by the return-to-NULL mutation; for example, return(1) is left alone by that mutation.
Assanali Amandykov and Pierre Donat-Bouillud
Removes any values set with set_openai_config(), reverting to configuration
taken from a .openai_config file or environment variables.
reset_openai_config()reset_openai_config()
Invisibly NULL.
set_openai_config(model = "gpt-4o-mini") reset_openai_config()set_openai_config(model = "gpt-4o-mini") reset_openai_config()
Overrides the API key, model and/or base URL used by the equivalent-mutant
detection workflow. Values set here take precedence over a .openai_config
file and over environment variables. Arguments left NULL are unchanged.
set_openai_config( api_key = NULL, model = NULL, base_url = NULL, max_parallel_requests = NULL )set_openai_config( api_key = NULL, model = NULL, base_url = NULL, max_parallel_requests = NULL )
api_key |
API key string. |
model |
Model name (e.g. |
base_url |
Base URL of an OpenAI-compatible Chat Completions API, such
as |
max_parallel_requests |
Maximum number of equivalence-detection API
requests to run concurrently. Use this to stay under a provider's
per-key parallel-request limit (exceeding it returns HTTP 429). |
Invisibly, the resulting configuration (see get_openai_config()).
set_openai_config(model = "gpt-4o-mini") get_openai_config()$model reset_openai_config()set_openai_config(model = "gpt-4o-mini") get_openai_config()$model reset_openai_config()