use crate::Interests;
use crate::attributes::{ExtensionScopeAttributeSet, PipelineAttributeSet};
use crate::context::{ControllerContext, ExtensionContext, PipelineContext};
use crate::control::NodeControlMsg;
use crate::runtime_services::PipelineRuntimeServices;
use otel_arrow_dfe_channel::mpsc;
use otel_arrow_dfe_config::engine::{
ResolvedOtelDataflowSpec, ResolvedPipelineConfig, ResolvedPipelineRole,
};
use otel_arrow_dfe_config::node::NodeKind;
use otel_arrow_dfe_config::pipeline::PipelineConfig;
use otel_arrow_dfe_config::policy::{DistributionTier, Policies};
use otel_arrow_dfe_telemetry::registry::TelemetryRegistryHandle;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use tokio::runtime::Builder;
use tokio::task::LocalSet;
pub mod capability;
#[cfg(any(test, feature = "test-utils"))]
pub mod dst;
pub mod exporter;
pub mod liveness;
pub mod node;
pub mod processor;
pub mod receiver;
pub use node::{test_node, test_nodes};
#[cfg(any(test, feature = "test-utils"))]
#[must_use]
pub fn test_pipeline_runtime_services() -> PipelineRuntimeServices {
create_test_pipeline_runtime_services()
}
fn create_test_pipeline_runtime_services() -> PipelineRuntimeServices {
PipelineRuntimeServices::new(Default::default())
.expect("test binary must link valid pdata codec registrations")
}
#[must_use]
pub fn test_pipeline_ctx() -> (PipelineContext, TelemetryRegistryHandle) {
test_pipeline_ctx_with_interests(Interests::empty())
}
#[must_use]
pub fn test_pipeline_ctx_with_interests(
interests: Interests,
) -> (PipelineContext, TelemetryRegistryHandle) {
test_pipeline_ctx_with_interests_and_duration_distribution(interests, DistributionTier::Normal)
}
#[must_use]
pub fn test_pipeline_ctx_with_interests_and_duration_distribution(
interests: Interests,
duration_distribution: DistributionTier,
) -> (PipelineContext, TelemetryRegistryHandle) {
let registry = TelemetryRegistryHandle::new();
let controller = ControllerContext::new(registry.clone());
let mut ctx = controller
.pipeline_context_with("test_grp".into(), "test_pipeline".into(), 0, 1, 0)
.with_node_context(
"test_node".into(),
"urn:test:processor:example".into(),
NodeKind::Processor,
HashMap::new(),
);
ctx.set_node_interests(interests);
ctx.set_node_duration_distribution(duration_distribution);
(ctx, registry)
}
pub fn install_test_context_bindings<PData: 'static + Clone + std::fmt::Debug>(
pipeline_ctx: &mut PipelineContext,
factory: &crate::PipelineFactory<PData>,
pipeline: PipelineConfig,
) -> Result<(), crate::error::Error> {
let resolved = ResolvedOtelDataflowSpec {
engine: Default::default(),
pipelines: vec![ResolvedPipelineConfig {
pipeline_group_id: pipeline_ctx.pipeline_group_id(),
pipeline_id: pipeline_ctx.pipeline_id(),
policies: Policies::resolve(pipeline.policies()),
pipeline,
topic_scope: otel_arrow_dfe_config::topic::TopicScope::GLOBAL,
role: ResolvedPipelineRole::Regular,
}],
};
pipeline_ctx
.set_compiled_context_bindings(factory.compile_initial_context(&resolved)?.bindings);
Ok(())
}
#[must_use]
pub fn test_extension_ctx() -> (ExtensionContext, TelemetryRegistryHandle) {
let registry = TelemetryRegistryHandle::new();
let controller = ControllerContext::new(registry.clone());
let scope = ExtensionScopeAttributeSet::pipeline(PipelineAttributeSet {
pipeline_group_id: "test_group".into(),
pipeline_id: "test_pipeline".into(),
..PipelineAttributeSet::default()
});
(ExtensionContext::new(controller, scope), registry)
}
#[cfg(any(test, feature = "test-utils"))]
#[must_use]
pub fn test_extension_effect_handler(
name: otel_arrow_dfe_config::ExtensionId,
) -> crate::extension::EffectHandler {
let (tx, _rx) = flume::bounded(1);
crate::extension::EffectHandler::new(
name,
otel_arrow_dfe_telemetry::reporter::MetricsReporter::new(tx),
None,
)
}
#[derive(Debug, PartialEq, Clone)]
pub struct TestMsg(pub String);
impl crate::ReceivedAtNode for TestMsg {
fn received_at_node(&mut self, _node_id: usize, _node_interests: Interests) {}
}
impl crate::processor::FlowMetricHook for TestMsg {}
impl TestMsg {
pub fn new<S: Into<String>>(content: S) -> Self {
TestMsg(content.into())
}
}
#[derive(Clone)]
pub struct CtrlMsgCounters {
timer_tick_count: Arc<AtomicUsize>,
message_count: Arc<AtomicUsize>,
config_count: Arc<AtomicUsize>,
shutdown_count: Arc<AtomicUsize>,
}
impl CtrlMsgCounters {
#[must_use]
pub fn new() -> Self {
CtrlMsgCounters {
timer_tick_count: Arc::new(AtomicUsize::new(0)),
message_count: Arc::new(AtomicUsize::new(0)),
config_count: Arc::new(AtomicUsize::new(0)),
shutdown_count: Arc::new(AtomicUsize::new(0)),
}
}
pub fn update_with<PData>(&self, msg: &NodeControlMsg<PData>) {
match msg {
NodeControlMsg::TimerTick { .. } => self.increment_timer_tick(),
NodeControlMsg::Config { .. } => self.increment_config(),
NodeControlMsg::Shutdown { .. } => self.increment_shutdown(),
_ => {}
}
}
pub fn increment_timer_tick(&self) {
_ = self
.timer_tick_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn increment_message(&self) {
_ = self
.message_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn increment_config(&self) {
_ = self
.config_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn increment_shutdown(&self) {
_ = self
.shutdown_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
#[must_use]
pub fn get_timer_tick_count(&self) -> usize {
self.timer_tick_count
.load(std::sync::atomic::Ordering::Relaxed)
}
#[must_use]
pub fn get_message_count(&self) -> usize {
self.message_count
.load(std::sync::atomic::Ordering::Relaxed)
}
#[must_use]
pub fn get_config_count(&self) -> usize {
self.config_count.load(std::sync::atomic::Ordering::Relaxed)
}
#[must_use]
pub fn get_shutdown_count(&self) -> usize {
self.shutdown_count
.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn assert(
&self,
timer_tick_count: usize,
message_count: usize,
config_count: usize,
shutdown_count: usize,
) {
assert_eq!(
self.get_timer_tick_count(),
timer_tick_count,
"Timer tick count mismatch"
);
assert_eq!(
self.get_message_count(),
message_count,
"Message count mismatch"
);
assert_eq!(
self.get_config_count(),
config_count,
"Config count mismatch"
);
assert_eq!(
self.get_shutdown_count(),
shutdown_count,
"Shutdown count mismatch"
);
}
}
impl Default for CtrlMsgCounters {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn setup_test_runtime() -> (tokio::runtime::Runtime, LocalSet) {
if tokio::runtime::Handle::try_current().is_ok() {
panic!(
"TestRuntime used inside an existing async runtime: change #[tokio::test] to #[test]."
);
}
let rt = Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create new runtime");
let local_tasks = LocalSet::new();
(rt, local_tasks)
}
#[must_use]
pub fn create_not_send_channel<T>(capacity: usize) -> (mpsc::Sender<T>, mpsc::Receiver<T>) {
mpsc::Channel::new(capacity)
}