tests/testthat/test-run_covr_skip_bioc_nstf.R

# Helper: minimal mapping DF used by downstream steps
make_mapping <- function() {
  data.frame(
    source_file = c("a.R", "b.R"),
    test_file   = c("test-a.R", NA_character_),  # one with a test, one without
    evidence    = c("filename_match", NA_character_),
    has_tests   = c(TRUE, FALSE),
    stringsAsFactors = FALSE
  )
}

test_that("diagnostic: coverage block functions are invoked (covr + reducer)", {
  fn <- run_covr_skip_bioc_nstf

  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  test_pkg_data <- list(bioc_run_ut_path = NA_character_)

  # Same setup as above (mapping, normalize, prepare, etc.)
  mockery::stub(fn, "get_pkg_name",         mockery::mock(pkg_name))
  mockery::stub(fn, "is_bioc_runit_layout", mockery::mock(TRUE))
  mockery::stub(fn, "get_biocgenerics_test_paths",
                mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests"))))
  mockery::stub(fn, "normalizePath", function(x, winslash = "/", mustWork = FALSE) x)

  mapping <- data.frame(
    source_file = "a.R", test_file = "test-a.R", evidence = "filename_match",
    has_tests = TRUE, stringsAsFactors = FALSE
  )
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf", mockery::mock(mapping))
  mockery::stub(fn, "prepare_for_bioc_runit",     mockery::mock(TRUE))
  mockery::stub(fn, "check_covr_skip_Bioc_nstf",  mockery::mock(data.frame(test_file = NA_character_, issue_type = "No tests skipped", stringsAsFactors = FALSE)))
  mockery::stub(fn, "get_function_no_tests",      mockery::mock(data.frame(fn = "b", stringsAsFactors = FALSE)))
  mockery::stub(fn, "tidyr::drop_na",             function(df) df)

  mockery::stub(fn, "ls",               mockery::mock(c("obj1")))
  mockery::stub(fn, "asNamespace",      mockery::mock(new.env()))
  mockery::stub(fn, "assign",           mockery::mock(NULL))
  mockery::stub(fn, "load_package_datasets", mockery::mock(character()))
  mockery::stub(fn, "loadedNamespaces", mockery::mock(c("methods")))
  mockery::stub(fn, "loadNamespace",    mockery::mock(NULL))
  # Must stub library() or you’ll abort here!
  mockery::stub(fn, "detach_pkg_if_attached", mockery::mock(TRUE))
  mockery::stub(fn, "library",          mockery::mock(NULL))
  mockery::stub(fn, "tempfile",         mockery::mock("/tmp/runner.R"))
  mockery::stub(fn, "writeLines",       mockery::mock(NULL))

  
  # avoid error from as.environment("package:mypkg")
  mockery::stub(fn, "as.environment", function(name) {
    # when calling as.environment("package:mypkg"), return a fresh env
    if (identical(name, paste0("package:", pkg_name))) return(new.env(parent = emptyenv()))
    # delegate other calls to base (rare here)
    base::as.environment(name)
  })
  
  
  # Spy/record calls to covr & reducer
  spy_env_cov <- mockery::mock(structure(list(), class = "covr_coverage"))
  spy_cov_to_list <- mockery::mock(list(totalcoverage = 75.0))
  spy_compute <- mockery::mock(list(
    total_cov = 0.50, percent_cov = 75.0,
    n_testfiles = 1L, n_no_function_tests = 1L, n_skipped_files = 0L
  ))

  mockery::stub(fn, "covr::environment_coverage", spy_env_cov)
  mockery::stub(fn, "covr::coverage_to_list",     spy_cov_to_list)
  mockery::stub(fn, "compute_total_coverage",     spy_compute)

  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)

  mockery::expect_called(spy_env_cov,   1)
  mockery::expect_called(spy_cov_to_list, 1)
  mockery::expect_called(spy_compute,   1)

  expect_identical(res$total_cov, 0.50)
  expect_identical(res$res_cov$coverage$totalcoverage, 75.0)
})

test_that("messages when package is not using BioC RUnit layout and returns default coverage when covr fails", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  
  # --- Stubs for early steps ---
  mock_get_pkg_name <- mockery::mock(pkg_name)
  mock_is_layout    <- mockery::mock(FALSE)  # trigger layout message
  
  mock_get_paths <- mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests")))
  mock_normalize <- mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE)
  
  # --- Mapping: succeed to progress into coverage path ---
  mock_get_mapping <- mockery::mock(make_mapping())
  
  # --- Cleaning branch: only run when bioc_run_ut_path is not NA ---
  # In this test, set bioc_run_ut_path = NA so the cleaning branch is skipped
  test_pkg_data <- list(bioc_run_ut_path = NA_character_)
  
  # --- prepare_for_bioc_runit: succeed ---
  mock_prepare <- mockery::mock(TRUE)
  
  # --- Problems: return "No tests skipped" (default/no skip) ---
  mock_problems <- mockery::mock(data.frame(
    test_file  = NA_character_,
    issue_type = "No tests skipped",
    stringsAsFactors = FALSE
  ))
  
  # --- Post-load mapping: functions with no tests ---
  mock_no_tests <- mockery::mock(data.frame(
    `function` = "b",
    stringsAsFactors = FALSE
  ))
  
  # --- tidyr::drop_na: identity (no change) ---
  mock_drop_na <- function(df) df
  
  # --- Env preparation (assign exported/internal objects + datasets) ---
  mock_ls <- mockery::mock(c("obj1", "obj2"))
  mock_asNamespace <- mockery::mock(new.env())
  mock_assign <- mockery::mock(NULL)
  mock_load_datasets <- mockery::mock(c("ds1", "ds2"))
  mock_message <- mockery::mock(NULL, cycle = TRUE)
  
  # --- Coverage block stubs: force an error in covr to take default path ---
  mock_loadedNamespaces <- mockery::mock(c("base", "stats", "utils", "methods"))  # so loadNamespace("methods") skipped
  mock_loadNamespace <- mockery::mock(NULL)
  mock_detach <- mockery::mock(TRUE)
  mock_library <- mockery::mock(NULL)
  mock_tempfile <- mockery::mock("/tmp/runner.R")
  mock_writeLines <- mockery::mock(NULL)
  
  # This triggers the error branch -> default coverage result
  mock_env_cov <- function(env, test_files) stop("instrumentation failed")
  # coverage_to_list is never reached when env_cov errors, but stub for safety
  mock_cov_to_list <- mockery::mock(list(filecoverage = matrix(0, nrow = 1), totalcoverage = 0))
  
  # --- cleanup_and_return_null used by error branches ---
  mock_cleanup <- mockery::mock(NULL)
  
  # Wire stubs
  mockery::stub(fn, "get_pkg_name",               mock_get_pkg_name)
  mockery::stub(fn, "is_bioc_runit_layout",       mock_is_layout)
  mockery::stub(fn, "get_biocgenerics_test_paths",mock_get_paths)
  mockery::stub(fn, "normalizePath",              mock_normalize)
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf", mock_get_mapping)
  mockery::stub(fn, "prepare_for_bioc_runit",     mock_prepare)
  mockery::stub(fn, "check_covr_skip_Bioc_nstf",  mock_problems)
  mockery::stub(fn, "get_function_no_tests",      mock_no_tests)
  mockery::stub(fn, "tidyr::drop_na",             mock_drop_na)
  mockery::stub(fn, "ls",                         mock_ls)
  mockery::stub(fn, "asNamespace",                mock_asNamespace)
  mockery::stub(fn, "assign",                     mock_assign)
  mockery::stub(fn, "load_package_datasets",      mock_load_datasets)
  mockery::stub(fn, "message",                    mock_message)
  mockery::stub(fn, "loadedNamespaces",           mock_loadedNamespaces)
  mockery::stub(fn, "loadNamespace",              mock_loadNamespace)
  mockery::stub(fn, "detach_pkg_if_attached",     mock_detach)
  mockery::stub(fn, "library",                    mock_library)
  mockery::stub(fn, "tempfile",                   mock_tempfile)
  mockery::stub(fn, "writeLines",                 mock_writeLines)
  mockery::stub(fn, "covr::environment_coverage", mock_env_cov)
  mockery::stub(fn, "covr::coverage_to_list",     mock_cov_to_list)
  mockery::stub(fn, "cleanup_and_return_null",    mock_cleanup)
  
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  
  # Assertions: default coverage path
  expect_type(res, "list")
  expect_true("res_cov" %in% names(res))
  expect_true(is.list(res$res_cov))
  expect_identical(res$total_cov, 0)
  expect_identical(res$res_cov$name, pkg_name)
  expect_true(is.list(res$res_cov$coverage))
  expect_identical(res$res_cov$coverage$totalcoverage, 0)
  expect_identical(res$res_cov$errors, "No RUnit or BiocGenerics configuration")
  
  # The first message should hint about BioC RUnit layout
  msgs <- mockery::mock_args(mock_message)
  # Flatten first invocation into a single string to check substring
  first_msg <- paste0(msgs[[1]], collapse = "")
  expect_match(first_msg, "BioC RUnit layout")
})


test_that("returns NULL when source-test mapping errors", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  test_pkg_data <- list(bioc_run_ut_path = NA_character_)
  
  mockery::stub(fn, "get_pkg_name",         mockery::mock(pkg_name))
  mockery::stub(fn, "is_bioc_runit_layout", mockery::mock(TRUE))
  mockery::stub(fn, "get_biocgenerics_test_paths", mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests"))))
  mockery::stub(fn, "normalizePath",        mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE))
  # Mapping: throw error so tryCatch invokes cleanup_and_return_null -> returns NULL -> early exit
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf", function(...) stop("boom-mapping"))
  mockery::stub(fn, "cleanup_and_return_null",           mockery::mock(NULL))
  
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  expect_null(res)
})


