diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index a003f2e78..2f7950c55 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -58,7 +58,9 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::rcmdcheck + extra-packages: | + any::rcmdcheck + otelsdk=?ignore-before-r=4.3.0 needs: check - uses: r-lib/actions/check-r-package@v2 diff --git a/DESCRIPTION b/DESCRIPTION index dac7aaf67..348378e2d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -42,6 +42,8 @@ Suggests: digest (>= 0.6.33), gh, knitr, + otel, + otelsdk, rmarkdown, rstudioapi, S7, diff --git a/R/otel.R b/R/otel.R new file mode 100644 index 000000000..192446310 --- /dev/null +++ b/R/otel.R @@ -0,0 +1,42 @@ +otel_tracer_name <- "org.r-lib.testthat" + +# generic otel helpers --------------------------------------------------------- + +otel_cache_tracer <- NULL +otel_local_active_span <- NULL + +local({ + otel_is_tracing <- FALSE + otel_tracer <- NULL + + otel_cache_tracer <<- function() { + requireNamespace("otel", quietly = TRUE) || return() + otel_tracer <<- otel::get_tracer(otel_tracer_name) + otel_is_tracing <<- tracer_enabled(otel_tracer) + } + + otel_local_active_span <<- function( + name, + label, + scope = parent.frame() + ) { + otel_is_tracing || return() + otel::start_local_active_span( + sprintf("%s %s", name, label), + tracer = otel_tracer, + activation_scope = scope + ) + } +}) + +tracer_enabled <- function(tracer) { + .subset2(tracer, "is_enabled")() +} + +with_otel_record <- function(expr) { + on.exit(otel_cache_tracer()) + otelsdk::with_otel_record({ + otel_cache_tracer() + expr + }) +} diff --git a/R/test-that.R b/R/test-that.R index 4e7fbae92..638edb0e6 100644 --- a/R/test-that.R +++ b/R/test-that.R @@ -51,6 +51,7 @@ test_code <- function(code, env, reporter = NULL, skip_on_empty = TRUE) { test <- test_description() if (!is.null(test)) { + otel_local_active_span("test that", test, scope = frame) reporter$start_test(context = reporter$.context, test = test) withr::defer(reporter$end_test(context = reporter$.context, test = test)) } diff --git a/R/testthat-package.R b/R/testthat-package.R index 61fa9b316..cd37f9674 100644 --- a/R/testthat-package.R +++ b/R/testthat-package.R @@ -30,3 +30,11 @@ the$in_check_reporter <- FALSE #' @importFrom lifecycle deprecated ## usethis namespace: end NULL + +# nocov start + +.onLoad <- function(libname, pkgname) { + otel_cache_tracer() +} + +# nocov end diff --git a/tests/testthat/test-otel.R b/tests/testthat/test-otel.R new file mode 100644 index 000000000..f60ab321a --- /dev/null +++ b/tests/testthat/test-otel.R @@ -0,0 +1,15 @@ +test_that("otel instrumentation works", { + skip_if_not_installed("otelsdk") + + record <- with_otel_record({ + test_that("otel testing", { + expect_equal(1, 1) + expect_error(stop("otel error")) + }) + }) + + traces <- record$traces + expect_length(traces, 1L) + expect_equal(traces[[1L]]$name, "test that otel instrumentation works / otel testing") + expect_equal(traces[[1L]]$instrumentation_scope$name , "org.r-lib.testthat") +})