# 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) # ============================================================================ test_that("initialize() creates default coverage tracker", { 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", { 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", { tracker <- CoverageTracker$new() expect_false(environmentIsLocked(tracker$code_lines)) expect_identical(parent.env(tracker$code_lines), emptyenv()) }) test_that("track() handles NULL arl_src", { tracker <- CoverageTracker$new() # Should not error expect_silent(tracker$track(NULL)) expect_equal(length(ls(tracker$coverage)), 0) }) test_that("track() handles missing fields", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { tracker <- CoverageTracker$new() tracker$reset() expect_equal(length(ls(tracker$coverage)), 0) }) test_that("reset() clears populated coverage", { 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", { 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", { 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", { 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", { 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", { 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", { # 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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("", html_content)) expect_true(grepl("", html_content)) expect_true(grepl("<table>", html_content)) }) test_that("report_html() uses custom report_title", { 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", html_content)) expect_true(grepl("

Custom Coverage Report

", html_content)) }) test_that("report_html() includes summary table", { 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("", html_content)) expect_true(grepl("", html_content)) expect_true(grepl("", html_content)) }) test_that("report_html() uses coverage percentage CSS classes", { 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", { 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("

", 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", { 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", { 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", { 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 \"\")", "(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", { 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("", html_content)) expect_true(grepl("", html_content)) }) test_that("report_json() errors when output_file is missing", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { tracker <- CoverageTracker$new() engine <- Engine$new(coverage_tracker = tracker) expect_s3_class(engine, "ArlEngine") }) test_that("Engine tracks coverage for executed code", { 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", { 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", { 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", { # 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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", { 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"]])) })

FileCoverage %