test_that("returns NULL when prepare_for_bioc_runit fails", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  test_pkg_data <- list(bioc_run_ut_path = NA_character_)
  
  mockery::stub(fn, "get_pkg_name",         mockery::mock(pkg_name))
  mockery::stub(fn, "is_bioc_runit_layout", mockery::mock(TRUE))
  mockery::stub(fn, "get_biocgenerics_test_paths", mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests"))))
  mockery::stub(fn, "normalizePath",        mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE))
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf", mockery::mock(make_mapping()))
  # Force prepare to error; tryCatch should call cleanup_and_return_null and return NULL
  mockery::stub(fn, "prepare_for_bioc_runit", function(...) stop("prep-error"))
  mockery::stub(fn, "cleanup_and_return_null", mockery::mock(NULL))
  
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  expect_null(res)
})


test_that("coverage error path yields a default covr_list even when mapping & problems succeed", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  
  test_pkg_data <- list(bioc_run_ut_path = NA_character_)  # skip cleaning branch
  
  # Layout ok
  mockery::stub(fn, "get_pkg_name",         mockery::mock(pkg_name))
  mockery::stub(fn, "is_bioc_runit_layout", mockery::mock(TRUE))
  
  # Paths, normalization
  mockery::stub(fn, "get_biocgenerics_test_paths", mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests"))))
  mockery::stub(fn, "normalizePath",        mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE))
  
  # Mapping and later pipeline
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf", mockery::mock(make_mapping()))
  mockery::stub(fn, "prepare_for_bioc_runit", mockery::mock(TRUE))
  mockery::stub(fn, "check_covr_skip_Bioc_nstf", mockery::mock(data.frame(
    test_file  = NA_character_,
    issue_type = "No tests skipped",
    stringsAsFactors = FALSE
  )))
  mockery::stub(fn, "get_function_no_tests", mockery::mock(data.frame(`function` = "b", stringsAsFactors = FALSE)))
  mockery::stub(fn, "tidyr::drop_na",        function(df) df)
  
  # Env prep for coverage
  mockery::stub(fn, "ls",               mockery::mock(c("obj1")))
  mockery::stub(fn, "asNamespace",      mockery::mock(new.env()))
  mockery::stub(fn, "assign",           mockery::mock(NULL))
  mockery::stub(fn, "load_package_datasets", mockery::mock(character()))
  mockery::stub(fn, "message",          mockery::mock(NULL, cycle = TRUE))
  
  # Coverage: trigger failure to default path
  mockery::stub(fn, "loadedNamespaces", mockery::mock(c("methods")))
  mockery::stub(fn, "loadNamespace",    mockery::mock(NULL))
  mockery::stub(fn, "detach_pkg_if_attached", mockery::mock(TRUE))
  mockery::stub(fn, "library",          mockery::mock(NULL))
  mockery::stub(fn, "tempfile",         mockery::mock("/tmp/runner.R"))
  mockery::stub(fn, "writeLines",       mockery::mock(NULL))
  mockery::stub(fn, "covr::environment_coverage", function(...) stop("covr-failed"))
  mockery::stub(fn, "covr::coverage_to_list", mockery::mock(list(filecoverage = matrix(0, nrow = 1), totalcoverage = 0)))
  mockery::stub(fn, "cleanup_and_return_null", mockery::mock(NULL))
  
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  
  expect_type(res, "list")
  expect_identical(res$total_cov, 0)
  expect_identical(res$res_cov$name, pkg_name)
  expect_identical(res$res_cov$coverage$totalcoverage, 0)
  expect_identical(res$res_cov$errors, "No RUnit or BiocGenerics configuration")
})

# Minimal helper mapping (one test file to pass mustWork normalization)
make_mapping <- function() {
  data.frame(
    source_file = c("a.R", "b.R"),
    test_file   = c("test-a.R", NA_character_),
    evidence    = c("filename_match", NA_character_),
    has_tests   = c(TRUE, FALSE),
    stringsAsFactors = FALSE
  )
}




test_that("error in coverage_to_list hits error branch but lines still produce messages and covr_list", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  test_pkg_data <- list(bioc_run_ut_path = NA_character_)
  
  # Upstream stubs to reach the block
  mockery::stub(fn, "get_pkg_name",         mockery::mock(pkg_name))
  mockery::stub(fn, "is_bioc_runit_layout", mockery::mock(TRUE))
  mockery::stub(fn, "get_biocgenerics_test_paths",
                mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests"))))
  mockery::stub(fn, "normalizePath", function(x, winslash = "/", mustWork = FALSE) x)
  
  # Mapping with one test to ensure test_files_clean is non-empty
  make_mapping <- function() {
    data.frame(
      source_file = c("a.R", "b.R"),
      test_file   = c("test-a.R", NA_character_),
      evidence    = c("filename_match", NA_character_),
      has_tests   = c(TRUE, FALSE),
      stringsAsFactors = FALSE
    )
  }
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf", mockery::mock(make_mapping()))
  
  mockery::stub(fn, "prepare_for_bioc_runit", mockery::mock(TRUE))
  mockery::stub(fn, "check_covr_skip_Bioc_nstf", mockery::mock(
    data.frame(test_file = NA_character_, issue_type = "No tests skipped", stringsAsFactors = FALSE)
  ))
  mockery::stub(fn, "get_function_no_tests", mockery::mock(data.frame(fn = "b", stringsAsFactors = FALSE)))
  mockery::stub(fn, "tidyr::drop_na", function(df) df)
  
  # Env prep / coverage stubs
  mockery::stub(fn, "ls",               mockery::mock(c("obj1")))
  mockery::stub(fn, "asNamespace",      mockery::mock(new.env()))
  mockery::stub(fn, "assign",           mockery::mock(NULL))
  mockery::stub(fn, "load_package_datasets", mockery::mock(character()))
  mockery::stub(fn, "loadedNamespaces", mockery::mock(c("methods")))
  mockery::stub(fn, "loadNamespace",    mockery::mock(NULL))
  mockery::stub(fn, "detach_pkg_if_attached", mockery::mock(TRUE))
  mockery::stub(fn, "library",          mockery::mock(NULL))
  mockery::stub(fn, "tempfile",         mockery::mock("/tmp/runner.R"))
  mockery::stub(fn, "writeLines",       mockery::mock(NULL))
  
  # keep coverage block alive by returning a benign env for "package:mypkg"
  mockery::stub(fn, "as.environment", function(name) {
    if (identical(name, paste0("package:", pkg_name))) {
      return(new.env(parent = emptyenv()))
    }
    base::as.environment(name)
  })
  
  # Force error in coverage_to_list -> error branch sets res_cov$errors and NA coverage fields
  mockery::stub(fn, "covr::environment_coverage", mockery::mock(structure(list(), class = "covr_coverage")))
  mockery::stub(fn, "covr::coverage_to_list", function(...) stop("explode"))
  
  # Stub compute_total_coverage to still return values (so the lines proceed)
  mockery::stub(fn, "compute_total_coverage", function(percent_cov, ...) {
    # percent_cov will be NA_real_ due to error branch; ignore and return defaults
    list(
      total_cov            = 0.00,
      percent_cov          = 0.00,
      n_testfiles          = 1L,
      n_no_function_tests  = 1L,
      n_skipped_files      = 0L
    )
  })
  
  # Capture messages & cleanup calls
  mock_msg   <- mockery::mock(NULL, cycle = TRUE)
  mock_clean <- mockery::mock(NULL)
  mockery::stub(fn, "message",                 mock_msg)
  mockery::stub(fn, "cleanup_and_return_null", mock_clean)
  
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  
  # Messages exist (Total Coverage 0.00%, Approx total coverage 0.00%)
  msg_calls <- mockery::mock_args(mock_msg)
  all_msgs  <- vapply(msg_calls, function(x) paste0(x, collapse = ""), character(1))
  total_line  <- all_msgs[grepl("^Total Coverage for mypkg is:", all_msgs)]
  approx_line <- all_msgs[grepl("^Approx total coverage:",       all_msgs)]
  
  expect_true(length(total_line)  >= 1)
  expect_true(length(approx_line) >= 1)
  expect_match(total_line[1],  "0%")
  expect_match(approx_line[1], "\\(0\\.00%\\)")
  
  # cleanup called once
  mockery::expect_called(mock_clean, 1)
  
  # covr_list assembled
  expect_type(res, "list")
  expect_true("res_cov" %in% names(res))
  # In error branch, res_cov$coverage$'totalcoverage' == NA_integer_
  expect_true(is.na(res$res_cov$coverage$totalcoverage))
  expect_true(inherits(res$res_cov$errors, "error") || is.character(res$res_cov$errors))
})


test_that("returns TRUE when test_pkg_data$has_BioG_test is TRUE regardless of files", {
  fn <- is_bioc_runit_layout
  
  # Mock filesystem as FALSE to ensure the first clause is sufficient
  mock_dir_exists  <- mockery::mock(FALSE)
  mock_file_exists <- mockery::mock(FALSE)
  
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(pkg_source_path = "/fake/path", test_pkg_data = list(has_BioG_test = TRUE))
  expect_true(res)
  
  # Both stubs should still be called once due to evaluation sequence of ||
  # (In R, || is short-circuiting, but since the first is TRUE, the others may not be called.
  # To be robust across implementations, we don't assert call counts here.)
})

test_that("returns FALSE when no BioC-RUnit indicators are present", {
  fn <- is_bioc_runit_layout
  
  mock_dir_exists  <- mockery::mock(FALSE)
  mock_file_exists <- mockery::mock(FALSE)
  
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  # test_pkg_data NULL or missing field -> isTRUE(NULL) => FALSE
  res1 <- fn(pkg_source_path = "/fake/path", test_pkg_data = NULL)
  expect_false(res1)

})

test_that("returns TRUE when inst/unitTests directory exists", {
  fn <- is_bioc_runit_layout
  
  # dir.exists should return TRUE; file.exists shouldn't need to be consulted,
  # but we mock it to a known value anyway.
  mock_dir_exists  <- mockery::mock(TRUE)
  mock_file_exists <- mockery::mock(FALSE)
  
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(pkg_source_path = "/fake/path", test_pkg_data = list(has_BioG_test = FALSE))
  expect_true(res)
  
  # Verify dir.exists was called once with constructed path
  mockery::expect_called(mock_dir_exists, 1)
  # We can also check the argument passed to dir.exists (optional)
  called_args <- mockery::mock_args(mock_dir_exists)
  expect_match(called_args[[1]][[1]], "/fake/path/inst/unitTests$")
})

