R version 4.6.0 beta (2026-04-10 r89868 ucrt) -- "Because it was There" Copyright (C) 2026 The R Foundation for Statistical Computing Platform: x86_64-w64-mingw32/x64 R is free software and comes with ABSOLUTELY NO WARRANTY. You are welcome to redistribute it under certain conditions. Type 'license()' or 'licence()' for distribution details. R is a collaborative project with many contributors. Type 'contributors()' for more information and 'citation()' on how to cite R or R packages in publications. Type 'demo()' for some demos, 'help()' for on-line help, or 'help.start()' for an HTML browser interface to help. Type 'q()' to quit R. > # remove.packages("HonestDiD") > # install.packages(".", repos=NULL, type="source") > # testthat::test_dir("tests") > # > # Syntax smoke tests: exercise as many exported entry points and option > # combinations as possible with tiny grids so the full suite runs fast. > # The goal is breadth (catching regressions in call-site plumbing, e.g. the > # CVXR API change behind issue #68) rather than numerical correctness. > > library(testthat) > library(HonestDiD) > data(BCdata_EventStudy) > > BC_betahat <- BCdata_EventStudy$betahat > BC_sigma <- BCdata_EventStudy$sigma > BC_numPrePeriods <- length(BCdata_EventStudy$prePeriodIndices) > BC_numPostPeriods <- length(BCdata_EventStudy$postPeriodIndices) > BC_l_vec <- basisVector(index = 1, size = BC_numPostPeriods) > > # Tiny knobs to keep the suite fast. The tests verify that each entry point > # runs and returns the expected object type, not that results are accurate, > # so single-value M/Mbar vectors and a 10-point grid are enough. > SYN_Mvec <- 0.1 > SYN_Mbarvec <- 0.5 > SYN_gridPoints <- 10 > SYN_grid.ub <- 1 > SYN_grid.lb <- -1 > > # Suppress benign noise: several low-level helpers print progress to stdout > # and the tiny grid occasionally produces empty accept-sets, which trip > # min()/max() warnings. Wrap runners so the tests stay focused on errors. > .quiet <- function(expr) base::suppressWarnings(base::suppressMessages( + utils::capture.output(val <- expr))) > .run <- function(expr) { + .quiet(val <- expr) + val + } > expect_runs <- function(expr) { + val <- .run(expr) + expect_false(base::is.null(val)) + base::invisible(val) + } > expect_tibble <- function(expr, expected_rows = NA) { + val <- .run(expr) + expect_true(base::inherits(val, "data.frame")) + if ( !base::is.na(expected_rows) ) expect_equal(base::nrow(val), expected_rows) + base::invisible(val) + } > > if ( Sys.getenv("HONESTDID_RUN_TESTS") == "1" ) { + test_that("HonestDiD utilities and low-level helpers run with no errors", { + # basisVector: both forms used in examples + expect_runs(basisVector(index = 1, size = BC_numPostPeriods)) + expect_runs(basisVector(index = BC_numPostPeriods, size = BC_numPostPeriods)) + + # Pre-period-based M bounds + expect_runs(DeltaSD_upperBound_Mpre(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + alpha = 0.05)) + expect_runs(DeltaSD_lowerBound_Mpre(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + alpha = 0.05, + grid.ub = SYN_grid.ub, + gridPoints = SYN_gridPoints)) + + # Direct FLCI entry point + flci <- expect_runs(findOptimalFLCI(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + M = SYN_Mvec, + alpha = 0.05, + seed = 0)) + expect_true(base::all(c("FLCI", "optimalVec", "optimalHalfLength", "status") %in% base::names(flci))) + + # Original (non-robust) CS + expect_tibble(constructOriginalCS(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec)) + }) + } else { + print("HonestDiD utilities run was skipped") + } [1] "HonestDiD utilities run was skipped" > > if ( Sys.getenv("HONESTDID_RUN_TESTS") == "1" ) { + test_that("createSensitivityResults covers all method x restriction combos", { + methods <- c("FLCI", "Conditional", "C-F", "C-LF") + + # No shape / sign restriction + for ( m in methods ) { + expect_tibble( + createSensitivityResults(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + method = m, + Mvec = SYN_Mvec, + alpha = 0.05), + expected_rows = base::length(SYN_Mvec)) + } + + # Bias direction restriction. FLCI ignores sign restrictions (it + # delegates to findOptimalFLCI and only warns about the extra argument), + # so its call path is already covered in (a); skip here to save time. + for ( m in base::setdiff(methods, "FLCI") ) { + for ( bd in c("positive", "negative") ) { + expect_tibble( + createSensitivityResults(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + method = m, + biasDirection = bd, + Mvec = SYN_Mvec, + alpha = 0.05), + expected_rows = base::length(SYN_Mvec)) + } + } + + # Monotonicity (shape) restriction. Same caveat as (b): FLCI ignores + # shape restrictions, so we skip it here. + for ( m in base::setdiff(methods, "FLCI") ) { + for ( md in c("increasing", "decreasing") ) { + expect_tibble( + createSensitivityResults(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + method = m, + monotonicityDirection = md, + Mvec = SYN_Mvec, + alpha = 0.05), + expected_rows = base::length(SYN_Mvec)) + } + } + }) + } else { + print("HonestDiD createSensitivityResults run was skipped") + } [1] "HonestDiD createSensitivityResults run was skipped" > > if ( Sys.getenv("HONESTDID_RUN_TESTS") == "1" ) { + test_that("createSensitivityResults_relativeMagnitudes covers all bound x method x restriction combos", { + methods <- c("C-LF", "Conditional") + bounds <- c("deviation from parallel trends", "deviation from linear trend") + + # No shape / sign restriction + for ( b in bounds ) { + for ( m in methods ) { + expect_tibble( + createSensitivityResults_relativeMagnitudes( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + bound = b, + method = m, + Mbarvec = SYN_Mbarvec, + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + alpha = 0.05), + expected_rows = base::length(SYN_Mbarvec)) + } + } + + # Bias direction restriction + for ( b in bounds ) { + for ( m in methods ) { + for ( bd in c("positive", "negative") ) { + expect_tibble( + createSensitivityResults_relativeMagnitudes( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + bound = b, + method = m, + biasDirection = bd, + Mbarvec = SYN_Mbarvec, + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + alpha = 0.05), + expected_rows = base::length(SYN_Mbarvec)) + } + } + } + + # Monotonicity (shape) restriction + for ( b in bounds ) { + for ( m in methods ) { + for ( md in c("increasing", "decreasing") ) { + expect_tibble( + createSensitivityResults_relativeMagnitudes( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + bound = b, + method = m, + monotonicityDirection = md, + Mbarvec = SYN_Mbarvec, + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + alpha = 0.05), + expected_rows = base::length(SYN_Mbarvec)) + } + } + } + }) + } else { + print("HonestDiD createSensitivityResults_relativeMagnitudes run was skipped") + } [1] "HonestDiD createSensitivityResults_relativeMagnitudes run was skipped" > > if ( Sys.getenv("HONESTDID_RUN_TESTS") == "1" ) { + test_that("Low-level computeConditionalCS_* entry points all run", { + # Baseline calls hit every hybrid_flag to catch solver-plumbing regressions + # (this is the path the CVXR API change in issue #68 broke). Restricted + # variants only need to exercise one hybrid to cover the call-site syntax. + for ( hf in c("ARP", "FLCI", "LF") ) { + expect_runs(computeConditionalCS_DeltaSD( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + M = SYN_Mvec, + alpha = 0.05, + hybrid_flag = hf, + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + } + for ( hf in c("ARP", "LF") ) { + expect_runs(computeConditionalCS_DeltaRM( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + Mbar = SYN_Mbarvec, alpha = 0.05, + hybrid_flag = hf, + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, seed = 0)) + expect_runs(computeConditionalCS_DeltaSDRM( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + Mbar = SYN_Mbarvec, alpha = 0.05, + hybrid_flag = hf, + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, seed = 0)) + } + + # Bias-direction restricted variants (one hybrid flag each) + for ( bd in c("positive", "negative") ) { + expect_runs(computeConditionalCS_DeltaSDB( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + M = SYN_Mvec, + alpha = 0.05, + biasDirection = bd, + hybrid_flag = "ARP", + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + expect_runs(computeConditionalCS_DeltaRMB( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + Mbar = SYN_Mbarvec, + alpha = 0.05, + biasDirection = bd, + hybrid_flag = "LF", + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + expect_runs(computeConditionalCS_DeltaSDRMB( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + Mbar = SYN_Mbarvec, + alpha = 0.05, + biasDirection = bd, + hybrid_flag = "LF", + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + } + + # Monotonicity-restricted variants (one hybrid flag each) + for ( md in c("increasing", "decreasing") ) { + expect_runs(computeConditionalCS_DeltaSDM( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + M = SYN_Mvec, + alpha = 0.05, + monotonicityDirection = md, + hybrid_flag = "ARP", + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + expect_runs(computeConditionalCS_DeltaRMM( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + Mbar = SYN_Mbarvec, + alpha = 0.05, + monotonicityDirection = md, + hybrid_flag = "LF", + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + expect_runs(computeConditionalCS_DeltaSDRMM( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + Mbar = SYN_Mbarvec, + alpha = 0.05, + monotonicityDirection = md, + hybrid_flag = "LF", + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb, + seed = 0)) + } + }) + } else { + print("HonestDiD computeConditionalCS run was skipped") + } [1] "HonestDiD computeConditionalCS run was skipped" > > if ( Sys.getenv("HONESTDID_RUN_TESTS") == "1" ) { + test_that("Plotting wrappers run with no errors", { + origCS <- .run(constructOriginalCS(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec)) + + # Need >= 2 M values so createSensitivityPlot can compute the between-M gap + robustSD <- .run(createSensitivityResults(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + method = "FLCI", + Mvec = c(0, 0.1))) + + robustRM <- .run(createSensitivityResults_relativeMagnitudes( + betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + l_vec = BC_l_vec, + bound = "deviation from parallel trends", + method = "C-LF", + Mbarvec = c(0, 0.5), + gridPoints = SYN_gridPoints, + grid.ub = SYN_grid.ub, + grid.lb = SYN_grid.lb)) + + expect_runs(createSensitivityPlot(robustResults = robustSD, + originalResults = origCS)) + expect_runs(createSensitivityPlot_relativeMagnitudes(robustResults = robustRM, + originalResults = origCS)) + + # Event-study plot: one with stdErrors, one with full sigma + timeVec <- base::c(base::seq(from = -BC_numPrePeriods, to = -1), + base::seq(from = 1, to = BC_numPostPeriods)) + stdErrors <- base::sqrt(base::diag(BC_sigma)) + + expect_runs(createEventStudyPlot(betahat = BC_betahat, + stdErrors = stdErrors, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + timeVec = timeVec, + referencePeriod = 0)) + + expect_runs(createEventStudyPlot(betahat = BC_betahat, + sigma = BC_sigma, + numPrePeriods = BC_numPrePeriods, + numPostPeriods = BC_numPostPeriods, + timeVec = timeVec, + referencePeriod = 0)) + }) + } else { + print("HonestDiD plotting run was skipped") + } [1] "HonestDiD plotting run was skipped" > > if ( Sys.getenv("HONESTDID_RUN_TESTS") == "1" ) { + test_that(".maxBiasFN, .minBiasFN, and .createConstraintsLinearTrend use .psolve correctly", { + # Simple 2D LP: delta in R^2, constrained by A %*% delta <= d. + # A = I_2, d = c(1, 1) means delta[1] <= 1 and delta[2] <= 1. + # v = c(1, 0) picks out delta[1], so max bias = 1 and min bias = -Inf + # (unbounded below). Use v = c(1, 1) so both max and min are finite: + # max = 2, min = -2 (with d = c(1,1) and A = rbind(I, -I)). + A <- base::rbind(diag(2), -diag(2)) + d <- base::rep(1, 4) + v <- base::c(1, 1) + + maxSoln <- HonestDiD:::.maxBiasFN(v = v, A = A, d = d) + expect_true(base::is.list(maxSoln)) + expect_true(!base::is.null(maxSoln$value)) + expect_true(!base::is.null(maxSoln$status)) + expect_equal(maxSoln$status, "optimal") + expect_equal(base::as.numeric(maxSoln$value), 2, tolerance = 1e-4) + + minSoln <- HonestDiD:::.minBiasFN(v = v, A = A, d = d) + expect_true(base::is.list(minSoln)) + expect_true(!base::is.null(minSoln$value)) + expect_true(!base::is.null(minSoln$status)) + expect_equal(minSoln$status, "optimal") + expect_equal(base::as.numeric(minSoln$value), -2, tolerance = 1e-4) + + # .createConstraintsLinearTrend: uses BC data to construct A/d via + # the standard pre-period constraint, then checks the returned list + # has the right structure and that maxBias >= minBias. + tVec <- base::c(base::seq(-BC_numPrePeriods, -1), base::seq(1, BC_numPostPeriods)) + lVec <- BC_l_vec + result <- HonestDiD:::.createConstraintsLinearTrend( + A = base::diag(base::length(tVec)), + d = base::rep(1, base::length(tVec)), + lVec = lVec, + tVec = tVec, + referencePeriod = 0) + expect_true(base::is.list(result)) + expect_true(base::all(c("A_trend", "d_trend") %in% base::names(result))) + expect_equal(base::nrow(result$A_trend), 2) + expect_true(result$d_trend[1] >= -result$d_trend[2]) + }) + } else { + print("HonestDiD .maxBiasFN/.minBiasFN run was skipped") + } [1] "HonestDiD .maxBiasFN/.minBiasFN run was skipped" > > proc.time() user system elapsed 0.59 0.10 0.68