tests/testthat/test-nvenc-docs.R

# Guards the documented consequence of D034: resolving hardware = "nvenc" runs
# the FFmpeg binary while the command is being built, so such a call is not
# binary-free even under run = FALSE (M54).
#
# The package has sixteen hand-written `@param hardware` blocks and no
# @inheritParams tying them together, so the docs themselves are exactly the
# hand-list that went stale in D024. This test is the procedure that replaces
# it: it enumerates the topics from the Rd rather than from anyone's memory, so
# a seventeenth verb gaining `hardware` without the sentence fails here.
#
# rd_sources() / rd_param_names() / topics_documenting() come from helper-rd.R.

# The VERB topics: every topic documenting `hardware` less the shared topic of
# the two capability helpers, which grew a `hardware` argument of their own
# (M100). The helpers document a backend name, not a verb's toggle: nothing
# there is built into a command, so "even under run = FALSE" has no referent.
# The same enumeration correction nvenc_hardware_exports() makes.
hardware_verb_topics <- function(rd) {
  topics <- topics_documenting(rd, "hardware")
  topics[!names(topics) %in% paste0(nvenc_hardware_helpers(), ".Rd")]
}

# The claim, in the wording the roxygen carries. Matched on a distinctive clause
# rather than the whole sentence, so rewrapping the block cannot break the test
# while removing the sentence still does.
probe_sentence <- "asks this FFmpeg build which encoders it has"

test_that("every topic documenting `hardware` states that nvenc probes FFmpeg", {
  rd <- rd_sources()
  skip_if(is.null(rd), "no Rd source available")
  topics <- hardware_verb_topics(rd)

  # A floor, not a count: it fails if the enumeration silently collapses (an
  # unreadable man/, an empty Rd_db) and reports a vacuous pass. Sixteen is the
  # measured count at M54; a verb gaining `hardware` later raises it.
  expect_gte(length(topics), 16L)

  missing <- names(topics)[!grepl(probe_sentence, topics, fixed = TRUE)]
  expect_identical(missing, character())
})

# M67 extended the sentence: the probe now happens once per session, and the
# discard call is how a caller escapes a stale answer. Asserted on the same
# enumerated topic set, so the new half cannot go stale on fifteen of sixteen.
memo_sentence <- "remembered for the rest of the R session"

test_that("every topic documenting `hardware` states the answer is remembered", {
  rd <- rd_sources()
  skip_if(is.null(rd), "no Rd source available")
  topics <- hardware_verb_topics(rd)
  expect_gte(length(topics), 16L)

  missing <- names(topics)[!grepl(memo_sentence, topics, fixed = TRUE)]
  expect_identical(missing, character())
})

test_that("every topic documenting `hardware` points at the discard call", {
  rd <- rd_sources()
  skip_if(is.null(rd), "no Rd source available")
  topics <- hardware_verb_topics(rd)
  missing <- names(topics)[!grepl("refresh_ffmpeg_capabilities", topics,
                                  fixed = TRUE)]
  expect_identical(missing, character())
})

test_that("the nvenc probe sentence is not claimed where `hardware` is absent", {
  # Keeps the guard honest in the other direction: if the sentence were pasted
  # package-wide, the test above would pass while saying nothing. Every topic
  # carrying the sentence must be a topic that documents `hardware`.
  rd <- rd_sources()
  skip_if(is.null(rd), "no Rd source available")
  carrying <- names(rd)[grepl(probe_sentence, rd, fixed = TRUE)]
  documenting <- names(topics_documenting(rd, "hardware"))
  expect_identical(sort(setdiff(carrying, documenting)), character())
})

# The stream-copy exception ---------------------------------------------------

# The sentence above is conditional -- it claims the probe for a call that
# RE-ENCODES the video. Four topics can be asked for nvenc on a stream copy,
# where the codec conflict each already documents aborts the call before the
# encoder is ever resolved. Those two facts are guarded together: the wording
# below, and the behavior it describes.

# Matched on a short clause: the sentence wraps at a different point on the
# `reencode` topics, and a wrap must not be able to break the guard.
copy_sentence <- "is caught first"
copy_topics <- c("segment_video", "segment_video_batch",
                 "separate_audio_video", "separate_audio_video_batch")

test_that("the stream-copy exception is stated on exactly the topics it applies to", {
  rd <- rd_sources()
  skip_if(is.null(rd), "no Rd source available")
  carrying <- sub("\\.Rd$", "", names(rd)[grepl(copy_sentence, rd, fixed = TRUE)])
  expect_identical(sort(carrying), sort(copy_topics))
})

test_that("a stream-copy nvenc call aborts without probing, as documented", {
  # Counting at ffmpeg_encoders() for the reason the D034 purity blocks give:
  # it is the seam has_hardware_encoder() reaches once the option seam is unset, and the
  # one place the binary would actually be consulted.
  f <- make_input()
  withr::local_options(tidymedia.hardware_encoders = NULL)
  probes <- 0L
  local_mocked_bindings(
    ffmpeg_encoders = function(...) {
      probes <<- probes + 1L
      tibble::tibble(name = "h264_nvenc")
    }
  )
  # Control: the re-encoding default DOES reach the resolver, so the sentence
  # the topics carry is not vacuous.
  segment_video(f, 0, 5, "out.mp4", hardware = "nvenc", run = FALSE)
  expect_gt(probes, 0L)

  # M67: the control above warms the session memo, so without this discard the
  # four calls below could never reach ffmpeg_encoders() whether or not they
  # reach has_hardware_encoder() -- `probes == 0L` would hold even if the codec-conflict
  # guard regressed to run AFTER resolve_hw_encoder(), which is the whole
  # regression this test exists to catch.
  forget_ffmpeg_capabilities()
  probes <- 0L
  expect_error(
    segment_video(f, 0, 5, "out.mp4", reencode = FALSE, hardware = "nvenc",
                  run = FALSE),
    "re-encoding cut"
  )
  expect_error(
    segment_video_batch(
      tibble::tibble(input = f, start = 0, end = 5, outfiles = "out.mp4"),
      reencode = FALSE, hardware = "nvenc", run = FALSE
    ),
    "re-encoding cut"
  )
  expect_error(
    separate_audio_video(f, "v.mp4", "a.m4a", hardware = "nvenc", run = FALSE),
    "re-encoding `video_codec`"
  )
  expect_error(
    separate_audio_video_batch(
      tibble::tibble(input = f, videofile = "v.mp4", audiofile = "a.m4a"),
      hardware = "nvenc", run = FALSE
    ),
    "re-encoding `video_codec`"
  )
  expect_identical(probes, 0L)
})

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.