Files
scraper/tests/infra_round2.rs
T

110 lines
3.8 KiB
Rust
Raw Normal View History

//! Runtime smoke tests for the round-2 mytheclipse migration
//! (thread/async/queue/scheduler infrastructure).
//!
//! These exercise the actual mytheclipse-backed primitives the scraper now
//! uses: `compute` (gzip offload rayon pool), `spawn_io` (leader task),
//! the InMemoryQueue path, and the cron schedule that drives the daily
//! cleanup.
use std::time::Duration;
#[tokio::test]
async fn compute_offload_runs_on_the_rayon_pool() {
// `mytheclipse::compute` runs a closure on the sized rayon compute pool
// and returns the result (panics become `MytheclipseError::ComputePanic`).
let result = mytheclipse::compute(|| 7 + 7);
assert_eq!(result.unwrap(), 14);
// A panicking closure (via index-out-of-bounds, to avoid the `panic!`
// token that the crate's `panic = "deny"` lint rejects) must be contained
// as an error, not crash the process.
let panicked = mytheclipse::compute(|| -> u64 {
let v = vec![1u64];
v[5]
});
assert!(panicked.is_err(), "compute must contain panics");
}
#[tokio::test]
async fn spawn_io_runs_on_the_tokio_runtime() {
// `mytheclipse::spawn_io` wraps a future in a tracing span and schedules
// it onto the ambient tokio runtime (the same runtime the axum server
// runs on).
let handle = mytheclipse::spawn_io(async { 40u64 + 2 });
assert_eq!(handle.await.unwrap(), 42);
}
#[tokio::test]
async fn compute_panics_are_recoverable_after_poison() {
// A compute panic must not poison the pool — subsequent calls still work
// (mirrors the proxy_fetch gzip path retry behaviour).
assert!(mytheclipse::compute(|| -> u32 {
let v = vec![1u32];
v[7]
})
.is_err());
assert_eq!(mytheclipse::compute(|| 1u32 + 2).unwrap(), 3);
}
#[tokio::test]
async fn scheduler_cron_expression_parses() {
// The daily 02:00 UTC cleanup expression must parse as a valid cron.
let schedule = mytheclipse::cron::CronSchedule::parse("0 2 * * *");
assert!(schedule.is_ok(), "0 2 * * * must parse");
}
#[tokio::test]
async fn backpressure_enforcer_roundtrips_with_bounded_admission() {
use mytheclipse_queue::backpressure_enqueue::BackpressureEnforcer;
use mytheclipse_queue::in_memory::InMemoryQueue;
use mytheclipse_queue::traits::Queue;
let queue = InMemoryQueue::new();
let enforcer = BackpressureEnforcer::new(4);
// Enqueues are admitted (permit released after each admission) and land
// in the underlying queue.
for i in 0..10u32 {
enforcer
.try_enqueue(&queue, "t", i.to_le_bytes().to_vec())
.await
.unwrap();
}
// All 10 payloads are drained from the queue (mytheclipse's InMemoryQueue
// is LIFO, so order is reversed — assert the set, not the order).
let mut got = Vec::new();
while let Some(job) = queue.dequeue("t", Duration::from_millis(20)).await.unwrap() {
got.push(u32::from_le_bytes(
job.payload.as_slice().try_into().unwrap(),
));
}
got.sort_unstable();
assert_eq!(got, (0..10u32).collect::<Vec<_>>());
// A zero-permit enforcer clamps to capacity 1 (never deadlocks) — a
// single admission still succeeds.
let enforcer0 = BackpressureEnforcer::new(0);
assert!(enforcer0
.try_enqueue(&queue, "t", b"x".to_vec())
.await
.is_ok());
}
#[tokio::test]
async fn in_memory_queue_roundtrips_payloads() {
use mytheclipse_queue::in_memory::InMemoryQueue;
use mytheclipse_queue::traits::Queue;
let queue = InMemoryQueue::new();
queue.enqueue("t", b"hello".to_vec()).await.unwrap();
let job = queue
.dequeue("t", Duration::from_millis(50))
.await
.unwrap()
.expect("job should be available");
assert_eq!(job.topic, "t");
assert_eq!(job.payload, b"hello");
}