Skip to content

Commit e810eb3

Browse files
committed
test: achieve 100% test coverage for phenotypic_indices.R
- added explicit test leveraging with_mocked_bindings to intercept cpp_symmetric_solve - verified the matrix conditioning error path correctly triggers - 299 phenotypic tests verified successfully
1 parent c89330f commit e810eb3

1 file changed

Lines changed: 71 additions & 36 deletions

File tree

tests/testthat/test-phenotypic_indices.R

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@
1313
setup_phen_data_small <- function(n = 3, seed = 42) {
1414
set.seed(seed)
1515
# Construct PD matrices via Cholesky
16-
L <- matrix(c(2, 0, 0,
17-
1, 2, 0,
18-
0.5, 1, 1.5), nrow = n, byrow = TRUE)
16+
L <- matrix(c(
17+
2, 0, 0,
18+
1, 2, 0,
19+
0.5, 1, 1.5
20+
), nrow = n, byrow = TRUE)
1921
P <- t(L) %*% L
2022
colnames(P) <- rownames(P) <- paste0("t", seq_len(n))
2123

22-
L2 <- matrix(c(1, 0, 0,
23-
0.6, 1, 0,
24-
0.2, 0.4, 0.8), nrow = n, byrow = TRUE)
24+
L2 <- matrix(c(
25+
1, 0, 0,
26+
0.6, 1, 0,
27+
0.2, 0.4, 0.8
28+
), nrow = n, byrow = TRUE)
2529
G <- t(L2) %*% L2
2630
colnames(G) <- rownames(G) <- paste0("t", seq_len(n))
2731

@@ -34,9 +38,11 @@ setup_phen_data_real <- function() {
3438
data("seldata", package = "selection.index", envir = environment())
3539
pmat <- phen_varcov(seldata[, 3:9], seldata[, 2], seldata[, 1])
3640
gmat <- gen_varcov(seldata[, 3:9], seldata[, 2], seldata[, 1])
37-
w <- as.numeric(c(10, 8, 6, 4, 2, 1, 1)) # 7-trait weights
38-
list(pmat = pmat, gmat = gmat, w = w, n = 7,
39-
trait_names = colnames(pmat))
41+
w <- as.numeric(c(10, 8, 6, 4, 2, 1, 1)) # 7-trait weights
42+
list(
43+
pmat = pmat, gmat = gmat, w = w, n = 7,
44+
trait_names = colnames(pmat)
45+
)
4046
}
4147

4248
# ==============================================================================
@@ -60,7 +66,7 @@ test_that(".index_metrics NA branches fire when G = zero matrix", {
6066

6167
test_that(".index_metrics PRE_constant uses GAY when provided", {
6268
d <- setup_phen_data_small()
63-
res_no_GAY <- smith_hazel(d$P, d$G, d$w)
69+
res_no_GAY <- smith_hazel(d$P, d$G, d$w)
6470
res_with_GAY <- smith_hazel(d$P, d$G, d$w, GAY = 10)
6571
# Without GAY: PRE = GA * 100; with GAY: PRE = GA * 100/10 = GA * 10
6672
expect_equal(res_with_GAY$PRE, res_no_GAY$PRE / 10, tolerance = 1e-6)
@@ -87,8 +93,10 @@ test_that("smith_hazel returns correct structure with small synthetic data", {
8793

8894
expect_s3_class(res, "smith_hazel")
8995
expect_s3_class(res, "selection_index")
90-
expect_named(res, c("b", "w", "Delta_G", "sigma_I", "GA", "PRE",
91-
"hI2", "rHI", "selection_intensity", "summary"))
96+
expect_named(res, c(
97+
"b", "w", "Delta_G", "sigma_I", "GA", "PRE",
98+
"hI2", "rHI", "selection_intensity", "summary"
99+
))
92100
expect_length(res$b, d$n)
93101
expect_length(res$w, d$n)
94102
expect_length(res$Delta_G, d$n)
@@ -124,10 +132,10 @@ test_that("smith_hazel handles matrix wmat with wcol selection", {
124132
test_that("smith_hazel respects custom selection_intensity", {
125133
d <- setup_phen_data_small()
126134
res_default <- smith_hazel(d$P, d$G, d$w)
127-
res_custom <- smith_hazel(d$P, d$G, d$w, selection_intensity = 1.755)
135+
res_custom <- smith_hazel(d$P, d$G, d$w, selection_intensity = 1.755)
128136

129137
expect_equal(res_custom$selection_intensity, 1.755)
130-
expect_equal(res_custom$b, res_default$b) # b doesn't change with intensity
138+
expect_equal(res_custom$b, res_default$b) # b doesn't change with intensity
131139
expect_equal(res_custom$sigma_I, res_default$sigma_I)
132140
expect_equal(res_custom$GA / res_default$GA, 1.755 / 2.063, tolerance = 1e-6)
133141
})
@@ -163,7 +171,7 @@ test_that("smith_hazel stops when pmat and gmat have different dimensions", {
163171
test_that("smith_hazel stops when wmat has wrong number of rows", {
164172
d <- setup_phen_data_small()
165173
expect_error(
166-
smith_hazel(d$P, d$G, wmat = c(1, 2)), # 2 instead of 3
174+
smith_hazel(d$P, d$G, wmat = c(1, 2)), # 2 instead of 3
167175
"Number of rows in wmat"
168176
)
169177
})
@@ -204,9 +212,11 @@ test_that("base_index returns correct structure with small synthetic data", {
204212

205213
expect_s3_class(res, "base_index")
206214
expect_s3_class(res, "selection_index")
207-
expect_named(res, c("b", "w", "Delta_G", "sigma_I", "GA", "PRE",
208-
"hI2", "rHI", "selection_intensity", "summary",
209-
"lpsi_comparison"))
215+
expect_named(res, c(
216+
"b", "w", "Delta_G", "sigma_I", "GA", "PRE",
217+
"hI2", "rHI", "selection_intensity", "summary",
218+
"lpsi_comparison"
219+
))
210220
# In Base Index b = w
211221
expect_equal(res$b, round(d$w, 4))
212222
expect_true(res$sigma_I > 0)
@@ -323,11 +333,11 @@ test_that("lpsi returns a data frame with expected columns", {
323333
res <- lpsi(ncomb = 3, pmat = d$pmat, gmat = d$gmat, wmat = wmat, wcol = 1)
324334

325335
expect_true(is.data.frame(res))
326-
expect_true("ID" %in% names(res))
327-
expect_true("GA" %in% names(res))
328-
expect_true("PRE" %in% names(res))
329-
expect_true("rHI" %in% names(res))
330-
expect_true("hI2" %in% names(res))
336+
expect_true("ID" %in% names(res))
337+
expect_true("GA" %in% names(res))
338+
expect_true("PRE" %in% names(res))
339+
expect_true("rHI" %in% names(res))
340+
expect_true("hI2" %in% names(res))
331341
expect_true("Rank" %in% names(res))
332342
expect_equal(nrow(res), choose(d$n, 3))
333343
})
@@ -336,7 +346,7 @@ test_that("lpsi with GAY produces correct PRE_constant", {
336346
d <- setup_phen_data_real()
337347
data("weight", package = "selection.index", envir = environment())
338348
wmat <- weight_mat(weight)
339-
res_no_GAY <- lpsi(3, d$pmat, d$gmat, wmat, wcol = 1)
349+
res_no_GAY <- lpsi(3, d$pmat, d$gmat, wmat, wcol = 1)
340350
res_with_GAY <- lpsi(3, d$pmat, d$gmat, wmat, wcol = 1, GAY = 5)
341351

342352
# With GAY: PRE = GA * 100/5 = GA * 20; without GAY: PRE = GA * 100
@@ -362,8 +372,10 @@ test_that("lpsi excluding_trait as character vector filters correctly", {
362372
data("weight", package = "selection.index", envir = environment())
363373
wmat <- weight_mat(weight)
364374
# Exclude by trait name
365-
res <- lpsi(3, d$pmat, d$gmat, wmat, wcol = 1,
366-
excluding_trait = c("sypp", "dtf"))
375+
res <- lpsi(3, d$pmat, d$gmat, wmat,
376+
wcol = 1,
377+
excluding_trait = c("sypp", "dtf")
378+
)
367379

368380
expect_true(is.data.frame(res))
369381
expect_lt(nrow(res), choose(d$n, 3))
@@ -417,7 +429,8 @@ test_that("lpsi excluding_trait data.frame warns when no column names match", {
417429

418430
test_that("lpsi excluding_trait data.frame stops when pmat has no colnames", {
419431
d <- setup_phen_data_small()
420-
P_nonames <- d$P; colnames(P_nonames) <- NULL
432+
P_nonames <- d$P
433+
colnames(P_nonames) <- NULL
421434
wmat <- matrix(d$w, ncol = 1)
422435
excl_df <- data.frame(t1 = 1)
423436
expect_error(
@@ -477,7 +490,7 @@ test_that("lpsi excluding_trait matrix without colnames triggers stop (via data.
477490
# Verify numeric matrix IS treated as numeric index (no error):
478491
d <- setup_phen_data_small()
479492
wmat <- matrix(d$w, ncol = 1)
480-
excl_mat <- matrix(1L, nrow = 1, ncol = 1) # numeric matrix: treated as index c(1)
493+
excl_mat <- matrix(1L, nrow = 1, ncol = 1) # numeric matrix: treated as index c(1)
481494
res <- lpsi(2, d$P, d$G, wmat, excluding_trait = excl_mat)
482495
# Trait 1 excluded from 2-trait combos: only (2,3) remains
483496
expect_equal(nrow(res), 1L)
@@ -619,15 +632,15 @@ test_that("print.base_index shows efficiency_ratio < 0.9 message", {
619632
w = c(1, 2, 3),
620633
Delta_G = c(0.5, 1.0, 1.5),
621634
sigma_I = 2.0,
622-
GA = 0.85,
635+
GA = 0.85,
623636
PRE = 85,
624637
hI2 = 0.7,
625638
rHI = 0.84,
626639
selection_intensity = 2.063,
627640
summary = data.frame(),
628641
lpsi_comparison = list(
629642
b_lpsi = c(0.5, 1.5, 2.5),
630-
GA_lpsi = 1.0,
643+
GA_lpsi = 1.0,
631644
PRE_lpsi = 100,
632645
hI2_lpsi = 0.8,
633646
rHI_lpsi = 0.89,
@@ -678,17 +691,17 @@ test_that("summary.base_index shows low-correlation warning when cor < 0.8", {
678691
list(
679692
b = c(1, 2, 3),
680693
w = setNames(c(1, 2, 3), c("t1", "t2", "t3")),
681-
Delta_G = setNames(c(1, -1, 1), c("t1", "t2", "t3")),
694+
Delta_G = setNames(c(1, -1, 1), c("t1", "t2", "t3")),
682695
sigma_I = 1,
683-
GA = 0.5,
696+
GA = 0.5,
684697
PRE = 50,
685698
hI2 = 0.6,
686699
rHI = 0.77,
687700
selection_intensity = 2.063,
688701
summary = data.frame(),
689702
lpsi_comparison = list(
690703
b_lpsi = c(2, 2, 2),
691-
GA_lpsi = 0.6,
704+
GA_lpsi = 0.6,
692705
PRE_lpsi = 60,
693706
hI2_lpsi = 0.7,
694707
rHI_lpsi = 0.84,
@@ -716,8 +729,8 @@ test_that("summary.base_index returns invisible(object)", {
716729

717730
test_that("smith_hazel GA >= base_index GA (LPSI is optimal)", {
718731
d <- setup_phen_data_real()
719-
res_sh <- smith_hazel(d$pmat, d$gmat, d$w)
720-
res_bi <- base_index(d$pmat, d$gmat, d$w, compare_to_lpsi = FALSE)
732+
res_sh <- smith_hazel(d$pmat, d$gmat, d$w)
733+
res_bi <- base_index(d$pmat, d$gmat, d$w, compare_to_lpsi = FALSE)
721734

722735
# LPSI maximises GA → smith_hazel GA should be >= base_index GA
723736
expect_gte(res_sh$GA, res_bi$GA - 1e-8)
@@ -738,9 +751,31 @@ test_that("lpsi(ncomb=n) top PRE matches smith_hazel PRE", {
738751
data("weight", package = "selection.index", envir = environment())
739752
wmat <- weight_mat(weight)
740753
res_lpsi <- lpsi(d$n, d$pmat, d$gmat, wmat, wcol = 1)
741-
res_sh <- smith_hazel(d$pmat, d$gmat, wmat[, 1], GAY = NULL)
754+
res_sh <- smith_hazel(d$pmat, d$gmat, wmat[, 1], GAY = NULL)
742755

743756
top_PRE <- res_lpsi$PRE[res_lpsi$Rank == 1]
744757
# PRE from lpsi uses PRE_constant = 100; smith_hazel also uses 100 when GAY is NULL
745758
expect_equal(top_PRE, round(res_sh$PRE, 4), tolerance = 0.01)
746759
})
760+
761+
# ==============================================================================
762+
# NEW COVERAGE TESTS — targeting previously uncovered lines
763+
# ==============================================================================
764+
765+
test_that("smith_hazel stops when b coefficients are not finite (line 222)", {
766+
d <- setup_phen_data_small()
767+
768+
# Mock cpp_symmetric_solve to return NAs to simulate poorly conditioned matrices
769+
testthat::with_mocked_bindings(
770+
cpp_symmetric_solve = function(A, B) {
771+
rep(NA_real_, length(d$w))
772+
},
773+
.package = "selection.index",
774+
code = {
775+
expect_error(
776+
smith_hazel(d$P, d$G, d$w),
777+
"Failed to compute index coefficients. Check matrix conditioning."
778+
)
779+
}
780+
)
781+
})

0 commit comments

Comments
 (0)