test_that("returns TRUE when tests/run_unitTests.R file exists", {
  fn <- is_bioc_runit_layout
  
  mock_dir_exists  <- mockery::mock(FALSE)
  mock_file_exists <- mockery::mock(TRUE)
  
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(pkg_source_path = "/fake/path", test_pkg_data = list(has_BioG_test = FALSE))
  expect_true(res)
  
  # Verify file.exists was called once with constructed path
  mockery::expect_called(mock_file_exists, 1)
  called_args <- mockery::mock_args(mock_file_exists)
  expect_match(called_args[[1]][[1]], "/fake/path/tests/run_unitTests\\.R$")
})

test_that("short-circuit behavior: when has_BioG_test is TRUE, other checks need not be TRUE", {
  fn <- is_bioc_runit_layout
  
  # We set dir.exists and file.exists to throw if called; if the function
  # short-circuits correctly, these should not be invoked.
  throwing_dir_exists <- function(...) stop("dir.exists should not be called")
  throwing_file_exists <- function(...) stop("file.exists should not be called")
  
  mockery::stub(fn, "dir.exists", throwing_dir_exists)
  mockery::stub(fn, "file.exists", throwing_file_exists)
  
  expect_true(fn(pkg_source_path = "/fake/path", test_pkg_data = list(has_BioG_test = TRUE)))
})

test_that("handles has_BioG_test = NULL or missing field as FALSE", {
  fn <- is_bioc_runit_layout
  
  mock_dir_exists  <- mockery::mock(FALSE)
  mock_file_exists <- mockery::mock(FALSE)
  
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  # Explicit NULL value
  res_null <- fn(pkg_source_path = "/fake/path", test_pkg_data = list(has_BioG_test = NULL))
  expect_false(res_null)

})

test_that("supports atomic pkg_source_path; only path construction matters", {
  fn <- is_bioc_runit_layout
  
  mock_dir_exists  <- mockery::mock(FALSE)
  mock_file_exists <- mockery::mock(FALSE)
  
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  # Try with a different path format; the stubs ensure no actual filesystem usage
  res <- fn(pkg_source_path = "C:\\proj", test_pkg_data = NULL)
  expect_false(res)
})


test_that("does nothing when package not attached and namespace not loaded", {
  fn <- detach_pkg_if_attached
  
  # search() contains no package:pkg
  mock_search <- mockery::mock(c("package:stats", "package:utils"))
  mock_detach <- mockery::mock(NULL)             # should not be called
  mock_is_ns   <- mockery::mock(FALSE)
  mock_unload  <- mockery::mock(NULL)            # should not be called
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  # Result is invisible TRUE
  v <- withVisible(fn("mypkg", unload_namespace = TRUE, quiet = TRUE))
  expect_true(v$value)
  expect_false(v$visible)
  
  # detach and unload should not be called
  mockery::expect_called(mock_detach, 0)
  mockery::expect_called(mock_unload, 0)
  
  # no messages because quiet = TRUE
  mockery::expect_called(mock_message, 0)
})

test_that("detaches when package attached; quiet=TRUE suppresses message", {
  fn <- detach_pkg_if_attached
  
  mock_search <- mockery::mock(c("package:mypkg", "package:stats"))
  mock_detach <- mockery::mock(NULL)
  mock_is_ns  <- mockery::mock(FALSE)     # namespace not loaded
  mock_unload <- mockery::mock(NULL)      # not called
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  res <- fn("mypkg", unload_namespace = TRUE, quiet = TRUE)
  expect_true(res)
  
  # detach should be called once with constructed search name and flags
  mockery::expect_called(mock_detach, 1)
  args <- mockery::mock_args(mock_detach)
  expect_equal(args[[1]][[1]], "package:mypkg")
  expect_true(isTRUE(args[[1]][["unload"]]))
  expect_true(isTRUE(args[[1]][["character.only"]]))
  
  # namespace unload should not be called (isNamespaceLoaded == FALSE)
  mockery::expect_called(mock_unload, 0)
  
  # quiet=TRUE means no message
  mockery::expect_called(mock_message, 0)
})

test_that("unloads namespace when loaded; quiet=TRUE suppresses message", {
  fn <- detach_pkg_if_attached
  
  mock_search <- mockery::mock(c("package:stats"))  # not attached
  mock_detach <- mockery::mock(NULL)                # not called
  mock_is_ns  <- mockery::mock(TRUE)                # namespace loaded
  mock_unload <- mockery::mock(NULL)
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  res <- fn("mypkg", unload_namespace = TRUE, quiet = TRUE)
  expect_true(res)
  
  mockery::expect_called(mock_detach, 0)
  mockery::expect_called(mock_unload, 1)
  unload_args <- mockery::mock_args(mock_unload)
  expect_equal(unload_args[[1]][[1]], "mypkg")
  
  mockery::expect_called(mock_message, 0)
})

test_that("both detach and unload occur when attached and namespace loaded", {
  fn <- detach_pkg_if_attached
  
  mock_search <- mockery::mock(c("package:mypkg", "package:stats"))
  mock_detach <- mockery::mock(NULL)
  mock_is_ns  <- mockery::mock(TRUE)
  mock_unload <- mockery::mock(NULL)
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  res <- fn("mypkg", unload_namespace = TRUE, quiet = TRUE)
  expect_true(res)
  
  mockery::expect_called(mock_detach, 1)
  mockery::expect_called(mock_unload, 1)
  mockery::expect_called(mock_message, 0)
})

test_that("unload_namespace = FALSE prevents namespace unloading", {
  fn <- detach_pkg_if_attached
  
  mock_search <- mockery::mock(c("package:mypkg"))
  mock_detach <- mockery::mock(NULL)
  mock_is_ns  <- mockery::mock(TRUE)    # would be loaded, but flag stops unload
  mock_unload <- mockery::mock(NULL)    # should not be called
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  res <- fn("mypkg", unload_namespace = FALSE, quiet = TRUE)
  expect_true(res)
  
  mockery::expect_called(mock_detach, 1)
  mockery::expect_called(mock_unload, 0)
})

test_that("try() respects silent flag; errors in detach/unload are swallowed when quiet=TRUE", {
  fn <- detach_pkg_if_attached
  
  mock_search <- mockery::mock(c("package:mypkg"))
  mock_detach <- function(...) stop("detach failed")  # throws
  mock_is_ns  <- mockery::mock(TRUE)
  mock_unload <- function(...) stop("unload failed")  # throws
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  # quiet=TRUE => try(..., silent = TRUE): errors are swallowed, function still returns TRUE
  expect_true(fn("mypkg", unload_namespace = TRUE, quiet = TRUE))
})

test_that("returns invisible(TRUE) in all paths", {
  fn <- detach_pkg_if_attached
  
  mock_search <- mockery::mock(c("package:mypkg"))
  mock_detach <- mockery::mock(NULL)
  mock_is_ns  <- mockery::mock(TRUE)
  mock_unload <- mockery::mock(NULL)
  mock_message <- mockery::mock(NULL)
  
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "isNamespaceLoaded", mock_is_ns)
  mockery::stub(fn, "unloadNamespace", mock_unload)
  mockery::stub(fn, "message", mock_message)
  
  v <- withVisible(fn("mypkg", unload_namespace = TRUE, quiet = TRUE))
  expect_true(v$value)
  expect_false(v$visible)
})


test_that("detaches target package and BioC infra packages in order", {
  fn <- prepare_for_bioc_runit
  
  # Mock the detacher to record calls
  mock_detach <- mockery::mock(NULL, NULL, NULL, NULL)  # 4 calls total expected
  # Mock load_all to prevent real load and to inspect args
  mock_load_all <- mockery::mock(NULL)
  
  # Stub symbols referenced inside the function
  mockery::stub(fn, "detach_pkg_if_attached", mock_detach)
  mockery::stub(fn, "pkgload::load_all", mock_load_all)
  
  # Use a fake path/pkg
  res <- fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg")
  
  expect_true(res)
  
  # Expect 4 detach calls: mypkg, IRanges, S4Vectors, BiocGenerics
  mockery::expect_called(mock_detach, 4)
  calls <- mockery::mock_args(mock_detach)
  
  expect_identical(calls[[1]][[1]], "mypkg")
  expect_identical(calls[[2]][[1]], "IRanges")
  expect_identical(calls[[3]][[1]], "S4Vectors")
  expect_identical(calls[[4]][[1]], "BiocGenerics")
  
  # Verify load_all called once, with path and quiet=TRUE
  mockery::expect_called(mock_load_all, 1)
  la_args <- mockery::mock_args(mock_load_all)
  expect_identical(la_args[[1]][[1]], "/tmp/mypkg")
  expect_true(isTRUE(la_args[[1]][["quiet"]]))
})

test_that("options(warn) is temporarily set to 1 and restored after function exits", {
  fn <- prepare_for_bioc_runit
  
  # Mocks to avoid real side effects
  mock_detach <- mockery::mock(NULL, NULL, NULL, NULL)
  mock_load_all <- mockery::mock(NULL)
  
  mockery::stub(fn, "detach_pkg_if_attached", mock_detach)
  mockery::stub(fn, "pkgload::load_all", mock_load_all)
  
  # Preserve and auto-restore original options/env using withr
  old_warn <- getOption("warn")
  withr::local_options(list(warn = old_warn))
  
  # Before call: whatever the test harness has
  expect_identical(getOption("warn"), old_warn)
  
  # Call the function (it will set warn=1 internally and restore on exit)
  res <- fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg")
  expect_true(res)
  
  # After return, warn should be restored to old_warn
  expect_identical(getOption("warn"), old_warn)
})

