tests/testthat/test-parallel-option-carry.R

# 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")
})

Try the tidymedia package in your browser

Any scripts or data that you put into this service are public.

tidymedia documentation built on Oct. 11, 2026, 5:08 p.m.