From a3e8e941f35ff9754532fd6615922f49850d000e Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:29:57 +0100 Subject: [PATCH 01/13] Serve CI data and settings to DSLite in tests --- .../init_studies_datasets.R | 12 +-- .../init_testing_datasets.R | 76 +++++++++++++++++-- tests/testthat/setup.R | 3 + 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/tests/testthat/connection_to_datasets/init_studies_datasets.R b/tests/testthat/connection_to_datasets/init_studies_datasets.R index 0639aac61..e124a0086 100644 --- a/tests/testthat/connection_to_datasets/init_studies_datasets.R +++ b/tests/testthat/connection_to_datasets/init_studies_datasets.R @@ -109,8 +109,7 @@ init.studies.dataset.cluster.int <- function(variables) } else { - #to do - #ds.test_env$login.data <- DSLite::setupCLUSTERTest("dsBase", env = ds.test_env) + ds.test_env$login.data <- dslite.login.data(c("cluster.int1", "cluster.int2", "cluster.int3"), c("CLUSTER_INT1", "CLUSTER_INT2", "CLUSTER_INT3")) } ds.test_env$stats.var <- variables } @@ -143,8 +142,7 @@ init.studies.dataset.cluster.slo <- function(variables) } else { - #to do - #ds.test_env$login.data <- DSLite::setupCLUSTERTest("dsBase", env = ds.test_env) + ds.test_env$login.data <- dslite.login.data(c("cluster.slo1", "cluster.slo2", "cluster.slo3"), c("CLUSTER_SLO1", "CLUSTER_SLO2", "CLUSTER_SLO3")) } ds.test_env$stats.var <- variables @@ -178,8 +176,7 @@ init.studies.dataset.anthro <- function(variables) } else { - #to do - #ds.test_env$login.data <- DSLite::setupCLUSTERTest("dsBase", env = ds.test_env) + ds.test_env$login.data <- dslite.login.data(c("study1", "study2", "study3"), c("anthro1", "anthro2", "anthro3")) } ds.test_env$stats.var <- variables @@ -213,8 +210,7 @@ init.studies.dataset.gamlss <- function(variables) } else { - #to do - #ds.test_env$login.data <- DSLite::setupCLUSTERTest("dsBase", env = ds.test_env) + ds.test_env$login.data <- dslite.login.data(c("study1", "study2", "study3"), c("gamlss1", "gamlss2", "gamlss3")) } ds.test_env$stats.var <- variables diff --git a/tests/testthat/connection_to_datasets/init_testing_datasets.R b/tests/testthat/connection_to_datasets/init_testing_datasets.R index 52fb7fcfd..920d3ae21 100644 --- a/tests/testthat/connection_to_datasets/init_testing_datasets.R +++ b/tests/testthat/connection_to_datasets/init_testing_datasets.R @@ -174,8 +174,7 @@ init.testing.dataset.factor_levels <- function() } else { - login.data <- DSLite::setupDATASETTest("dsBase", env = ds.test_env) - ds.test_env$login.data <- subset(login.data, server=="study1") + ds.test_env$login.data <- dslite.login.data(c("GROUP1", "GROUP2", "GROUP3"), c("FACTOR_LEVELS1", "FACTOR_LEVELS2", "FACTOR_LEVELS3")) } ds.test_env$stats.var <- list('ID', 'COLOURS', 'COLOURS.NUMBERS', 'POSITIVE.NUMBERS', 'NEGATIVE.NUMBERS', 'NUMBERS', 'POSITIVE.DECIMAL', 'NEGATIVE.DECIMAL', 'DECIMAL', 'PLANETS.CHARACTERS') @@ -203,18 +202,76 @@ init.testing.dataset.factor_levels.1 <- function() } else { - login.data <- DSLite::setupDATASETTest("dsBase", env = ds.test_env) - ds.test_env$login.data <- subset(login.data, server=="study1") + ds.test_env$login.data <- dslite.login.data(c("GROUP1"), c("FACTOR_LEVELS1")) } ds.test_env$stats.var <- list('ID', 'COLOURS', 'COLOURS.NUMBERS', 'POSITIVE.NUMBERS', 'NEGATIVE.NUMBERS', 'NUMBERS', 'POSITIVE.DECIMAL', 'NEGATIVE.DECIMAL', 'DECIMAL', 'PLANETS.CHARACTERS') } } +# DSLite reads methods only from inst/DATASHIELD; Opal/Rock also read the DESCRIPTION fields of every installed package, which is where e.g. dsDanger declares them +description.methods <- function() +{ + if (is.null(ds.test_env$description.methods)) + { + packages <- installed.packages(fields = c("AggregateMethods", "AssignMethods")) + packages <- packages[!duplicated(packages[, "Package"]), , drop = FALSE] + packages <- packages[!(is.na(packages[, "AggregateMethods"]) & is.na(packages[, "AssignMethods"])), , drop = FALSE] + packages <- packages[!nzchar(sapply(packages[, "Package"], system.file, file = "DATASHIELD")), , drop = FALSE] + ds.test_env$description.methods <- packages[, c("Package", "AggregateMethods", "AssignMethods"), drop = FALSE] + } + ds.test_env$description.methods +} + +register.description.methods <- function(server) +{ + methods <- description.methods() + for (i in seq_len(nrow(methods))) + { + package <- methods[i, "Package"] + for (method in setdiff(strsplit(gsub("\\s", "", methods[i, "AggregateMethods"]), ",")[[1]], NA)) + server$aggregateMethod(method, paste0(package, "::", method)) + for (method in setdiff(strsplit(gsub("\\s", "", methods[i, "AssignMethods"]), ",")[[1]], NA)) + server$assignMethod(method, paste0(package, "::", method)) + } +} + +# A real server keeps its R warnings to itself; DSLite raises them in the client session, where testthat records each one +DSLiteTestServer <- R6::R6Class("DSLiteTestServer", inherit = DSLite::DSLiteServer, + public = list( + aggregate = function(sid, expr) suppressWarnings(super$aggregate(sid, expr)), + assignExpr = function(sid, symbol, expr) suppressWarnings(super$assignExpr(sid, symbol, expr)) + ) +) +methods::setOldClass(c("DSLiteTestServer", "DSLiteServer")) + +# Serve the data_files tables that CI uploads to Opal/Armadillo, not DSLite's bundled copies, which hold different data +ci.tables <- function(table.names) +{ + files <- list.files("data_files", pattern = "[.]rda$", recursive = TRUE, full.names = TRUE) + tables <- lapply(table.names, function(table.name) { + file <- files[sub("[.]rda$", "", basename(files)) == sub("^.*[.]", "", table.name)] + if (length(file) != 1) + stop("No unique data_files table for DSLite table '", table.name, "'", call. = FALSE) + get(load(file, envir = environment())) + }) + setNames(tables, table.names) +} + log.in.data.server <- function() { + variables <- ds.test_env$stats.var + if (ds.test_env$driver == "DSLiteDriver") + { + ds.test_env$dslite.server <- DSLiteTestServer$new(tables = ci.tables(unique(ds.test_env$login.data$table))) + # Opal/Armadillo treat the variables filter as a set; DSLite would duplicate repeated columns as 'X.1' + variables <- unique(variables) + # CI servers run with 'default.datashield.privacyControlLevel' set to 'permissive' + ds.test_env$dslite.server$option("default.datashield.privacyControlLevel", deparse("permissive")) + register.description.methods(ds.test_env$dslite.server) + } # ds.test_env$connections <- datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=ds.test_env$stats.var, opts = getOption("datashield.opts", list(ssl_verifyhost=0, ssl_verifypeer=0))) - ds.test_env$connections <- datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=ds.test_env$stats.var, opts = getOption("datashield.opts")) + ds.test_env$connections <- datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=variables, opts = getOption("datashield.opts")) } @@ -305,3 +362,12 @@ disconnect.testing.dataset.factor_levels.1 <- function() { log.out.data.server() } + +dslite.login.data <- function(servers, tables) +{ + options(datashield.env = ds.test_env) + builder <- DSI::newDSLoginBuilder(.silent = TRUE) + for (i in seq_along(servers)) + builder$append(server = servers[[i]], url = "dslite.server", table = tables[[i]], driver = "DSLiteDriver") + builder$build() +} diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index e535a9e95..5360f2830 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -26,6 +26,9 @@ library(DSOpal) library(DSMolgenisArmadillo) library(DSLite) +# 'dgr-' files rely on an earlier file in the same session having attached dsDangerClient; attach it here so each file also runs on its own +if (requireNamespace("dsDangerClient", quietly = TRUE)) library(dsDangerClient) + source("dstest_functions/ds_expect_variables.R") source("perf_tests/perf_rate.R") source("connection_to_datasets/login_details.R") From 77f46e92a0c72ab23daa8f253e79212030018b3f Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:30:23 +0100 Subject: [PATCH 02/13] Accept DSLite environment name in ds.ls tests --- tests/testthat/test-smk-ds.ls.R | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tests/testthat/test-smk-ds.ls.R b/tests/testthat/test-smk-ds.ls.R index 91dd745a4..97aeab05b 100644 --- a/tests/testthat/test-smk-ds.ls.R +++ b/tests/testthat/test-smk-ds.ls.R @@ -17,6 +17,8 @@ connect.studies.dataset.cnsim(list("LAB_TSC", "LAB_TRIG")) +expected.environment <- if (ds.test_env$driver == "DSLiteDriver") "^DSLiteEnv_" else "^R_GlobalEnv$" + test_that("setup", { ds_expect_variables(c("D")) }) @@ -31,15 +33,15 @@ test_that("simple ls", { expect_length(res, 3) expect_length(res$sim1, 2) - expect_equal(res$sim1$environment.searched, "R_GlobalEnv") + expect_match(res$sim1$environment.searched, expected.environment) expect_length(res$sim1$objects.found, 1) expect_equal(res$sim1$objects.found[1], "D") expect_length(res$sim2, 2) - expect_equal(res$sim2$environment.searched, "R_GlobalEnv") + expect_match(res$sim2$environment.searched, expected.environment) expect_length(res$sim2$objects.found, 1) expect_equal(res$sim2$objects.found[1], "D") expect_length(res$sim3, 2) - expect_equal(res$sim3$environment.searched, "R_GlobalEnv") + expect_match(res$sim3$environment.searched, expected.environment) expect_length(res$sim3$objects.found, 1) expect_equal(res$sim3$objects.found[1], "D") }) @@ -49,13 +51,13 @@ test_that("simple ls, with envir", { expect_length(res, 3) expect_length(res$sim1, 3) - expect_equal(res$sim1$environment.searched, "R_GlobalEnv") + expect_match(res$sim1$environment.searched, expected.environment) expect_length(res$sim1$objects.found, 0) expect_length(res$sim2, 3) - expect_equal(res$sim2$environment.searched, "R_GlobalEnv") + expect_match(res$sim2$environment.searched, expected.environment) expect_length(res$sim2$objects.found, 0) expect_length(res$sim3, 3) - expect_equal(res$sim3$environment.searched, "R_GlobalEnv") + expect_match(res$sim3$environment.searched, expected.environment) expect_length(res$sim3$objects.found, 0) }) @@ -64,13 +66,13 @@ test_that("simple ls, with envir, with wildcard postfix", { expect_length(res, 3) expect_length(res$sim1, 3) - expect_equal(res$sim1$environment.searched, "R_GlobalEnv") + expect_match(res$sim1$environment.searched, expected.environment) expect_length(res$sim1$objects.found, 0) expect_length(res$sim2, 3) - expect_equal(res$sim2$environment.searched, "R_GlobalEnv") + expect_match(res$sim2$environment.searched, expected.environment) expect_length(res$sim2$objects.found, 0) expect_length(res$sim3, 3) - expect_equal(res$sim3$environment.searched, "R_GlobalEnv") + expect_match(res$sim3$environment.searched, expected.environment) expect_length(res$sim3$objects.found, 0) }) @@ -79,13 +81,13 @@ test_that("simple ls, with envir, with wildcard infix", { expect_length(res, 3) expect_length(res$sim1, 3) - expect_equal(res$sim1$environment.searched, "R_GlobalEnv") + expect_match(res$sim1$environment.searched, expected.environment) expect_length(res$sim1$objects.found, 0) expect_length(res$sim2, 3) - expect_equal(res$sim2$environment.searched, "R_GlobalEnv") + expect_match(res$sim2$environment.searched, expected.environment) expect_length(res$sim2$objects.found, 0) expect_length(res$sim3, 3) - expect_equal(res$sim3$environment.searched, "R_GlobalEnv") + expect_match(res$sim3$environment.searched, expected.environment) expect_length(res$sim3$objects.found, 0) }) @@ -94,13 +96,13 @@ test_that("simple ls, with envir, with wildcard prefix", { expect_length(res, 3) expect_length(res$sim1, 3) - expect_equal(res$sim1$environment.searched, "R_GlobalEnv") + expect_match(res$sim1$environment.searched, expected.environment) expect_length(res$sim1$objects.found, 0) expect_length(res$sim2, 3) - expect_equal(res$sim2$environment.searched, "R_GlobalEnv") + expect_match(res$sim2$environment.searched, expected.environment) expect_length(res$sim2$objects.found, 0) expect_length(res$sim3, 3) - expect_equal(res$sim3$environment.searched, "R_GlobalEnv") + expect_match(res$sim3$environment.searched, expected.environment) expect_length(res$sim3$objects.found, 0) }) From db6e4733a7a3c9c9e63419e0e8edd6414a65255d Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:30:35 +0100 Subject: [PATCH 03/13] Match server errors without the server's wrapper --- tests/testthat/test-arg-ds.dataFrameSort.R | 12 +++++----- tests/testthat/test-arg-ds.dataFrameSubset.R | 6 ++--- tests/testthat/test-arg-ds.glmSLMA.R | 6 ++--- tests/testthat/test-disc-ds.cor.R | 4 ++-- tests/testthat/test-disc-ds.sample.R | 24 ++++++++++---------- tests/testthat/test-disc-ds.table.R | 6 ++--- tests/testthat/test-smk-ds.sample.R | 6 ++--- tests/testthat/test-smk-ds.table.R | 8 +++---- tests/testthat/test-smk-isDefined.R | 18 +++++++-------- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/testthat/test-arg-ds.dataFrameSort.R b/tests/testthat/test-arg-ds.dataFrameSort.R index 52061313f..9f9ec8024 100644 --- a/tests/testthat/test-arg-ds.dataFrameSort.R +++ b/tests/testthat/test-arg-ds.dataFrameSort.R @@ -26,9 +26,9 @@ test_that("dataFrameSort_noargs", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$sim1, "* Error in strsplit\\(df.name, split = \"\"\\) : non-character argument*") - expect_match(res.errors$sim2, "* Error in strsplit\\(df.name, split = \"\"\\) : non-character argument*") - expect_match(res.errors$sim3, "* Error in strsplit\\(df.name, split = \"\"\\) : non-character argument*") + expect_match(res.errors$sim1, "non-character argument*") + expect_match(res.errors$sim2, "non-character argument*") + expect_match(res.errors$sim3, "non-character argument*") }) # context("ds.dataFrameSort::arg::sorted dataframe, of factors") @@ -41,9 +41,9 @@ test_that("dataFrameSort_factors", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$sim1, "* Error : specified sort.key variable is of type 'factor'*") - expect_match(res.errors$sim2, "* Error : specified sort.key variable is of type 'factor'*") - expect_match(res.errors$sim3, "* Error : specified sort.key variable is of type 'factor'*") + expect_match(res.errors$sim1, "specified sort.key variable is of type 'factor'*") + expect_match(res.errors$sim2, "specified sort.key variable is of type 'factor'*") + expect_match(res.errors$sim3, "specified sort.key variable is of type 'factor'*") }) # diff --git a/tests/testthat/test-arg-ds.dataFrameSubset.R b/tests/testthat/test-arg-ds.dataFrameSubset.R index 0799acf9f..dfce52dce 100644 --- a/tests/testthat/test-arg-ds.dataFrameSubset.R +++ b/tests/testthat/test-arg-ds.dataFrameSubset.R @@ -89,9 +89,9 @@ test_that("dataFrameSubset_erros", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$sim1, "* Error : FAILED: Boolean.operator must be: '==', '!=', '<', '<=', '>' or '>='*") - expect_match(res.errors$sim2, "* Error : FAILED: Boolean.operator must be: '==', '!=', '<', '<=', '>' or '>='*") - expect_match(res.errors$sim3, "* Error : FAILED: Boolean.operator must be: '==', '!=', '<', '<=', '>' or '>='*") + expect_match(res.errors$sim1, "FAILED: Boolean.operator must be: '==', '!=', '<', '<=', '>' or '>='*") + expect_match(res.errors$sim2, "FAILED: Boolean.operator must be: '==', '!=', '<', '<=', '>' or '>='*") + expect_match(res.errors$sim3, "FAILED: Boolean.operator must be: '==', '!=', '<', '<=', '>' or '>='*") }) # diff --git a/tests/testthat/test-arg-ds.glmSLMA.R b/tests/testthat/test-arg-ds.glmSLMA.R index 4180f91af..0b63e1f89 100644 --- a/tests/testthat/test-arg-ds.glmSLMA.R +++ b/tests/testthat/test-arg-ds.glmSLMA.R @@ -27,9 +27,9 @@ test_that("glmSLMA_errors", { res.errors<-datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$sim1, "* Error in model\\.frame\\.default\\(formula = formula2use, data = dataTable, : \n invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") - expect_match(res.errors$sim2, "* Error in model\\.frame\\.default\\(formula = formula2use, data = dataTable, : \n invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") - expect_match(res.errors$sim3, "* Error in model\\.frame\\.default\\(formula = formula2use, data = dataTable, : \n invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") + expect_match(res.errors$sim1, "invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") + expect_match(res.errors$sim2, "invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") + expect_match(res.errors$sim3, "invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") }) # diff --git a/tests/testthat/test-disc-ds.cor.R b/tests/testthat/test-disc-ds.cor.R index 88da21bbd..ce6045432 100644 --- a/tests/testthat/test-disc-ds.cor.R +++ b/tests/testthat/test-disc-ds.cor.R @@ -54,7 +54,7 @@ test_that("simple disc test, type='split'", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 1) - expect_match(res.errors$sim2, "* Error : ERROR: The ratio of the number of variables over the number of individual-level\\n records exceeds the allowed threshold, there is a possible risk of disclosure") + expect_match(res.errors$sim2, "ERROR: The ratio of the number of variables over the number of individual-level\\n records exceeds the allowed threshold, there is a possible risk of disclosure") }) test_that("simple disc test, naAction='pairwise.complete', type='split'", { @@ -79,7 +79,7 @@ test_that("simple disc test, type='combine'", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 1) - expect_match(res.errors$sim2, "* Error : ERROR: The ratio of the number of variables over the number of individual-level\\n records exceeds the allowed threshold, there is a possible risk of disclosure") + expect_match(res.errors$sim2, "ERROR: The ratio of the number of variables over the number of individual-level\\n records exceeds the allowed threshold, there is a possible risk of disclosure") # expect_length(res, 5) # expect_length(res$`Error message`, 3) diff --git a/tests/testthat/test-disc-ds.sample.R b/tests/testthat/test-disc-ds.sample.R index ce74288a7..10c622841 100644 --- a/tests/testthat/test-disc-ds.sample.R +++ b/tests/testthat/test-disc-ds.sample.R @@ -27,38 +27,38 @@ test_that("cov_erros", { res1.errors <- DSI::datashield.errors() expect_length(res1.errors, 3) - expect_match(res1.errors$sim1, "* Error : FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") - expect_match(res1.errors$sim2, "* Error : FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") - expect_match(res1.errors$sim3, "* Error : FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") + expect_match(res1.errors$sim1, "FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") + expect_match(res1.errors$sim2, "FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") + expect_match(res1.errors$sim3, "FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") expect_error(ds.sample(x="D", size=1234, prob="This_line_is_very_very_very_very_very_very_long", newobj="obj2"), "There are some DataSHIELD errors, list them with datashield.errors()", fixed = TRUE) res2.errors <- DSI::datashield.errors() expect_length(res2.errors, 3) - expect_match(res2.errors$sim1, "* Error : FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") - expect_match(res2.errors$sim2, "* Error : FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") - expect_match(res2.errors$sim3, "* Error : FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") + expect_match(res2.errors$sim1, "FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") + expect_match(res2.errors$sim2, "FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") + expect_match(res2.errors$sim3, "FAILED: the character string denoting the argument is too long and may be disclosive - please shorten") expect_error(ds.sample(x="D", size=2, newobj="obj3"), "There are some DataSHIELD errors, list them with datashield.errors()", fixed = TRUE) res3.errors <- DSI::datashield.errors() expect_length(res3.errors, 3) - expect_match(res3.errors$sim1, "* Error : FAILED: disclosure risk, as the length of the subset to be created is less than nfilter.subset") - expect_match(res3.errors$sim2, "* Error : FAILED: disclosure risk, as the length of the subset to be created is less than nfilter.subset") - expect_match(res3.errors$sim3, "* Error : FAILED: disclosure risk, as the length of the subset to be created is less than nfilter.subset") + expect_match(res3.errors$sim1, "FAILED: disclosure risk, as the length of the subset to be created is less than nfilter.subset") + expect_match(res3.errors$sim2, "FAILED: disclosure risk, as the length of the subset to be created is less than nfilter.subset") + expect_match(res3.errors$sim3, "FAILED: disclosure risk, as the length of the subset to be created is less than nfilter.subset") expect_error(ds.sample(x="D", size=2162, newobj="obj4"), "There are some DataSHIELD errors, list them with datashield.errors()", fixed = TRUE) res4.errors <- DSI::datashield.errors() expect_length(res4.errors, 1) - expect_match(res4.errors$sim1, "* Error : FAILED: disclosure risk using differencing: original object length minus subset length less than nfilter.subset") + expect_match(res4.errors$sim1, "FAILED: disclosure risk using differencing: original object length minus subset length less than nfilter.subset") expect_error(ds.sample(x="D", size=3086, newobj="obj5"), "There are some DataSHIELD errors, list them with datashield.errors()", fixed = TRUE) res5.errors <- DSI::datashield.errors() expect_length(res5.errors, 2) - expect_match(res5.errors$sim1, "* Error : FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)") - expect_match(res5.errors$sim2, "* Error : FAILED: disclosure risk using differencing: original object length minus subset length less than nfilter.subset") + expect_match(res5.errors$sim1, "FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)") + expect_match(res5.errors$sim2, "FAILED: disclosure risk using differencing: original object length minus subset length less than nfilter.subset") }) # diff --git a/tests/testthat/test-disc-ds.table.R b/tests/testthat/test-disc-ds.table.R index b51a75f91..0b7a474d7 100644 --- a/tests/testthat/test-disc-ds.table.R +++ b/tests/testthat/test-disc-ds.table.R @@ -63,9 +63,9 @@ test_that("table rvar is big numeric", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$study1, "* Error : FAILED: this variable has too many levels and may be disclosive. It exceeds the max number of levels allowed by nfilter.levels.max: that is 40. In this study this variable has 71 factor levels") - expect_match(res.errors$study2, "* Error : FAILED: this variable has too many levels and may be disclosive. It exceeds the max number of levels allowed by nfilter.levels.max: that is 40. In this study this variable has 71 factor levels") - expect_match(res.errors$study3, "* Error : FAILED: this variable has too many levels and may be disclosive. It exceeds the max number of levels allowed by nfilter.levels.max: that is 40. In this study this variable has 71 factor levels") + expect_match(res.errors$study1, "FAILED: this variable has too many levels and may be disclosive. It exceeds the max number of levels allowed by nfilter.levels.max: that is 40. In this study this variable has 71 factor levels") + expect_match(res.errors$study2, "FAILED: this variable has too many levels and may be disclosive. It exceeds the max number of levels allowed by nfilter.levels.max: that is 40. In this study this variable has 71 factor levels") + expect_match(res.errors$study3, "FAILED: this variable has too many levels and may be disclosive. It exceeds the max number of levels allowed by nfilter.levels.max: that is 40. In this study this variable has 71 factor levels") }) # diff --git a/tests/testthat/test-smk-ds.sample.R b/tests/testthat/test-smk-ds.sample.R index aab0dc935..0686e8589 100644 --- a/tests/testthat/test-smk-ds.sample.R +++ b/tests/testthat/test-smk-ds.sample.R @@ -117,9 +117,9 @@ test_that("simple test, error", { res.errors <- DSI::datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$survival1, "* Error : FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)*") - expect_match(res.errors$survival2, "* Error : FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)*") - expect_match(res.errors$survival3, "* Error : FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)*") + expect_match(res.errors$survival1, "FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)*") + expect_match(res.errors$survival2, "FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)*") + expect_match(res.errors$survival3, "FAILED: if sampling without replacement size must be less than or equal to length\\(x\\)*") }) # context("ds.sample::smk::test nonexistent object") diff --git a/tests/testthat/test-smk-ds.table.R b/tests/testthat/test-smk-ds.table.R index f944e1e59..16f63f365 100644 --- a/tests/testthat/test-smk-ds.table.R +++ b/tests/testthat/test-smk-ds.table.R @@ -92,8 +92,8 @@ test_that("simple table 3D", { expect_length(res.errors, 2) - expect_match(res.errors$study1, "* Error : Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") - expect_match(res.errors$study2, "* Error : Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") + expect_match(res.errors$study1, "Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") + expect_match(res.errors$study2, "Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") }) test_that("simple table 1D, with assign", { @@ -142,8 +142,8 @@ test_that("simple table 3D, with assign", { expect_length(res.errors, 2) - expect_match(res.errors$study1, "* Error : Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") - expect_match(res.errors$study2, "* Error : Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") + expect_match(res.errors$study1, "Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") + expect_match(res.errors$study2, "Failed: at least one cell has a non-zero count less than nfilter.tab i.e. 3*") }) # diff --git a/tests/testthat/test-smk-isDefined.R b/tests/testthat/test-smk-isDefined.R index 606fbdfe9..77ffa32c0 100644 --- a/tests/testthat/test-smk-isDefined.R +++ b/tests/testthat/test-smk-isDefined.R @@ -51,9 +51,9 @@ test_that("default test, dataframe column E$A", { expect_length(class(err), 1) expect_true(all("list" %in% class(err))) expect_length(err, 3) - expect_match(err$discordant1, "* : object 'E' not found") - expect_match(err$discordant2, "* : object 'E' not found") - expect_match(err$discordant3, "* : object 'E' not found") + expect_match(err$discordant1, "object 'E' not found") + expect_match(err$discordant2, "object 'E' not found") + expect_match(err$discordant3, "object 'E' not found") }) test_that("default test, dataframe column D$A", { @@ -122,9 +122,9 @@ test_that("error.message=FALSE test, dataframe column E$A", { expect_length(class(err), 1) expect_true(all("list" %in% class(err))) expect_length(err, 3) - expect_match(err$discordant1, "* : object 'E' not found") - expect_match(err$discordant2, "* : object 'E' not found") - expect_match(err$discordant3, "* : object 'E' not found") + expect_match(err$discordant1, "object 'E' not found") + expect_match(err$discordant2, "object 'E' not found") + expect_match(err$discordant3, "object 'E' not found") }) test_that("error.message=FALSE test, dataframe column D$A", { @@ -221,9 +221,9 @@ test_that("error.message=TRUE test, dataframe column E$A", { expect_length(class(err), 1) expect_true(all("list" %in% class(err))) expect_length(err, 3) - expect_match(err$discordant1, "* : object 'E' not found") - expect_match(err$discordant2, "* : object 'E' not found") - expect_match(err$discordant3, "* : object 'E' not found") + expect_match(err$discordant1, "object 'E' not found") + expect_match(err$discordant2, "object 'E' not found") + expect_match(err$discordant3, "object 'E' not found") }) test_that("error.message=TRUE test, dataframe column D$A", { From 811445539045f6da4fa667c9396ef56f96db9d89 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:30:47 +0100 Subject: [PATCH 04/13] Add DSLite cases to error checks --- tests/testthat/test-arg-ds.foobar.R | 40 ++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-arg-ds.foobar.R b/tests/testthat/test-arg-ds.foobar.R index 19f959f28..c24e87cbe 100644 --- a/tests/testthat/test-arg-ds.foobar.R +++ b/tests/testthat/test-arg-ds.foobar.R @@ -32,6 +32,8 @@ test_that("NULL connections", { expect_error(datashield.aggregate(conns=NULL, expr=calltext), "unable to find an inherited method for function 'dsIsAsync' for signature 'conn = \"NULL\"'", fixed=TRUE) } else if (ds.test_env$driver == "OpalDriver") { expect_error(datashield.aggregate(conns=NULL, expr=calltext), "unable to find an inherited method for function 'dsIsAsync' for signature 'conn = \"NULL\"'", fixed=TRUE) + } else if (ds.test_env$driver == "DSLiteDriver") { + expect_error(datashield.aggregate(conns=NULL, expr=calltext), "unable to find an inherited method for function 'dsIsAsync' for signature 'conn = \"NULL\"'", fixed=TRUE) } else { fail(message = "Unknown driver type", info = ds.test_env$driver) } @@ -58,11 +60,23 @@ test_that("non existent aggregate foobarDS", { expect_length(errs, 3) expect_length(errs$sim1, 1) - expect_true(errs$sim1 %in% c("Command 'fooBarDS()' failed on 'sim1': No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "Bad request: No such DataSHIELD 'AGGREGATE' method with name: fooBarDS")) + if (ds.test_env$driver == "DSLiteDriver") { + expect_match(errs$sim1, "^DataSHIELD configuration does not allow expression: fooBarDS\n") + } else { + expect_true(errs$sim1 %in% c("Command 'fooBarDS()' failed on 'sim1': No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "Bad request: No such DataSHIELD 'AGGREGATE' method with name: fooBarDS")) + } expect_length(errs$sim2, 1) - expect_true(errs$sim2 %in% c("Command 'fooBarDS()' failed on 'sim2': No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "Bad request: No such DataSHIELD 'AGGREGATE' method with name: fooBarDS")) + if (ds.test_env$driver == "DSLiteDriver") { + expect_match(errs$sim2, "^DataSHIELD configuration does not allow expression: fooBarDS\n") + } else { + expect_true(errs$sim2 %in% c("Command 'fooBarDS()' failed on 'sim2': No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "Bad request: No such DataSHIELD 'AGGREGATE' method with name: fooBarDS")) + } expect_length(errs$sim3, 1) - expect_true(errs$sim3 %in% c("Command 'fooBarDS()' failed on 'sim3': No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "Bad request: No such DataSHIELD 'AGGREGATE' method with name: fooBarDS")) + if (ds.test_env$driver == "DSLiteDriver") { + expect_match(errs$sim3, "^DataSHIELD configuration does not allow expression: fooBarDS\n") + } else { + expect_true(errs$sim3 %in% c("Command 'fooBarDS()' failed on 'sim3': No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'AGGREGATE' method with name: fooBarDS", "Bad request: No such DataSHIELD 'AGGREGATE' method with name: fooBarDS")) + } }) # context("ds.foobar::arg::assign") @@ -72,6 +86,8 @@ test_that("NULL connections", { expect_error(datashield.assign(conns=NULL, symbol="new_obj", value=calltext), "unable to find an inherited method for function 'dsIsAsync' for signature 'conn = \"NULL\"'", fixed=TRUE) } else if (ds.test_env$driver == "OpalDriver") { expect_error(datashield.assign(conns=NULL, symbol="new_obj", value=calltext), "unable to find an inherited method for function 'dsIsAsync' for signature 'conn = \"NULL\"'", fixed=TRUE) + } else if (ds.test_env$driver == "DSLiteDriver") { + expect_error(datashield.assign(conns=NULL, symbol="new_obj", value=calltext), "unable to find an inherited method for function 'dsIsAsync' for signature 'conn = \"NULL\"'", fixed=TRUE) } else { fail(message = "Unknown driver type", info = ds.test_env$driver) } @@ -105,11 +121,23 @@ test_that("non existent assign foobarDS", { errs <- datashield.errors() expect_length(errs, 3) expect_length(errs$sim1, 1) - expect_true(errs$sim1 %in% c("Command 'fooBarDS()' failed on 'sim1': No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "Bad request: No such DataSHIELD 'ASSIGN' method with name: fooBarDS")) + if (ds.test_env$driver == "DSLiteDriver") { + expect_match(errs$sim1, "^DataSHIELD configuration does not allow expression: fooBarDS\n") + } else { + expect_true(errs$sim1 %in% c("Command 'fooBarDS()' failed on 'sim1': No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "Bad request: No such DataSHIELD 'ASSIGN' method with name: fooBarDS")) + } expect_length(errs$sim2, 1) - expect_true(errs$sim2 %in% c("Command 'fooBarDS()' failed on 'sim2': No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "Bad request: No such DataSHIELD 'ASSIGN' method with name: fooBarDS")) + if (ds.test_env$driver == "DSLiteDriver") { + expect_match(errs$sim2, "^DataSHIELD configuration does not allow expression: fooBarDS\n") + } else { + expect_true(errs$sim2 %in% c("Command 'fooBarDS()' failed on 'sim2': No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "Bad request: No such DataSHIELD 'ASSIGN' method with name: fooBarDS")) + } expect_length(errs$sim3, 1) - expect_true(errs$sim3 %in% c("Command 'fooBarDS()' failed on 'sim3': No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "Bad request: No such DataSHIELD 'ASSIGN' method with name: fooBarDS")) + if (ds.test_env$driver == "DSLiteDriver") { + expect_match(errs$sim3, "^DataSHIELD configuration does not allow expression: fooBarDS\n") + } else { + expect_true(errs$sim3 %in% c("Command 'fooBarDS()' failed on 'sim3': No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "[Client error: (400) Bad Request] No such DataSHIELD 'ASSIGN' method with name: fooBarDS", "Bad request: No such DataSHIELD 'ASSIGN' method with name: fooBarDS")) + } }) # From cf633d404d01a0c59852017b68d6b3dc7b036246 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:30:58 +0100 Subject: [PATCH 05/13] Skip tests that DSLite cannot run --- tests/testthat/test-_-vm-test.R | 1 + tests/testthat/test-expt_dgr-ds.dataFrameSort.R | 4 ++++ tests/testthat/test-expt_dgr-ds.dataFrameSubset.R | 6 ++++++ tests/testthat/test-math-ds.mean.R | 2 ++ tests/testthat/test-math-ds.var.R | 2 ++ tests/testthat/test-math_dgr-ds.skewness.R | 2 ++ tests/testthat/test-smk-ds.igb_standards.R | 2 ++ tests/testthat/test-smk-ds.metadata.R | 1 + tests/testthat/test-smk_dgr-ds.recodeValues-factor.R | 2 ++ .../testthat/test-smk_dgr-ds.recodeValues-factor_missing.R | 2 ++ tests/testthat/test-smk_dgr-ds.recodeValues.R | 2 ++ 11 files changed, 26 insertions(+) diff --git a/tests/testthat/test-_-vm-test.R b/tests/testthat/test-_-vm-test.R index 7b5af1f4e..03ac32339 100644 --- a/tests/testthat/test-_-vm-test.R +++ b/tests/testthat/test-_-vm-test.R @@ -26,6 +26,7 @@ init.testing.datasets() test_that("The virtual machine is loaded. ", { + skip_if(ds.test_env$driver == "DSLiteDriver", "there is no Opal virtual machine under DSLite") response <- httr::HEAD(url=ds.test_env$ping_address, config=ds.test_env$ping_config) expect_true(http_status(response)$reason %in% c("OK", "Unauthorized")) }) diff --git a/tests/testthat/test-expt_dgr-ds.dataFrameSort.R b/tests/testthat/test-expt_dgr-ds.dataFrameSort.R index 9ff0bca2b..7c5439df8 100644 --- a/tests/testthat/test-expt_dgr-ds.dataFrameSort.R +++ b/tests/testthat/test-expt_dgr-ds.dataFrameSort.R @@ -131,6 +131,7 @@ test_that('dataset 3', # context("ds.dataFrameSort::expt_dgr::multiple::numeric::ascending") test_that('all datasets', { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.all.datasets() all.local.values <- list(ds.test_env$local.values.1, ds.test_env$local.values.2, ds.test_env$local.values.3) .test.data.frame.sorting("D","LOGICAL",FALSE,"numeric","server.data",all.local.values) @@ -152,6 +153,7 @@ test_that('all datasets', # context("ds.dataFrameSort::expt_dgr::single::numeric::ascending") test_that('dataset 1', { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.dataset.1() all.local.values <- list(ds.test_env$local.values.1) .test.data.frame.sorting("D","LOGICAL",FALSE,"numeric","server.data",all.local.values) @@ -173,6 +175,7 @@ test_that('dataset 1', # context("ds.dataFrameSort::expt_dgr::multiple::numeric::descending") test_that('all datasets', { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.all.datasets() all.local.values <- list(ds.test_env$local.values.1, ds.test_env$local.values.2, ds.test_env$local.values.3) .test.data.frame.sorting("D","LOGICAL",TRUE,"numeric","server.data",all.local.values) @@ -196,6 +199,7 @@ test_that('all datasets', # context("ds.dataFrameSort::expt_dgr::single::numeric::descending") test_that('dataset 1', { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.dataset.1() all.local.values <- list(ds.test_env$local.values.1) .test.data.frame.sorting("D","LOGICAL",TRUE,"numeric","server.data",all.local.values) diff --git a/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R b/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R index 278403ec9..87b358350 100644 --- a/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R +++ b/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R @@ -127,6 +127,7 @@ test_that("dataset 3", # context("ds.dataFrameSubset::expt_dgr::multiple::subset_by_rows_columns") test_that("all datasets", { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.all.datasets() local.df.list<-list(ds.test_env$local.values.1,ds.test_env$local.values.2,ds.test_env$local.values.3) subset.by.rows.cols("D","INTEGER","NUMERIC",1:4,">",TRUE,"subset.server",local.df.list) @@ -146,6 +147,7 @@ test_that("all datasets", # context("ds.dataFrameSubset::expt_dgr::single::subset_by_rows_columns") test_that("dataset 1", { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.dataset.1() local.df.list<-list(ds.test_env$local.values.1) subset.by.rows.cols("D","INTEGER","NUMERIC",1:4,">",TRUE,"subset.server",local.df.list) @@ -166,6 +168,7 @@ test_that("dataset 1", # context("ds.dataFrameSubset::expt_dgr::multiple::subset_by_rows_columns") test_that("all datasets", { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.all.datasets() local.df.list<-list(ds.test_env$local.values.1,ds.test_env$local.values.2,ds.test_env$local.values.3) subset.by.rows.cols("D","INTEGER","NUMERIC",1:4,">",TRUE,"subset.server",local.df.list) @@ -184,6 +187,7 @@ test_that("all datasets", # context("ds.dataFrameSubset::expt_dgr::single::subset_by_rows_columns") test_that("dataset 2", { + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") connect.dataset.2() local.df.list<-list(ds.test_env$local.values.2) subset.by.rows.cols("D","INTEGER","NUMERIC",1:4,">",TRUE,"subset.server",local.df.list) @@ -203,6 +207,7 @@ test_that("dataset 2", # context("ds.dataFrameSubset::expt_dgr::multiple::all_rows_subset_by_columns") test_that("all datasets", { connect.all.datasets() + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") local.df.list<-list(ds.test_env$local.values.1,ds.test_env$local.values.2,ds.test_env$local.values.3) subset.by.cols("D","INTEGER",1:4,TRUE,"subset.server",local.df.list) subset.by.cols("D","NEGATIVE_INTEGER",c(2,5,10:15),FALSE,"subset.server",local.df.list) @@ -215,6 +220,7 @@ test_that("all datasets", # context("ds.dataFrameSubset::expt_dgr::multiple::all_rows_subset_by_columns") test_that("dataset 3", { connect.dataset.3() + skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") local.df.list<-list(ds.test_env$local.values.3) subset.by.cols("D","INTEGER",1:4,TRUE,"subset.server",local.df.list) subset.by.cols("D","NEGATIVE_INTEGER",c(2,5,10:15),FALSE,"subset.server",local.df.list) diff --git a/tests/testthat/test-math-ds.mean.R b/tests/testthat/test-math-ds.mean.R index 4c2a15593..3a749dc22 100644 --- a/tests/testthat/test-math-ds.mean.R +++ b/tests/testthat/test-math-ds.mean.R @@ -65,6 +65,7 @@ test_that("residual deviation tends to 0", # context("ds.mean::math::location_parameter::single") test_that("mean(X+a) - mean(X) = a", { + skip_if(ds.test_env$driver == "DSLiteDriver", "DSLite's parser misreads '+' in expressions as a signed number") connect.dataset.1() .test.location.parameter('D$INTEGER') @@ -80,6 +81,7 @@ test_that("mean(X+a) - mean(X) = a", # context("ds.mean::math::location_parameter::multiple") test_that("mean(X+a) - mean(X) = a", { + skip_if(ds.test_env$driver == "DSLiteDriver", "DSLite's parser misreads '+' in expressions as a signed number") connect.all.datasets() .test.location.parameter('D$INTEGER') diff --git a/tests/testthat/test-math-ds.var.R b/tests/testthat/test-math-ds.var.R index e281c5d62..9d5557905 100644 --- a/tests/testthat/test-math-ds.var.R +++ b/tests/testthat/test-math-ds.var.R @@ -102,6 +102,7 @@ test_that("variance is to the power of 2 of the standard deviation", # context("ds.var::math::location::parameter::single") test_that("var (X+a) = Var(X)", { + skip_if(ds.test_env$driver == "DSLiteDriver", "DSLite's parser misreads '+' in expressions as a signed number") connect.dataset.1() .test.location.parameter('D$INTEGER') .test.location.parameter('D$NON_NEGATIVE_INTEGER') @@ -116,6 +117,7 @@ test_that("var (X+a) = Var(X)", # context("ds.var::math::location::parameter::multiple") test_that("var (X+a) = Var(X)", { + skip_if(ds.test_env$driver == "DSLiteDriver", "DSLite's parser misreads '+' in expressions as a signed number") connect.all.datasets() .test.location.parameter('D$INTEGER') .test.location.parameter('D$NON_NEGATIVE_INTEGER') diff --git a/tests/testthat/test-math_dgr-ds.skewness.R b/tests/testthat/test-math_dgr-ds.skewness.R index d743de0b1..feec02010 100644 --- a/tests/testthat/test-math_dgr-ds.skewness.R +++ b/tests/testthat/test-math_dgr-ds.skewness.R @@ -15,6 +15,7 @@ source("definition_tests/def-ds.skewness.R") # context("ds.skewness::math_dgr::mean.median.mode::multiple") test_that("skewness >0 & mode0 & mode0 & Q3-Q2>Q2-Q1 or skewness<0 & Q3-Q2' in expressions as a signed number") + connect.studies.dataset.anthro(list("age", "sex", "weight", "height", "muac")) test_that("setup", { diff --git a/tests/testthat/test-smk-ds.metadata.R b/tests/testthat/test-smk-ds.metadata.R index 6394bb9d3..6669e238c 100644 --- a/tests/testthat/test-smk-ds.metadata.R +++ b/tests/testthat/test-smk-ds.metadata.R @@ -69,6 +69,7 @@ test_that("data.frame metadata", { }) test_that("column metadata", { + skip_if(ds.test_env$driver == "DSLiteDriver", "DSLite has no Opal variable metadata") res <- ds.metadata(x='D$LAB_TSC') if (ds.test_env$driver == "ArmadilloDriver") { diff --git a/tests/testthat/test-smk_dgr-ds.recodeValues-factor.R b/tests/testthat/test-smk_dgr-ds.recodeValues-factor.R index ae3defa68..395c7a3d5 100644 --- a/tests/testthat/test-smk_dgr-ds.recodeValues-factor.R +++ b/tests/testthat/test-smk_dgr-ds.recodeValues-factor.R @@ -15,6 +15,8 @@ # context("ds.recodeValues::smk_dgr::factor::setup") +skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") + connect.studies.dataset.cnsim(list("GENDER", "PM_BMI_CATEGORICAL")) test_that("setup", { diff --git a/tests/testthat/test-smk_dgr-ds.recodeValues-factor_missing.R b/tests/testthat/test-smk_dgr-ds.recodeValues-factor_missing.R index e091080e4..de3e7a844 100644 --- a/tests/testthat/test-smk_dgr-ds.recodeValues-factor_missing.R +++ b/tests/testthat/test-smk_dgr-ds.recodeValues-factor_missing.R @@ -15,6 +15,8 @@ # context("ds.recodeValues::smk_dgr::factor_missing::setup") +skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") + connect.studies.dataset.cnsim(list("GENDER", "PM_BMI_CATEGORICAL")) test_that("setup", { diff --git a/tests/testthat/test-smk_dgr-ds.recodeValues.R b/tests/testthat/test-smk_dgr-ds.recodeValues.R index 0828712fa..9e5bed625 100644 --- a/tests/testthat/test-smk_dgr-ds.recodeValues.R +++ b/tests/testthat/test-smk_dgr-ds.recodeValues.R @@ -15,6 +15,8 @@ # context("ds.recodeValues::smk_dgr::setup") +skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") + connect.studies.dataset.survival(list("time.id")) test_that("setup", { From b186d71510227c3791a855a33132196dc93081fd Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:31:11 +0100 Subject: [PATCH 06/13] Add DSLite job to the test workflow --- .../workflows/dsBaseClient_test_suite.yaml | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/.github/workflows/dsBaseClient_test_suite.yaml b/.github/workflows/dsBaseClient_test_suite.yaml index c094b4a41..1f202dafb 100644 --- a/.github/workflows/dsBaseClient_test_suite.yaml +++ b/.github/workflows/dsBaseClient_test_suite.yaml @@ -837,3 +837,73 @@ jobs: 'log:coverage': `Codecov` }}); + + ################################################################################ + # DSLite - the servers run inside the R session, so dsBase is installed into + # the runner's R library instead of a server. Runs the same files as the Opal + # and Armadillo shards, except perf, as DSLite timings are not comparable; + # DSLite is fast enough to run them all in one job. + ################################################################################ + dslite-dsbase: + name: DSLite tests + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + DS_DRIVER: DSLiteDriver + DSDANGER_REF: '6.3.4' + TEST_FILTER: 'smk-ds.[a-lA-L]|smk-(checkClass|isDefined)|smk-ds.[m-vM-V]|arg-|datachk-|disc-|expt-|smk_expt-|math-|_-|__dgr-|datachk_dgr-|smk_dgr-|arg_dgr-|disc_dgr-|smk_expt_dgr-|expt_dgr-|math_dgr-' + steps: + - name: Checkout dsBaseClient + uses: actions/checkout@v5 + with: + path: dsBaseClient + + - uses: ./dsBaseClient/.github/actions/setup-r-with-dsbase-deps + + - name: Check out dsBase + uses: actions/checkout@v5 + with: + repository: datashield/dsBase + ref: ${{ env.DSBASE_REF }} + path: dsBase + + - name: Install dsBase + run: R -q -e 'pak::pkg_install("local::./dsBase")' + + - name: Install dsDanger + run: R -q -e 'pak::pkg_install("github::datashield/dsDanger@${{ env.DSDANGER_REF }}")' + + - name: Run tests with JUnit report + run: | + R -q -e ' + devtools::load_all(quiet = TRUE); + library(testthat); + output_file <- file("test_console_output.txt"); + sink(output_file, split = TRUE); + junit_rep <- JunitReporter$new(file = file.path(getwd(), "test_results.xml")); + progress_rep <- ProgressReporter$new(max_failures = 999999); + multi_rep <- MultiReporter$new(reporters = list(progress_rep, junit_rep)); + options("datashield.return_errors" = FALSE, "default_driver" = "${{ env.DS_DRIVER }}"); + test_dir("tests/testthat", filter = "${{ env.TEST_FILTER }}", reporter = multi_rep, stop_on_failure = FALSE)' || R_EXIT=$? + cat test_console_output.txt + n_tests=$(grep -c ' entries in test_results.xml) - treating as a failure rather than a silent pass." + R_EXIT=1 + fi + n_failed=$(grep -oE '<(failure|error)[ >]' test_results.xml | wc -l | tr -d ' ') + if [ "${R_EXIT:-0}" -eq 0 ] && [ "${n_failed:-0}" -gt 0 ]; then + echo "$n_failed test failure(s)/error(s) in test_results.xml - failing this shard." + R_EXIT=1 + fi + exit "${R_EXIT:-0}" + working-directory: dsBaseClient + + - name: Upload shard results + if: always() + uses: actions/upload-artifact@v6 + with: + name: dslite + path: | + dsBaseClient/test_results.xml + dsBaseClient/test_console_output.txt From 06eca4c4097f29d7ca64a2b2d58f63f2b85f5f6a Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:29:53 +0100 Subject: [PATCH 07/13] Skip DSLite tests before connecting and fix lint --- .../init_testing_datasets.R | 18 +++++++++--------- .../test-expt_dgr-ds.dataFrameSubset.R | 6 ++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/testthat/connection_to_datasets/init_testing_datasets.R b/tests/testthat/connection_to_datasets/init_testing_datasets.R index 920d3ae21..7ed058bd7 100644 --- a/tests/testthat/connection_to_datasets/init_testing_datasets.R +++ b/tests/testthat/connection_to_datasets/init_testing_datasets.R @@ -217,7 +217,7 @@ description.methods <- function() packages <- installed.packages(fields = c("AggregateMethods", "AssignMethods")) packages <- packages[!duplicated(packages[, "Package"]), , drop = FALSE] packages <- packages[!(is.na(packages[, "AggregateMethods"]) & is.na(packages[, "AssignMethods"])), , drop = FALSE] - packages <- packages[!nzchar(sapply(packages[, "Package"], system.file, file = "DATASHIELD")), , drop = FALSE] + packages <- packages[!nzchar(vapply(packages[, "Package"], system.file, character(1), file = "DATASHIELD")), , drop = FALSE] ds.test_env$description.methods <- packages[, c("Package", "AggregateMethods", "AssignMethods"), drop = FALSE] } ds.test_env$description.methods @@ -225,13 +225,13 @@ description.methods <- function() register.description.methods <- function(server) { - methods <- description.methods() - for (i in seq_len(nrow(methods))) + declared <- description.methods() + for (i in seq_len(nrow(declared))) { - package <- methods[i, "Package"] - for (method in setdiff(strsplit(gsub("\\s", "", methods[i, "AggregateMethods"]), ",")[[1]], NA)) + package <- declared[i, "Package"] + for (method in setdiff(strsplit(gsub("\\s", "", declared[i, "AggregateMethods"]), ",")[[1]], NA)) server$aggregateMethod(method, paste0(package, "::", method)) - for (method in setdiff(strsplit(gsub("\\s", "", methods[i, "AssignMethods"]), ",")[[1]], NA)) + for (method in setdiff(strsplit(gsub("\\s", "", declared[i, "AssignMethods"]), ",")[[1]], NA)) server$assignMethod(method, paste0(package, "::", method)) } } @@ -250,10 +250,10 @@ ci.tables <- function(table.names) { files <- list.files("data_files", pattern = "[.]rda$", recursive = TRUE, full.names = TRUE) tables <- lapply(table.names, function(table.name) { - file <- files[sub("[.]rda$", "", basename(files)) == sub("^.*[.]", "", table.name)] - if (length(file) != 1) + tableFile <- files[sub("[.]rda$", "", basename(files)) == sub("^.*[.]", "", table.name)] + if (length(tableFile) != 1) stop("No unique data_files table for DSLite table '", table.name, "'", call. = FALSE) - get(load(file, envir = environment())) + get(load(tableFile, envir = environment())) }) setNames(tables, table.names) } diff --git a/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R b/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R index 87b358350..198a5fc20 100644 --- a/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R +++ b/tests/testthat/test-expt_dgr-ds.dataFrameSubset.R @@ -206,8 +206,9 @@ test_that("dataset 2", # context("ds.dataFrameSubset::expt_dgr::multiple::all_rows_subset_by_columns") test_that("all datasets", - { connect.all.datasets() + { skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") + connect.all.datasets() local.df.list<-list(ds.test_env$local.values.1,ds.test_env$local.values.2,ds.test_env$local.values.3) subset.by.cols("D","INTEGER",1:4,TRUE,"subset.server",local.df.list) subset.by.cols("D","NEGATIVE_INTEGER",c(2,5,10:15),FALSE,"subset.server",local.df.list) @@ -219,8 +220,9 @@ test_that("all datasets", # context("ds.dataFrameSubset::expt_dgr::multiple::all_rows_subset_by_columns") test_that("dataset 3", - { connect.dataset.3() + { skip_if(ds.test_env$driver == "DSLiteDriver", "dsDanger server functions look objects up outside the DSLite session") + connect.dataset.3() local.df.list<-list(ds.test_env$local.values.3) subset.by.cols("D","INTEGER",1:4,TRUE,"subset.server",local.df.list) subset.by.cols("D","NEGATIVE_INTEGER",c(2,5,10:15),FALSE,"subset.server",local.df.list) From 17495736fe4d90c7ad67f77dfc5693a122eb416e Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:03:26 +0100 Subject: [PATCH 08/13] broaden assertion to handle different lme versions --- tests/testthat/dstest_functions/ds_expect_variables.R | 4 ++-- tests/testthat/test-arg-ds.lmerSLMA.R | 2 +- tests/testthat/test-smk-ds.lmerSLMA.R | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/testthat/dstest_functions/ds_expect_variables.R b/tests/testthat/dstest_functions/ds_expect_variables.R index abb504c78..16d770afd 100644 --- a/tests/testthat/dstest_functions/ds_expect_variables.R +++ b/tests/testthat/dstest_functions/ds_expect_variables.R @@ -12,10 +12,10 @@ # Tests # -ds_expect_variables <- function(expected.variables) { +ds_expect_variables <- function(expected.variables, optional.variables = NULL) { studies.current.variables <- ds.ls() for (study.current.variables in studies.current.variables) { - expect_setequal(study.current.variables$objects.found, expected.variables) + expect_setequal(setdiff(study.current.variables$objects.found, optional.variables), expected.variables) } } diff --git a/tests/testthat/test-arg-ds.lmerSLMA.R b/tests/testthat/test-arg-ds.lmerSLMA.R index d5f6510da..7f6b3aca3 100644 --- a/tests/testthat/test-arg-ds.lmerSLMA.R +++ b/tests/testthat/test-arg-ds.lmerSLMA.R @@ -54,7 +54,7 @@ test_that("simple lmerSLMA tesing (mis)use of arguments", { # context("ds.lmerSLMA::arg::shutdown") test_that("shutdown", { - ds_expect_variables(c("D", "offset", "offset.to.use", "weights", "weights.to.use")) + ds_expect_variables(c("D", "offset.to.use", "weights.to.use"), optional.variables = c("offset", "weights")) }) disconnect.studies.dataset.cluster.int() diff --git a/tests/testthat/test-smk-ds.lmerSLMA.R b/tests/testthat/test-smk-ds.lmerSLMA.R index 620abc63c..6baafa66a 100644 --- a/tests/testthat/test-smk-ds.lmerSLMA.R +++ b/tests/testthat/test-smk-ds.lmerSLMA.R @@ -81,7 +81,7 @@ test_that("test offsets and weights", { test_that("shutdown", { #note the offset and weights objects below are artefacts - ds_expect_variables(c("D", "D2", "offset", "some.offsets", "some.weights", "weights")) + ds_expect_variables(c("D", "D2", "some.offsets", "some.weights"), optional.variables = c("offset", "weights")) }) disconnect.studies.dataset.cluster.int() @@ -284,7 +284,7 @@ test_that("fails if the object is the wrong type", { # context("ds.lmerSLMA::smk::shutdown phase 2") test_that("shutdown", { - ds_expect_variables(c("D", "offset", "weights")) + ds_expect_variables(c("D"), optional.variables = c("offset", "weights")) }) disconnect.studies.dataset.cluster.slo() From bbdc3c4515dce1d8b15841c600b2616d282e0dd1 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:15:07 +0100 Subject: [PATCH 09/13] fix lint errors --- .../connection_to_datasets/init_studies_datasets.R | 8 ++++---- .../connection_to_datasets/init_testing_datasets.R | 5 ++--- tests/testthat/setup.R | 2 +- tests/testthat/test-arg-ds.glmSLMA.R | 6 +++--- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/testthat/connection_to_datasets/init_studies_datasets.R b/tests/testthat/connection_to_datasets/init_studies_datasets.R index e124a0086..97c595dcb 100644 --- a/tests/testthat/connection_to_datasets/init_studies_datasets.R +++ b/tests/testthat/connection_to_datasets/init_studies_datasets.R @@ -109,7 +109,7 @@ init.studies.dataset.cluster.int <- function(variables) } else { - ds.test_env$login.data <- dslite.login.data(c("cluster.int1", "cluster.int2", "cluster.int3"), c("CLUSTER_INT1", "CLUSTER_INT2", "CLUSTER_INT3")) + ds.test_env$login.data <- dslite.login.data(c("cluster.int1", "cluster.int2", "cluster.int3"), c("CLUSTER_INT1", "CLUSTER_INT2", "CLUSTER_INT3")) # nolint: object_usage_linter. } ds.test_env$stats.var <- variables } @@ -142,7 +142,7 @@ init.studies.dataset.cluster.slo <- function(variables) } else { - ds.test_env$login.data <- dslite.login.data(c("cluster.slo1", "cluster.slo2", "cluster.slo3"), c("CLUSTER_SLO1", "CLUSTER_SLO2", "CLUSTER_SLO3")) + ds.test_env$login.data <- dslite.login.data(c("cluster.slo1", "cluster.slo2", "cluster.slo3"), c("CLUSTER_SLO1", "CLUSTER_SLO2", "CLUSTER_SLO3")) # nolint: object_usage_linter. } ds.test_env$stats.var <- variables @@ -176,7 +176,7 @@ init.studies.dataset.anthro <- function(variables) } else { - ds.test_env$login.data <- dslite.login.data(c("study1", "study2", "study3"), c("anthro1", "anthro2", "anthro3")) + ds.test_env$login.data <- dslite.login.data(c("study1", "study2", "study3"), c("anthro1", "anthro2", "anthro3")) # nolint: object_usage_linter. } ds.test_env$stats.var <- variables @@ -210,7 +210,7 @@ init.studies.dataset.gamlss <- function(variables) } else { - ds.test_env$login.data <- dslite.login.data(c("study1", "study2", "study3"), c("gamlss1", "gamlss2", "gamlss3")) + ds.test_env$login.data <- dslite.login.data(c("study1", "study2", "study3"), c("gamlss1", "gamlss2", "gamlss3")) # nolint: object_usage_linter. } ds.test_env$stats.var <- variables diff --git a/tests/testthat/connection_to_datasets/init_testing_datasets.R b/tests/testthat/connection_to_datasets/init_testing_datasets.R index 7ed058bd7..92803e297 100644 --- a/tests/testthat/connection_to_datasets/init_testing_datasets.R +++ b/tests/testthat/connection_to_datasets/init_testing_datasets.R @@ -237,7 +237,7 @@ register.description.methods <- function(server) } # A real server keeps its R warnings to itself; DSLite raises them in the client session, where testthat records each one -DSLiteTestServer <- R6::R6Class("DSLiteTestServer", inherit = DSLite::DSLiteServer, +DSLiteTestServer <- R6::R6Class("DSLiteTestServer", inherit = DSLite::DSLiteServer, # nolint: missing_package_linter. public = list( aggregate = function(sid, expr) suppressWarnings(super$aggregate(sid, expr)), assignExpr = function(sid, symbol, expr) suppressWarnings(super$assignExpr(sid, symbol, expr)) @@ -271,7 +271,7 @@ log.in.data.server <- function() register.description.methods(ds.test_env$dslite.server) } # ds.test_env$connections <- datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=ds.test_env$stats.var, opts = getOption("datashield.opts", list(ssl_verifyhost=0, ssl_verifypeer=0))) - ds.test_env$connections <- datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=variables, opts = getOption("datashield.opts")) + ds.test_env$connections <- DSI::datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=variables, opts = getOption("datashield.opts")) } @@ -365,7 +365,6 @@ disconnect.testing.dataset.factor_levels.1 <- function() dslite.login.data <- function(servers, tables) { - options(datashield.env = ds.test_env) builder <- DSI::newDSLoginBuilder(.silent = TRUE) for (i in seq_along(servers)) builder$append(server = servers[[i]], url = "dslite.server", table = tables[[i]], driver = "DSLiteDriver") diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index 5360f2830..e75eb835a 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -27,7 +27,7 @@ library(DSMolgenisArmadillo) library(DSLite) # 'dgr-' files rely on an earlier file in the same session having attached dsDangerClient; attach it here so each file also runs on its own -if (requireNamespace("dsDangerClient", quietly = TRUE)) library(dsDangerClient) +if (requireNamespace("dsDangerClient", quietly = TRUE)) library(dsDangerClient) # nolint: missing_package_linter, undesirable_function_linter. source("dstest_functions/ds_expect_variables.R") source("perf_tests/perf_rate.R") diff --git a/tests/testthat/test-arg-ds.glmSLMA.R b/tests/testthat/test-arg-ds.glmSLMA.R index 0b63e1f89..f016e971b 100644 --- a/tests/testthat/test-arg-ds.glmSLMA.R +++ b/tests/testthat/test-arg-ds.glmSLMA.R @@ -27,9 +27,9 @@ test_that("glmSLMA_errors", { res.errors<-datashield.errors() expect_length(res.errors, 3) - expect_match(res.errors$sim1, "invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") - expect_match(res.errors$sim2, "invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") - expect_match(res.errors$sim3, "invalid type \\(NULL\\) for variable 'D\\$LAB_TRIG'") + expect_match(res.errors$sim1, "invalid type (NULL) for variable 'D$LAB_TRIG'", fixed = TRUE) + expect_match(res.errors$sim2, "invalid type (NULL) for variable 'D$LAB_TRIG'", fixed = TRUE) + expect_match(res.errors$sim3, "invalid type (NULL) for variable 'D$LAB_TRIG'", fixed = TRUE) }) # From a35572ea956566ef930e4e8ad098b5f56ffd7b95 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:25:09 +0100 Subject: [PATCH 10/13] Fix remaining lint findings --- tests/testthat/connection_to_datasets/init_testing_datasets.R | 2 +- tests/testthat/dstest_functions/ds_expect_variables.R | 2 +- tests/testthat/setup.R | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/connection_to_datasets/init_testing_datasets.R b/tests/testthat/connection_to_datasets/init_testing_datasets.R index 92803e297..623ce9cb1 100644 --- a/tests/testthat/connection_to_datasets/init_testing_datasets.R +++ b/tests/testthat/connection_to_datasets/init_testing_datasets.R @@ -237,7 +237,7 @@ register.description.methods <- function(server) } # A real server keeps its R warnings to itself; DSLite raises them in the client session, where testthat records each one -DSLiteTestServer <- R6::R6Class("DSLiteTestServer", inherit = DSLite::DSLiteServer, # nolint: missing_package_linter. +DSLiteTestServer <- R6::R6Class("DSLiteTestServer", inherit = DSLite::DSLiteServer, # nolint: namespace_linter. public = list( aggregate = function(sid, expr) suppressWarnings(super$aggregate(sid, expr)), assignExpr = function(sid, symbol, expr) suppressWarnings(super$assignExpr(sid, symbol, expr)) diff --git a/tests/testthat/dstest_functions/ds_expect_variables.R b/tests/testthat/dstest_functions/ds_expect_variables.R index 16d770afd..4aa188f7b 100644 --- a/tests/testthat/dstest_functions/ds_expect_variables.R +++ b/tests/testthat/dstest_functions/ds_expect_variables.R @@ -16,6 +16,6 @@ ds_expect_variables <- function(expected.variables, optional.variables = NULL) { studies.current.variables <- ds.ls() for (study.current.variables in studies.current.variables) { - expect_setequal(setdiff(study.current.variables$objects.found, optional.variables), expected.variables) + testthat::expect_setequal(setdiff(study.current.variables$objects.found, optional.variables), expected.variables) } } diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index e75eb835a..b752fb402 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -27,7 +27,7 @@ library(DSMolgenisArmadillo) library(DSLite) # 'dgr-' files rely on an earlier file in the same session having attached dsDangerClient; attach it here so each file also runs on its own -if (requireNamespace("dsDangerClient", quietly = TRUE)) library(dsDangerClient) # nolint: missing_package_linter, undesirable_function_linter. +if (requireNamespace("dsDangerClient", quietly = TRUE)) library(dsDangerClient) # nolint: missing_package_linter, undesirable_function_linter, unused_import_linter. source("dstest_functions/ds_expect_variables.R") source("perf_tests/perf_rate.R") From dab4eaa789c83d4711fc0e18b0fb5da1521e5ba5 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:50:24 +0100 Subject: [PATCH 11/13] Use lint exception for expect_setequal --- tests/testthat/dstest_functions/ds_expect_variables.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/dstest_functions/ds_expect_variables.R b/tests/testthat/dstest_functions/ds_expect_variables.R index 4aa188f7b..f3ff81025 100644 --- a/tests/testthat/dstest_functions/ds_expect_variables.R +++ b/tests/testthat/dstest_functions/ds_expect_variables.R @@ -16,6 +16,6 @@ ds_expect_variables <- function(expected.variables, optional.variables = NULL) { studies.current.variables <- ds.ls() for (study.current.variables in studies.current.variables) { - testthat::expect_setequal(setdiff(study.current.variables$objects.found, optional.variables), expected.variables) + expect_setequal(setdiff(study.current.variables$objects.found, optional.variables), expected.variables) # nolint: object_usage_linter. } } From 727c3405bde77f3161beb1381deae3faee0786a0 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:34:42 +0100 Subject: [PATCH 12/13] Trim DSLite job comment --- .github/workflows/dsBaseClient_test_suite.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/dsBaseClient_test_suite.yaml b/.github/workflows/dsBaseClient_test_suite.yaml index 1f202dafb..9f58dff99 100644 --- a/.github/workflows/dsBaseClient_test_suite.yaml +++ b/.github/workflows/dsBaseClient_test_suite.yaml @@ -840,9 +840,7 @@ jobs: ################################################################################ # DSLite - the servers run inside the R session, so dsBase is installed into - # the runner's R library instead of a server. Runs the same files as the Opal - # and Armadillo shards, except perf, as DSLite timings are not comparable; - # DSLite is fast enough to run them all in one job. + # the runner's R library. Perf tests are excluded as timings are not comparable. ################################################################################ dslite-dsbase: name: DSLite tests From 2428f57f9c2a0b24be5f6331caf83bbf99b08667 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:57:46 +0100 Subject: [PATCH 13/13] Use DSLite's include option for dsDanger methods --- .../init_testing_datasets.R | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/tests/testthat/connection_to_datasets/init_testing_datasets.R b/tests/testthat/connection_to_datasets/init_testing_datasets.R index 623ce9cb1..079cfabe0 100644 --- a/tests/testthat/connection_to_datasets/init_testing_datasets.R +++ b/tests/testthat/connection_to_datasets/init_testing_datasets.R @@ -209,33 +209,6 @@ init.testing.dataset.factor_levels.1 <- function() } } -# DSLite reads methods only from inst/DATASHIELD; Opal/Rock also read the DESCRIPTION fields of every installed package, which is where e.g. dsDanger declares them -description.methods <- function() -{ - if (is.null(ds.test_env$description.methods)) - { - packages <- installed.packages(fields = c("AggregateMethods", "AssignMethods")) - packages <- packages[!duplicated(packages[, "Package"]), , drop = FALSE] - packages <- packages[!(is.na(packages[, "AggregateMethods"]) & is.na(packages[, "AssignMethods"])), , drop = FALSE] - packages <- packages[!nzchar(vapply(packages[, "Package"], system.file, character(1), file = "DATASHIELD")), , drop = FALSE] - ds.test_env$description.methods <- packages[, c("Package", "AggregateMethods", "AssignMethods"), drop = FALSE] - } - ds.test_env$description.methods -} - -register.description.methods <- function(server) -{ - declared <- description.methods() - for (i in seq_len(nrow(declared))) - { - package <- declared[i, "Package"] - for (method in setdiff(strsplit(gsub("\\s", "", declared[i, "AggregateMethods"]), ",")[[1]], NA)) - server$aggregateMethod(method, paste0(package, "::", method)) - for (method in setdiff(strsplit(gsub("\\s", "", declared[i, "AssignMethods"]), ",")[[1]], NA)) - server$assignMethod(method, paste0(package, "::", method)) - } -} - # A real server keeps its R warnings to itself; DSLite raises them in the client session, where testthat records each one DSLiteTestServer <- R6::R6Class("DSLiteTestServer", inherit = DSLite::DSLiteServer, # nolint: namespace_linter. public = list( @@ -258,17 +231,25 @@ ci.tables <- function(table.names) setNames(tables, table.names) } +# Matches the CI servers: same tables, permissive privacy control, dsDanger methods available +new.dslite.server <- function(table.names) +{ + server <- DSLiteTestServer$new( + tables = ci.tables(table.names), + config = DSLite::defaultDSConfiguration(include = c("dsBase", "dsDanger")) # nolint: namespace_linter. + ) + server$option("default.datashield.privacyControlLevel", deparse("permissive")) + server +} + log.in.data.server <- function() { variables <- ds.test_env$stats.var if (ds.test_env$driver == "DSLiteDriver") { - ds.test_env$dslite.server <- DSLiteTestServer$new(tables = ci.tables(unique(ds.test_env$login.data$table))) + ds.test_env$dslite.server <- new.dslite.server(unique(ds.test_env$login.data$table)) # Opal/Armadillo treat the variables filter as a set; DSLite would duplicate repeated columns as 'X.1' variables <- unique(variables) - # CI servers run with 'default.datashield.privacyControlLevel' set to 'permissive' - ds.test_env$dslite.server$option("default.datashield.privacyControlLevel", deparse("permissive")) - register.description.methods(ds.test_env$dslite.server) } # ds.test_env$connections <- datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=ds.test_env$stats.var, opts = getOption("datashield.opts", list(ssl_verifyhost=0, ssl_verifypeer=0))) ds.test_env$connections <- DSI::datashield.login(logins=ds.test_env$login.data, assign=TRUE,variables=variables, opts = getOption("datashield.opts"))