test_that('R_TESTS is empty during test (local_envvar)', {
  is_windows <- identical(.Platform$OS.type, "windows")
  
  # Ensure clean slate first
  Sys.unsetenv("R_TESTS")
  
  # Temporarily set for the duration of the test; withr restores automatically
  withr::local_envvar(if (is_windows) list("R_TESTS" = "") else list(R_TESTS = ""))
  
  # ... run things that depend on R_TESTS="", or assertions ...
  expect_identical(Sys.getenv("R_TESTS"), "")

})



test_that("POSIX branch: sets R_TESTS='' and restores to UNSET when originally unset", {
  # Only run on Linux/macOS to exercise the POSIX branch
  
  # Skip on Windows with explanation
  skip_if(
    condition = .Platform$OS.type == "windows",
    message = paste(
      "Skipped on Windows: this test exercises the POSIX-only behaviour of",
      "prepare_for_bioc_runit where R_TESTS is set to '' and restored to UNSET.",
      "Windows does not reliably distinguish empty vs UNSET environment variables."
    )
  )
  
  
  fn <- prepare_for_bioc_runit
  
  # Mock non-essential collaborators
  mock_detach   <- mockery::mock(NULL, NULL, NULL, NULL)
  mock_load_all <- mockery::mock(NULL)
  mockery::stub(fn, "detach_pkg_if_attached", mock_detach)
  mockery::stub(fn, "pkgload::load_all", mock_load_all)
  
  # Ensure R_TESTS is UNSET to begin with
  Sys.unsetenv("R_TESTS")
  
  # Belt & suspenders: ensure we leave R_TESTS unset after the test, even if assertions fail
  withr::defer({
    Sys.unsetenv("R_TESTS")
  })
  
  # Call the function under test (should set R_TESTS="" and register on.exit restoration)
  res <- fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg")
  expect_true(res)
  
  # After the function returns, on.exit should have restored to UNSET
  # expect_true(is.na(Sys.getenv("R_TESTS", unset = NA_character_)))
})


test_that("POSIX branch: sets R_TESTS='' and restores to previous value when originally set (mocked)", {
  
  skip_if(
    condition = .Platform$OS.type == "windows",
    message = paste("Skipped on Windows: this test exercises POSIX-specific behaviour where R_TESTS must be ",
    "set to '' then restored. Windows environment handling (cmd.exe/PowerShell) treats empty ",
    "environment variables differently and cannot reliably distinguish '' from prior values, ",
    "making the restoration semantics untestable.")
  )
  
  
  fn <- prepare_for_bioc_runit
  
  # Seed original value so old_R_TESTS inside fn is non-NA
  original <- "previous-value"
  Sys.setenv(R_TESTS = original)
  
  # --- Mocks ---
  mock_setenv   <- mockery::mock(NULL, NULL)   # first for "", then for restore to original
  mock_unsetenv <- mockery::mock(NULL)         # should NOT be called in this path
  
  mock_detach   <- mockery::mock(NULL, NULL, NULL, NULL)
  mock_load_all <- mockery::mock(NULL)
  
  mockery::stub(fn, "Sys.setenv",   mock_setenv)
  mockery::stub(fn, "Sys.unsetenv", mock_unsetenv)
  mockery::stub(fn, "detach_pkg_if_attached", mock_detach)
  mockery::stub(fn, "pkgload::load_all",      mock_load_all)
  
  # Execute
  res <- fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg")
  expect_true(res)
  
  # Assertions:
  set_calls <- mockery::mock_args(mock_setenv)
  
  # 1) It sets R_TESTS to empty string initially
  expect_true(any(vapply(set_calls, function(x) identical(x, list(R_TESTS = "")), logical(1))),
              info = "Sys.setenv(R_TESTS = \"\") should be called in POSIX branch")
})  



test_that("run_covr_skip_bioc_nstf: cleaning branch -> happy path updates mapping and proceeds", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  
  # --- Stubs for early steps ---
  mock_get_pkg_name <- mockery::mock(pkg_name)
  mock_is_layout    <- mockery::mock(FALSE)  # trigger layout message path
  mock_get_paths    <- mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests")))
  mock_normalize    <- mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE)
  
  # Mapping before cleaning; keep simple to avoid make_mapping() dependency
  mock_get_mapping  <- mockery::mock(list(files = c("a.R", "b.R")))
  
  # --- Cleaning branch enabled: non-NA path ---
  test_pkg_data <- list(bioc_run_ut_path = file.path(pkg_source_path, "inst", "unitTests", "runTests.R"))
  
  # On happy path, cleaning returns a *non-NULL* updated mapping
  mock_remove_map <- mockery::mock(list(files = c("a.R")))
  
  # --- prepare_for_bioc_runit: succeed ---
  mock_prepare <- mockery::mock(TRUE)
  
  # --- Problems: default/no skip ---
  mock_problems <- mockery::mock(data.frame(
    test_file  = NA_character_,
    issue_type = "No tests skipped",
    stringsAsFactors = FALSE
  ))
  
  # --- Post-load mapping: functions with no tests ---
  mock_no_tests <- mockery::mock(data.frame(`function` = "b", stringsAsFactors = FALSE))
  
  # --- tidyr::drop_na: identity (no change) ---
  mock_drop_na <- function(df) df
  
  # --- Env prep ---
  mock_ls            <- mockery::mock(c("obj1", "obj2"))
  mock_asNamespace   <- mockery::mock(new.env())
  mock_assign        <- mockery::mock(NULL)
  mock_load_datasets <- mockery::mock(c("ds1", "ds2"))
  mock_message       <- mockery::mock(NULL, cycle = TRUE)
  
  # --- Coverage block stubs: force covr error -> default coverage result ---
  mock_loadedNamespaces <- mockery::mock(c("base", "stats", "utils", "methods"))
  mock_loadNamespace    <- mockery::mock(NULL)
  mock_detach           <- mockery::mock(TRUE)
  mock_library          <- mockery::mock(NULL)
  mock_tempfile         <- mockery::mock("/tmp/runner.R")
  mock_writeLines       <- mockery::mock(NULL)
  mock_env_cov          <- function(env, test_files) stop("instrumentation failed")
  mock_cov_to_list      <- mockery::mock(list(filecoverage = matrix(0, nrow = 1), totalcoverage = 0))
  
  # --- cleanup called by error branches (should NOT be called here) ---
  mock_cleanup <- mockery::mock(NULL)
  
  # Wire stubs
  mockery::stub(fn, "get_pkg_name",                        mock_get_pkg_name)
  mockery::stub(fn, "is_bioc_runit_layout",                mock_is_layout)
  mockery::stub(fn, "get_biocgenerics_test_paths",         mock_get_paths)
  mockery::stub(fn, "normalizePath",                       mock_normalize)
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf",   mock_get_mapping)
  mockery::stub(fn, "remove_run_unitTests_mapping",        mock_remove_map)
  mockery::stub(fn, "prepare_for_bioc_runit",              mock_prepare)
  mockery::stub(fn, "check_covr_skip_Bioc_nstf",           mock_problems)
  mockery::stub(fn, "get_function_no_tests",               mock_no_tests)
  mockery::stub(fn, "tidyr::drop_na",                      mock_drop_na)
  mockery::stub(fn, "ls",                                  mock_ls)
  mockery::stub(fn, "asNamespace",                         mock_asNamespace)
  mockery::stub(fn, "assign",                              mock_assign)
  mockery::stub(fn, "load_package_datasets",               mock_load_datasets)
  mockery::stub(fn, "message",                             mock_message)
  mockery::stub(fn, "loadedNamespaces",                    mock_loadedNamespaces)
  mockery::stub(fn, "loadNamespace",                       mock_loadNamespace)
  mockery::stub(fn, "detach_pkg_if_attached",              mock_detach)
  mockery::stub(fn, "library",                             mock_library)
  mockery::stub(fn, "tempfile",                            mock_tempfile)
  mockery::stub(fn, "writeLines",                          mock_writeLines)
  mockery::stub(fn, "covr::environment_coverage",          mock_env_cov)
  mockery::stub(fn, "covr::coverage_to_list",              mock_cov_to_list)
  mockery::stub(fn, "cleanup_and_return_null",             mock_cleanup)
  
  # Execute
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  
  # Assertions specific to the cleaning branch
  
  # Ensure the cleaning message was emitted
  msg_args <- mockery::mock_args(mock_message)
  expect_true(any(vapply(msg_args, function(x) {
    any(grepl(paste0("performing source test map cleaning for ", pkg_name),
              paste0(unlist(x), collapse = "")))
  }, logical(1))))
  
  
  # Function should proceed (non-NULL result expected given our covr stubs)
  expect_false(is.null(res))
})  


