test_that("create self process", { expect_error(ps_handle("foobar"), class = "invalid_argument") expect_error(ps_handle(time = 123), class = "invalid_argument") ps <- ps_handle() expect_identical(ps_pid(ps), Sys.getpid()) }) test_that("format", { ps <- ps_handle() expect_match(format(ps), format_regexp()) }) test_that("print", { ps <- ps_handle() expect_output(print(ps), format_regexp()) }) test_that("pid", { ## Argument check expect_error(ps_pid(123), class = "invalid_argument") ## Self ps <- ps_handle() expect_identical(ps_pid(ps), Sys.getpid()) ## Child p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_identical(ps_pid(ps), p1$get_pid()) skip_if_no_processx() ## Even if it has quit already p2 <- processx::process$new(px(), c("sleep", "10")) on.exit(p2$kill(), add = TRUE) pid2 <- p2$get_pid() ps <- ps_handle(pid2) p2$kill() expect_false(p2$is_alive()) expect_identical(ps_pid(ps), pid2) }) test_that("create_time", { ## Argument check expect_error(ps_create_time(123), class = "invalid_argument") skip_if_no_processx() p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_identical(p1$get_start_time(), ps_create_time(ps)) }) test_that("is_running", { ## Argument check expect_error(ps_is_running(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) p1$kill() timeout <- Sys.time() + 5 while (ps_is_running(ps) && Sys.time() < timeout) Sys.sleep(0.05) expect_false(ps_is_running(ps)) }) test_that("parent", { ## Argument check expect_error(ps_parent(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) pp <- ps_parent(ps) expect_equal(ps_pid(pp), Sys.getpid()) }) test_that("ppid", { ## Argument check expect_error(ps_ppid(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_ppid(ps), Sys.getpid()) }) test_that("name", { ## Argument check expect_error(ps_name(123), class = "invalid_argument") skip_if_no_processx() p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_true(ps_name(ps) %in% c("px", "px.exe")) ## Long names are not truncated file.copy( px(), tmp <- paste0(tempfile(pattern = "file1234567890123456"), ".bat")) on.exit(unlink(tmp), add = TRUE) Sys.chmod(tmp, "0755") p2 <- processx::process$new(tmp, c("sleep", "10")) on.exit(p2$kill(), add = TRUE) ps <- ps_handle(p2$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_name(ps), basename(tmp)) }) test_that("exe", { ## Argument check expect_error(ps_exe(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) exe <- ps_exe(ps) # In qemu the first entry is qemu, the second entry is the exe if (!grepl("qemu", exe)) { expect_equal(ps_exe(ps), realpath(px())) } }) test_that("cmdline", { ## Argument check expect_error(ps_cmdline(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) cmd <- ps_cmdline(ps) # in qemu, need to drop the first two if (grepl("qemu", cmd[1])) { cmd <- cmd[-(1:2)] } expect_equal(cmd, c(px(), "sleep", "10")) }) test_that("cwd", { ## Argument check expect_error(ps_cwd(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10"), wd = tempdir()) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(normalizePath(ps_cwd(ps)), normalizePath(tempdir())) }) test_that("environ, environ_raw", { ## Argument check expect_error(ps_environ(123), class = "invalid_argument") skip_if_no_processx() rnd <- basename(tempfile()) p1 <- processx::process$new(px(), c("sleep", "10"), env = c(FOO = rnd)) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_environ(ps)[["FOO"]], rnd) expect_true(paste0("FOO=", rnd) %in% ps_environ_raw(ps)) }) test_that("num_threads", { ## Argument check expect_error(ps_num_threads(123), class = "invalid_argument") ## sleep should be single-threaded p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) # This is not reliable in qemu if (!grepl("qemu", ps_exe(ps))) { expect_equal(ps_num_threads(ps), 1) } ## TODO: more threads? }) test_that("suspend, resume", { ## Argument check expect_error(ps_suspend(123), class = "invalid_argument") expect_error(ps_resume(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) ps_suspend(ps) timeout <- Sys.time() + 60 while (Sys.time() < timeout && ps_status(ps) != "stopped") Sys.sleep(0.05) expect_equal(ps_status(ps), "stopped") expect_true(p1$is_alive()) expect_true(ps_is_running(ps)) ps_resume(ps) timeout <- Sys.time() + 60 while (Sys.time() < timeout && ps_status(ps) == "stopped") Sys.sleep(0.05) expect_true(ps_status(ps) %in% c("running", "sleeping")) expect_true(p1$is_alive()) expect_true(ps_is_running(ps)) ps_kill(ps) }) test_that("kill", { ## Argument check expect_error(ps_kill(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) ps_kill(ps) timeout <- Sys.time() + 5 while (Sys.time() < timeout && ps_is_running(ps)) Sys.sleep(0.05) expect_false(p1$is_alive()) expect_false(ps_is_running(ps)) if (ps_os_type()[["POSIX"]]) { expect_equal(p1$get_exit_status(), - signals()$SIGTERM) } }) test_that("children", { ## Argument check expect_error(ps_children(123), class = "invalid_argument") ## This fails on CRAN, and I cannot reproduce it anywhere else skip_on_cran() skip_if_no_processx() p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) p2 <- processx::process$new(px(), c("sleep", "10")) on.exit(p2$kill(), add = TRUE) ch <- ps_children(ps_handle()) expect_true(length(ch) >= 2) pids <- map_int(ch, ps_pid) expect_true(p1$get_pid() %in% pids) expect_true(p2$get_pid() %in% pids) ## We don't do this on Windows, because the parent process might be ## gone by now, and then it fails with no_such_process if (ps_os_type()[["POSIX"]]) { ch3 <- ps_children(ps_parent(ps_handle()), recursive = TRUE) pids3 <- map_int(ch3, ps_pid) expect_true(Sys.getpid() %in% pids3) expect_true(p1$get_pid() %in% pids3) expect_true(p2$get_pid() %in% pids3) } }) test_that("num_fds", { skip_in_rstudio() skip_on_cran() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) me <- ps_handle() orig <- ps_num_fds(me) f <- file(tmp, open = "w") on.exit(close(f), add = TRUE) expect_equal(ps_num_fds(me), orig + 1) }) test_that("open_files", { skip_in_rstudio() skip_on_cran() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) f <- file(tmp, open = "w") on.exit(try(close(f), silent = TRUE), add = TRUE) files <- ps_open_files(ps_handle()) expect_true(basename(tmp) %in% basename(files$path)) close(f) files <- ps_open_files(ps_handle()) expect_false(basename(tmp) %in% basename(files$path)) }) test_that("interrupt", { skip_on_cran() px <- processx::process$new(px(), c("sleep", "10")) on.exit(px$kill(), add = TRUE) ps <- ps_handle(px$get_pid()) expect_true(ps_is_running(ps)) ps_interrupt(ps) deadline <- Sys.time() + 3 while (ps_is_running(ps) && Sys.time() < deadline) Sys.sleep(0.05) expect_true(Sys.time() < deadline) expect_false(ps_is_running(ps)) if (ps_os_type()[["POSIX"]]) expect_equal(px$get_exit_status(), -2) }) test_that("cpu affinity", { skip_on_cran() skip_on_covr() skip_on_os("mac") orig <- ps::ps_get_cpu_affinity() expect_true(length(orig) <= ps::ps_cpu_count()) do <- function() { ps::ps_set_cpu_affinity(affinity = 0:0) ps::ps_get_cpu_affinity() } expect_equal(callr::r(do), 0:0) }) test_that("kill 2", { skip_on_cran() p <- processx::process$new(px(), c("sleep", "3")) on.exit(p$kill(), add = TRUE) ph <- p$as_ps_handle() done <- if (ps_os_type()[["WINDOWS"]]) "killed" else "terminated" expect_equal(ps_kill(ph), done) expect_equal(ps_kill(ph), "dead") # multiple processes p1 <- processx::process$new(px(), c("sleep", "3")) on.exit(p1$kill(), add = TRUE) ph1 <- p1$as_ps_handle() p2 <- processx::process$new(px(), c("sleep", "3")) on.exit(p2$kill(), add = TRUE) ph2 <- p2$as_ps_handle() expect_equal(ps_kill(list(ph1, ph2)), c(done, done)) expect_equal(ps_kill(list(ph1, ph2)), c("dead", "dead")) # some dead, some alive p3 <- processx::process$new(px(), c("sleep", "3")) on.exit(p3$kill(), add = TRUE) ph3 <- p3$as_ps_handle() p4 <- processx::process$new(px(), c("sleep", "3")) on.exit(p4$kill(), add = TRUE) ph4 <- p4$as_ps_handle() expect_equal(ps_kill(ph3), done) expect_equal(ps_kill(list(ph3, ph4)), c("dead", done)) # error up front for pid 0 if (ps_os_type()[["MACOS"]]) { p5 <- processx::process$new(px(), c("sleep", "3")) on.exit(p5$kill(), add = TRUE) ph5 <- p5$as_ps_handle() ph6 <- ps_handle(0) expect_snapshot(error = TRUE, { ps_kill(list(ph5, ph6)) }) expect_true(p5$is_alive()) p5$kill() } # access denied for some processes if (ps_os_type()[["MACOS"]]) { p7 <- processx::process$new(px(), c("sleep", "3")) on.exit(p7$kill(), add = TRUE) ph7 <- p7$as_ps_handle() ph8 <- ps_handle(1) p9 <- processx::process$new(px(), c("sleep", "3")) on.exit(p9$kill(), add = TRUE) ph9 <- p9$as_ps_handle() expect_snapshot(error = TRUE, { ps_kill(list(ph7, ph8, ph9)) }) expect_false(p7$is_alive()) expect_true(ps_is_running(ph8)) expect_false(p9$is_alive()) } })