Nothing
# The carrier itself, in one process ------------------------------------------
#
# These run everywhere: they are about what carry_options() does to an option
# list, which needs no worker. The parallel cases below need a plan and a fake
# binary and skip accordingly.
test_that("carry_options() runs .f under the values captured in the parent", {
withr::local_options(
tidymedia.timeout = 42, tidymedia.hardware_encoders = "h264_nvenc"
)
seen <- carry_options(function() {
list(
timeout = getOption("tidymedia.timeout"),
encoders = getOption("tidymedia.hardware_encoders")
)
})
# Change the parent AFTER the wrap: what the wrapper installs must be what
# was captured, not what happens to be set when the mapped call runs.
options(tidymedia.timeout = 7, tidymedia.hardware_encoders = "hevc_nvenc")
expect_equal(seen(), list(timeout = 42, encoders = "h264_nvenc"))
})
test_that("carry_options() restores the prior values when .f returns", {
withr::local_options(
tidymedia.timeout = 42, tidymedia.hardware_encoders = "h264_nvenc"
)
wrapped <- carry_options(function() NULL)
withr::local_options(
tidymedia.timeout = 7, tidymedia.hardware_encoders = "hevc_nvenc"
)
wrapped()
expect_equal(getOption("tidymedia.timeout"), 7)
expect_equal(getOption("tidymedia.hardware_encoders"), "hevc_nvenc")
})
test_that("carry_options() restores the prior values when .f aborts", {
withr::local_options(
tidymedia.timeout = 42, tidymedia.hardware_encoders = "h264_nvenc"
)
wrapped <- carry_options(function() {
cli::cli_abort("mapped call failed", class = "tm_test_failure")
})
withr::local_options(
tidymedia.timeout = 7, tidymedia.hardware_encoders = "hevc_nvenc"
)
expect_error(wrapped(), class = "tm_test_failure")
expect_equal(getOption("tidymedia.timeout"), 7)
expect_equal(getOption("tidymedia.hardware_encoders"), "hevc_nvenc")
})
test_that("an option unset in the parent is unset for the mapped call", {
withr::local_options(tidymedia.hardware_encoders = NULL)
wrapped <- carry_options(function() {
# getOption()'s default only applies when the name is absent from the
# option list, so this distinguishes "unset" from "set to NULL".
getOption("tidymedia.hardware_encoders", default = "absent")
})
withr::local_options(tidymedia.hardware_encoders = "hevc_nvenc")
expect_equal(wrapped(), "absent")
expect_equal(getOption("tidymedia.hardware_encoders"), "hevc_nvenc")
})
test_that("an unset timeout is carried as the no-limit sentinel", {
withr::local_options(tidymedia.timeout = NULL)
wrapped <- carry_options(function() getOption("tidymedia.timeout"))
expect_equal(wrapped(), 0)
})
test_that("a bad limit is refused when the carrier is built, before .f runs", {
ran <- FALSE
for (bad in list(0.5, -1, NA, "2")) {
withr::local_options(tidymedia.timeout = bad)
expect_error(
carry_options(function() ran <<- TRUE),
class = "rlang_error"
)
}
expect_false(ran)
})
# The parallel harness --------------------------------------------------------
#
# Everything below needs three things the machine may not have: a POSIX shell
# for the fake binaries, a `future` cluster, and workers running the same
# tidymedia source this file is testing. Each is a skip, and the milestone's
# evidence comes from a run where none of them fired.
# tm_fake_programs(): stand-in `ffmpeg` and `ffprobe` that log and then hang.
#
# Every invocation is appended to one shared log, so a spawn inside a worker is
# visible from the parent afterwards. `-encoders` answers immediately -- the
# encoder probe is a question, not a run, and a batch that only compiles must
# not take 30 seconds to prove it asked. Everything else sleeps well past any
# limit these tests set, which is what makes the kill observable.
tm_fake_programs <- function(env = parent.frame()) {
dir <- withr::local_tempdir(.local_envir = env)
log <- file.path(dir, "calls.log")
for (prog in c("ffmpeg", "ffprobe")) {
path <- file.path(dir, prog)
writeLines(
c(
"#!/bin/sh",
sprintf('printf "%%s %%s\\n" "%s" "$*" >> "%s"', prog, log),
"case \" $* \" in",
" *-encoders*)",
" echo ' ------'",
" echo ' V....D h264_nvenc NVIDIA NVENC H.264 encoder'",
" echo ' V....D hevc_nvenc NVIDIA NVENC hevc encoder'",
" exit 0 ;;",
"esac",
"sleep 30"
),
path
)
Sys.chmod(path, "0755")
}
list(dir = dir, log = log)
}
# tm_fake_calls(): the fake binaries' log, one line per invocation.
tm_fake_calls <- function(fake) {
if (!file.exists(fake$log)) return(character(0))
readLines(fake$log, warn = FALSE)
}
# tm_carry_fingerprint(): what the carrier looks like in THIS process.
#
# `devtools::test()` runs the parent against the source and the workers against
# the INSTALLED package, so a worker can silently be running last release's
# code. Comparing the carrier's own source between the two is the only check
# that catches that; a version number would not, since a dev version is
# installed too.
tm_carry_fingerprint <- function() {
ns <- tryCatch(asNamespace("tidymedia"), error = function(e) NULL)
if (is.null(ns)) return(NA_character_)
paste(
vapply(
c("carry_options", "carried_option_values"),
function(f) {
tryCatch(
paste(deparse(body(get(f, envir = ns))), collapse = ""),
error = function(e) NA_character_
)
},
character(1)
),
collapse = "\n"
)
}
# local_carry_harness(): fake binaries first on PATH, then a fresh two-worker
# cluster, then two things asserted before any cell is trusted.
#
# The cluster is booted AFTER the PATH edit and is built fresh for each test
# rather than reusing `future`'s cached one, for two reasons: a cached cluster
# was started under the real PATH and would never see the fakes, and a worker
# that has already answered an encoder probe carries that memo into the next
# test, which is exactly what the AC2 control counts.
local_carry_harness <- function(env = parent.frame(), workers = 2L) {
testthat::skip_on_cran()
testthat::skip_on_os("windows")
testthat::skip_if_not_installed("furrr")
testthat::skip_if_not_installed("future")
fake <- tm_fake_programs(env)
withr::local_path(fake$dir, action = "prefix", .local_envir = env)
# base R's `parallel`, not `parallelly`: a fresh PSOCK cluster is all this
# needs, and `parallelly` is not among the package's declared dependencies.
cl <- parallel::makePSOCKcluster(workers)
withr::defer(parallel::stopCluster(cl), envir = env)
old_plan <- future::plan(future::cluster, workers = cl)
withr::defer(future::plan(old_plan), envir = env)
probes <- seq_len(workers * 2L)
opts <- furrr::furrr_options(chunk_size = 1, seed = TRUE)
fingerprints <- unlist(furrr::future_map_chr(
probes, function(i) tm_carry_fingerprint(), .options = opts
))
# isTRUE(): a worker that could not load the namespace fingerprints as NA, and
# a bare `if (!all(...))` on an NA raises rather than taking this skip.
if (!isTRUE(all(fingerprints == tm_carry_fingerprint()))) {
testthat::skip(
"workers load an installed tidymedia whose carrier differs from the source"
)
}
# Not a skip: the PATH edit either reached the workers or the fixture is
# broken, and a green run on the real FFmpeg would be evidence of nothing.
seen <- unlist(furrr::future_map_chr(
probes, function(i) unname(Sys.which("ffmpeg")), .options = opts
))
# Sys.which() resolves symlinks (macOS's /var -> /private/var), so compare
# realpaths rather than strings.
testthat::expect_equal(
unique(seen), normalizePath(file.path(fake$dir, "ffmpeg"))
)
fake
}
# tm_batch_jobs(): an N-row jobs table over readable inputs.
tm_batch_jobs <- function(n, env = parent.frame()) {
tibble::tibble(
input = vapply(seq_len(n), function(i) make_input(env = env), character(1)),
output = file.path(withr::local_tempdir(.local_envir = env),
sprintf("out%d.mp4", seq_len(n)))
)
}
tm_plain_pipeline <- function(input, output, ...) {
ffm_files(input, output)
}
# AC1 -- the limit reaches the worker, in each site's own shape ----------------
test_that("a limit set in the parent kills FFmpeg inside an ffm_batch worker", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(2)
withr::local_options(tidymedia.timeout = 1)
expect_warning(
out <- ffm_batch(jobs, function(input, output, ...) {
tidymedia::ffm_files(input, output)
}, run = TRUE, parallel = TRUE),
class = "tidymedia_batch_timeout"
)
expect_equal(out$success, rep(FALSE, 2))
# The kill has to be what the worker actually attempted, not merely a FALSE:
# the fake logs every invocation, so an empty log would mean the row failed
# for some other reason entirely.
expect_true(any(grepl("^ffmpeg ", tm_fake_calls(fake))))
})
test_that("a limit set in the parent kills FFprobe inside a probe_all worker", {
fake <- local_carry_harness()
files <- c(make_input(), make_input())
withr::local_options(tidymedia.timeout = 1)
expect_warning(
res <- probe_all(files, parallel = TRUE),
regexp = "timed out rather than being unreadable"
)
expect_equal(nrow(res$container), 2L)
expect_equal(sort(res$container$file), sort(files))
# An NA row: `file` is the only column a file that never answered can fill.
expect_equal(setdiff(names(res$container), "file"), character(0))
expect_true(any(grepl("^ffprobe ", tm_fake_calls(fake))))
})
test_that("a limit set in the parent aborts a two-pass loudnorm worker", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(2)
withr::local_options(tidymedia.timeout = 1)
expect_error(
normalize_audio_batch(jobs, two_pass = TRUE, parallel = TRUE),
class = "tidymedia_timeout"
)
expect_true(any(grepl("^ffmpeg ", tm_fake_calls(fake))))
})
test_that("every parallel fan-out in the package has a case above", {
r_dir <- testthat::test_path("..", "..", "R")
files <- if (dir.exists(r_dir)) {
list.files(r_dir, pattern = "[.][Rr]$", full.names = TRUE)
} else {
character(0)
}
# dir.exists() alone is not enough to know we are looking at SOURCES. An
# INSTALLED package also has an `R/` directory, holding the lazyload database
# (`<pkg>.rdb`/`.rdx`) and no `.R` file at all -- so under covr, which runs the
# tests from an installed copy, the directory existed, the listing came back
# empty, and this guard failed three assertions about a domain it could not
# see rather than skipping (measured on CI 2026-08-27). Key the skip on
# finding the sources this guard actually greps, not on the directory.
skip_if(
!length(files) || !any(basename(files) == "ffm_batch.R"),
"package sources are not on disk in this run"
)
sites <- unlist(lapply(files, function(f) {
hits <- grep("furrr::future_", readLines(f, warn = FALSE), value = FALSE)
if (!length(hits)) return(character(0))
paste0(basename(f), ":", hits)
}))
# The domain must not be silently empty: a rename of furrr's call would leave
# this guard green over nothing.
expect_gt(length(sites), 0)
covered <- c(
"ffm_batch.R" = "pipeline build (AC2: the encoder probe is its only spawn)",
"ffm_batch.R" = "job run (AC1)",
"ffprobe.R" = "probe_all (AC1)",
"loudnorm_two_pass.R" = "loudnorm phase 1 (AC1)"
)
expect_setequal(sub(":.*$", "", sites), unique(names(covered)))
expect_equal(length(sites), length(covered))
})
# AC2 -- the encoder override reaches the worker -------------------------------
tm_nvenc_pipeline <- function(input, output, ...) {
enc <- if (tidymedia::has_hardware_encoder("h264", "nvenc")) {
tidymedia::hardware_encoder("h264", "nvenc")
} else {
"libx264"
}
tidymedia::ffm_codec(tidymedia::ffm_files(input, output), video = enc)
}
test_that("an encoder override set in the parent spares the worker the probe", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(4)
withr::local_options(tidymedia.hardware_encoders = "h264_nvenc")
par <- ffm_batch(jobs, tm_nvenc_pipeline, run = FALSE, parallel = TRUE)
seq <- ffm_batch(jobs, tm_nvenc_pipeline, run = FALSE, parallel = FALSE)
expect_false(any(grepl("-encoders", tm_fake_calls(fake), fixed = TRUE)))
expect_equal(par$command, seq$command)
# State the answer independently of the run that produced it: the override
# names the nvenc encoder, so a command compiled without it would be visible.
expect_true(all(grepl("h264_nvenc", par$command, fixed = TRUE)))
})
test_that("without an override each worker asks its own FFmpeg once", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(4)
withr::local_options(tidymedia.hardware_encoders = NULL)
par <- ffm_batch(jobs, tm_nvenc_pipeline, run = FALSE, parallel = TRUE)
probes <- grep("-encoders", tm_fake_calls(fake), fixed = TRUE, value = TRUE)
expect_equal(length(probes), 2L)
expect_true(all(grepl("h264_nvenc", par$command, fixed = TRUE)))
})
# AC3 -- one batch warning, over every row the limit killed --------------------
test_that("a parallel batch warns once, naming how many jobs the limit killed", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(3)
withr::local_options(tidymedia.timeout = 1)
seen <- list()
out <- withCallingHandlers(
ffm_batch(jobs, function(input, output, ...) {
tidymedia::ffm_files(input, output)
}, run = TRUE, parallel = TRUE),
tidymedia_batch_timeout = function(w) {
seen[[length(seen) + 1L]] <<- w
invokeRestart("muffleWarning")
}
)
expect_equal(out$success, rep(FALSE, 3))
expect_length(seen, 1L)
expect_match(conditionMessage(seen[[1]]), "3 jobs timed out")
})
# AC4 -- the worker gets its own settings back --------------------------------
tm_read_settings <- function(i) {
list(
pid = as.character(Sys.getpid()),
timeout = getOption("tidymedia.timeout"),
encoders = getOption("tidymedia.hardware_encoders")
)
}
# tm_by_pid(): index one fan-out's readings by the process that made them, so
# two fan-outs are compared worker-for-worker rather than position-for-position
# -- nothing says future hands the same element to the same worker twice.
tm_by_pid <- function(readings) {
out <- lapply(readings, function(r) r[c("timeout", "encoders")])
names(out) <- vapply(readings, function(r) r$pid, character(1))
out[!duplicated(names(out))]
}
test_that("a carried fan-out gives each worker its own settings back", {
local_carry_harness()
opts <- furrr::furrr_options(chunk_size = 1, seed = TRUE)
n <- 6L
stamp <- function(i) {
options(
tidymedia.timeout = 5,
tidymedia.hardware_encoders = paste0("worker_", Sys.getpid())
)
tm_read_settings(i)
}
before <- tm_by_pid(furrr::future_map(seq_len(n), stamp, .options = opts))
expect_gte(length(before), 2L)
withr::local_options(
tidymedia.timeout = 1, tidymedia.hardware_encoders = "parent_only"
)
during <- furrr::future_map(
seq_len(n), carry_options(tm_read_settings), .options = opts
)
# The carry has to have happened, or the restoration below is a claim about
# nothing: every worker read the parent's values, not its own.
expect_equal(unique(vapply(during, function(r) r$timeout, numeric(1))), 1)
expect_equal(
unique(vapply(during, function(r) r$encoders, character(1))), "parent_only"
)
after_return <- tm_by_pid(
furrr::future_map(seq_len(n), tm_read_settings, .options = opts)
)
shared <- intersect(names(before), names(after_return))
expect_gte(length(shared), 2L)
expect_equal(after_return[shared], before[shared])
expect_error(furrr::future_map(
seq_len(n),
carry_options(function(i) stop("the mapped call failed")),
.options = opts
))
after_error <- tm_by_pid(
furrr::future_map(seq_len(n), tm_read_settings, .options = opts)
)
shared <- intersect(names(before), names(after_error))
expect_gte(length(shared), 2L)
expect_equal(after_error[shared], before[shared])
})
# AC5 -- a bad limit is refused before anything is dispatched ------------------
test_that("an invalid limit is refused identically on both branches", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(2)
marker <- file.path(withr::local_tempdir(), "ran.txt")
builder <- function(input, output, ...) {
cat("ran\n", file = marker, append = TRUE)
tidymedia::ffm_files(input, output)
}
for (bad in list(0.5, -1, NA, "2")) {
withr::local_options(tidymedia.timeout = bad)
par <- tryCatch(
ffm_batch(jobs, builder, run = FALSE, parallel = TRUE),
error = identity
)
seql <- tryCatch(
ffm_batch(jobs, builder, run = TRUE, parallel = FALSE),
error = identity
)
expect_s3_class(par, "rlang_error")
expect_equal(class(par), class(seql))
expect_equal(conditionMessage(par), conditionMessage(seql))
}
expect_false(file.exists(marker))
expect_equal(tm_fake_calls(fake), character(0))
})
# M072 -- a per-call limit reaches the worker too ------------------------------
#
# The carrier captures the limit in the parent at fan-out time, so a limit
# established for one call rides into the worker exactly as a session-wide one
# does. These cells set the SESSION limit to a value that is not the per-call
# one and time the kill, because a fan-out that carried the session value
# instead would still kill the job -- just 25 seconds later. The fakes sleep
# 30 s, so both limits are observable and they cannot be confused.
tm_session_limit_not_2 <- 25
# Comfortably above a 2-second kill and comfortably below a 25-second one. The
# fakes are shell scripts that die on the first signal, so the SIGINT ->
# SIGTERM -> SIGKILL ladder (M69) does not stretch this the way a blocked
# FFmpeg does.
tm_per_call_budget <- 20
tm_elapsed <- function(expr) {
start <- Sys.time()
force(expr)
as.numeric(difftime(Sys.time(), start, units = "secs"))
}
test_that("a per-call limit kills FFmpeg inside an ffm_batch worker", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(2)
withr::local_options(tidymedia.timeout = tm_session_limit_not_2)
took <- tm_elapsed(
expect_warning(
out <- with_timeout(
ffm_batch(jobs, function(input, output, ...) {
tidymedia::ffm_files(input, output)
}, run = TRUE, parallel = TRUE),
2
),
class = "tidymedia_batch_timeout"
)
)
expect_lt(took, tm_per_call_budget)
expect_equal(out$success, rep(FALSE, 2))
expect_true(any(grepl("^ffmpeg ", tm_fake_calls(fake))))
expect_equal(getOption("tidymedia.timeout"), tm_session_limit_not_2)
})
test_that("a per-call limit kills FFprobe inside a probe_all worker", {
fake <- local_carry_harness()
files <- c(make_input(), make_input())
withr::local_options(tidymedia.timeout = tm_session_limit_not_2)
took <- tm_elapsed(
expect_warning(
res <- with_timeout(probe_all(files, parallel = TRUE), 2),
regexp = "timed out rather than being unreadable"
)
)
expect_lt(took, tm_per_call_budget)
expect_equal(nrow(res$container), 2L)
expect_true(any(grepl("^ffprobe ", tm_fake_calls(fake))))
expect_equal(getOption("tidymedia.timeout"), tm_session_limit_not_2)
})
test_that("a per-call limit aborts a two-pass loudnorm worker", {
fake <- local_carry_harness()
jobs <- tm_batch_jobs(2)
withr::local_options(tidymedia.timeout = tm_session_limit_not_2)
took <- tm_elapsed(
expect_error(
with_timeout(
normalize_audio_batch(jobs, two_pass = TRUE, parallel = TRUE),
2
),
class = "tidymedia_timeout"
)
)
expect_lt(took, tm_per_call_budget)
expect_true(any(grepl("^ffmpeg ", tm_fake_calls(fake))))
expect_equal(getOption("tidymedia.timeout"), tm_session_limit_not_2)
})
test_that("every parallel fan-out is accounted for under a per-call limit", {
r_dir <- testthat::test_path("..", "..", "R")
files <- if (dir.exists(r_dir)) {
list.files(r_dir, pattern = "[.][Rr]$", full.names = TRUE)
} else {
character(0)
}
# Same skip as the AC1 guard above, for the same reason: an installed copy
# has an R/ directory holding a lazyload database and no sources to grep.
skip_if(
!length(files) || !any(basename(files) == "ffm_batch.R"),
"package sources are not on disk in this run"
)
sites <- unlist(lapply(files, function(f) {
hits <- grep("furrr::future_", readLines(f, warn = FALSE), value = FALSE)
if (!length(hits)) return(character(0))
paste0(basename(f), ":", hits)
}))
expect_gt(length(sites), 0)
# Three of the four fan-outs run jobs and are timed above. The fourth builds
# pipelines: its only spawn is the encoder probe, which answers at once, so
# there is no kill to observe there and this records that rather than
# implying a cell exists.
covered <- c(
"ffm_batch.R" = "pipeline build (no kill: the encoder probe answers at once)",
"ffm_batch.R" = "job run (timed above)",
"ffprobe.R" = "probe_all (timed above)",
"loudnorm_two_pass.R" = "loudnorm phase 1 (timed above)"
)
expect_setequal(sub(":.*$", "", sites), unique(names(covered)))
expect_equal(length(sites), length(covered))
})
# M082 -- the third seam makes the same round trip ---------------------------
test_that("the track check is carried raw, its unset state included", {
# Raw, not resolved: an unset seam is carried as ABSENT, so a worker with its
# own answer keeps it. That is the encoder override's rule; the limit's rule
# would carry TRUE here and displace the worker's setting.
withr::local_options(list(tidymedia.check_tracks = FALSE))
expect_false(carried_option_values()$tidymedia.check_tracks)
withr::local_options(list(tidymedia.check_tracks = NULL))
expect_null(carried_option_values()$tidymedia.check_tracks)
# And the name is in the list at all, unset or not, so `options()` can remove
# it in the worker rather than leaving the worker's own value standing.
expect_true("tidymedia.check_tracks" %in% names(carried_option_values()))
})
test_that("a switched-off track check reaches every worker, and the parent keeps its setting", {
local_carry_harness()
opts <- furrr::furrr_options(chunk_size = 1, seed = TRUE)
read <- function(i) {
list(pid = as.character(Sys.getpid()),
check = getOption("tidymedia.check_tracks", default = "absent"))
}
withr::local_options(list(tidymedia.check_tracks = FALSE))
seen <- furrr::future_map(seq_len(6L), carry_options(read), .options = opts)
# More than one worker read it, or "every worker" is a claim about one.
expect_gte(length(unique(vapply(seen, function(r) r$pid, character(1)))), 2L)
expect_identical(unique(vapply(seen, function(r) r$check, logical(1))), FALSE)
expect_false(getOption("tidymedia.check_tracks"))
# The control: with the parent unset the workers read their own default, so
# the FALSE above came from the carry rather than from a worker that had it
# anyway.
withr::local_options(list(tidymedia.check_tracks = NULL))
seen <- furrr::future_map(seq_len(6L), carry_options(read), .options = opts)
expect_identical(unique(vapply(seen, function(r) r$check, character(1))),
"absent")
})
test_that("a parallel batch leaves the parent's track-check setting as it found it", {
local_carry_harness()
jobs <- tm_batch_jobs(4)
withr::local_options(list(tidymedia.check_tracks = FALSE))
ffm_batch(jobs, tm_nvenc_pipeline, run = FALSE, parallel = TRUE)
expect_false(getOption("tidymedia.check_tracks"))
withr::local_options(list(tidymedia.check_tracks = NULL))
ffm_batch(jobs, tm_nvenc_pipeline, run = FALSE, parallel = TRUE)
expect_identical(getOption("tidymedia.check_tracks", default = "absent"),
"absent")
})
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.