test_that("run_covr_skip_bioc_nstf: cleaning branch -> error calls cleanup_and_return_null and returns NULL", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  
  # --- Stubs for early steps ---
  mock_get_pkg_name <- mockery::mock(pkg_name)
  mock_is_layout    <- mockery::mock(FALSE)
  mock_get_paths    <- mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests")))
  mock_normalize    <- mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE)
  mock_get_mapping  <- mockery::mock(list(files = c("a.R", "b.R")))
  
  # --- Cleaning branch enabled ---
  test_pkg_data <- list(bioc_run_ut_path = file.path(pkg_source_path, "inst", "unitTests", "runTests.R"))
  
  # remove_run_unitTests_mapping throws -> tryCatch error handler runs
  mock_remove_map <- mockery::mock(stop("boom"))
  mock_cleanup     <- mockery::mock(NULL)  # will be called by error handler
  
  # --- Prepare rest (won't be reached if function returns NULL early) ---
  mock_prepare <- mockery::mock(TRUE)
  mock_problems <- mockery::mock(data.frame(test_file = NA_character_, issue_type = "No tests skipped", stringsAsFactors = FALSE))
  mock_no_tests <- mockery::mock(data.frame(`function` = "b", stringsAsFactors = FALSE))
  mock_drop_na  <- function(df) df
  mock_ls            <- mockery::mock(c("obj1", "obj2"))
  mock_asNamespace   <- mockery::mock(new.env())
  mock_assign        <- mockery::mock(NULL)
  mock_load_datasets <- mockery::mock(c("ds1", "ds2"))
  mock_message       <- mockery::mock(NULL, cycle = TRUE)
  mock_loadedNamespaces <- mockery::mock(c("base", "stats", "utils", "methods"))
  mock_loadNamespace    <- mockery::mock(NULL)
  mock_detach           <- mockery::mock(TRUE)
  mock_library          <- mockery::mock(NULL)
  mock_tempfile         <- mockery::mock("/tmp/runner.R")
  mock_writeLines       <- mockery::mock(NULL)
  mock_env_cov          <- function(env, test_files) stop("instrumentation failed")
  mock_cov_to_list      <- mockery::mock(list(filecoverage = matrix(0, nrow = 1), totalcoverage = 0))
  
  # Wire stubs
  mockery::stub(fn, "get_pkg_name",                        mock_get_pkg_name)
  mockery::stub(fn, "is_bioc_runit_layout",                mock_is_layout)
  mockery::stub(fn, "get_biocgenerics_test_paths",         mock_get_paths)
  mockery::stub(fn, "normalizePath",                       mock_normalize)
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf",   mock_get_mapping)
  mockery::stub(fn, "remove_run_unitTests_mapping",        mock_remove_map)
  mockery::stub(fn, "prepare_for_bioc_runit",              mock_prepare)
  mockery::stub(fn, "check_covr_skip_Bioc_nstf",           mock_problems)
  mockery::stub(fn, "get_function_no_tests",               mock_no_tests)
  mockery::stub(fn, "tidyr::drop_na",                      mock_drop_na)
  mockery::stub(fn, "ls",                                  mock_ls)
  mockery::stub(fn, "asNamespace",                         mock_asNamespace)
  mockery::stub(fn, "assign",                              mock_assign)
  mockery::stub(fn, "load_package_datasets",               mock_load_datasets)
  mockery::stub(fn, "message",                             mock_message)
  mockery::stub(fn, "loadedNamespaces",                    mock_loadedNamespaces)
  mockery::stub(fn, "loadNamespace",                       mock_loadNamespace)
  mockery::stub(fn, "detach_pkg_if_attached",              mock_detach)
  mockery::stub(fn, "library",                             mock_library)
  mockery::stub(fn, "tempfile",                            mock_tempfile)
  mockery::stub(fn, "writeLines",                          mock_writeLines)
  mockery::stub(fn, "covr::environment_coverage",          mock_env_cov)
  mockery::stub(fn, "covr::coverage_to_list",              mock_cov_to_list)
  mockery::stub(fn, "cleanup_and_return_null",             mock_cleanup)
  
  # Execute
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  
  # Result should be NULL due to error path + cleanup handler return
  expect_null(res)
  
  # Assert error message composed and passed to cleanup
  cleanup_args <- mockery::mock_args(mock_cleanup)
  expect_true(
    any(vapply(cleanup_args, function(x) {
      is.character(x[[1]]) &&
        grepl(paste0("^Error in cleaning mapping for ", pkg_name, " : boom$"), x[[1]])
    }, logical(1))),
    info = "cleanup_and_return_null should receive composed error message"
  )
  
  # Ensure the cleaning message was emitted
  msg_args <- mockery::mock_args(mock_message)
  
  
  expect_true(
    any(
      vapply(
        msg_args,
        function(x) {
          any(
            grepl(
              paste0("performing source test map cleaning for ", pkg_name),
              paste0(unlist(x), collapse = "")
            )
          )
        },
        logical(1)
      )
    )
  )
  
    
})


test_that("run_covr_skip_bioc_nstf: cleaning branch -> NULL mapping returns NULL early", {
  fn <- run_covr_skip_bioc_nstf
  
  pkg_source_path <- "/fake/pkg"
  pkg_name <- "mypkg"
  
  # --- Stubs for early steps ---
  mock_get_pkg_name <- mockery::mock(pkg_name)
  mock_is_layout    <- mockery::mock(FALSE)
  mock_get_paths    <- mockery::mock(list(bioc_unitTests = file.path(pkg_source_path, "inst", "unitTests")))
  mock_normalize    <- mockery::mock(function(x, winslash = "/", mustWork = FALSE) x, cycle = TRUE)
  mock_get_mapping  <- mockery::mock(list(files = c("a.R", "b.R")))
  
  # --- Cleaning branch enabled ---
  test_pkg_data <- list(bioc_run_ut_path = file.path(pkg_source_path, "inst", "unitTests", "runTests.R"))
  
  # Cleaning returns NULL -> branch should return(NULL) immediately
  mock_remove_map <- mockery::mock(NULL)
  mock_cleanup     <- mockery::mock(NULL)  # not used in this path
  
  # --- Prepare rest (won't be reached if returns early) ---
  mock_prepare <- mockery::mock(TRUE)
  mock_problems <- mockery::mock(data.frame(test_file = NA_character_, issue_type = "No tests skipped", stringsAsFactors = FALSE))
  mock_no_tests <- mockery::mock(data.frame(`function` = "b", stringsAsFactors = FALSE))
  mock_drop_na  <- function(df) df
  mock_ls            <- mockery::mock(c("obj1", "obj2"))
  mock_asNamespace   <- mockery::mock(new.env())
  mock_assign        <- mockery::mock(NULL)
  mock_load_datasets <- mockery::mock(c("ds1", "ds2"))
  mock_message       <- mockery::mock(NULL, cycle = TRUE)
  mock_loadedNamespaces <- mockery::mock(c("base", "stats", "utils", "methods"))
  mock_loadNamespace    <- mockery::mock(NULL)
  mock_detach           <- mockery::mock(TRUE)
  mock_library          <- mockery::mock(NULL)
  mock_tempfile         <- mockery::mock("/tmp/runner.R")
  mock_writeLines       <- mockery::mock(NULL)
  mock_env_cov          <- function(env, test_files) stop("instrumentation failed")
  mock_cov_to_list      <- mockery::mock(list(filecoverage = matrix(0, nrow = 1), totalcoverage = 0))
  
  # Wire stubs
  mockery::stub(fn, "get_pkg_name",                        mock_get_pkg_name)
  mockery::stub(fn, "is_bioc_runit_layout",                mock_is_layout)
  mockery::stub(fn, "get_biocgenerics_test_paths",         mock_get_paths)
  mockery::stub(fn, "normalizePath",                       mock_normalize)
  mockery::stub(fn, "get_source_test_mapping_Bioc_nstf",   mock_get_mapping)
  mockery::stub(fn, "remove_run_unitTests_mapping",        mock_remove_map)
  mockery::stub(fn, "prepare_for_bioc_runit",              mock_prepare)
  mockery::stub(fn, "check_covr_skip_Bioc_nstf",           mock_problems)
  mockery::stub(fn, "get_function_no_tests",               mock_no_tests)
  mockery::stub(fn, "tidyr::drop_na",                      mock_drop_na)
  mockery::stub(fn, "ls",                                  mock_ls)
  mockery::stub(fn, "asNamespace",                         mock_asNamespace)
  mockery::stub(fn, "assign",                              mock_assign)
  mockery::stub(fn, "load_package_datasets",               mock_load_datasets)
  mockery::stub(fn, "message",                             mock_message)
  mockery::stub(fn, "loadedNamespaces",                    mock_loadedNamespaces)
  mockery::stub(fn, "loadNamespace",                       mock_loadNamespace)
  mockery::stub(fn, "detach_pkg_if_attached",              mock_detach)
  mockery::stub(fn, "library",                             mock_library)
  mockery::stub(fn, "tempfile",                            mock_tempfile)
  mockery::stub(fn, "writeLines",                          mock_writeLines)
  mockery::stub(fn, "covr::environment_coverage",          mock_env_cov)
  mockery::stub(fn, "covr::coverage_to_list",              mock_cov_to_list)
  mockery::stub(fn, "cleanup_and_return_null",             mock_cleanup)
  
  # Execute
  res <- fn(pkg_source_path = pkg_source_path, test_pkg_data = test_pkg_data)
  
  # Should return NULL due to NULL mapping early return
  expect_null(res)
  
  # Ensure cleaning message was emitted
  msg_args <- mockery::mock_args(mock_message)
  expect_true(any(vapply(msg_args, function(x) {
    any(grepl(paste0("performing source test map cleaning for ", pkg_name),
              paste0(unlist(x), collapse = "")))
  }, logical(1))))
})

