diff --git a/R/loadfast.R b/R/loadfast.R index 5f9255f..6c54503 100644 --- a/R/loadfast.R +++ b/R/loadfast.R @@ -227,6 +227,9 @@ load_fast <- function(path = ".", helpers = TRUE, attach_testthat = NULL, full = "]" ) + .loadfast.run_onload(ns_env, abs_path, pkg_name) + .timer("incr .onLoad") + .loadfast.source_helpers(abs_path, pkg_env, helpers, attach_testthat, pkg_name) .timer("TOTAL (incremental)") return(invisible(ns_env)) @@ -398,6 +401,9 @@ load_fast <- function(path = ".", helpers = TRUE, attach_testthat = NULL, full = } .timer("attach testthat") + .loadfast.run_onload(ns_env, abs_path, pkg_name) + .timer(".onLoad") + pkg_env <- attach(NULL, name = pkg_env_name) list2env(as.list(ns_env, all.names = FALSE), envir = pkg_env) list2env(as.list(impenv, all.names = TRUE), envir = pkg_env) @@ -496,6 +502,17 @@ load_fast_register_reload <- function(path = ".", files, reason = NULL) { invisible(TRUE) } +.loadfast.run_onload <- function(ns_env, abs_path, pkg_name) { + if (!exists(".onLoad", envir = ns_env, inherits = FALSE)) return(invisible(NULL)) + tryCatch( + get(".onLoad", envir = ns_env, inherits = FALSE)(dirname(abs_path), pkg_name), + error = function(e) { + warning("Error in .onLoad() for '", pkg_name, "': ", conditionMessage(e), call. = FALSE) + } + ) + invisible(NULL) +} + .loadfast.source_one <- function(f, ns_env) { s4_pattern <- "no definition for class" tryCatch( diff --git a/TECHNICAL_DEBT.md b/TECHNICAL_DEBT.md index 640c26d..92a44c8 100644 --- a/TECHNICAL_DEBT.md +++ b/TECHNICAL_DEBT.md @@ -48,24 +48,6 @@ If the `Package:` field in `DESCRIPTION` changes in place for the same directory ## Low-priority debt -### 5. Load hook behavior is not explicitly tested -**Why this matters** - -The loader aims to behave like a practical development-time package loader, but the test suite does not currently pin down the behavior of `.onLoad()` and `.onAttach()` hooks. - -**Risk** -- Hook execution semantics may drift unnoticed during refactors -- Full and incremental loads may behave inconsistently -- Package state initialized by hooks may be wrong in edge cases - -**Preferred fix** -- Add focused tests that define and exercise `.onLoad()` / `.onAttach()` in a temp package -- Verify behavior on first load, incremental reload, and `full = TRUE` -- Document any intentional differences from `devtools::load_all()` - -**Priority** -- Low - ## Low-priority debt ### 3. Testthat detection logic is duplicated diff --git a/test_loadfast.R b/test_loadfast.R index 1b02b21..f93ec7f 100644 --- a/test_loadfast.R +++ b/test_loadfast.R @@ -1931,6 +1931,62 @@ check("lockfile: full reload resets warning baseline", quote( !any(grepl("renv.lock changed", lock_reload_full$warnings)) )) +# --- 4l: .onLoad hook is called during full and incremental loads --- +cat("\n--- 4l: .onLoad hook execution ---\n\n") + +tmp_onload <- tempfile("loadfast_onload_") +copy_baseline(tmp_onload) +remove_renv_lock(tmp_onload) + +writeLines(c( + ".onLoad_call_count <- 0L", + ".onLoad <- function(libname, pkgname) {", + " .onLoad_call_count <<- .onLoad_call_count + 1L", + " .onLoad_libname <<- libname", + " .onLoad_pkgname <<- pkgname", + "}" +), file.path(tmp_onload, "R", "zzz.R")) + +ns_onload <- load_fast(tmp_onload, helpers = FALSE, attach_testthat = FALSE) + +check(".onLoad: is called on full load", quote( + exists(".onLoad_call_count", envir = ns_onload, inherits = FALSE) && + get(".onLoad_call_count", envir = ns_onload) == 1L +)) + +check(".onLoad: receives correct pkgname", quote( + get(".onLoad_pkgname", envir = ns_onload) == "devpackage" +)) + +check(".onLoad: receives dirname(abs_path) as libname", quote( + identical( + normalizePath(get(".onLoad_libname", envir = ns_onload), mustWork = FALSE), + normalizePath(dirname(tmp_onload), mustWork = FALSE) + ) +)) + +# Incremental reload — nothing changed => short-circuit, .onLoad not re-called +ns_onload_nochg <- load_fast(tmp_onload, helpers = FALSE, attach_testthat = FALSE) + +check(".onLoad: not called again on no-change reload", quote( + get(".onLoad_call_count", envir = ns_onload_nochg) == 1L +)) + +# Trigger an incremental reload by modifying base.R +writeLines(c( + "add <- function(a, b) a + b + 9999" +), file.path(tmp_onload, "R", "base.R")) + +ns_onload_incr <- load_fast(tmp_onload, helpers = FALSE, attach_testthat = FALSE) + +check(".onLoad: called again on incremental reload (files changed)", quote( + get(".onLoad_call_count", envir = ns_onload_incr) == 2L +)) + +check(".onLoad: add() reflects incremental change", quote( + get("add", envir = ns_onload_incr)(1, 2) == 10002 +)) + # ============================================================================ # Summary # ============================================================================