From 70d14e1026292d9310845be37777db95fd20ad23 Mon Sep 17 00:00:00 2001 From: Anderson E Date: Thu, 18 Dec 2025 09:48:49 -0500 Subject: [PATCH 1/7] v1 --- R/runWithOutputs.R | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 R/runWithOutputs.R diff --git a/R/runWithOutputs.R b/R/runWithOutputs.R new file mode 100644 index 0000000..dc8e23a --- /dev/null +++ b/R/runWithOutputs.R @@ -0,0 +1,32 @@ +runWithOutputs <- function(script) { + root <- here::here() + + # 1. Capture start time to establish a baseline for file modifications + t0 <- Sys.time() + + # 2. Execute the script in a fresh R session + callr::rscript(script, wd = root) + + # 3. List candidate files using `fd` + res <- processx::run( + "fd", + args = c("--type", "f", "--exclude", "renv"), + wd = root + ) + + if (res$stdout != "") { + files <- unlist(strsplit(trimws(res$stdout), "\n")) + abs_paths <- fs::path(root, files) + changed <- files[file.mtime(abs_paths) > t0] + + # 5. Output results as YAML + if (length(changed) > 0) { + changed <- sort(changed) + yaml_str <- paste0( + "outputs:\n", + paste0(" - \"", changed, "\"\n", collapse = "") + ) + cli::cli_code(yaml_str) + } + } +} From df6477eb2c00da1b984dd3eb21ed913856435baa Mon Sep 17 00:00:00 2001 From: Anderson E Date: Thu, 18 Dec 2025 09:58:04 -0500 Subject: [PATCH 2/7] docs --- DESCRIPTION | 3 +++ NAMESPACE | 1 + R/runWithOutputs.R | 17 +++++++++++++++++ man/runWithOutputs.Rd | 26 ++++++++++++++++++++++++++ tests/testthat/test-runWithOutputs.R | 27 +++++++++++++++++++++++++++ 5 files changed, 74 insertions(+) create mode 100644 man/runWithOutputs.Rd create mode 100644 tests/testthat/test-runWithOutputs.R diff --git a/DESCRIPTION b/DESCRIPTION index ed5bb9c..24fbad4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,6 +17,7 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 Imports: dplyr, + callr, magrittr (>= 2.0.3), XML, utils, @@ -25,6 +26,7 @@ Imports: stringr, fs, cli, + here, rmarkdown, tinytex, pmtables, @@ -34,6 +36,7 @@ Imports: htmltools, shiny, pdftools, + processx, stringi Suggests: knitr, diff --git a/NAMESPACE b/NAMESPACE index 2cdafe9..b6825d5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,6 +21,7 @@ export(logSummary) export(renderQCReport) export(renderQCSummary) export(repoHistory) +export(runWithOutputs) export(svnExport) export(svnLog) export(with_demoRepo) diff --git a/R/runWithOutputs.R b/R/runWithOutputs.R index dc8e23a..318cfc8 100644 --- a/R/runWithOutputs.R +++ b/R/runWithOutputs.R @@ -1,3 +1,20 @@ + #' Run a Script and Report Outputs + #' + #' Runs an R script from the project root in a clean R session and reports any + #' files modified during the run as YAML. Uses `fd` to identify files (excluding + #' the `renv` directory) and prints the relative paths for downstream tooling. + #' +#' @param script Path to the script to execute. +#' +#' @return Invisibly returns `NULL`; prints YAML of files changed since the +#' script started. +#' +#' @examples +#' \dontrun{ +#' runWithOutputs("path/to/script.R") +#' } +#' +#' @export runWithOutputs <- function(script) { root <- here::here() diff --git a/man/runWithOutputs.Rd b/man/runWithOutputs.Rd new file mode 100644 index 0000000..a49d82a --- /dev/null +++ b/man/runWithOutputs.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/runWithOutputs.R +\name{runWithOutputs} +\alias{runWithOutputs} +\title{Run a Script and Report Outputs} +\usage{ +runWithOutputs(script) +} +\arguments{ +\item{script}{Path to the script to execute.} +} +\value{ +Invisibly returns \code{NULL}; prints YAML of files changed since the +script started. +} +\description{ +Runs an R script from the project root in a clean R session and reports any +files modified during the run as YAML. Uses \code{fd} to identify files (excluding +the \code{renv} directory) and prints the relative paths for downstream tooling. +} +\examples{ +\dontrun{ + runWithOutputs("path/to/script.R") +} + +} diff --git a/tests/testthat/test-runWithOutputs.R b/tests/testthat/test-runWithOutputs.R new file mode 100644 index 0000000..804648e --- /dev/null +++ b/tests/testthat/test-runWithOutputs.R @@ -0,0 +1,27 @@ +# Tests for runWithOutputs + +test_that("runWithOutputs reports newly created files", { + skip_if(Sys.which("fd") == "", "fd command not available") + + root <- here::here() + tmp_dir <- file.path(root, "tmp_runWithOutputs") + dir.create(tmp_dir, recursive = TRUE, showWarnings = FALSE) + withr::defer(unlink(tmp_dir, recursive = TRUE, force = TRUE)) + + artifact <- file.path(tmp_dir, "artifact.txt") + script <- file.path(tmp_dir, "make_artifact.R") + + writeLines( + c( + "Sys.sleep(0.1)", + sprintf("writeLines('test output', '%s')", artifact) + ), + script + ) + + out <- testthat::capture_output(runWithOutputs(script)) + + expect_true(file.exists(artifact)) + expect_match(out, "outputs:") + expect_match(out, "tmp_runWithOutputs/artifact.txt") +}) From ee3a708b2af825e3948439d2ca1ac6251e647c9b Mon Sep 17 00:00:00 2001 From: anderson Date: Thu, 18 Dec 2025 11:46:24 -0500 Subject: [PATCH 3/7] fix tests --- DESCRIPTION | 4 +- R/reviewPackage.R | 5 +- R/runWithOutputs.R | 107 +++++++++++++------- man/runWithOutputs.Rd | 22 ++-- tests/testthat/test-runWithOutputs.R | 144 +++++++++++++++++++++++---- 5 files changed, 208 insertions(+), 74 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 24fbad4..d17c9be 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -36,8 +36,8 @@ Imports: htmltools, shiny, pdftools, - processx, - stringi + stringi, + yaml Suggests: knitr, testthat (>= 3.0.0), diff --git a/R/reviewPackage.R b/R/reviewPackage.R index 0ef8bd9..0024e27 100644 --- a/R/reviewPackage.R +++ b/R/reviewPackage.R @@ -29,6 +29,9 @@ globalVariables( "path", "page", "type", - "code_path" + "code_path", + "rel_path", + "modification_time", + "size" ) ) \ No newline at end of file diff --git a/R/runWithOutputs.R b/R/runWithOutputs.R index 318cfc8..d4c1744 100644 --- a/R/runWithOutputs.R +++ b/R/runWithOutputs.R @@ -1,49 +1,80 @@ - #' Run a Script and Report Outputs - #' - #' Runs an R script from the project root in a clean R session and reports any - #' files modified during the run as YAML. Uses `fd` to identify files (excluding - #' the `renv` directory) and prints the relative paths for downstream tooling. - #' -#' @param script Path to the script to execute. +#' Run a Script and Report Outputs #' -#' @return Invisibly returns `NULL`; prints YAML of files changed since the -#' script started. +#' Runs an R script from the project root in a clean R session and reports any +#' files saved (created or updated) during the run. #' -#' @examples -#' \dontrun{ -#' runWithOutputs("path/to/script.R") -#' } +#' @param script Path to the script to execute. +#' @param root Path to the project root. Defaults to `here::here()`. +#' @param exclude_dirs Character vector of directories relative to `root` to ignore. #' +#' @return Invisibly returns a vector of relative paths for changed files. #' @export -runWithOutputs <- function(script) { - root <- here::here() +runWithOutputs <- function( + script, + root = here::here(), + exclude_dirs = c("renv", ".svn", ".git") +) { + # Normalize script path to absolute so callr finds it regardless of 'wd' change + script_abs <- fs::path_abs(script) - # 1. Capture start time to establish a baseline for file modifications - t0 <- Sys.time() + # Snapshot before + # We track size as well to catch changes that might happen within the same second + # on low-resolution file systems if the content length changes. + before <- fs::dir_info(root, recurse = TRUE, type = "file") %>% + dplyr::select(path, modification_time, size) - # 2. Execute the script in a fresh R session - callr::rscript(script, wd = root) + # --- Start Header --- + div_start <- cli::cli_div(theme = list(rule = list(color = "cyan"))) + cli::cli_rule( + left = cli::style_bold(cli::bg_cyan(cli::col_white(" runWithOutputs() "))), + right = "START" + ) + cli::cli_bullets(c( + "*" = paste0(cli::style_bold("Script: "), cli::col_blue("{.path {script}}")) + )) + cli::cli_end(div_start) - # 3. List candidate files using `fd` - res <- processx::run( - "fd", - args = c("--type", "f", "--exclude", "renv"), - wd = root + # Execute script + # We use callr::rscript to run in a fresh session + callr::rscript(script_abs, wd = root, show = TRUE) + + # --- End Header --- + div_end <- cli::cli_div(theme = list(rule = list(color = "cyan"))) + cli::cli_rule( + left = cli::style_bold(cli::bg_cyan(cli::col_white(" runWithOutputs() "))), + right = "COMPLETE" ) + cli::cli_end(div_end) + + # Snapshot after + after <- fs::dir_info(root, recurse = TRUE, type = "file") %>% + dplyr::select(path, modification_time, size) + + # Detect changes + changed <- dplyr::anti_join( + after, + before, + by = c("path", "modification_time", "size") + ) %>% + dplyr::mutate(rel_path = fs::path_rel(path, start = root)) + + # Filter exclusions using regex + if (length(exclude_dirs) > 0) { + safe_dirs <- gsub(".", "\\.", exclude_dirs, fixed = TRUE) + # Ensure we match directories at the start of the relative path + pattern <- paste0("^(", paste(safe_dirs, collapse = "|"), ")/") + changed <- dplyr::filter(changed, !grepl(pattern, rel_path)) + } + + # Output + out_paths <- sort(changed$rel_path) - if (res$stdout != "") { - files <- unlist(strsplit(trimws(res$stdout), "\n")) - abs_paths <- fs::path(root, files) - changed <- files[file.mtime(abs_paths) > t0] - - # 5. Output results as YAML - if (length(changed) > 0) { - changed <- sort(changed) - yaml_str <- paste0( - "outputs:\n", - paste0(" - \"", changed, "\"\n", collapse = "") - ) - cli::cli_code(yaml_str) - } + if (length(out_paths) > 0) { + cli::cli_alert_success("Files saved by this run:") + cli::cli_code(yaml::as.yaml(list(outputs = out_paths))) + invisible(out_paths) + } else { + cli::cli_alert_success(cli::col_silver("No files were saved.")) + invisible(character(0)) } } diff --git a/man/runWithOutputs.Rd b/man/runWithOutputs.Rd index a49d82a..055c0b8 100644 --- a/man/runWithOutputs.Rd +++ b/man/runWithOutputs.Rd @@ -4,23 +4,23 @@ \alias{runWithOutputs} \title{Run a Script and Report Outputs} \usage{ -runWithOutputs(script) +runWithOutputs( + script, + root = here::here(), + exclude_dirs = c("renv", ".svn", ".git") +) } \arguments{ \item{script}{Path to the script to execute.} + +\item{root}{Path to the project root. Defaults to \code{here::here()}.} + +\item{exclude_dirs}{Character vector of directories relative to \code{root} to ignore.} } \value{ -Invisibly returns \code{NULL}; prints YAML of files changed since the -script started. +Invisibly returns a vector of relative paths for changed files. } \description{ Runs an R script from the project root in a clean R session and reports any -files modified during the run as YAML. Uses \code{fd} to identify files (excluding -the \code{renv} directory) and prints the relative paths for downstream tooling. -} -\examples{ -\dontrun{ - runWithOutputs("path/to/script.R") -} - +files saved (created or updated) during the run. } diff --git a/tests/testthat/test-runWithOutputs.R b/tests/testthat/test-runWithOutputs.R index 804648e..ba2e5c4 100644 --- a/tests/testthat/test-runWithOutputs.R +++ b/tests/testthat/test-runWithOutputs.R @@ -1,27 +1,127 @@ -# Tests for runWithOutputs - -test_that("runWithOutputs reports newly created files", { - skip_if(Sys.which("fd") == "", "fd command not available") - - root <- here::here() - tmp_dir <- file.path(root, "tmp_runWithOutputs") - dir.create(tmp_dir, recursive = TRUE, showWarnings = FALSE) - withr::defer(unlink(tmp_dir, recursive = TRUE, force = TRUE)) +test_that("runWithOutputs detects newly created files", { + with_demoRepo({ + # Setup: Create a script that generates a new output file + script_path <- "script/generate_data.R" + output_file <- "data/new_result.txt" + + # using fs::dir_create handles parent dirs and existence checks automatically + fs::dir_create("script") + + writeLines( + text = c( + 'dir.create("data", showWarnings = FALSE)', + paste0('writeLines("content", "', output_file, '")') + ), + con = script_path + ) + + # Execution + result <- runWithOutputs(script_path, root = getwd()) + + # Verification: Check return value + expect_true(output_file %in% result) + # Ensure the script itself is not listed as an output + expect_false(script_path %in% result) + }) +}) - artifact <- file.path(tmp_dir, "artifact.txt") - script <- file.path(tmp_dir, "make_artifact.R") +test_that("runWithOutputs detects modified files", { + with_demoRepo({ + # Setup: Create an existing file + target_file <- "data/existing_file.txt" + fs::dir_create("data") + writeLines("old content", target_file) + + # OPTIMIZATION: Manually backdate the file timestamp. + # This ensures the test is robust without using Sys.sleep(). + Sys.setFileTime(target_file, Sys.time() - 60) + + script_path <- "script/modify_data.R" + fs::dir_create("script") + + writeLines( + text = paste0('writeLines("new content", "', target_file, '")'), + con = script_path + ) + + # Execution + result <- runWithOutputs(script_path, root = getwd()) + + # Verification + expect_true(target_file %in% result) + }) +}) - writeLines( - c( - "Sys.sleep(0.1)", - sprintf("writeLines('test output', '%s')", artifact) - ), - script - ) +test_that("runWithOutputs returns empty vector when no files change", { + with_demoRepo({ + script_path <- "script/do_nothing.R" + fs::dir_create("script") + writeLines("print('No file changes here')", script_path) + + # Execution + result <- runWithOutputs(script_path, root = getwd()) + + expect_identical(result, character(0)) + }) +}) - out <- testthat::capture_output(runWithOutputs(script)) +test_that("runWithOutputs respects default excluded directories", { + with_demoRepo({ + # Setup: Create directories + fs::dir_create("renv") + fs::dir_create("scratch") + fs::dir_create("data") + fs::dir_create("script") + + script_path <- "script/mixed_outputs.R" + + # Script writes to: + # 1. renv (should be ignored by DEFAULT exclusion) + # 2. scratch (should be included, as it's not a default exclusion) + # 3. data (should be included) + writeLines( + text = c( + 'writeLines("a", "renv/ignored.txt")', + 'writeLines("b", "scratch/included.txt")', + 'writeLines("c", "data/important.txt")' + ), + con = script_path + ) + + # Execution + result <- runWithOutputs(script_path, root = getwd()) + + # Verification + expect_true("data/important.txt" %in% result) + expect_true("scratch/included.txt" %in% result) + expect_false("renv/ignored.txt" %in% result) + }) +}) - expect_true(file.exists(artifact)) - expect_match(out, "outputs:") - expect_match(out, "tmp_runWithOutputs/artifact.txt") +test_that("runWithOutputs respects custom excluded directories argument", { + with_demoRepo({ + fs::dir_create("custom_folder") + script_path <- "script.R" + + writeLines('writeLines("x", "custom_folder/file.txt")', script_path) + + # Pass a custom exclusion list + result <- runWithOutputs( + script_path, + root = getwd(), + exclude_dirs = c("custom_folder") + ) + + expect_false("custom_folder/file.txt" %in% result) + }) }) + +test_that("runWithOutputs errors if the script execution fails", { + with_demoRepo({ + script_path <- "broken.R" + writeLines("stop('Critical error')", script_path) + + # Verification + expect_error(runWithOutputs(script_path, root = getwd())) + }) +}) \ No newline at end of file From e490cb5888bd3a502b46c1a7c042e42dd5a2cef6 Mon Sep 17 00:00:00 2001 From: anderson Date: Thu, 18 Dec 2025 12:19:19 -0500 Subject: [PATCH 4/7] docs --- DESCRIPTION | 3 +- R/runWithOutputs.R | 68 +++++++++++--------- man/runWithOutputs.Rd | 17 ++--- tests/testthat/test-runWithOutputs.R | 92 +++++++++++++++++----------- 4 files changed, 107 insertions(+), 73 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d17c9be..f188968 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,7 +37,8 @@ Imports: shiny, pdftools, stringi, - yaml + yaml, + purrr Suggests: knitr, testthat (>= 3.0.0), diff --git a/R/runWithOutputs.R b/R/runWithOutputs.R index d4c1744..6c57134 100644 --- a/R/runWithOutputs.R +++ b/R/runWithOutputs.R @@ -1,44 +1,50 @@ -#' Run a Script and Report Outputs +#' Execute Script and Detect File Changes #' -#' Runs an R script from the project root in a clean R session and reports any -#' files saved (created or updated) during the run. +#' Runs an R script in an isolated session and identifies files that were created +#' or modified during execution. It detects changes by comparing the file system +#' state (modification time and size) before and after the run. #' -#' @param script Path to the script to execute. -#' @param root Path to the project root. Defaults to `here::here()`. -#' @param exclude_dirs Character vector of directories relative to `root` to ignore. +#' @param script Character. The path to the R script to execute. +#' @param root Character. The project root directory. Defaults to `here::here()`. +#' @param exclude_dirs Character vector. A list of top-level directories relative +#' to `root` to ignore when scanning for changes (e.g., "renv", ".git"). #' -#' @return Invisibly returns a vector of relative paths for changed files. +#' @return Invisibly returns a character vector of relative paths for all files +#' that were created or updated. #' @export runWithOutputs <- function( script, root = here::here(), exclude_dirs = c("renv", ".svn", ".git") ) { - # Normalize script path to absolute so callr finds it regardless of 'wd' change + # Normalize paths and calculate relative path for the UI script_abs <- fs::path_abs(script) + script_rel <- fs::path_rel(script_abs, start = root) - # Snapshot before - # We track size as well to catch changes that might happen within the same second - # on low-resolution file systems if the content length changes. + # 1. Capture Initial State + # Define file state by path, modification time, and size. before <- fs::dir_info(root, recurse = TRUE, type = "file") %>% dplyr::select(path, modification_time, size) - # --- Start Header --- + # --- UI Header --- + # Shows the relative path in the badge for immediate context. div_start <- cli::cli_div(theme = list(rule = list(color = "cyan"))) cli::cli_rule( - left = cli::style_bold(cli::bg_cyan(cli::col_white(" runWithOutputs() "))), + left = cli::style_bold(cli::bg_cyan(cli::col_white(paste0( + " runWithOutputs('", + script_rel, + "') " + )))), right = "START" ) - cli::cli_bullets(c( - "*" = paste0(cli::style_bold("Script: "), cli::col_blue("{.path {script}}")) - )) cli::cli_end(div_start) - # Execute script - # We use callr::rscript to run in a fresh session + # 2. Execute Script + # Run in a clean, separate R session to ensure isolation. callr::rscript(script_abs, wd = root, show = TRUE) - # --- End Header --- + # --- UI Footer --- + # Provides a clean "closing bracket" for the script output. div_end <- cli::cli_div(theme = list(rule = list(color = "cyan"))) cli::cli_rule( left = cli::style_bold(cli::bg_cyan(cli::col_white(" runWithOutputs() "))), @@ -46,11 +52,13 @@ runWithOutputs <- function( ) cli::cli_end(div_end) - # Snapshot after + # 3. Capture Final State after <- fs::dir_info(root, recurse = TRUE, type = "file") %>% dplyr::select(path, modification_time, size) - # Detect changes + # 4. Compute State Differences + # Identify files where the (path, time, size) tuple in the 'after' snapshot + # does not strictly match the 'before' snapshot. changed <- dplyr::anti_join( after, before, @@ -58,15 +66,17 @@ runWithOutputs <- function( ) %>% dplyr::mutate(rel_path = fs::path_rel(path, start = root)) - # Filter exclusions using regex - if (length(exclude_dirs) > 0) { - safe_dirs <- gsub(".", "\\.", exclude_dirs, fixed = TRUE) - # Ensure we match directories at the start of the relative path - pattern <- paste0("^(", paste(safe_dirs, collapse = "|"), ")/") - changed <- dplyr::filter(changed, !grepl(pattern, rel_path)) + # 5. Apply Exclusions + # Filter out files where the top-level directory matches an exclusion pattern. + if (length(exclude_dirs) > 0 && nrow(changed) > 0) { + changed <- changed %>% + dplyr::filter( + !fs::path_split(rel_path) %>% + purrr::map_lgl(~ .x[1] %in% exclude_dirs) + ) } - # Output + # 6. Report Results out_paths <- sort(changed$rel_path) if (length(out_paths) > 0) { @@ -74,7 +84,7 @@ runWithOutputs <- function( cli::cli_code(yaml::as.yaml(list(outputs = out_paths))) invisible(out_paths) } else { - cli::cli_alert_success(cli::col_silver("No files were saved.")) + cli::cli_alert_info(cli::col_silver("No files were saved.")) invisible(character(0)) } } diff --git a/man/runWithOutputs.Rd b/man/runWithOutputs.Rd index 055c0b8..d8bae77 100644 --- a/man/runWithOutputs.Rd +++ b/man/runWithOutputs.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/runWithOutputs.R \name{runWithOutputs} \alias{runWithOutputs} -\title{Run a Script and Report Outputs} +\title{Execute Script and Detect File Changes} \usage{ runWithOutputs( script, @@ -11,16 +11,19 @@ runWithOutputs( ) } \arguments{ -\item{script}{Path to the script to execute.} +\item{script}{Character. The path to the R script to execute.} -\item{root}{Path to the project root. Defaults to \code{here::here()}.} +\item{root}{Character. The project root directory. Defaults to \code{here::here()}.} -\item{exclude_dirs}{Character vector of directories relative to \code{root} to ignore.} +\item{exclude_dirs}{Character vector. A list of top-level directories relative +to \code{root} to ignore when scanning for changes (e.g., "renv", ".git").} } \value{ -Invisibly returns a vector of relative paths for changed files. +Invisibly returns a character vector of relative paths for all files +that were created or updated. } \description{ -Runs an R script from the project root in a clean R session and reports any -files saved (created or updated) during the run. +Runs an R script in an isolated session and identifies files that were created +or modified during execution. It detects changes by comparing the file system +state (modification time and size) before and after the run. } diff --git a/tests/testthat/test-runWithOutputs.R b/tests/testthat/test-runWithOutputs.R index ba2e5c4..40d3f6c 100644 --- a/tests/testthat/test-runWithOutputs.R +++ b/tests/testthat/test-runWithOutputs.R @@ -3,10 +3,9 @@ test_that("runWithOutputs detects newly created files", { # Setup: Create a script that generates a new output file script_path <- "script/generate_data.R" output_file <- "data/new_result.txt" - - # using fs::dir_create handles parent dirs and existence checks automatically - fs::dir_create("script") - + + fs::dir_create("script") + writeLines( text = c( 'dir.create("data", showWarnings = FALSE)', @@ -14,14 +13,12 @@ test_that("runWithOutputs detects newly created files", { ), con = script_path ) - + # Execution result <- runWithOutputs(script_path, root = getwd()) - - # Verification: Check return value - expect_true(output_file %in% result) - # Ensure the script itself is not listed as an output - expect_false(script_path %in% result) + + # Verification: Ensure only the expected output file is returned + expect_setequal(result, output_file) }) }) @@ -31,54 +28,77 @@ test_that("runWithOutputs detects modified files", { target_file <- "data/existing_file.txt" fs::dir_create("data") writeLines("old content", target_file) - - # OPTIMIZATION: Manually backdate the file timestamp. - # This ensures the test is robust without using Sys.sleep(). + + # Backdate the file timestamp to ensure detection without Sys.sleep() Sys.setFileTime(target_file, Sys.time() - 60) - + script_path <- "script/modify_data.R" fs::dir_create("script") - + writeLines( text = paste0('writeLines("new content", "', target_file, '")'), con = script_path ) - + # Execution result <- runWithOutputs(script_path, root = getwd()) - + # Verification expect_true(target_file %in% result) }) }) +test_that("runWithOutputs detects file 'touch' (overwrite with identical content)", { + with_demoRepo({ + target_file <- "data/config.yml" + fs::dir_create("data") + content <- "fixed_settings: true" + + # 1. Create file + writeLines(content, target_file) + + # 2. Backdate it + Sys.setFileTime(target_file, Sys.time() - 60) + + # 3. Script overwrites it with EXACT SAME content + script_path <- "script/refresh_config.R" + fs::dir_create("script") + writeLines( + text = paste0('writeLines("', content, '", "', target_file, '")'), + con = script_path + ) + + # 4. Execution + result <- runWithOutputs(script_path, root = getwd()) + + # 5. Verification: Identical size but new timestamp should trigger detection + expect_true(target_file %in% result) + }) +}) + test_that("runWithOutputs returns empty vector when no files change", { with_demoRepo({ script_path <- "script/do_nothing.R" fs::dir_create("script") writeLines("print('No file changes here')", script_path) - + # Execution result <- runWithOutputs(script_path, root = getwd()) - + expect_identical(result, character(0)) }) }) test_that("runWithOutputs respects default excluded directories", { with_demoRepo({ - # Setup: Create directories fs::dir_create("renv") fs::dir_create("scratch") fs::dir_create("data") fs::dir_create("script") - + script_path <- "script/mixed_outputs.R" - - # Script writes to: - # 1. renv (should be ignored by DEFAULT exclusion) - # 2. scratch (should be included, as it's not a default exclusion) - # 3. data (should be included) + + # Script writes to an ignored dir and two included dirs writeLines( text = c( 'writeLines("a", "renv/ignored.txt")', @@ -87,10 +107,10 @@ test_that("runWithOutputs respects default excluded directories", { ), con = script_path ) - + # Execution result <- runWithOutputs(script_path, root = getwd()) - + # Verification expect_true("data/important.txt" %in% result) expect_true("scratch/included.txt" %in% result) @@ -102,16 +122,16 @@ test_that("runWithOutputs respects custom excluded directories argument", { with_demoRepo({ fs::dir_create("custom_folder") script_path <- "script.R" - + writeLines('writeLines("x", "custom_folder/file.txt")', script_path) - + # Pass a custom exclusion list result <- runWithOutputs( - script_path, - root = getwd(), + script_path, + root = getwd(), exclude_dirs = c("custom_folder") ) - + expect_false("custom_folder/file.txt" %in% result) }) }) @@ -120,8 +140,8 @@ test_that("runWithOutputs errors if the script execution fails", { with_demoRepo({ script_path <- "broken.R" writeLines("stop('Critical error')", script_path) - - # Verification + + # Verification: callr should propagate the error to runWithOutputs expect_error(runWithOutputs(script_path, root = getwd())) }) -}) \ No newline at end of file +}) From fe174ab9334689c9492e5ec708d74361693c3e25 Mon Sep 17 00:00:00 2001 From: anderson Date: Thu, 18 Dec 2025 12:28:56 -0500 Subject: [PATCH 5/7] pkgdown --- _pkgdown.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 2b777ba..f28297e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -42,3 +42,7 @@ reference: - logRemove - repoHistory - getTFs + - title: Execution Tools + desc: Run scripts and track side effects + contents: + - runWithOutputs From 6b7d45f3cc5b468e5da3efa633d85abeb4566390 Mon Sep 17 00:00:00 2001 From: anderson Date: Thu, 18 Dec 2025 12:41:03 -0500 Subject: [PATCH 6/7] change to CI to not error on R CMD warnings --- .github/workflows/main.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 5e2693e..c258bb4 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -47,8 +47,6 @@ jobs: any::rcmdcheck upgrade: 'TRUE' - uses: r-lib/actions/check-r-package@v2 - with: - error-on: '"note"' - name: Check pkgdown shell: Rscript {0} run: pkgdown::check_pkgdown() From 2c895d30296a81fe37a5dd0720369efadba470ac Mon Sep 17 00:00:00 2001 From: anderson Date: Thu, 18 Dec 2025 12:44:12 -0500 Subject: [PATCH 7/7] ws --- R/reviewPackage.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/reviewPackage.R b/R/reviewPackage.R index 0024e27..38a904a 100644 --- a/R/reviewPackage.R +++ b/R/reviewPackage.R @@ -34,4 +34,4 @@ globalVariables( "modification_time", "size" ) -) \ No newline at end of file +)