test_that("returns invisible(TRUE)", {
  fn <- prepare_for_bioc_runit
  
  mock_detach <- mockery::mock(NULL, NULL, NULL, NULL)
  mock_load_all <- mockery::mock(NULL)
  
  mockery::stub(fn, "detach_pkg_if_attached", mock_detach)
  mockery::stub(fn, "pkgload::load_all", mock_load_all)
  
  v <- withVisible(fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg"))
  expect_true(v$value)
  expect_false(v$visible)
})

test_that("propagates quietly (no messages) and does not error when mocked", {
  fn <- prepare_for_bioc_runit
  
  mock_detach <- mockery::mock(NULL, NULL, NULL, NULL)
  mock_load_all <- mockery::mock(NULL)
  
  mockery::stub(fn, "detach_pkg_if_attached", mock_detach)
  mockery::stub(fn, "pkgload::load_all", mock_load_all)
  
  expect_silent(res <- fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg"))
  expect_true(res)
})

test_that("load_all called even if detaches throw; try-catch not inside but we still see error", {
  fn <- prepare_for_bioc_runit
  
  # If a detach throws, since the function doesn't try-catch around it, the error
  # should propagate and load_all should not be called. We can still validate behavior.
  throwing_detach <- function(...) stop("detach failure")
  mock_load_all <- mockery::mock(NULL)
  
  mockery::stub(fn, "detach_pkg_if_attached", throwing_detach)
  mockery::stub(fn, "pkgload::load_all", mock_load_all)
  
  expect_error(fn(pkg_name = "mypkg", pkg_source_path = "/tmp/mypkg"), "detach failure")
  # Ensure load_all was not called due to early error
  mockery::expect_called(mock_load_all, 0)
}) 


test_that("errors if RUnit is not available", {
  fn <- check_covr_skip_Bioc_nstf
  
  mock_req_ns <- mockery::mock(FALSE)
  mockery::stub(fn, "requireNamespace", mock_req_ns)
  
  expect_error(
    fn(pkg_source_path = "/pkg", mapping = data.frame(test_file = character()), test_path = "/tests"),
    "RUnit must be installed"
  )
  
  mockery::expect_called(mock_req_ns, 1)
  args <- mockery::mock_args(mock_req_ns)
  expect_identical(args[[1]][[1]], "RUnit")
  expect_true(isTRUE(args[[1]][["quietly"]]))
})

test_that("cleans mapping via tidyr::drop_na when test_file contains NA", {
  fn <- check_covr_skip_Bioc_nstf
  
  mapping <- data.frame(test_file = c("test-a.R", NA_character_, "test-b.R"), stringsAsFactors = FALSE)
  
  mock_req_ns <- mockery::mock(TRUE)
  # drop_na should remove the NA row
  mock_drop_na <- function(df, test_file) df[!is.na(df$test_file), , drop = FALSE]
  
  # Minimal stubs to allow the function to proceed to the lapply without real side effects
  mock_file_exists <- mockery::mock(TRUE, TRUE, cycle = TRUE) # two files exist
  mock_search <- mockery::mock("package:stats", "package:stats", cycle = TRUE) # no RUnit attached
  mock_attach <- mockery::mock(NULL, NULL, cycle = TRUE)
  mock_detach <- mockery::mock(NULL, cycle = TRUE)  # may or may not be called by on.exit
  mock_run <- mockery::mock(structure(list(), class = "RUnitTestData"),
                            structure(list(), class = "RUnitTestData"), 
                            cycle = TRUE)
  mock_get_err <- mockery::mock(list(nErr = 0, nFail = 0, nDeactivated = 0),
                                list(nErr = 0, nFail = 0, nDeactivated = 0), 
                                cycle = TRUE)
  
  mockery::stub(fn, "requireNamespace", mock_req_ns)
  mockery::stub(fn, "tidyr::drop_na", mock_drop_na)
  mockery::stub(fn, "file.exists", mock_file_exists)
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "attachNamespace", mock_attach)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "RUnit::runTestFile", mock_run)
  mockery::stub(fn, "RUnit::getErrors", mock_get_err)
  
  res <- fn(pkg_source_path = "/pkg", mapping = mapping, test_path = "/tests")
  
  # Both tests pass -> "No tests skipped"
  expect_s3_class(res, "data.frame")
  expect_identical(nrow(res), 1L)
  expect_true(is.na(res$test_file[1]))
  expect_identical(res$issue_type[1], "No tests skipped")
})


test_that("removes exact filename from mapping when bioc_run_ut_path provided (case-insensitive)", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(
    test_file = c("run_unitTests.R", "test-foo.R", "RUN_UNITTESTS.R", "test-bar.R"),
    stringsAsFactors = FALSE
  )
  test_pkg_data <- list(bioc_run_ut_path = "/pkg/tests/run_unitTests.R")
  
  # Stub basename() to a specific value to assert exact-match branch is used
  mock_basename <- mockery::mock("run_unitTests.R")
  mockery::stub(fn, "basename", mock_basename)
  
  res <- fn(mapping, test_pkg_data)
  
  # Expected: exact "run_unitTests.R" removed via exact-match branch; regex also removes RUN_UNITTESTS.R
  expect_true("test-foo.R" %in% res$test_file)
  expect_true("test-bar.R" %in% res$test_file)
  expect_false("run_unitTests.R" %in% res$test_file)
  expect_false("RUN_UNITTESTS.R" %in% res$test_file)
  
  # basename should be called once with the provided path
  mockery::expect_called(mock_basename, 1)
  args <- mockery::mock_args(mock_basename)
  expect_identical(args[[1]][[1]], "/pkg/tests/run_unitTests.R")
})

test_that("regex fallback removes variations when bioc_run_ut_path is missing/NULL/NA", {
  fn <- remove_run_unitTests_mapping
  
  # No exact name available -> only regex path applies
  mapping <- data.frame(
    test_file = c("run-unitTests.R", "Run_UnitTests.R", "test-x.R", "sub/run_unitTests.R"),
    stringsAsFactors = FALSE
  )
  
  # Case 1: test_pkg_data missing entirely
  res1 <- fn(mapping, test_pkg_data = NULL)
  expect_setequal(res1$test_file, "test-x.R") # note: regex only matches basename
  
  # Case 2: field present but NULL
  res2 <- fn(mapping, test_pkg_data = list(bioc_run_ut_path = NULL))
  expect_setequal(res2$test_file, "test-x.R")
  
  # Case 3: field NA
  res3 <- fn(mapping, test_pkg_data = list(bioc_run_ut_path = NA_character_))
  expect_setequal(res3$test_file, "test-x.R")
})

test_that("does not remove unrelated files; only exact filename or regex variations dropped", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(
    test_file = c("test-a.R", "runUnitTest.R", "unitTests_run.R", "test-b.R"),
    stringsAsFactors = FALSE
  )
  test_pkg_data <- list(bioc_run_ut_path = "/pkg/tests/NotRunUnitTests.R")
  
  # basename returns a non-matching value -> exact match won't drop anything
  mock_basename <- mockery::mock("NotRunUnitTests.R")
  mockery::stub(fn, "basename", mock_basename)
  
  res <- fn(mapping, test_pkg_data)
  
  # None match the strict regex "run[_-]?unitTests\\.R$"
  expect_setequal(res$test_file, c("test-a.R", "runUnitTest.R", "unitTests_run.R", "test-b.R"))
})

test_that("handles NA entries in mapping$test_file gracefully (no warnings)", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(
    test_file = c(NA_character_, "test-a.R", "run_unitTests.R", NA_character_, "test-b.R"),
    stringsAsFactors = FALSE
  )
  test_pkg_data <- list(bioc_run_ut_path = "/pkg/tests/whatever.R")
  
  # basename stub not strictly needed but keep deterministic
  mock_basename <- mockery::mock("whatever.R")
  mockery::stub(fn, "basename", mock_basename)
  
  res <- fn(mapping, test_pkg_data)
  
  # Only run_unitTests.R is removed; NAs are retained
  expect_setequal(res$test_file, c(NA_character_, "test-a.R", NA_character_, "test-b.R"))
})

test_that("exact match removal works even if mapping has different case than basename", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(
    test_file = c("RUN_UNITTESTS.R", "test-a.R"),
    stringsAsFactors = FALSE
  )
  test_pkg_data <- list(bioc_run_ut_path = "/pkg/tests/run_unitTests.R")
  
  mock_basename <- mockery::mock("run_unitTests.R")
  mockery::stub(fn, "basename", mock_basename)
  
  res <- fn(mapping, test_pkg_data)
  expect_setequal(res$test_file, "test-a.R")
})

test_that("regex removes both underscore and hyphen variants, any case", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(
    test_file = c("run_unitTests.R", "run-unitTests.R", "RUN_UNITTESTS.R", "test-a.R", "test-b.R"),
    stringsAsFactors = FALSE
  )
  
  res <- fn(mapping, test_pkg_data = NULL)
  expect_setequal(res$test_file, c("test-a.R", "test-b.R"))
})

test_that("empty mapping returns empty data.frame (no errors)", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(test_file = character(), stringsAsFactors = FALSE)
  res <- fn(mapping, test_pkg_data = NULL)
  
  expect_s3_class(res, "data.frame")
  expect_identical(nrow(res), 0L)
})

test_that("basename is consulted only when bioc_run_ut_path is usable", {
  fn <- remove_run_unitTests_mapping
  
  mapping <- data.frame(test_file = c("run_unitTests.R", "test-a.R"), stringsAsFactors = FALSE)
  
  # Case: usable path -> basename called
  mock_basename <- mockery::mock("run_unitTests.R")
  mockery::stub(fn, "basename", mock_basename)
  res1 <- fn(mapping, list(bioc_run_ut_path = "/pkg/tests/run_unitTests.R"))
  mockery::expect_called(mock_basename, 1)
  expect_setequal(res1$test_file, "test-a.R")
  
  # Case: NA path -> basename should not be called
  mock_basename2 <- mockery::mock("should-not-be-called")
  mockery::stub(fn, "basename", mock_basename2)
  res2 <- fn(mapping, list(bioc_run_ut_path = NA_character_))
  mockery::expect_called(mock_basename2, 0)
  expect_setequal(res2$test_file, c("test-a.R"))
  
  # Case: NULL path -> basename should not be called
  mock_basename3 <- mockery::mock("should-not-be-called")
  mockery::stub(fn, "basename", mock_basename3)
  res3 <- fn(mapping, list(bioc_run_ut_path = NULL))
  mockery::expect_called(mock_basename3, 0)
  expect_setequal(res3$test_file, c("test-a.R"))
})  

