test_that("process works", { px <- get_tool("px") p <- process$new(px, c("sleep", "5")) on.exit(try_silently(p$kill(grace = 0)), add = TRUE) expect_true(p$is_alive()) }) test_that("get_exit_status", { px <- get_tool("px") p <- process$new(px, c("return", "1")) on.exit(p$kill(), add = TRUE) p$wait() expect_identical(p$get_exit_status(), 1L) }) test_that("non existing process", { skip_if_no_srcrefs() withr::local_options(width = 400) expect_snapshot( error = TRUE, process$new(tempfile()), transform = function(x) transform_line_number(transform_tempdir(x)), variant = sysname() ) ## This closes connections in finalizers gc() }) test_that("post processing", { px <- get_tool("px") p <- process$new( px, c("return", "0"), post_process = function() "foobar" ) p$wait(5000) p$kill() expect_equal(p$get_result(), "foobar") p <- process$new( px, c("sleep", "5"), post_process = function() "yep" ) expect_snapshot(error = TRUE, p$get_result()) p$kill() expect_equal(p$get_result(), "yep") ## Only runs once xx <- 0 p <- process$new( px, c("return", "0"), post_process = function() xx <<- xx + 1 ) p$wait(5000) p$kill() p$get_result() expect_equal(xx, 1) p$get_result() expect_equal(xx, 1) }) test_that("working directory", { px <- get_tool("px") dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) cat("foo\nbar\n", file = file.path(tmp, "file")) p <- process$new(px, c("cat", "file"), wd = tmp, stdout = "|") on.exit(p$kill(), add = TRUE) p$wait() expect_equal(p$read_all_output_lines(), c("foo", "bar")) }) test_that("working directory does not exist", { skip_if_no_srcrefs() px <- get_tool("px") expect_snapshot( error = TRUE, process$new(px, wd = tempfile()), transform = function(x) transform_line_number(transform_px(x)), variant = sysname() ) ## This closes connections in finalizers gc() }) test_that("R process is installed with a SIGTERM cleanup handler", { # https://github.com/r-lib/callr/pull/250 skip_if_not_installed("callr", "3.7.3.9001") # Needs POSIX signal handling skip_on_os("windows") # Enabled case withr::local_envvar(c(PROCESSX_R_SIGTERM_CLEANUP = "true")) out <- tempfile() fn <- function(file) { file.create(tempfile()) writeLines(tempdir(), file) } p <- callr::r_session$new() p$run(fn, list(file = out)) p_temp_dir <- readLines(out) expect_true(dir.exists(p_temp_dir)) p$signal(ps::signals()$SIGTERM) p$wait() expect_false(dir.exists(p_temp_dir)) # Disabled case withr::local_envvar(c(PROCESSX_R_SIGTERM_CLEANUP = NA_character_)) # Just in case R adds tempdir cleanup on SIGTERM skip_on_cran() p <- callr::r_session$new() p$run(fn, list(file = out)) p_temp_dir <- readLines(out) expect_true(dir.exists(p_temp_dir)) p$signal(ps::signals()$SIGTERM) p$wait() # Was not cleaned up expect_true(dir.exists(p_temp_dir)) }) test_that("linux_pdeathsig kills child when parent exits", { skip_if(!is_linux()) skip_if(is_valgrind()) skip_if(is_asan()) skip_if(is_ubsan()) skip_on_cran() px <- get_tool("px") pidfile <- tempfile() on.exit(unlink(pidfile), add = TRUE) # Start a long-lived parent that spawns a grandchild with linux_pdeathsig # and writes its PID to a file, then sleeps. We SIGKILL the parent # explicitly — instantaneous, no sanitizer cleanup delay — so PDEATHSIG # fires at a known time regardless of ASAN/valgrind overhead. bg <- callr::r_bg( function(px, pidfile) { p <- processx::process$new( px, c("sleep", "100"), cleanup = FALSE, linux_pdeathsig = TRUE ) writeLines(as.character(p$get_pid()), pidfile) Sys.sleep(600) }, args = list(px = px, pidfile = pidfile) ) on.exit(bg$kill(), add = TRUE) deadline <- get_deadline(secs = 5) while (!file.exists(pidfile) && Sys.time() < deadline) { Sys.sleep(0.05) } skip_if(!file.exists(pidfile), "grandchild did not start in time") grandchild_pid <- as.integer(readLines(pidfile)) on.exit(tools::pskill(grandchild_pid, tools::SIGKILL), add = TRUE) bg$kill() bg$wait() deadline <- get_deadline(secs = 3) while (process__exists(grandchild_pid) && Sys.time() < deadline) { Sys.sleep(0.05) } expect_false(process__exists(grandchild_pid)) }) test_that("without linux_pdeathsig child survives parent exit", { skip_if(!is_linux()) skip_if(is_valgrind()) skip_if(is_asan()) skip_if(is_ubsan()) skip_on_cran() px <- get_tool("px") pidfile <- tempfile() on.exit(unlink(pidfile), add = TRUE) bg <- callr::r_bg( function(px, pidfile) { p <- processx::process$new( px, c("sleep", "100"), cleanup = FALSE, linux_pdeathsig = FALSE ) writeLines(as.character(p$get_pid()), pidfile) Sys.sleep(600) }, args = list(px = px, pidfile = pidfile) ) on.exit(bg$kill(), add = TRUE) deadline <- get_deadline(secs = 5) while (!file.exists(pidfile) && Sys.time() < deadline) { Sys.sleep(0.05) } skip_if(!file.exists(pidfile), "grandchild did not start in time") grandchild_pid <- as.integer(readLines(pidfile)) on.exit(tools::pskill(grandchild_pid, tools::SIGKILL), add = TRUE) bg$kill() bg$wait() Sys.sleep(0.2) expect_true(process__exists(grandchild_pid)) }) test_that("linux_pdeathsig input validation", { px <- get_tool("px") # Valid: FALSE (default) p <- process$new(px, c("return", "0"), linux_pdeathsig = FALSE) p$wait() # Valid signal numbers are accepted on all platforms; non-Linux just warns if (is_linux()) { p <- process$new(px, c("return", "0"), linux_pdeathsig = TRUE) p$wait() p <- process$new(px, c("return", "0"), linux_pdeathsig = tools::SIGTERM) p$wait() } else { expect_warning( process$new(px, c("return", "0"), linux_pdeathsig = TRUE)$wait(), "ignored on non-Linux" ) } # Invalid: string, negative, zero expect_error(process$new(px, c("return", "0"), linux_pdeathsig = "foo")) expect_error(process$new(px, c("return", "0"), linux_pdeathsig = -1)) expect_error(process$new(px, c("return", "0"), linux_pdeathsig = 0)) }) test_that("get_end_time", { px <- get_tool("px") p <- process$new(px, c("sleep", "1")) on.exit(p$kill(), add = TRUE) before <- Sys.time() expect_null(p$get_end_time()) p$wait() after <- Sys.time() et <- p$get_end_time() expect_s3_class(et, "POSIXct") expect_gte(as.double(et), as.double(before)) expect_gte(as.double(et), as.double(p$get_start_time())) # cached: second call returns the same value expect_equal(p$get_end_time(), et) })