"engine$load_file_in_env evaluates source into environment"
"(load ...) evaluates file in current environment"
"(load path env) evaluates file in the provided environment"
"(load ...) does not resolve stdlib by name"
"(import ...) loads module exports into environment"
"(import ...) does not re-evaluate loaded modules"
"(import ...) errors on missing modules and exports"
"(import \"path\") loads module by path and attaches exports"
"second (import \"path\") does not reload module"
"relative import paths resolve from importing file's directory"
"(import symbol) is module name, (import \"string\") is path"

(import "path") loads module by path and attaches exports

<arl_error/error/condition>
Error: File not found: D:	emp\2026_02_23_11_20_16_15377\Rtmp4WUrHm\filee18602f496a/changing-file.arl

# Tests for ModuleCache (R/module-cache.R): module caching system
# Default compiler flags for cache tests (all TRUE = default compiler state)
enable_tco = TRUE, enable_constant_folding = TRUE,
enable_dead_code_elim = TRUE, enable_strength_reduction = TRUE,
enable_identity_elim = TRUE, enable_truthiness_opt = TRUE,
enable_begin_simplify = TRUE, enable_boolean_flatten = TRUE
test_that("get_paths() returns NULL for non-existent file", {
test_that("get_paths() returns expected structure", {
writeLines("(module test (export foo))", tmp_file)
expect_true(all(c("cache_dir", "code_cache", "code_r", "file_hash") %in% names(paths)))
expect_true(startsWith(normalizePath(paths$cache_dir, mustWork = FALSE, winslash = "/"),
normalizePath(user_cache, mustWork = FALSE, winslash = "/")))
expect_true(nchar(paths$file_hash) == 32) # MD5 hash length
test_that("get_paths() hash changes when file content changes", {
writeLines("(module test (export foo bar))", tmp_file)
test_that("write_code() creates cache files", {
result <- cache$write_code("test", compiled_body, c("foo"), FALSE, FALSE, tmp_file, paths$file_hash, compiler_flags = .test_compiler_flags)
# .code.R only written when debug_cache is TRUE
test_that("write_code() creates human-readable .code.R file when debug_cache is TRUE", {
compiled_body <- list(quote(foo <- 42), quote(bar <- "test"))
cache$write_code("test", compiled_body, c("foo", "bar"), TRUE, FALSE, tmp_file, paths$file_hash, compiler_flags = .test_compiler_flags)
cache$write_code("test", compiled_body, c("foo"), FALSE, FALSE, tmp_file, paths$file_hash, compiler_flags = .test_compiler_flags)
test_that("load_code() returns NULL for non-existent cache", {
test_that("load_code() loads cache data", {
test_that("load_code() returns NULL for version mismatch", {
test_that("load_code() returns NULL for coverage mismatch", {
cache$write_code("test", compiled_body, c("foo"), FALSE, FALSE, tmp_file, paths$file_hash, coverage = TRUE, compiler_flags = .test_compiler_flags)
# Loading without coverage should reject it
result <- cache$load_code(paths$code_cache, tmp_file, coverage = FALSE)
test_that("load_code() returns NULL for missing coverage field (pre-upgrade cache)", {
# Write a cache, then strip the coverage field to simulate old format
test_that("load_code() returns NULL for hash mismatch", {
cache$write_code("test", compiled_body, c("foo"), FALSE, FALSE, tmp_file, paths1$file_hash, compiler_flags = .test_compiler_flags)
# Old cache with old hash should be invalid
# Integration test: cache hit path excludes _-prefixed names from export-all
test_that("cache hit path excludes _-prefixed names from export-all exports", {
"  (define _private-helper (lambda () 2))",
"  (define __also-private (lambda () 3)))"
# First load — populates cache and module registry
# Second load with a fresh engine — should hit cache
info = "cache hit path must exclude _-prefixed names")
info = "cache hit path must exclude .__-prefixed names")
test_that("write_code() stores compiler_flags in cache data", {
flags <- c(enable_tco = TRUE, enable_constant_folding = FALSE,
enable_begin_simplify = TRUE, enable_boolean_flatten = TRUE)
cache$write_code("test", compiled_body, c("foo"), FALSE, FALSE, tmp_file,
test_that("load_code() rejects cache with mismatched compiler_flags", {
flags1 <- c(enable_tco = TRUE, enable_constant_folding = TRUE,
flags2 <- c(enable_tco = FALSE, enable_constant_folding = TRUE,
result_same <- cache$load_code(paths$code_cache, tmp_file, file_hash = paths$file_hash,
# Load with different flags — should reject (and deletes cache file)
result_diff <- cache$load_code(paths$code_cache, tmp_file, file_hash = paths$file_hash,
test_that("load_code() rejects cache with NULL compiler_flags (pre-upgrade)", {
flags <- c(enable_tco = TRUE, enable_constant_folding = TRUE,
# Strip compiler_flags to simulate old cache format
result <- cache$load_code(paths$code_cache, tmp_file, file_hash = paths$file_hash,
# --- default_packages NULL rejection ---
test_that("load_code() rejects cache with NULL default_packages (pre-upgrade)", {
# Set default_packages to NULL to simulate old cache format
result <- cache$load_code(paths$code_cache, tmp_file, file_hash = paths$file_hash)
test_that("write_code() cleans up old cache files for same source", {
# Write cache for hash H1 (with debug_cache to test .code.R cleanup)
on.exit(options(arl.debug_cache = NULL), add = TRUE)
cache$write_code("test", list(quote(foo <- 42)), c("foo"), FALSE, FALSE,
cache$write_code("test", list(quote(foo <- 42), quote(bar <- 1)), c("foo", "bar"),
# H1 files should be gone, H2 files should exist
# --- TOCTOU: write_code uses provided cache_paths, not fresh get_paths ---
test_that("write_code() uses provided cache_paths instead of recomputing", {
# Change file content (simulates TOCTOU: file changed between read and cache write)
# Write cache with explicitly provided cache_paths from V1
# This should write to V1's filename, not recompute a V2 hash
result <- cache$write_code("test", list(quote(x <- 1)), c("x"), FALSE, FALSE,
tmp_file, paths_v1$file_hash, cache_paths = paths_v1)
info = "cache file should be at V1 path (provided cache_paths)")
# The V2 hash file should NOT exist (we didn't recompute)
info = "no cache file should exist at V2 path")
test_that("code cache is written for loaded modules", {
# Verify: .code.rds written under R_user_dir
cache_files <- list.files(paths$cache_dir, pattern = "\\.rds$", full.names = FALSE)
expect_true(code_cache_exists, "expr cache (.code.rds) should be written")
test_that("code cache is safe with file changes", {
# The expr cache (compiled expressions) is the safe default.
# It verifies that changes to module files are properly detected through cache invalidation.
# Create a simple file (not a module, to avoid import complexity)
# Reload in new engine (simulating fresh session)
# Verify the change is picked up (cache was invalidated by content hash)
test_that("code cache reused across engine instances", {
# Second engine should find and use the cache
# Both should have access to the module's export
test_that("get_paths() always uses R_user_dir for cache", {
writeLines("(module tmp-test (export x) (define x 1))", tmp_file)
# Cache dir should always be under R_user_dir
normalizePath(user_cache, mustWork = FALSE, winslash = "/")),
info = "cache_dir should be under R_user_dir")
info = "cache_dir should contain /modules/ subdirectory")