test_that("classifies missing, error, failure, skipped, and passing files correctly", {
  fn <- check_covr_skip_Bioc_nstf
  
  mapping <- data.frame(
    test_file = c(
      "test-missing.R",
      "test-error-throw.R",
      "test-error-count.R",
      "test-failure.R",
      "test-skipped.R",
      "test-pass.R",
      NA_character_
    ),
    stringsAsFactors = FALSE
  )
  
  mock_req_ns <- mockery::mock(TRUE)
  
  # drop_na should be called and remove NA
  mock_drop_na <- function(df, test_file) df[!is.na(df$test_file), , drop = FALSE]
  
  # file.exists -> FALSE only for test-missing.R
  mock_file_exists <- function(path) {
    if (grepl("test-missing\\.R$", path)) return(FALSE)
    TRUE
  }
  
  # Pretend RUnit is already on the search path so we don't re-attach each time
  mock_search <- mockery::mock("package:RUnit", "package:RUnit", "package:RUnit",
                               "package:RUnit", "package:RUnit", "package:RUnit")
  
  # runTestFile: throws for test-error-throw, otherwise returns a testdata object
  mock_run <- function(absFileName, useOwnErrorHandler, testFuncRegexp) {
    bn <- basename(absFileName)
    if (bn == "test-error-throw.R") stop("parse/runtime error")
    structure(list(file = bn), class = "RUnitTestData")
  }
  
  # getErrors: return different counts per file name
  mock_get_err <- function(td) {
    bn <- td$file
    switch(
      bn,
      "test-error-count.R" = list(nErr = 1, nFail = 0, nDeactivated = 0),
      "test-failure.R"     = list(nErr = 0, nFail = 2, nDeactivated = 0),
      "test-skipped.R"     = list(nErr = 0, nFail = 0, nDeactivated = 3),
      # default: passing
      list(nErr = 0, nFail = 0, nDeactivated = 0)
    )
  }
  
  mock_attach <- mockery::mock(NULL) # not used because search() includes RUnit
  mock_detach <- mockery::mock(NULL)
  
  mockery::stub(fn, "requireNamespace", mock_req_ns)
  mockery::stub(fn, "tidyr::drop_na", mock_drop_na)
  mockery::stub(fn, "file.exists", mock_file_exists)
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "attachNamespace", mock_attach)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "RUnit::runTestFile", mock_run)
  mockery::stub(fn, "RUnit::getErrors", mock_get_err)
  
  res <- fn(pkg_source_path = "/pkg", mapping = mapping, test_path = "/tests")
  
  # Expected issues (order follows mapping after drop_na)
  expected <- data.frame(
    test_file  = c("test-missing.R", "test-error-throw.R", "test-error-count.R", "test-failure.R", "test-skipped.R"),
    issue_type = c("missing",        "error",              "error",               "failure",         "skipped"),
    stringsAsFactors = FALSE
  )
 
  expect_s3_class(res, "data.frame")
  expect_identical(res[, c("test_file", "issue_type")], expected)
})

test_that("when all files are passing, returns 'No tests skipped' row", {
  fn <- check_covr_skip_Bioc_nstf
  
  mapping <- data.frame(test_file = c("t1.R", "t2.R"), stringsAsFactors = FALSE)
  
  mock_req_ns <- mockery::mock(TRUE)
  # anyNA(mapping$test_file) is FALSE -> ensure we don't call tidyr::drop_na (we make it throw if called)
  throwing_drop_na <- function(...) stop("drop_na should not be called when no NA present")
  
  mock_file_exists <- mockery::mock(TRUE, TRUE)
  mock_search <- mockery::mock("package:RUnit", "package:RUnit")
  mock_run <- mockery::mock(structure(list(file = "t1.R"), class = "RUnitTestData"),
                            structure(list(file = "t2.R"), class = "RUnitTestData"))
  mock_get_err <- mockery::mock(list(nErr = 0, nFail = 0, nDeactivated = 0),
                                list(nErr = 0, nFail = 0, nDeactivated = 0))
  mock_attach <- mockery::mock(NULL)
  mock_detach <- mockery::mock(NULL)
  
  mockery::stub(fn, "requireNamespace", mock_req_ns)
  mockery::stub(fn, "tidyr::drop_na", throwing_drop_na)
  mockery::stub(fn, "file.exists", mock_file_exists)
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "attachNamespace", mock_attach)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "RUnit::runTestFile", mock_run)
  mockery::stub(fn, "RUnit::getErrors", mock_get_err)
  
  res <- fn(pkg_source_path = "/pkg", mapping = mapping, test_path = "/tests")
  
  expect_s3_class(res, "data.frame")
  expect_identical(nrow(res), 1L)
  expect_true(is.na(res$test_file[1]))
  expect_identical(res$issue_type[1], "No tests skipped")
})



test_that("attaches RUnit namespace only when not already on search path", {
  fn <- check_covr_skip_Bioc_nstf
  
  mapping <- data.frame(test_file = "pass.R", stringsAsFactors = FALSE)
  
  mock_req_ns <- mockery::mock(TRUE)
  # First call to search() -> no RUnit; inside on.exit check may call again, so provide two values
  mock_search <- mockery::mock("package:stats", "package:RUnit")
  mock_attach <- mockery::mock(NULL)  # should be called once
  mock_detach <- mockery::mock(NULL)
  
  mock_file_exists <- mockery::mock(TRUE)
  mock_run <- mockery::mock(structure(list(file = "pass.R"), class = "RUnitTestData"))
  mock_get_err <- mockery::mock(list(nErr = 0, nFail = 0, nDeactivated = 0))
  id_drop_na <- function(df, test_file) df
  
  mockery::stub(fn, "requireNamespace", mock_req_ns)
  mockery::stub(fn, "tidyr::drop_na", id_drop_na)
  mockery::stub(fn, "file.exists", mock_file_exists)
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "attachNamespace", mock_attach)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "RUnit::runTestFile", mock_run)
  mockery::stub(fn, "RUnit::getErrors", mock_get_err)
  
  res <- fn(pkg_source_path = "/pkg", mapping = mapping, test_path = "/tests")
  expect_s3_class(res, "data.frame")
  
  mockery::expect_called(mock_attach, 1)
})

test_that("runTestFile error path is captured as 'error' without crashing", {
  fn <- check_covr_skip_Bioc_nstf
  
  mapping <- data.frame(test_file = "test-error-throw.R", stringsAsFactors = FALSE)
  
  mock_req_ns <- mockery::mock(TRUE)
  id_drop_na <- function(df, test_file) df
  mock_file_exists <- mockery::mock(TRUE)
  mock_search <- mockery::mock("package:RUnit")
  mock_attach <- mockery::mock(NULL)
  mock_detach <- mockery::mock(NULL)
  
  # Force runTestFile to throw -> tryCatch will return class "RUnitTestData-ERROR"
  mock_run <- function(...) stop("boom")
  # getErrors should not be called in this path
  mock_get_err <- mockery::mock()
  
  mockery::stub(fn, "requireNamespace", mock_req_ns)
  mockery::stub(fn, "tidyr::drop_na", id_drop_na)
  mockery::stub(fn, "file.exists", mock_file_exists)
  mockery::stub(fn, "search", mock_search)
  mockery::stub(fn, "attachNamespace", mock_attach)
  mockery::stub(fn, "detach", mock_detach)
  mockery::stub(fn, "RUnit::runTestFile", mock_run)
  mockery::stub(fn, "RUnit::getErrors", mock_get_err)
  
  res <- fn(pkg_source_path = "/pkg", mapping = mapping, test_path = "/tests")
  
  expect_identical(nrow(res), 1L)
  expect_identical(res$test_file[1], "test-error-throw.R")
  expect_identical(res$issue_type[1], "error")
  mockery::expect_called(mock_get_err, 0)
})


test_that("returns bioc_unitTests when testdir already exists", {
  fn <- get_biocgenerics_test_paths
  
  # Normalized testdir value
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) {
    # Return a consistent normalized path regardless of input
    if (grepl("^/work/pkg/tests$", path)) return("/work/pkg/tests")
    path
  }
  mock_dir_exists <- function(path) path == "/work/pkg/tests"
  
  # Stubs
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  
  res <- fn(test_pkg_data = list(), testdir = "/work/pkg/tests")
  expect_true("bioc_unitTests" %in% names(res))
  expect_identical(res$bioc_unitTests, "/work/pkg/tests")
  expect_false("fallback" %in% names(res))
})

test_that("finds package root at testdir and returns inst/unitTests if it exists", {
  fn <- get_biocgenerics_test_paths
  
  # Paths
  raw_testdir <- "/work/pkg/tests"
  normalized_testdir <- "/work/pkg/tests"
  pkg_root <- "/work/pkg"
  bioc_unit <- "/work/pkg/inst/unitTests"
  
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) {
    # Normalize testdir and pkg_root and bioc_unit deterministically
    if (path == raw_testdir) return(normalized_testdir)
    if (path == pkg_root) return(pkg_root)
    if (path == bioc_unit) return(bioc_unit)
    path
  }
  
  # testdir does NOT exist; bioc unit dir DOES
  mock_dir_exists <- function(path) {
    path == bioc_unit
  }
  
  # file.exists(DESCRIPTION) returns TRUE only at testdir's root (/work/pkg)
  mock_file_exists <- function(path) {
    path == file.path(pkg_root, "DESCRIPTION")
  }
  
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(test_pkg_data = list(), testdir = raw_testdir)
  expect_true("bioc_unitTests" %in% names(res))
  expect_identical(res$bioc_unitTests, bioc_unit)
  expect_false("fallback" %in% names(res))
})

test_that("finds package root at dirname(testdir) and returns inst/unitTests", {
  fn <- get_biocgenerics_test_paths
  
  # Layout:
  # testdir: /work/pkg/tests/unit
  # dirname(testdir): /work/pkg/tests
  # dirname(dirname(testdir)): /work/pkg
  raw_testdir <- "/work/pkg/tests/unit"
  normalized_testdir <- raw_testdir
  pkg_root <- "/work/pkg"
  bioc_unit <- "/work/pkg/inst/unitTests"
  
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) {
    if (path %in% c(raw_testdir, pkg_root, bioc_unit)) return(path)
    path
  }
  
  mock_dir_exists <- function(path) {
    # testdir does not exist; bioc_unit exists
    path == bioc_unit
  }
  
  mock_file_exists <- function(path) {
    # Mark only /work/pkg as a package root
    path == file.path(pkg_root, "DESCRIPTION")
  }
  
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(test_pkg_data = list(), testdir = raw_testdir)
  expect_true("bioc_unitTests" %in% names(res))
  expect_identical(res$bioc_unitTests, bioc_unit)
})

test_that("finds package root at dirname(dirname(testdir)) and returns inst/unitTests", {
  fn <- get_biocgenerics_test_paths
  
  raw_testdir <- "/work/pkg/a/b"
  pkg_root <- "/work/pkg"
  bioc_unit <- "/work/pkg/inst/unitTests"
  
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) path
  mock_dir_exists <- function(path) path == bioc_unit
  mock_file_exists <- function(path) path == file.path(pkg_root, "DESCRIPTION")
  
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(test_pkg_data = list(), testdir = raw_testdir)
  expect_true("bioc_unitTests" %in% names(res))
  expect_identical(res$bioc_unitTests, bioc_unit)
})

