Nothing
#' @srrstats {G5.0} The function is tested with a standard data set from a published paper.
#' @srrstats {G5.1} The function is tested with a standard data set. The data set is
#' created within and used to test the package. The data set is exported so that users
#' can confirm tests and run examples.
#' @srrstats {G5.2} Unit tests demonstrate error messages and compare results with expected values.
#' @srrstats {G5.2a} Every message produced by `stop()` is unique.
#' @srrstats {G5.2b} Unit tests demonstrate error messages and compare results with expected values.
#' @srrstats {G5.4} Unit tests include correctness tests to test that statistical algorithms produce expected results to some fixed test data sets.
#' @srrstats {G5.4c} Unit tests include stored values that are drawn from a published paper output.
#' @srrstats {G5.5} Correctness tests are run with a fixed random seed.
#' @srrstats {G5.6} Unit tests include parameter recovery checks to test that the implementation produces expected results given data with known properties.
#' @srrstats {G5.6a} Parameter recovery tests are expected to be within a defined tolerance rather than exact values.
#' @srrstats {G5.7} Unit tests include algorithm performance checks to test that the function performs as expected as parameters change.
#' @srrstats {G5.8} See sub-tags for responses.
#' @srrstats {G5.8a} Unit tests include checks for zero-length data.
#' @srrstats {G5.8b} Unit tests include checks for unsupported data types.
#' @srrstats {G5.8c} Unit tests include checks for data with 'NA' fields.
#' @srrstats {G5.8d} Unit tests include checks for data outside the scope of the algorithm.
#' @srrstats {G5.9} Unit tests include noise susceptibility tests for expected stochastic behavior.
#' @srrstats {G5.9a} Unit tests check that adding trivial noise to data does not meaningfully change results.
#' @srrstats {G5.9b} Unit tests check that different random seeds do not meaningfully change results.
#' @srrstats {G5.10} All unit tests run as part of continuous integration.
#' @srrstats {RE7.1} Unit tests check for noiseless, exact relationships between
#' predictor (independent) and response (dependent) data.
#' @srrstats {RE7.1a} Unit tests confirm that model fitting is at least as fast
#' or faster than testing with equivalent noisy data.
#' @srrstats {RE7.2} Unit tests demonstrate that output objects retain aspects
#' of input data such as case names.
#' @srrstats {RE7.3} Unit tests demonstrate expected behavior when `rga` object
#' is submitted to the accessor methods `print` and `plot`.
test_that("data.frame input works the same as separate vectors", {
times <- c(100, 200, 300)
failures <- c(1, 2, 1)
df <- data.frame(times = times, failures = failures)
res_df <- rga(df)
res_vec <- rga(times, failures)
expect_s3_class(res_df, "rga")
expect_s3_class(res_vec, "rga")
expect_equal(res_df$betas, res_vec$betas)
expect_equal(res_df$lambdas, res_vec$lambdas)
})
test_that("data.frame without required columns errors", {
df1 <- data.frame(x = 1:3, y = 1:3)
expect_error(
rga(df1),
"must contain columns 'times' and 'failures'"
)
df2 <- data.frame(times = 1:3)
expect_error(
rga(df2),
"must contain columns 'times' and 'failures'"
)
})
test_that("NA or NaN in data.frame input throws error", {
df_na <- data.frame(times = c(100, 200, NA), failures = c(1, 2, 1))
df_nan <- data.frame(times = c(100, 200, NaN), failures = c(1, 2, 1))
df_fail_na <- data.frame(times = c(100, 200, 300), failures = c(1, NA, 1))
expect_error(rga(df_na), "'times' contains missing")
expect_error(rga(df_nan), "'times' contains missing")
expect_error(rga(df_fail_na), "'failures' contains missing")
})
test_that("data.frame with non-positive or infinite values errors", {
df_zero_time <- data.frame(times = c(0, 100, 200), failures = c(1, 2, 1))
df_neg_fail <- data.frame(times = c(100, 200, 300), failures = c(1, -1, 1))
df_inf_time <- data.frame(times = c(100, Inf, 200), failures = c(1, 2, 1))
expect_error(rga(df_zero_time), "must be finite and > 0")
expect_error(rga(df_neg_fail), "must be finite and > 0")
expect_error(rga(df_inf_time), "must be finite and > 0")
})
# Helper: a minimal valid pair of inputs
valid_times <- c(100, 200, 300)
valid_failures <- c(1, 2, 1)
test_that("data frame without required columns errors", {
df_bad <- data.frame(a = 1:3, b = 1:3)
expect_error(rga(df_bad),
"If a data frame is provided, it must contain columns 'times' and 'failures'.",
fixed = TRUE
)
})
test_that("NA / NaN checks for times and failures", {
expect_error(rga(c(1, NA), valid_failures),
"'times' contains missing (NA) or NaN values.",
fixed = TRUE
)
expect_error(rga(valid_times, c(1, NaN, 1)),
"'failures' contains missing (NA) or NaN values.",
fixed = TRUE
)
})
test_that("type checks for times and failures", {
expect_error(rga(list(1, 2, 3), valid_failures),
"'times' must be a numeric vector.",
fixed = TRUE
)
expect_error(rga(valid_times, list(1, 2, 3)),
"'failures' must be a numeric vector.",
fixed = TRUE
)
})
test_that("empty vector checks", {
expect_error(rga(numeric(0), numeric(0)),
"'times' cannot be empty.",
fixed = TRUE
)
# ensure failures empty triggers its message
expect_error(rga(valid_times, numeric(0)),
"'failures' cannot be empty.",
fixed = TRUE
)
})
test_that("length mismatch check", {
expect_error(rga(c(1, 2), c(1, 2, 3)),
"The length of 'times' and 'failures' must be equal.",
fixed = TRUE
)
})
test_that("finite and >0 checks for times and failures", {
expect_error(rga(c(1, Inf, 3), c(1, 1, 1)),
"All values in 'times' must be finite and > 0.",
fixed = TRUE
)
expect_error(rga(c(1, 2, 3), c(1, 0, 2)),
"All values in 'failures' must be finite and > 0.",
fixed = TRUE
)
})
test_that("model_type validation errors", {
# non-single character
expect_error(rga(valid_times, valid_failures, model_type = c("a", "b")),
"'model_type' must be a single character string.",
fixed = TRUE
)
# invalid string - match.arg produces its own message; check for 'one of'
expect_error(
rga(valid_times, valid_failures, model_type = "not-a-model"),
"one of"
)
})
test_that("breaks argument validation", {
# breaks must be numeric non-empty vector if provided
expect_error(rga(valid_times, valid_failures, model_type = "Piecewise NHPP", breaks = character(1)),
"'breaks' must be a non-empty numeric vector if provided.",
fixed = TRUE
)
expect_error(rga(valid_times, valid_failures, model_type = "Piecewise NHPP", breaks = numeric(0)),
"'breaks' must be a non-empty numeric vector if provided.",
fixed = TRUE
)
# breaks values must be finite and > 0
expect_error(rga(valid_times, valid_failures, model_type = "Piecewise NHPP", breaks = c(100, -1)),
"All values in 'breaks' must be finite and > 0.",
fixed = TRUE
)
# breaks only allowed with piecewise model
expect_error(rga(valid_times, valid_failures, model_type = "Crow-AMSAA", breaks = c(150)),
"'breaks' can only be used with the 'Piecewise NHPP' model.",
fixed = TRUE
)
})
test_that("conf_level validation", {
expect_error(rga(valid_times, valid_failures, conf_level = c(0.9, 0.95)),
"'conf_level' must be a single numeric value.",
fixed = TRUE
)
expect_error(rga(valid_times, valid_failures, conf_level = 1),
"'conf_level' must be between 0 and 1 (exclusive).",
fixed = TRUE
)
expect_error(rga(valid_times, valid_failures, conf_level = 0),
"'conf_level' must be between 0 and 1 (exclusive).",
fixed = TRUE
)
})
test_that("print.rga errors when input is not rga", {
expect_error(print.rga(list()),
"'x' must be an object of class 'rga'.",
fixed = TRUE
)
})
test_that("plot.rga argument type checks and malformed object", {
# Create a minimal valid-looking rga object so argument checks after inherits run
x_good <- list(
model = list(model = data.frame(log_times = 1, log_cum_failures = 1)),
fitted_values = 1,
lower_bounds = 1,
upper_bounds = 1
)
class(x_good) <- "rga"
# inherits check (non-rga)
expect_error(plot.rga(list()),
"'x' must be an object of class 'rga'.",
fixed = TRUE
)
# conf_bounds must be single logical
expect_error(plot.rga(x_good, conf_bounds = "nope"),
"'conf_bounds' must be a single logical value.",
fixed = TRUE
)
# legend must be single logical
expect_error(plot.rga(x_good, conf_bounds = TRUE, legend = "nope"),
"'legend' must be a single logical value.",
fixed = TRUE
)
# log must be single logical
expect_error(plot.rga(x_good, conf_bounds = TRUE, legend = TRUE, log = "nope"),
"'log' must be a single logical value.",
fixed = TRUE
)
# legend_pos must be single character string
expect_error(plot.rga(x_good, conf_bounds = TRUE, legend = TRUE, log = FALSE, legend_pos = c("a", "b")),
"'legend_pos' must be a single character string.",
fixed = TRUE
)
# malformed rga: missing required model$model columns
x_bad_model <- list(model = list(model = data.frame(foo = 1, bar = 2)))
class(x_bad_model) <- "rga"
expect_error(plot.rga(x_bad_model),
"The 'rga' object appears malformed or missing model data.",
fixed = TRUE
)
})
library(testthat)
test_that("Crow-AMSAA parameter recovery works (log-log simulation)", {
set.seed(123)
# True parameters (log-log)
beta_true <- 1.3
lambda_true <- 0.01
# Observation times (cumulative)
n <- 200
t_obs <- seq(50, 5000, length.out = n)
log_t_obs <- log(t_obs)
# Mean cumulative failures in log-log space
log_mu <- log(lambda_true) + beta_true * log_t_obs
mu <- exp(log_mu)
# Poisson increments, strictly positive
failures <- as.integer(pmax(rpois(n, diff(c(0, mu))), 1))
# Interarrival times (increments)
times <- c(t_obs[1], diff(t_obs))
# Fit Crow-AMSAA model
fit <- rga(times, failures, model_type = "Crow-AMSAA")
tol_beta <- 0.1
tol_lambda <- 0.01
expect_true(abs(fit$betas - beta_true) < tol_beta,
info = paste("beta estimate:", fit$betas)
)
expect_true(abs(fit$lambdas - lambda_true) < tol_lambda,
info = paste("lambda estimate:", fit$lambdas)
)
})
test_that("Piecewise NHPP parameter recovery works", {
# True params
beta1 <- 0.7
beta2 <- 1.4
lambda1 <- 0.02
tb <- 200
lambda2 <- lambda1 * tb^(beta1 - beta2) # continuity at breakpoint
# Generate observation times and interval inputs for rga()
time_points <- seq(5, 1000, length.out = 200)
intervals <- c(time_points[1], diff(time_points))
# True cumulative counts (piecewise power law, continuous at tb)
cumN <- ifelse(time_points <= tb,
lambda1 * (time_points^beta1),
lambda2 * (time_points^beta2)
)
failures <- c(cumN[1], diff(cumN))
stopifnot(all(failures > 0))
# Run rga with the known breakpoint (simpler, direct parameter recovery)
res <- rga(
times = intervals, failures = failures,
model_type = "Piecewise NHPP", breaks = tb, conf_level = 0.95
)
# Helper: extract numeric betas from rga output (handles usual segmented structure)
get_betas_num <- function(b) {
if (is.numeric(b)) {
return(as.numeric(b))
}
if (is.list(b) && "log_times" %in% names(b)) {
mat <- b$log_times
if ("Est." %in% colnames(mat)) {
return(as.numeric(mat[, "Est."]))
}
if ("Estimate" %in% colnames(mat)) {
return(as.numeric(mat[, "Estimate"]))
}
return(as.numeric(mat[, 1]))
}
stop("Can't extract betas from rga output.")
}
est_betas <- get_betas_num(res$betas)
expect_length(est_betas, 2)
expect_equal(est_betas[1], beta1, tolerance = 1e-6)
expect_equal(est_betas[2], beta2, tolerance = 1e-6)
# Lambdas should be numeric vector
est_lambdas <- as.numeric(res$lambdas)
expect_length(est_lambdas, 2)
expect_equal(est_lambdas[1], lambda1, tolerance = 1e-4)
expect_equal(est_lambdas[2], lambda2, tolerance = 1e-4)
# Breakpoint (rga stores on log-scale) -> back-transform and compare
expect_true(!is.null(res$breakpoints))
recovered_tb <- exp(res$breakpoints[1])
expect_equal(recovered_tb, tb, tolerance = 1e-6)
})
test_that("rga() handles increasing dataset sizes efficiently", {
set.seed(123)
sizes <- c(100, 500, 1000, 5000)
beta_true <- 1.2
lambda_true <- 0.01
for (n in sizes) {
t_obs <- seq(50, 5000, length.out = n)
log_mu <- log(lambda_true) + beta_true * log(t_obs)
mu <- exp(log_mu)
failures <- as.integer(pmax(rpois(n, diff(c(0, mu))), 1))
times <- c(t_obs[1], diff(t_obs))
start_time <- Sys.time()
fit <- rga(times, failures, model_type = "Crow-AMSAA")
end_time <- Sys.time()
runtime <- as.numeric(difftime(end_time, start_time, units = "secs"))
message(sprintf("n = %d, runtime = %.3f sec", n, runtime))
expect_true(abs(fit$betas - beta_true) < 0.1)
expect_true(runtime < 5) # fail if runtime > 5 sec
}
})
test_that("Crow-AMSAA is robust to small noise in times", {
set.seed(101)
# True parameters
beta_true <- 1.2
lambda_true <- 0.01
n <- 200
t_obs <- seq(50, 5000, length.out = n)
log_mu <- log(lambda_true) + beta_true * log(t_obs)
mu <- exp(log_mu)
failures <- as.integer(pmax(rpois(n, diff(c(0, mu))), 1))
times <- c(t_obs[1], diff(t_obs))
# Fit on original data
fit_orig <- rga(times, failures, model_type = "Crow-AMSAA")
# Add trivial Gaussian noise to times (~0.5% of original value)
times_noisy <- times * (1 + rnorm(n, mean = 0, sd = 0.005))
fit_noisy <- rga(times_noisy, failures, model_type = "Crow-AMSAA")
# Parameters should not change meaningfully
expect_true(abs(fit_noisy$betas - fit_orig$betas) < 0.02)
expect_true(abs(fit_noisy$lambdas - fit_orig$lambdas) < 0.001)
})
test_that("Crow-AMSAA is robust to small noise in failures", {
set.seed(202)
beta_true <- 1.2
lambda_true <- 0.01
n <- 200
t_obs <- seq(50, 5000, length.out = n)
log_mu <- log(lambda_true) + beta_true * log(t_obs)
mu <- exp(log_mu)
failures <- as.integer(pmax(rpois(n, diff(c(0, mu))), 1))
times <- c(t_obs[1], diff(t_obs))
fit_orig <- rga(times, failures, model_type = "Crow-AMSAA")
# Add tiny Gaussian noise to failures and round to integers
failures_noisy <- as.integer(pmax(failures + rnorm(n, mean = 0, sd = 0.05), 1))
fit_noisy <- rga(times, failures_noisy, model_type = "Crow-AMSAA")
expect_true(abs(fit_noisy$betas - fit_orig$betas) < 0.05)
expect_true(abs(fit_noisy$lambdas - fit_orig$lambdas) < 0.01)
})
test_that("Piecewise NHPP is robust to small noise in times and failures", {
set.seed(303)
beta_true <- c(0.8, 1.5)
lambda_true <- c(0.02, NA)
break_time <- 2500
n <- 200
t_obs <- seq(100, 5000, length.out = n)
log_lambda2 <- log(lambda_true[1]) + beta_true[1] * log(break_time) - beta_true[2] * log(break_time)
lambda_true[2] <- exp(log_lambda2)
log_mu <- numeric(n)
for (i in seq_along(t_obs)) {
if (t_obs[i] <= break_time) {
log_mu[i] <- log(lambda_true[1]) + beta_true[1] * log(t_obs[i])
} else {
log_mu[i] <- log_lambda2 + beta_true[2] * log(t_obs[i])
}
}
mu <- exp(log_mu)
failures <- as.integer(pmax(rpois(n, diff(c(0, mu))), 1))
times <- c(t_obs[1], diff(t_obs))
fit_orig <- rga(times, failures, model_type = "Piecewise NHPP", breaks = c(break_time))
# Add small noise
times_noisy <- times * (1 + rnorm(n, 0, 0.005))
failures_noisy <- as.integer(pmax(failures + rnorm(n, 0, 0.05), 1))
fit_noisy <- rga(times_noisy, failures_noisy, model_type = "Piecewise NHPP", breaks = c(break_time))
slopes_orig <- fit_orig$betas$log_times[, "Est."]
slopes_noisy <- fit_noisy$betas$log_times[, "Est."]
intercepts_orig <- fit_orig$lambdas
intercepts_noisy <- fit_noisy$lambdas
expect_true(all(abs(slopes_noisy - slopes_orig) < 0.05))
expect_true(all(abs(intercepts_noisy - intercepts_orig) < 0.01))
})
test_that("rga works with input from testdata (Crow-AMSAA)", {
data("testdata", package = "ReliaGrowR")
g1 <- subset(testdata, LRU == "G1")
times <- g1$Cum_ETI
failures <- g1$Failure_Count
fit <- rga(times, failures, model_type = "Crow-AMSAA", conf_level = 0.95)
expect_true(all(fit$fitted_values > 0))
expect_true(all(fit$lower_bounds > 0))
expect_true(all(fit$upper_bounds > 0))
})
# Create a simple dataset for testing
times <- c(100, 200, 300, 400, 500)
failures <- c(1, 2, 1, 3, 2)
rga_obj <- rga(times, failures)
test_that("print.rga works correctly", {
# Output should include key phrases
expect_output(print(rga_obj), "Reliability Growth Analysis")
expect_output(print(rga_obj), "Model Type: Crow-AMSAA")
expect_output(print(rga_obj), "Log-likelihood:")
expect_output(print(rga_obj), "AIC:")
expect_output(print(rga_obj), "BIC:")
# Should invisibly return the same object
expect_invisible(print(rga_obj))
})
test_that("plot.rga basic functionality works", {
# Just checks that no error is thrown
expect_silent(plot(rga_obj))
# With options
expect_silent(plot(rga_obj, conf_bounds = FALSE, legend = FALSE))
expect_silent(plot(rga_obj, log = TRUE))
expect_silent(plot(rga_obj, legend_pos = "topright"))
})
test_that("plot.rga argument validation works", {
expect_error(plot(rga_obj, conf_bounds = "yes"), "'conf_bounds' must be a single logical value")
expect_error(plot(rga_obj, legend = c(TRUE, FALSE)), "'legend' must be a single logical value")
expect_error(plot(rga_obj, log = NA), "missing value where TRUE/FALSE needed")
expect_error(plot(rga_obj, legend_pos = 1), "'legend_pos' must be a single character string")
})
# Optional: visual regression tests (requires vdiffr)
if (requireNamespace("vdiffr", quietly = TRUE)) {
test_that("plot.rga visual output is stable", {
vdiffr::expect_doppelganger("Crow-AMSAA plot", function() plot(rga_obj))
})
}
test_that("rga() errors on perfect (noiseless) collinearity between predictor and response", {
# Perfect power-law relationship between cumulative time and cumulative failures
n <- 200
cum_time <- seq(1, n)
lambda <- 2.0
beta <- 0.75
cum_failures <- lambda * cum_time^beta
# failures per interval (must be positive)
failures <- diff(c(0, cum_failures))
# Expect perfect-collinearity error
expect_error(
rga(times = rep(1, n), failures = failures),
regexp = "Perfect collinearity detected"
)
# also check with data.frame input
df <- data.frame(times = rep(1, n), failures = failures)
expect_error(
rga(df),
regexp = "Perfect collinearity detected"
)
})
test_that("rga() fits near-noiseless data and is at least as fast as noisy data (Crow-AMSAA)", {
# Don't run these tests on the CRAN build servers
skip_on_cran()
set.seed(42)
n <- 800
cum_time <- seq(1, n)
lambda <- 5.0
beta <- 0.6
cum_failures <- lambda * cum_time^beta
failures_per_interval <- diff(c(0, cum_failures))
# Create a near-noiseless version
near_noise <- rnorm(n, mean = 0, sd = 1e-2)
cum_failures_near <- cum_failures * exp(near_noise)
failures_near <- diff(c(0, cum_failures_near))
# Create a clearly noisy version
big_noise <- rnorm(n, mean = 0, sd = 0.1)
cum_failures_noisy <- cum_failures * exp(big_noise)
failures_noisy <- diff(c(0, cum_failures_noisy))
# Ensure positiveness
failures_near <- pmax(failures_near, .Machine$double.eps)
failures_noisy <- pmax(failures_noisy, .Machine$double.eps)
# Timing: Crow-AMSAA model (default)
t_near <- system.time({
fit_near <- rga(times = rep(1, n), failures = failures_near, model_type = "Crow-AMSAA")
})[["elapsed"]]
t_noisy <- system.time({
fit_noisy <- rga(times = rep(1, n), failures = failures_noisy, model_type = "Crow-AMSAA")
})[["elapsed"]]
# Require near-noiseless to be <= 1.2 * noisy (allow CI variability)
expect_true(
t_near <= t_noisy * 1.2 + 0.1,
info = sprintf("Noiseless fit took %.3fs vs noisy %.3fs", t_near, t_noisy)
)
})
test_that("rga() fits near-noiseless data with user-supplied breaks (Piecewise NHPP) and is reasonably fast", {
set.seed(101)
n <- 400
cum_time <- seq(1, n)
lambda <- 3.0
beta <- 0.8
cum_failures <- lambda * cum_time^beta
failures <- diff(c(0, cum_failures))
# near-noiseless
near_noise <- rnorm(n, mean = 0, sd = 5e-3) # changed from 1e-4 to 5e-3
cum_failures_near <- cum_failures * exp(near_noise)
failures_near <- diff(c(0, cum_failures_near))
failures_near <- pmax(failures_near, .Machine$double.eps)
big_noise <- rnorm(n, mean = 0, sd = 0.1)
cum_failures_noisy <- cum_failures * exp(big_noise)
failures_noisy <- diff(c(0, cum_failures_noisy))
failures_noisy <- pmax(failures_noisy, .Machine$double.eps)
# choose a breakpoint roughly in the middle (on original scale)
break_pt <- floor(n / 2)
t_near_pw <- system.time({
fit_near_pw <- rga(
times = rep(1, n), failures = failures_near,
model_type = "Piecewise NHPP", breaks = break_pt
)
})[["elapsed"]]
t_noisy_pw <- system.time({
fit_noisy_pw <- rga(
times = rep(1, n), failures = failures_noisy,
model_type = "Piecewise NHPP", breaks = break_pt
)
})[["elapsed"]]
# Require near-noiseless to be <= 1.2 * noisy (allow CI variability)
expect_true(
t_near_pw <= t_noisy_pw * 1.2 + 0.1,
info = sprintf("Noiseless fit took %.3fs vs noisy %.3fs", t_near_pw, t_noisy_pw)
)
})
test_that("rga output retains row / case names from input", {
# Case 1: Named numeric vectors
times <- c(A = 100, B = 200, C = 300, D = 400, E = 500)
failures <- c(A = 1, B = 2, C = 1, D = 3, E = 2)
res_named <- rga(times, failures)
# input names preserved in stored inputs
expect_equal(names(res_named$times), names(times))
expect_equal(names(res_named$failures), names(failures))
# Case 2: Piecewise NHPP
# Use a dataset that can reasonably produce a breakpoint (small example)
times2 <- c(t1 = 10, t2 = 20, t3 = 30, t4 = 40, t5 = 50)
failures2 <- c(t1 = 1, t2 = 1, t3 = 2, t4 = 3, t5 = 5)
res_piecewise <- rga(times2, failures2, model_type = "Piecewise NHPP", breaks = c(30))
expect_equal(names(res_piecewise$times), names(times2))
expect_equal(names(res_piecewise$failures), names(failures2))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.