#![allow(clippy::unwrap_used)]
#![expect(
clippy::expect_used,
reason = "this entire module is test-only fixture/helper infrastructure (most of it gated \
behind the test-util feature); every .expect() here is on a fixture Mutex/UTF-8 \
conversion that cannot fail in a single-threaded test harness"
)]
#[must_use]
pub fn test_uri(unix_path: &str) -> url::Url {
#[cfg(windows)]
let owned;
#[cfg(windows)]
let path: &str = {
owned = format!("C:{unix_path}");
&owned
};
#[cfg(not(windows))]
let path: &str = unix_path;
url::Url::from_file_path(path).expect("test_uri: fixture path must be a valid file URL")
}
pub struct MockMetadata {
name: crate::PackageName,
latest_version: crate::ConcreteVersion,
}
impl MockMetadata {
#[must_use]
pub fn new(
name: impl Into<crate::PackageName>,
latest_version: impl Into<crate::ConcreteVersion>,
) -> Self {
Self {
name: name.into(),
latest_version: latest_version.into(),
}
}
}
impl crate::Metadata for MockMetadata {
fn name(&self) -> &crate::PackageName {
&self.name
}
fn description(&self) -> Option<&str> {
None
}
fn repository(&self) -> Option<&str> {
None
}
fn documentation(&self) -> Option<&str> {
None
}
fn latest_version(&self) -> &crate::ConcreteVersion {
&self.latest_version
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
pub const ADVERSARIAL_URL_SEGMENTS: &[&str] = &[
".",
"..",
"../../etc/passwd",
"a b",
"a?b=1",
"a#frag",
"%2e%2e",
];
pub fn assert_dot_segment_gated_or_contained(
resolve: impl Fn(&str) -> Option<String>,
expected_host: &str,
expected_path_prefix: &str,
) {
assert_dot_segment_gated_or_contained_transformed(
resolve,
str::to_string,
expected_host,
expected_path_prefix,
);
}
pub fn assert_dot_segment_gated_or_contained_transformed(
resolve: impl Fn(&str) -> Option<String>,
transform: impl Fn(&str) -> String,
expected_host: &str,
expected_path_prefix: &str,
) {
for segment in ADVERSARIAL_URL_SEGMENTS {
let Some(built) = resolve(segment) else {
continue;
};
let parsed = url::Url::parse(&built).unwrap_or_else(|e| {
panic!("adversarial segment {segment:?} produced an unparsable URL {built:?}: {e}")
});
assert_eq!(
parsed.host_str(),
Some(expected_host),
"adversarial segment {segment:?} escaped host: {built}"
);
assert!(
parsed.path().starts_with(expected_path_prefix),
"adversarial segment {segment:?} escaped path prefix {expected_path_prefix:?}: {built}"
);
let expected_fragment = transform(segment);
assert!(
!expected_fragment.is_empty(),
"adversarial segment {segment:?} transformed to an empty fragment but `resolve` \
still returned Some(url) — an empty identifier must be rejected (return `None`) \
before reaching the sink, since an empty survival check would trivially pass \
(\"\".contains(\"\") is always true) and hide a real gate deletion"
);
if expected_fragment == "." || expected_fragment == ".." {
let matched = parsed.path_segments().is_some_and(|mut segs| {
segs.any(|seg| {
urlencoding::decode(seg)
.is_ok_and(|decoded| decoded.starts_with(expected_fragment.as_str()))
})
});
assert!(
matched,
"adversarial segment {segment:?} (expected to survive as {expected_fragment:?}) \
did not survive as its own path segment (silently dropped/collapsed by \
dot-segment normalization?): built url {built}"
);
} else {
let decoded_path = urlencoding::decode(parsed.path()).unwrap_or_else(|e| {
panic!(
"adversarial segment {segment:?}'s URL path failed to percent-decode: {built:?}: {e}"
)
});
assert!(
decoded_path.contains(&expected_fragment),
"adversarial segment {segment:?} (expected to survive as {expected_fragment:?}) \
did not survive intact in the decoded path (silently dropped/collapsed by \
dot-segment normalization?): decoded path {decoded_path:?}, built url {built}"
);
}
}
}
#[cfg(feature = "test-util")]
mod tracing_capture {
use std::cell::{Cell, RefCell};
use std::sync::Once;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::{Context, Filter, SubscriberExt};
thread_local! {
static BUFFER: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
static MAX_LEVEL: Cell<Option<tracing::Level>> = const { Cell::new(None) };
}
#[derive(Clone, Default)]
struct ThreadLocalWriter;
impl std::io::Write for ThreadLocalWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
BUFFER.with(|b| b.borrow_mut().extend_from_slice(buf));
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for ThreadLocalWriter {
type Writer = Self;
fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
struct ThreadLocalLevelFilter;
impl<S> Filter<S> for ThreadLocalLevelFilter {
fn enabled(&self, meta: &tracing::Metadata<'_>, _cx: &Context<'_, S>) -> bool {
MAX_LEVEL.with(|cell| cell.get().is_some_and(|max| *meta.level() <= max))
}
}
static INIT: Once = Once::new();
fn ensure_installed() {
INIT.call_once(|| {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_writer(ThreadLocalWriter)
.without_time()
.with_target(false)
.with_filter(ThreadLocalLevelFilter);
let subscriber = tracing_subscriber::registry().with(fmt_layer);
tracing::subscriber::set_global_default(subscriber)
.expect("test-capture tracing subscriber must install exactly once per process");
tracing::callsite::rebuild_interest_cache();
});
}
struct CaptureGuard;
impl Drop for CaptureGuard {
fn drop(&mut self) {
MAX_LEVEL.with(|cell| cell.set(None));
}
}
fn begin_capture(max_level: tracing::Level) -> CaptureGuard {
ensure_installed();
assert!(
MAX_LEVEL.with(Cell::get).is_none(),
"nested capture_tracing_output* is not supported: the inner call's buffer/level \
would silently clobber the outer call's"
);
BUFFER.with(|b| b.borrow_mut().clear());
MAX_LEVEL.with(|cell| cell.set(Some(max_level)));
CaptureGuard
}
fn take_captured() -> String {
BUFFER.with(|b| {
String::from_utf8(std::mem::take(&mut *b.borrow_mut()))
.expect("tracing output is valid utf8")
})
}
pub(super) fn capture_at(max_level: tracing::Level, f: impl FnOnce()) -> String {
let _guard = begin_capture(max_level);
f();
take_captured()
}
pub(super) async fn capture_async_at(
max_level: tracing::Level,
fut: impl std::future::Future<Output = ()>,
) -> String {
let _guard = begin_capture(max_level);
fut.await;
take_captured()
}
}
#[cfg(feature = "test-util")]
#[must_use]
pub fn capture_tracing_output(f: impl FnOnce()) -> String {
tracing_capture::capture_at(tracing::Level::INFO, f)
}
#[cfg(feature = "test-util")]
#[must_use]
pub fn capture_tracing_output_at(max_level: tracing::Level, f: impl FnOnce()) -> String {
tracing_capture::capture_at(max_level, f)
}
#[cfg(feature = "test-util")]
pub async fn capture_tracing_output_async(fut: impl std::future::Future<Output = ()>) -> String {
tracing_capture::capture_async_at(tracing::Level::INFO, fut).await
}
#[cfg(feature = "test-util")]
pub async fn capture_tracing_output_async_at(
max_level: tracing::Level,
fut: impl std::future::Future<Output = ()>,
) -> String {
tracing_capture::capture_async_at(max_level, fut).await
}
struct StubDependency {
name: crate::PackageName,
}
impl crate::Dependency for StubDependency {
fn name(&self) -> &crate::PackageName {
&self.name
}
fn name_range(&self) -> crate::position::Range {
crate::position::Range::default()
}
fn version_requirement(&self) -> Option<&crate::VersionReq> {
None
}
fn version_range(&self) -> Option<crate::position::Range> {
None
}
fn source(&self) -> crate::parser::DependencySource {
crate::parser::DependencySource::Registry
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
struct StubParseResult {
dependencies: Vec<StubDependency>,
uri: url::Url,
}
impl crate::ParseResult for StubParseResult {
fn dependencies(&self) -> Vec<&dyn crate::Dependency> {
self.dependencies
.iter()
.map(|d| d as &dyn crate::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&std::path::Path> {
None
}
fn uri(&self) -> &url::Url {
&self.uri
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[must_use]
pub fn stub_parse_result_with_dependencies(count: usize) -> Box<dyn crate::ParseResult> {
Box::new(StubParseResult {
dependencies: (0..count)
.map(|i| StubDependency {
name: crate::PackageName::new(format!("dep-{i}")),
})
.collect(),
uri: test_uri("/project/manifest.toml"),
})
}
#[cfg(all(test, feature = "test-util"))]
mod tests {
use super::capture_tracing_output;
#[test]
fn concurrent_untraced_touch_does_not_poison_active_capture() {
fn emit_marker() {
tracing::warn!("deps-core-1006-concurrent-touch-regression-marker");
}
let output = capture_tracing_output(|| {
let (untraced_touched_tx, untraced_touched_rx) = std::sync::mpsc::channel();
let handle = std::thread::spawn(move || {
emit_marker();
untraced_touched_tx.send(()).unwrap();
});
untraced_touched_rx.recv().unwrap();
emit_marker();
handle.join().unwrap();
});
assert!(
output.contains("deps-core-1006-concurrent-touch-regression-marker"),
"capture was empty after a concurrent sibling-thread untraced touch — \
tracing-core's per-callsite Interest cache regressed (#1006): {output:?}"
);
}
#[test]
fn capture_filters_by_level_and_does_not_leak_across_calls() {
let output = capture_tracing_output(|| {
tracing::debug!("deps-core-1006-level-filter-marker-should-not-appear");
});
assert!(
!output.contains("deps-core-1006-level-filter-marker-should-not-appear"),
"capture_tracing_output's default INFO filter captured a DEBUG emission — \
level filtering regressed: {output:?}"
);
tracing::warn!("deps-core-1006-stale-marker-should-not-leak");
let output = capture_tracing_output(|| {
tracing::warn!("deps-core-1006-fresh-marker");
});
assert!(
!output.contains("deps-core-1006-stale-marker-should-not-leak"),
"a warn! emitted outside any capture window leaked into the next capture's \
result — cross-call isolation regressed: {output:?}"
);
assert!(
output.contains("deps-core-1006-fresh-marker"),
"the current capture's own emission is missing: {output:?}"
);
}
}