Files
windmill/backend/windmill-runtime-nativets/tests/otel_init.rs

89 lines
4.0 KiB
Rust

//! Regression test for the deno_telemetry 0.31 nativets-fetch panic.
//!
//! Setup: when EE's `load_internal_otel_exporter` runs (HTTP tracing
//! enabled), it calls `deno_telemetry::init` and then flips
//! `DENO_OTEL_INITIALIZED=true` so `js_eval` will run
//! `globalThis.__bootstrapOtel()` inside the nativets JsRuntime. That
//! bootstrap unconditionally sets JS-side `TRACING_ENABLED=true`, which
//! makes `deno_fetch`'s `fetch()` call `builtinTracer().startSpan(...)`
//! — and `OtelTracer::builtin()` does `OTEL_GLOBALS.get().unwrap()`.
//!
//! In deno_telemetry 0.31, `init` was given an early-return guard: if
//! `tracing_enabled`, `metrics_enabled`, and `console` are all
//! off/Ignore, it returns `Ok(())` *without populating OTEL_GLOBALS*.
//! v1.700.0 was passing `OtelConfig::default()` (all off) — so the JS
//! bootstrap proceeded but the Rust-side OnceCell was empty, and the
//! first `fetch()` panicked the tokio worker in a context that cannot
//! unwind. Fixed in v1.702.0 by passing `tracing_enabled: true` +
//! `console: Capture` from `load_internal_otel_exporter` (PRs #573 EE
//! / #9163 OSS), matching the JS bootstrap shape `[1, 0, 1, 0]`.
//!
//! This test pins that contract directly against `deno_telemetry::
//! init` — the function the EE call site invokes — so the next time
//! the dep is bumped and someone is tempted to "simplify" the config
//! back to `OtelConfig::default()`, CI catches it.
//!
//! Lives in `tests/` (not `src/`) so it gets its own test binary and
//! its own process — `OTEL_GLOBALS` is a `OnceCell`, so sharing it
//! with the smoke tests in `src/smoke_tests.rs` would race.
use windmill_runtime_nativets::deno_telemetry;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn ee_otel_init_config_populates_otel_globals() {
// deno_telemetry::init builds an HttpExporter (uses rustls) — it
// needs a process-wide CryptoProvider, which the real binary
// installs in setup_deno_runtime / main. Mirror that here.
let _ = rustls::crypto::ring::default_provider().install_default();
// Pre-condition: nothing else in this test binary has touched the
// OnceCell, so it must be empty.
assert!(
deno_telemetry::OTEL_GLOBALS.get().is_none(),
"OTEL_GLOBALS must start empty in a fresh test process"
);
// Step 1: reproduce the footgun. `OtelConfig::default()` has
// tracing/metrics off and `console = Ignore`, which trips the
// 0.31 early-return — Ok(()) but OnceCell stays empty.
deno_telemetry::init(
deno_telemetry::OtelRuntimeConfig {
runtime_name: "windmill-nativets-test".into(),
runtime_version: "0".into(),
},
deno_telemetry::OtelConfig::default(),
)
.expect("init with default config returns Ok (early-return)");
assert!(
deno_telemetry::OTEL_GLOBALS.get().is_none(),
"deno_telemetry 0.31 contract: init with all-disabled config \
must NOT populate OTEL_GLOBALS — if this changes on a future \
bump, load_internal_otel_exporter can drop the explicit \
tracing_enabled/console fields."
);
// Step 2: re-call init with the exact config shape that the EE
// `load_internal_otel_exporter` ships — this is what production
// hits, so the test exercises the actual prod call path.
deno_telemetry::init(
deno_telemetry::OtelRuntimeConfig {
runtime_name: "windmill-nativets".into(),
runtime_version: "0".into(),
},
deno_telemetry::OtelConfig {
tracing_enabled: true,
console: deno_telemetry::OtelConsoleConfig::Capture,
..Default::default()
},
)
.expect("init with tracing_enabled config must succeed on a fresh OnceCell");
assert!(
deno_telemetry::OTEL_GLOBALS.get().is_some(),
"load_internal_otel_exporter's OtelConfig must populate \
OTEL_GLOBALS so OtelTracer::builtin() does not panic on \
.unwrap() once __bootstrapOtel flips JS-side TRACING_ENABLED"
);
}