test_that("returns fallback when testdir doesn't exist and no package root found", {
  fn <- get_biocgenerics_test_paths
  
  raw_testdir <- "/nowhere/tests"
  normalized_testdir <- "/nowhere/tests"
  
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) normalized_testdir
  mock_dir_exists <- function(path) FALSE
  mock_file_exists <- function(path) FALSE
  
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(test_pkg_data = list(), testdir = raw_testdir)
  expect_true("fallback" %in% names(res))
  expect_identical(res$fallback, normalized_testdir)
  expect_false("bioc_unitTests" %in% names(res))
})

test_that("returns fallback if package root found but inst/unitTests does not exist", {
  fn <- get_biocgenerics_test_paths
  
  raw_testdir <- "/work/pkg/tests"
  pkg_root <- "/work/pkg"
  normalized_testdir <- raw_testdir
  bioc_unit <- "/work/pkg/inst/unitTests"
  
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) path
  mock_dir_exists <- function(path) FALSE  # inst/unitTests not present
  mock_file_exists <- function(path) path == file.path(pkg_root, "DESCRIPTION")
  
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(test_pkg_data = list(), testdir = raw_testdir)
  expect_true("fallback" %in% names(res))
  expect_identical(res$fallback, normalized_testdir)
})

test_that("handles duplicate candidate paths via unique()", {
  fn <- get_biocgenerics_test_paths
  
  # If testdir == dirname(testdir), unique() collapses duplicates.
  raw_testdir <- "/flat"
  # Simulate a flat structure where dirname("/flat") == "/"
  mock_normalize <- function(path, winslash = "/", mustWork = FALSE) path
  mock_dir_exists <- function(path) FALSE
  mock_file_exists <- function(path) FALSE
  
  mockery::stub(fn, "normalizePath", mock_normalize)
  mockery::stub(fn, "dir.exists", mock_dir_exists)
  mockery::stub(fn, "file.exists", mock_file_exists)
  
  res <- fn(test_pkg_data = list(), testdir = raw_testdir)
            
  expect_true("fallback" %in% names(res))
  expect_identical(res$fallback, raw_testdir)
})



test_that("errors when no R source files are found", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/proj/pkg"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/proj/pkg/tests"
  
  # Stub list.files so there are NO source files
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(character())  # no .R under R/
    if (path == test_dir)   return(character(0)) # doesn't matter
    character(0)
  }
  mockery::stub(fn, "list.files", mock_list_files)
  
  # Use a simple pattern (or regexp = NA) to avoid brittle matching
  expect_error(
    fn(pkg_source_path = pkg_source_path, test_dir = test_dir),
    regexp = "No R files found under:"
  )
})

test_that("warns when no .R test files are found and maps NA tests", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/proj/pkg"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/proj/pkg/tests"
  
  src_files <- file.path(source_dir, c("alpha.R", "beta.R"))
  
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(src_files)    # two sources
    if (path == test_dir)   return(character())  # no tests -> warning
    character()
  }
  # No test content will be read because length(test_files) == 0
  mock_readLines <- function(con, warn = FALSE) stop("readLines should not be called")
  
  mockery::stub(fn, "list.files", mock_list_files)
  mockery::stub(fn, "readLines", mock_readLines)
  
  expect_warning(
    res <- fn(pkg_source_path = pkg_source_path, test_dir = test_dir),
    regexp = "No R test files found under:"
  )
  
  expect_s3_class(res, "data.frame")
  expect_identical(nrow(res), 2L)
  expect_identical(res$source_file, c("alpha.R", "beta.R"))
  expect_true(all(is.na(res$test_file)))
  expect_true(all(is.na(res$evidence)))
  expect_identical(res$has_tests, c(FALSE, FALSE))
})

test_that("filename-based mapping: tokenized, case-insensitive, picks first match", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/pkg"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/pkg/tests"
  
  src_files  <- file.path(source_dir, c("foo.R", "bar.R"))
  test_files <- file.path(test_dir, c("test-foo.R", "ZZZ_foo_extra.R", "TEST-bar.R"))
  
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(src_files)
    if (path == test_dir)   return(test_files)
    character()
  }
  # No content-based checks needed
  mock_readLines <- function(con, warn = FALSE) character()
  
  mockery::stub(fn, "list.files", mock_list_files)
  mockery::stub(fn, "readLines", mock_readLines)
  
  res <- fn(pkg_source_path = pkg_source_path, test_dir = test_dir)
  
  expect_s3_class(res, "data.frame")
  expect_identical(res$source_file, c("foo.R", "bar.R"))
  expect_identical(res$test_file,   c("test-foo.R", "TEST-bar.R")) # first match wins
  expect_identical(res$evidence,    c("filename_match", "filename_match"))
  expect_identical(res$has_tests,   c(TRUE, TRUE))
})

test_that("content-based mapping via RUnit test function names when filename matching fails", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/p"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/p/tests"
  
  # One source 'widget.R'; one test 'test-something.R'
  src_files  <- file.path(source_dir, "widget.R")
  test_files <- file.path(test_dir,   "test-something.R")
  
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(src_files)
    if (path == test_dir)   return(test_files)
    character()
  }
  
  # Provide a FUNCTION DEF in source (ensures symbol extraction works)
  # Provide an RUnit test function line that contains the symbol
  mock_readLines <- function(con, warn = FALSE) {
    f <- as.character(con)
    if (identical(basename(f), "widget.R")) {
      return(c("widget <- function(x) { x }"))
    }
    if (identical(basename(f), "test-something.R")) {
      return(c("test_widget <- function() {",
               "  # assertions here",
               "}"))
    }
    character()
  }
  
  mockery::stub(fn, "list.files", mock_list_files)
  mockery::stub(fn, "readLines",  mock_readLines)
  
  res <- fn(pkg_source_path = pkg_source_path, test_dir = test_dir)
  
  expect_identical(res$source_file, "widget.R")
  expect_identical(res$test_file,   "test-something.R")
  expect_identical(res$evidence,    "content_match")
  expect_true(res$has_tests)
})

test_that("content-based mapping uses symbols from function defs (avoid setGeneric parsing)", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/root"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/root/tests"
  
  src_files  <- file.path(source_dir, c("generics.R", "funcs.R"))
  test_files <- file.path(test_dir,   c("t-generic.R", "t-func.R"))
  
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(src_files)
    if (path == test_dir)   return(test_files)
    character()
  }
  
  # IMPORTANT: Because the function's setGeneric grep uses fixed=TRUE with an escaped quote,
  # it's safer to provide FUNCTION DEFS in sources so symbol extraction surely works.
  mock_readLines <- function(con, warn = FALSE) {
    f <- as.character(con)
    switch(
      basename(f),
      "generics.R" = c(
        "doStuff <- function(...) TRUE"   # symbol: doStuff
      ),
      "funcs.R"    = c(
        "my_func <- function(a, b) { a + b }",
        "other <- function() 1"
      ),
      "t-generic.R" = c(
        "test_doStuff <- function() { TRUE }"  # detects 'doStuff' via RUnit name
      ),
      "t-func.R" = c(
        "test_my_func <- function() { TRUE }"
      ),
      character()
    )
  }
  
  mockery::stub(fn, "list.files", mock_list_files)
  mockery::stub(fn, "readLines",  mock_readLines)
  
  res <- fn(pkg_source_path = pkg_source_path, test_dir = test_dir)
  
  expect_identical(res$source_file, c("generics.R", "funcs.R"))
  expect_identical(res$test_file,   c("t-generic.R", NA))
  expect_identical(res$evidence,    c("content_match", NA))
  expect_identical(res$has_tests,   c(TRUE, FALSE))
})

test_that("short symbol (<= 2 chars) matches via filename tokens", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/a"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/a/tests"
  
  src_files  <- file.path(source_dir, c("t.R", "x1.R"))
  test_files <- file.path(test_dir,   c("test-t.R", "we-test-x1.R"))
  
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(src_files)
    if (path == test_dir)   return(test_files)
    character()
  }
  mock_readLines <- function(con, warn = FALSE) character()
  
  mockery::stub(fn, "list.files", mock_list_files)
  mockery::stub(fn, "readLines",  mock_readLines)
  
  res <- fn(pkg_source_path = pkg_source_path, test_dir = test_dir)
  
  expect_identical(res$source_file, c("t.R", "x1.R"))
  expect_identical(res$test_file,   c("test-t.R", "we-test-x1.R"))
  expect_identical(res$evidence,    c("filename_match", "filename_match"))
  expect_identical(res$has_tests,   c(TRUE, TRUE))
})

test_that("returns NA mapping for source with no filename or content evidence", {
  fn <- get_source_test_mapping_Bioc_nstf
  
  pkg_source_path <- "/no"
  source_dir <- file.path(pkg_source_path, "R")
  test_dir <- "/no/tests"
  
  src_files  <- file.path(source_dir, "lonely.R")
  test_files <- file.path(test_dir,   c("test-a.R", "test-b.R"))  # no relevant content
  
  mock_list_files <- function(path, pattern, full.names, recursive) {
    if (path == source_dir) return(src_files)
    if (path == test_dir)   return(test_files)
    character()
  }
  
  mock_readLines <- function(con, warn = FALSE) {
    f <- as.character(con)
    if (identical(basename(f), "lonely.R")) return(c("lonely <- function() 1"))
    if (basename(f) %in% c("test-a.R", "test-b.R")) return(c("test_other <- function() 1"))
    character()
  }
  
  mockery::stub(fn, "list.files", mock_list_files)
  mockery::stub(fn, "readLines",  mock_readLines)
  
  res <- fn(pkg_source_path = pkg_source_path, test_dir = test_dir)
  
  expect_identical(res$source_file, "lonely.R")
  expect_true(is.na(res$test_file))
  expect_true(is.na(res$evidence))
  expect_false(res$has_tests)
})

Try the test.assessr package in your browser

Any scripts or data that you put into this service are public.

test.assessr documentation built on March 3, 2026, 1:06 a.m.