Nothing
# Unit tests for CoverageTracker (R/coverage.R)
#
# NOTE: To clean up root directory, manually remove:
# cleanup_docs.sh, CLEANUP_NEEDED.md, validate_tests.R
# ============================================================================
# Test Harness Infrastructure
# ============================================================================
# Normalize a path for consistent cross-platform comparison (resolves symlinks,
# converts backslashes to forward slashes). Must match normalize_path_absolute()
# in coverage.R so test keys align with tracker keys.
# When the file doesn't exist yet, normalizes the parent directory (which does
# exist) to ensure symlinks like /var -> /private/var are resolved.
norm_path <- function(path) {
if (file.exists(path)) return(normalizePath(path, winslash = "/", mustWork = TRUE))
file.path(normalizePath(dirname(path), winslash = "/", mustWork = TRUE), basename(path))
}
# Create .arl test files with controlled content
create_arl_file <- function(content, dir = NULL) {
if (is.null(dir)) {
file <- tempfile(fileext = ".arl")
} else {
dir.create(dir, recursive = TRUE, showWarnings = FALSE)
file <- file.path(dir, sprintf("test%d.arl", sample(1:10000, 1)))
}
writeLines(content, file)
norm_path(file)
}
# Create mock arl_src for testing track()
make_arl_src <- function(file, start_line, end_line) {
list(
file = file,
start_line = start_line,
end_line = end_line
)
}
# ============================================================================
# Phase 1: Foundation Tests (Initialize + Track)
# ============================================================================
thin <- make_cran_thinner()
test_that("initialize() creates default coverage tracker", {
thin()
tracker <- CoverageTracker$new()
expect_type(tracker$coverage, "environment")
expect_true(tracker$enabled)
expect_equal(length(tracker$all_files), 0)
expect_type(tracker$code_lines, "environment")
})
test_that("initialize() creates coverage environment with correct properties", {
thin()
tracker <- CoverageTracker$new()
# Check environment properties
expect_false(environmentIsLocked(tracker$coverage))
expect_identical(parent.env(tracker$coverage), emptyenv())
})
test_that("initialize() creates code_lines environment with correct properties", {
thin()
tracker <- CoverageTracker$new()
expect_false(environmentIsLocked(tracker$code_lines))
expect_identical(parent.env(tracker$code_lines), emptyenv())
})
test_that("track() handles NULL arl_src", {
thin()
tracker <- CoverageTracker$new()
# Should not error
expect_silent(tracker$track(NULL))
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("track() handles missing fields", {
thin()
tracker <- CoverageTracker$new()
# Missing file
expect_silent(tracker$track(list(start_line = 1, end_line = 1)))
# Missing start_line
expect_silent(tracker$track(list(file = "test.arl", end_line = 1)))
# Missing end_line
expect_silent(tracker$track(list(file = "test.arl", start_line = 1)))
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("track() does nothing when enabled=FALSE", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
tracker$set_enabled(FALSE)
arl_src <- make_arl_src(tmp, start_line = 1, end_line = 1)
tracker$track(arl_src)
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("track() handles non-existent file gracefully", {
thin()
tracker <- CoverageTracker$new()
arl_src <- make_arl_src("/nonexistent/file.arl", start_line = 1, end_line = 1)
# Should not error, just skip tracking
expect_silent(tracker$track(arl_src))
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("track() marks single line as executed", {
thin()
tmp <- create_arl_file(c(";; comment", "(define x 1)", "(define y 2)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
arl_src <- make_arl_src(tmp, start_line = 2, end_line = 2)
tracker$track(arl_src)
key <- paste0(tmp, ":2")
expect_equal(tracker$coverage[[key]], 1L)
expect_equal(length(ls(tracker$coverage)), 1)
})
test_that("track() marks multi-line range", {
thin()
tmp <- create_arl_file(c("(define x 1)", "(define y 2)", "(define z 3)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
arl_src <- make_arl_src(tmp, start_line = 1, end_line = 3)
tracker$track(arl_src)
expect_equal(tracker$coverage[[paste0(tmp, ":1")]], 1L)
expect_equal(tracker$coverage[[paste0(tmp, ":2")]], 1L)
expect_equal(tracker$coverage[[paste0(tmp, ":3")]], 1L)
expect_equal(length(ls(tracker$coverage)), 3)
})
test_that("track() increments count on multiple executions", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
arl_src <- make_arl_src(tmp, start_line = 1, end_line = 1)
tracker$track(arl_src)
tracker$track(arl_src)
tracker$track(arl_src)
key <- paste0(tmp, ":1")
expect_equal(tracker$coverage[[key]], 3L)
})
test_that("track() lazy-loads code_lines cache", {
thin()
tmp <- create_arl_file(c(";; comment", "(define x 1)", "", "(define y 2)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
# code_lines should be empty initially
expect_equal(length(ls(tracker$code_lines)), 0)
arl_src <- make_arl_src(tmp, start_line = 2, end_line = 2)
tracker$track(arl_src)
# Now code_lines should be populated for this file
expect_true(tmp %in% ls(tracker$code_lines))
code_line_set <- tracker$code_lines[[tmp]]
expect_true(2 %in% code_line_set)
expect_true(4 %in% code_line_set)
expect_false(1 %in% code_line_set) # comment
expect_false(3 %in% code_line_set) # blank
})
test_that("track() filters comment and blank lines", {
thin()
tmp <- create_arl_file(c(
";; This is a comment",
"",
"(define x 1)",
" ;; Another comment",
"(define y 2)"
))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
# Track entire file
arl_src <- make_arl_src(tmp, start_line = 1, end_line = 5)
tracker$track(arl_src)
# Only lines 3 and 5 should be tracked (code lines)
expect_equal(length(ls(tracker$coverage)), 2)
expect_true(!is.null(tracker$coverage[[paste0(tmp, ":3")]]))
expect_true(!is.null(tracker$coverage[[paste0(tmp, ":5")]]))
expect_null(tracker$coverage[[paste0(tmp, ":1")]])
expect_null(tracker$coverage[[paste0(tmp, ":2")]])
expect_null(tracker$coverage[[paste0(tmp, ":4")]])
})
test_that("track() respects custom code_line_pattern", {
thin()
tmp <- create_arl_file(c(
"# Python-style comment",
"",
"def foo():",
" pass"
))
on.exit(unlink(tmp), add = TRUE)
# Use pattern that matches non-comment lines in Python-style
tracker <- CoverageTracker$new(code_line_pattern = "^\\s*[^#\\s]")
arl_src <- make_arl_src(tmp, start_line = 1, end_line = 4)
tracker$track(arl_src)
# Lines 3 and 4 should match (not starting with # or blank)
code_lines <- tracker$code_lines[[tmp]]
expect_true(3 %in% code_lines)
expect_true(4 %in% code_lines)
expect_false(1 %in% code_lines)
expect_false(2 %in% code_lines)
})
# ============================================================================
# Phase 2: Discovery & State Management
# ============================================================================
test_that("discover_files() with NULL search_paths uses stdlib", {
thin()
tracker <- CoverageTracker$new(search_paths = NULL)
tracker$discover_files()
# Should find stdlib files
expect_true(length(tracker$all_files) > 0)
# All files should be .arl files
expect_true(all(grepl("\\.arl$", tracker$all_files)))
})
test_that("discover_files() with custom search_paths", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
writeLines("(define x 1)", file.path(tmp_dir, "test1.arl"))
writeLines("(define y 2)", file.path(tmp_dir, "test2.arl"))
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
expect_equal(length(tracker$all_files), 2)
expect_true(all(grepl("\\.arl$", tracker$all_files)))
})
test_that("discover_files() searches recursively", {
thin()
tmp_dir <- tempfile()
dir.create(file.path(tmp_dir, "subdir", "nested"), recursive = TRUE)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
writeLines("(define x 1)", file.path(tmp_dir, "test1.arl"))
writeLines("(define y 2)", file.path(tmp_dir, "subdir", "test2.arl"))
writeLines("(define z 3)", file.path(tmp_dir, "subdir", "nested", "test3.arl"))
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
expect_equal(length(tracker$all_files), 3)
})
test_that("discover_files() excludes test directories by default", {
thin()
tmp_dir <- tempfile()
dir.create(file.path(tmp_dir, "src"), recursive = TRUE)
dir.create(file.path(tmp_dir, "tests"), recursive = TRUE)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
writeLines("(define x 1)", file.path(tmp_dir, "src", "code.arl"))
writeLines("(define test 1)", file.path(tmp_dir, "tests", "test.arl"))
tracker <- CoverageTracker$new(search_paths = tmp_dir, include_tests = FALSE)
tracker$discover_files()
expect_equal(length(tracker$all_files), 1)
expect_true(grepl("src/code.arl", tracker$all_files))
expect_false(any(grepl("tests/", tracker$all_files)))
})
test_that("discover_files() includes test directories when include_tests=TRUE", {
thin()
tmp_dir <- tempfile()
dir.create(file.path(tmp_dir, "src"), recursive = TRUE)
dir.create(file.path(tmp_dir, "tests"), recursive = TRUE)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
writeLines("(define x 1)", file.path(tmp_dir, "src", "code.arl"))
writeLines("(define test 1)", file.path(tmp_dir, "tests", "test.arl"))
tracker <- CoverageTracker$new(search_paths = tmp_dir, include_tests = TRUE)
tracker$discover_files()
expect_equal(length(tracker$all_files), 2)
expect_true(any(grepl("tests/", tracker$all_files)))
})
test_that("discover_files() handles non-existent directory", {
thin()
tracker <- CoverageTracker$new(search_paths = "/nonexistent/directory")
# Should not error, just return empty
expect_silent(tracker$discover_files())
expect_equal(length(tracker$all_files), 0)
})
test_that("discover_files() handles multiple search_paths", {
thin()
tmp_dir1 <- tempfile()
tmp_dir2 <- tempfile()
dir.create(tmp_dir1)
dir.create(tmp_dir2)
on.exit({
unlink(tmp_dir1, recursive = TRUE)
unlink(tmp_dir2, recursive = TRUE)
})
writeLines("(define x 1)", file.path(tmp_dir1, "test1.arl"))
writeLines("(define y 2)", file.path(tmp_dir2, "test2.arl"))
tracker <- CoverageTracker$new(search_paths = c(tmp_dir1, tmp_dir2))
tracker$discover_files()
expect_equal(length(tracker$all_files), 2)
})
test_that("discover_files() deduplicates files", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
writeLines("(define x 1)", file.path(tmp_dir, "test.arl"))
# Pass same directory twice
tracker <- CoverageTracker$new(search_paths = c(tmp_dir, tmp_dir))
tracker$discover_files()
# Should only find the file once
expect_equal(length(tracker$all_files), 1)
})
test_that("discover_files() populates code_lines cache", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
tmp_file <- norm_path(file.path(tmp_dir, "test.arl"))
writeLines(c(";; comment", "(define x 1)", "", "(define y 2)"), tmp_file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# code_lines should be populated
expect_true(tmp_file %in% ls(tracker$code_lines))
code_lines <- tracker$code_lines[[tmp_file]]
expect_true(2 %in% code_lines)
expect_true(4 %in% code_lines)
expect_false(1 %in% code_lines)
expect_false(3 %in% code_lines)
})
test_that("discover_files() handles empty directory", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
expect_equal(length(tracker$all_files), 0)
})
test_that("get_summary() returns empty list for empty coverage", {
thin()
tracker <- CoverageTracker$new()
summary <- tracker$get_summary()
expect_type(summary, "list")
expect_equal(length(summary), 0)
})
test_that("get_summary() returns single file/line structure", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
arl_src <- make_arl_src(tmp, start_line = 1, end_line = 1)
tracker$track(arl_src)
summary <- tracker$get_summary()
expect_equal(length(summary), 1)
expect_true(tmp %in% names(summary))
expect_equal(summary[[tmp]][["1"]], 1L)
})
test_that("get_summary() returns multiple files/lines structure", {
thin()
tmp1 <- create_arl_file(c("(define x 1)", "(define y 2)"))
tmp2 <- create_arl_file(c("(define z 3)"))
on.exit({
unlink(tmp1)
unlink(tmp2)
})
tracker <- CoverageTracker$new()
tracker$track(make_arl_src(tmp1, 1, 2))
tracker$track(make_arl_src(tmp2, 1, 1))
summary <- tracker$get_summary()
expect_equal(length(summary), 2)
expect_true(tmp1 %in% names(summary))
expect_true(tmp2 %in% names(summary))
expect_equal(length(summary[[tmp1]]), 2)
expect_equal(length(summary[[tmp2]]), 1)
})
test_that("get_summary() handles malformed keys gracefully", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
# Manually add a malformed key (no colon)
assign("badkey", 5L, envir = tracker$coverage)
# Should not error
summary <- tracker$get_summary()
# Malformed key should be ignored
expect_false("badkey" %in% names(summary))
})
test_that("get_summary() creates nested list structure correctly", {
thin()
tmp <- create_arl_file(c("(define x 1)", "(define y 2)", "(define z 3)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
tracker$track(make_arl_src(tmp, 1, 1))
tracker$track(make_arl_src(tmp, 1, 1)) # Execute line 1 twice
tracker$track(make_arl_src(tmp, 3, 3)) # Execute line 3 once
summary <- tracker$get_summary()
# Check nested access: summary[[file]][[line]] = count
expect_equal(summary[[tmp]][["1"]], 2L)
expect_null(summary[[tmp]][["2"]]) # Line 2 not executed
expect_equal(summary[[tmp]][["3"]], 1L)
})
test_that("reset() clears empty coverage", {
thin()
tracker <- CoverageTracker$new()
tracker$reset()
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("reset() clears populated coverage", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
tracker$track(make_arl_src(tmp, 1, 1))
expect_equal(length(ls(tracker$coverage)), 1)
tracker$reset()
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("reset() preserves all_files and code_lines", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
tmp_file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define x 1)"), tmp_file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
tracker$track(make_arl_src(tmp_file, 1, 1))
expect_equal(length(tracker$all_files), 1)
expect_equal(length(ls(tracker$code_lines)), 1)
expect_equal(length(ls(tracker$coverage)), 1)
tracker$reset()
# all_files and code_lines preserved
expect_equal(length(tracker$all_files), 1)
expect_equal(length(ls(tracker$code_lines)), 1)
# But coverage cleared
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("set_enabled() toggles tracking", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
expect_true(tracker$enabled)
tracker$set_enabled(FALSE)
expect_false(tracker$enabled)
tracker$set_enabled(TRUE)
expect_true(tracker$enabled)
})
test_that("disabled tracker ignores track() calls", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
tracker$set_enabled(FALSE)
tracker$track(make_arl_src(tmp, 1, 1))
expect_equal(length(ls(tracker$coverage)), 0)
})
test_that("re-enabling resumes tracking", {
thin()
tmp <- create_arl_file(c("(define x 1)", "(define y 2)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
# Track line 1
tracker$track(make_arl_src(tmp, 1, 1))
expect_equal(length(ls(tracker$coverage)), 1)
# Disable and try to track line 2
tracker$set_enabled(FALSE)
tracker$track(make_arl_src(tmp, 2, 2))
expect_equal(length(ls(tracker$coverage)), 1) # Still only line 1
# Re-enable and track line 2
tracker$set_enabled(TRUE)
tracker$track(make_arl_src(tmp, 2, 2))
expect_equal(length(ls(tracker$coverage)), 2) # Now both lines
})
# ============================================================================
# Phase 3: Console Reporting
# ============================================================================
test_that("report_console() shows message for empty coverage", {
thin()
tracker <- CoverageTracker$new()
output <- capture.output(tracker$report_console())
expect_true(any(grepl("No coverage data|0\\.0+%|0/0", output)))
})
test_that("report_console() discovers files if all_files empty", {
thin()
# Use custom path so we know what to expect
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
tmp_file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", tmp_file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
expect_equal(length(tracker$all_files), 0)
# Track something so coverage isn't empty (otherwise it returns early)
tracker$track(make_arl_src(tmp_file, 1, 1))
# report_console should trigger discover_files
output <- capture.output(tracker$report_console())
expect_true(length(tracker$all_files) > 0)
})
test_that("report_console() shows single file with partial coverage", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
tmp_file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define x 1)", "(define y 2)", "(define z 3)"), tmp_file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Only track line 1
tracker$track(make_arl_src(tmp_file, 1, 1))
output <- capture.output(tracker$report_console())
output_text <- paste(output, collapse = "\n")
# Should show filename
expect_true(grepl("test\\.arl", output_text))
# Should show coverage percentage (33% = 1/3 lines)
expect_true(grepl("33\\.[0-9]+%", output_text) || grepl("1/3", output_text))
})
test_that("report_console() shows multiple files sorted alphabetically", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file_z <- file.path(tmp_dir, "z.arl")
file_a <- file.path(tmp_dir, "a.arl")
file_m <- file.path(tmp_dir, "m.arl")
writeLines("(define x 1)", file_z)
writeLines("(define y 2)", file_a)
writeLines("(define z 3)", file_m)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track at least one line in each file so they appear in output
tracker$track(make_arl_src(file_z, 1, 1))
tracker$track(make_arl_src(file_a, 1, 1))
tracker$track(make_arl_src(file_m, 1, 1))
output <- capture.output(tracker$report_console())
output_text <- paste(output, collapse = "\n")
# Find positions of filenames
pos_a <- regexpr("a\\.arl", output_text)
pos_m <- regexpr("m\\.arl", output_text)
pos_z <- regexpr("z\\.arl", output_text)
# Should be in alphabetical order
expect_true(pos_a > 0, info = "a.arl not found in output")
expect_true(pos_m > 0, info = "m.arl not found in output")
expect_true(pos_z > 0, info = "z.arl not found in output")
expect_true(pos_a < pos_m, info = "Files not in alphabetical order")
expect_true(pos_m < pos_z, info = "Files not in alphabetical order")
})
test_that("report_console() calculates total lines correctly", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file1 <- file.path(tmp_dir, "file1.arl")
file2 <- file.path(tmp_dir, "file2.arl")
writeLines(c("(define x 1)", "(define y 2)"), file1) # 2 lines
writeLines(c("(define z 3)", "(define w 4)", "(define v 5)"), file2) # 3 lines
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track 1 line from file1, 2 lines from file2 = 3/5 total
tracker$track(make_arl_src(file1, 1, 1))
tracker$track(make_arl_src(file2, 1, 2))
output <- capture.output(tracker$report_console())
output_text <- paste(output, collapse = "\n")
# Should show total: 3/5 = 60%
expect_true(grepl("60\\.0+%", output_text) || grepl("3/5", output_text))
})
test_that("report_console() writes to file when output_file specified", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
out_file <- tempfile(fileext = ".txt")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(out_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
tracker$track(make_arl_src(file, 1, 1))
# Capture console to ensure nothing printed
output <- capture.output(tracker$report_console(output_file = out_file))
# File should exist and contain report
expect_true(file.exists(out_file))
file_content <- readLines(out_file)
expect_true(length(file_content) > 0)
expect_true(any(grepl("test\\.arl", file_content)))
})
test_that("report_console() outputs to console when no output_file", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
tracker$track(make_arl_src(file, 1, 1))
output <- capture.output(tracker$report_console())
expect_true(length(output) > 0)
expect_true(any(grepl("test\\.arl", output)))
})
# ============================================================================
# Phase 4: Advanced Reporting (HTML & JSON)
# ============================================================================
test_that("report_html() errors when output_file is missing", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(
search_paths = tmp_dir,
output_prefix = "test_prefix"
)
tracker$discover_files()
expect_error(tracker$report_html(), "output_file is required")
})
test_that("report_html() uses custom output path", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
suppressMessages(tracker$report_html(output_file = html_file))
expect_true(file.exists(html_file))
})
test_that("report_html() auto-creates output directory", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
output_dir <- file.path(tmp_dir, "deep", "nested", "dir")
html_file <- file.path(output_dir, "report.html")
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
expect_false(dir.exists(output_dir))
suppressMessages(tracker$report_html(output_file = html_file))
expect_true(dir.exists(output_dir))
expect_true(file.exists(html_file))
})
test_that("report_html() auto-discovers files if needed", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
# Don't call discover_files()
expect_equal(length(tracker$all_files), 0)
suppressMessages(tracker$report_html(output_file = html_file))
# Should have auto-discovered
expect_true(length(tracker$all_files) > 0)
expect_true(file.exists(html_file))
})
test_that("report_html() generates valid HTML structure", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
expect_true(grepl("<!DOCTYPE html>", html_content))
expect_true(grepl("<title>", html_content))
expect_true(grepl("<table>", html_content))
})
test_that("report_html() uses custom report_title", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(
search_paths = tmp_dir,
report_title = "Custom Coverage Report"
)
tracker$discover_files()
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
expect_true(grepl("<title>Custom Coverage Report</title>", html_content))
expect_true(grepl("<h1>Custom Coverage Report</h1>", html_content))
})
test_that("report_html() includes summary table", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file1 <- file.path(tmp_dir, "test1.arl")
file2 <- file.path(tmp_dir, "test2.arl")
writeLines(c("(define x 1)", "(define y 2)"), file1)
writeLines("(define z 3)", file2)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
tracker$track(make_arl_src(file1, 1, 1))
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
expect_true(grepl("<table>", html_content))
expect_true(grepl("<th>File</th>", html_content))
expect_true(grepl("<th>Coverage %</th>", html_content))
})
test_that("report_html() uses coverage percentage CSS classes", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
# Create file with multiple lines for varying coverage
file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define a 1)", "(define b 2)", "(define c 3)", "(define d 4)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track 3 out of 4 lines = 75%
tracker$track(make_arl_src(file, 1, 3))
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
# Should have CSS classes for coverage percentage
expect_true(grepl("class=.(pct-high|pct-medium|pct-low).", html_content))
})
test_that("report_html() includes detailed file view sections", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define x 1)", "(define y 2)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
tracker$track(make_arl_src(file, 1, 1))
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
# Should have h2 for file sections (not h3)
expect_true(grepl("<h2>", html_content))
# Should have file-content div
expect_true(grepl("class=.file-content.", html_content))
})
test_that("report_html() shows line-by-line coverage with classes", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define x 1)", "(define y 2)", "(define z 3)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track only line 1
tracker$track(make_arl_src(file, 1, 1))
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
# Should have covered and uncovered classes
expect_true(grepl("covered", html_content))
expect_true(grepl("uncovered", html_content))
})
test_that("report_html() displays hit counts", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track line 3 times
tracker$track(make_arl_src(file, 1, 1))
tracker$track(make_arl_src(file, 1, 1))
tracker$track(make_arl_src(file, 1, 1))
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
# Should show hit count in format like "3x"
expect_true(grepl("3x", html_content))
})
test_that("report_html() properly escapes HTML special characters", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
content <- c(
"(define x \"<tag>\")",
"(define y \"a & b\")",
"(define z \"x > y\")"
)
writeLines(content, file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
# Critical: HTML special characters must be escaped
expect_true(grepl("<tag>", html_content))
expect_true(grepl("a & b", html_content))
expect_true(grepl("x > y", html_content))
})
test_that("report_html() generates valid HTML for empty coverage", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Don't track anything
suppressMessages(tracker$report_html(output_file = html_file))
expect_true(file.exists(html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
expect_true(grepl("<!DOCTYPE html>", html_content))
expect_true(grepl("<title>", html_content))
})
test_that("report_json() errors when output_file is missing", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(
search_paths = tmp_dir,
output_prefix = "test_json"
)
tracker$discover_files()
expect_error(tracker$report_json(), "output_file is required")
})
test_that("report_json() uses custom output path", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
json_file <- tempfile(fileext = ".json")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(json_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
suppressMessages(tracker$report_json(output_file = json_file))
expect_true(file.exists(json_file))
})
test_that("report_json() auto-creates output directory", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
output_dir <- file.path(tmp_dir, "deep", "nested", "dir")
json_file <- file.path(output_dir, "coverage.json")
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
expect_false(dir.exists(output_dir))
suppressMessages(tracker$report_json(output_file = json_file))
expect_true(dir.exists(output_dir))
expect_true(file.exists(json_file))
})
test_that("report_json() auto-discovers files if needed", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
json_file <- tempfile(fileext = ".json")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(json_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
expect_equal(length(tracker$all_files), 0)
suppressMessages(tracker$report_json(output_file = json_file))
expect_true(length(tracker$all_files) > 0)
expect_true(file.exists(json_file))
})
test_that("report_json() generates codecov structure", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
json_file <- tempfile(fileext = ".json")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(json_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines("(define x 1)", file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
suppressMessages(tracker$report_json(output_file = json_file))
if (requireNamespace("jsonlite", quietly = TRUE)) {
data <- jsonlite::fromJSON(json_file)
expect_true("coverage" %in% names(data))
expect_equal(length(data$coverage), 1)
}
})
test_that("report_json() uses line coverage list format", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
json_file <- tempfile(fileext = ".json")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(json_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define x 1)", "(define y 2)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track line 1 twice
tracker$track(make_arl_src(file, 1, 1))
tracker$track(make_arl_src(file, 1, 1))
suppressMessages(tracker$report_json(output_file = json_file))
if (requireNamespace("jsonlite", quietly = TRUE)) {
data <- jsonlite::fromJSON(json_file)
# Get the coverage data for this file
coverage_list <- data$coverage[[1]]
# Line 1 should have count 2
expect_equal(coverage_list[[1]], 2)
# Line 2 should have count 0 (uncovered)
expect_equal(coverage_list[[2]], 0)
}
})
test_that("report_json() uses null for non-code lines", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
json_file <- tempfile(fileext = ".json")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(json_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines(c(";; comment", "(define x 1)", "", "(define y 2)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
tracker$track(make_arl_src(file, 2, 2))
suppressMessages(tracker$report_json(output_file = json_file))
if (requireNamespace("jsonlite", quietly = TRUE)) {
data <- jsonlite::fromJSON(json_file)
coverage_list <- data$coverage[[1]]
# Line 1 (comment) should be null/NA (jsonlite converts null to NA)
expect_true(is.na(coverage_list[[1]]))
# Line 2 (code) should have count
expect_equal(coverage_list[[2]], 1)
# Line 3 (blank) should be null/NA
expect_true(is.na(coverage_list[[3]]))
# Line 4 (code) should have count 0 (uncovered)
expect_equal(coverage_list[[4]], 0)
}
})
test_that("report_json() shows 0 for uncovered code lines", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
json_file <- tempfile(fileext = ".json")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(json_file)
})
file <- file.path(tmp_dir, "test.arl")
writeLines(c("(define x 1)", "(define y 2)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track only line 1
tracker$track(make_arl_src(file, 1, 1))
suppressMessages(tracker$report_json(output_file = json_file))
if (requireNamespace("jsonlite", quietly = TRUE)) {
data <- jsonlite::fromJSON(json_file)
coverage_list <- data$coverage[[1]]
# Line 1 covered
expect_equal(coverage_list[[1]], 1)
# Line 2 uncovered (should be 0, not null)
expect_equal(coverage_list[[2]], 0)
}
})
# ============================================================================
# Phase 5: Integration Tests
# ============================================================================
test_that("Engine accepts coverage_tracker parameter", {
thin()
tracker <- CoverageTracker$new()
engine <- Engine$new(coverage_tracker = tracker)
expect_s3_class(engine, "ArlEngine")
})
test_that("Engine tracks coverage for executed code", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define add (lambda (x y) (+ x y)))",
"(define result (add 1 2))"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Both lines should be covered
expect_equal(length(summary[[tmp]]), 2)
expect_true("1" %in% names(summary[[tmp]]))
expect_true("2" %in% names(summary[[tmp]]))
})
test_that("disabled coverage tracker doesn't track", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c("(define x 1)"), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
tracker$set_enabled(FALSE)
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Should have no coverage data
expect_equal(length(summary), 0)
})
test_that("coverage tracking persists across multiple evaluations", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define counter 0)",
"(define inc (lambda () (set! counter (+ counter 1))))"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
# Load file multiple times
engine$load_file_in_env(tmp)
engine$load_file_in_env(tmp)
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Line 1 should be executed 3 times
expect_equal(summary[[tmp]][["1"]], 3)
# Line 2 should be executed 3 times
expect_equal(summary[[tmp]][["2"]], 3)
})
test_that("Engine$get_coverage() returns NULL when coverage not enabled", {
thin()
# Explicitly create engine without coverage tracker to avoid inheriting
# one from getOption("arl.coverage_tracker") under covr instrumentation
engine <- Engine$new()
expect_null(engine$get_coverage())
})
test_that("Engine$get_coverage() returns data frame with correct structure", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c("(define x 1)", "(define y 2)"), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
result <- suppressWarnings(
engine$get_coverage(),
classes = "simpleWarning"
)
expect_s3_class(result, "data.frame")
expect_true(all(c("file", "total_lines", "covered_lines", "coverage_pct") %in% names(result)))
expect_true(nrow(result) > 0)
total <- attr(result, "total")
expect_type(total, "list")
expect_true(all(c("total_lines", "covered_lines", "coverage_pct") %in% names(total)))
})
test_that("Engine$get_coverage() reports correct coverage stats", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c("(define x 1)", "(define y 2)", "(define z 3)"), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
result <- suppressWarnings(
engine$get_coverage(),
classes = "simpleWarning"
)
row <- result[result$file == tmp, ]
expect_equal(nrow(row), 1)
expect_equal(row$total_lines, 3)
expect_equal(row$covered_lines, 3)
expect_equal(row$coverage_pct, 100)
})
# ============================================================================
# Edge Cases
# ============================================================================
test_that("handles empty files", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- norm_path(file.path(tmp_dir, "empty.arl"))
writeLines(character(0), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Should not error
expect_equal(length(tracker$all_files), 1)
# code_lines should show 0 code lines
expect_true(file %in% ls(tracker$code_lines))
expect_equal(length(tracker$code_lines[[file]]), 0)
})
test_that("handles files with only comments and blanks", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "comments.arl")
writeLines(c(";; Comment 1", "", ";; Comment 2", ""), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Should have 0 code lines
expect_equal(length(tracker$code_lines[[file]]), 0)
# Report should handle gracefully
output <- capture.output(tracker$report_console())
expect_true(length(output) > 0)
})
test_that("handles 100% coverage", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)
file <- file.path(tmp_dir, "full.arl")
writeLines(c("(define x 1)", "(define y 2)"), file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
# Track all lines
tracker$track(make_arl_src(file, 1, 2))
output <- capture.output(tracker$report_console())
output_text <- paste(output, collapse = "\n")
# Should show 100%
expect_true(grepl("100\\.0+%", output_text))
})
test_that("handles very large execution counts", {
thin()
tmp <- create_arl_file(c("(define x 1)"))
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new()
arl_src <- make_arl_src(tmp, 1, 1)
# Execute 1000 times (reduced from 10000 for speed)
for (i in 1:1000) {
tracker$track(arl_src)
}
key <- paste0(tmp, ":1")
expect_equal(tracker$coverage[[key]], 1000L)
# Reports should handle large counts
output <- capture.output(suppressWarnings(
tracker$report_console(),
classes = "simpleWarning"
))
expect_true(length(output) > 0)
})
test_that("HTML escaping prevents XSS", {
thin()
tmp_dir <- tempfile()
dir.create(tmp_dir)
html_file <- tempfile(fileext = ".html")
on.exit({
unlink(tmp_dir, recursive = TRUE)
unlink(html_file)
})
file <- file.path(tmp_dir, "xss.arl")
# Potential XSS vectors
content <- c(
"(define x \"<script>alert('xss')</script>\")",
"(define y \"<img src=x onerror=alert(1)>\")",
"(define z \"<iframe src=evil.com></iframe>\")"
)
writeLines(content, file)
tracker <- CoverageTracker$new(search_paths = tmp_dir)
tracker$discover_files()
suppressMessages(tracker$report_html(output_file = html_file))
html_content <- paste(readLines(html_file), collapse = "\n")
# Check that dangerous strings are properly escaped
expect_true(grepl("<script>", html_content))
expect_true(grepl("<img", html_content))
expect_true(grepl("<iframe", html_content))
# Should not have literal unescaped versions
expect_false(grepl("<script>alert", html_content))
expect_false(grepl("<img src=x onerror", html_content))
})
# ============================================================================
# Phase 6: Call-time Coverage Tests (function body instrumentation)
# ============================================================================
test_that("uncalled function body is NOT covered", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define f",
" (lambda (x)",
" (+ x 1)",
" (* x 2)))"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
# Load file but do NOT call f
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Line 1 should be covered (top-level define is evaluated)
expect_true("1" %in% names(summary[[tmp]]))
# Lines 3 and 4 (lambda body) should NOT be covered since f was never called
expect_null(summary[[tmp]][["3"]])
expect_null(summary[[tmp]][["4"]])
})
test_that("called function body IS covered", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define f",
" (lambda (x)",
" (+ x 1)",
" (* x 2)))",
"(f 5)"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Line 1 should be covered (top-level define)
expect_true("1" %in% names(summary[[tmp]]))
# Line 5 should be covered (top-level call)
expect_true("5" %in% names(summary[[tmp]]))
# Lines 3 and 4 (lambda body) should now be covered since f was called
expect_true(!is.null(summary[[tmp]][["3"]]))
expect_true(!is.null(summary[[tmp]][["4"]]))
})
test_that("module loading does not mark entire file as covered", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(module test-cov-mod (export f g)",
" (define f",
" (lambda (x)",
" (+ x 1)))",
" (define g",
" (lambda (x)",
" (* x 2)))",
" (define h",
" (lambda (x)",
" (- x 1))))"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
# Load the module - defines f, g, h but doesn't call them
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Module line and define lines should be partially covered
# but function body lines (4, 7, 10) should NOT be covered
# since none of f, g, h were actually called
expect_null(summary[[tmp]][["4"]])
expect_null(summary[[tmp]][["7"]])
expect_null(summary[[tmp]][["10"]])
})
# ============================================================================
# Phase 7: Branch-level Coverage Tests
# ============================================================================
test_that("if expression only covers taken then-branch", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define f",
" (lambda (x)",
" (if (> x 0)",
" (+ x 1)",
" (* x 2))))",
"(f 5)"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Line 4 (then-branch) should be covered
expect_true(!is.null(summary[[tmp]][["4"]]))
# Line 5 (else-branch) should NOT be covered
expect_null(summary[[tmp]][["5"]])
})
test_that("if expression only covers taken else-branch", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define f",
" (lambda (x)",
" (if (> x 0)",
" (+ x 1)",
" (* x 2))))",
"(f -3)"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Line 4 (then-branch) should NOT be covered
expect_null(summary[[tmp]][["4"]])
# Line 5 (else-branch) should be covered
expect_true(!is.null(summary[[tmp]][["5"]]))
})
test_that("if expression covers both branches when both are taken", {
thin()
tmp <- norm_path(tempfile(fileext = ".arl"))
writeLines(c(
"(define f",
" (lambda (x)",
" (if (> x 0)",
" (+ x 1)",
" (* x 2))))",
"(f 5)",
"(f -3)"
), tmp)
on.exit(unlink(tmp), add = TRUE)
tracker <- CoverageTracker$new(search_paths = dirname(tmp))
engine <- Engine$new(coverage_tracker = tracker)
tracker$discover_files()
engine$load_file_in_env(tmp)
summary <- tracker$get_summary()
# Both branches should be covered since we called f with positive and negative
expect_true(!is.null(summary[[tmp]][["4"]]))
expect_true(!is.null(summary[[tmp]][["5"]]))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.