use dora_node_api::{DoraNode, Event, IntoArrow};
use std::{
fs,
path::{Path, PathBuf},
process::Command,
thread,
time::{Duration, Instant},
};
const SENDER_DELAY: Duration = Duration::from_millis(1500);
const SENDER_LINGER: Duration = Duration::from_secs(4);
const RECEIVER_IDLE: Duration = Duration::from_secs(3);
const RECEIVER_DEADLINE: Duration = Duration::from_secs(5);
const TIMER_PERIOD_MS: u64 = 10;
const RUN_LENGTH: &str = "8s";
#[test]
#[ignore = "requires the dora CLI built from this checkout (see module docs)"]
fn slow_consumer_keeps_backpressure_commit() -> eyre::Result<()> {
let cli = cli_path()?;
let executable = std::env::current_exe()?;
for with_timer in [false, true] {
let root = std::env::temp_dir().join(format!("dora-pressure-{}", uuid::Uuid::now_v7()));
fs::create_dir_all(&root)?;
let result = root.join("received.txt");
let mut inputs = serde_json::json!({
"commit": {"source": "sender/commit", "queue_size": 4, "queue_policy": "backpressure"}
});
if with_timer {
inputs["tick"] = serde_json::json!(format!("dora/timer/millis/{TIMER_PERIOD_MS}"));
}
let graph = serde_json::json!({"nodes": [
{"id": "receiver", "path": executable, "args": "--ignored --exact receiver_node --nocapture",
"env": {"DORA_PRESSURE_RESULT": result}, "inputs": inputs},
{"id": "sender", "path": executable, "args": "--ignored --exact sender_node --nocapture", "outputs": ["commit"]}
]});
let graph_path = root.join("graph.yaml");
fs::write(&graph_path, serde_yaml::to_string(&graph)?)?;
let output = Command::new(&cli)
.args(["run", "--stop-after", RUN_LENGTH])
.arg(&graph_path)
.current_dir(&root)
.output()?;
fs::write(root.join("stdout.log"), &output.stdout)?;
fs::write(root.join("stderr.log"), &output.stderr)?;
let received =
fs::read_to_string(&result).unwrap_or_else(|err| format!("<no result file: {err}>"));
if !output.status.success() || received != "received" {
eyre::bail!(
"with_timer={with_timer}: dora run exited with {:?}, receiver reported \
{received:?}; evidence kept at {}",
output.status,
root.display()
);
}
let _ = fs::remove_dir_all(&root);
}
Ok(())
}
fn cli_path() -> eyre::Result<PathBuf> {
if let Some(cli) = std::env::var_os("DORA_BACKPRESSURE_TEST_CLI") {
return Ok(cli.into());
}
let target_root = std::env::var_os("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../target"));
let cli = target_root
.join("debug")
.join(format!("dora{}", std::env::consts::EXE_SUFFIX));
eyre::ensure!(
cli.exists(),
"no dora CLI at {}; run `cargo build -p dora-cli` or set DORA_BACKPRESSURE_TEST_CLI",
cli.display()
);
Ok(cli)
}
fn launched_as_node(name: &str) -> bool {
if std::env::var_os("DORA_NODE_CONFIG").is_some() {
return true;
}
eprintln!("{name}: not launched as a dataflow node; nothing to do");
false
}
#[test]
#[ignore = "helper process launched as a dataflow node"]
fn sender_node() -> eyre::Result<()> {
if !launched_as_node("sender_node") {
return Ok(());
}
let (mut node, _events) = DoraNode::init_from_env()?;
thread::sleep(SENDER_DELAY);
node.send_output(
"commit".into(),
Default::default(),
vec![1_u64].into_arrow(),
)?;
thread::sleep(SENDER_LINGER);
Ok(())
}
#[test]
#[ignore = "helper process launched as a dataflow node"]
fn receiver_node() -> eyre::Result<()> {
if !launched_as_node("receiver_node") {
return Ok(());
}
let (_node, mut events) = DoraNode::init_from_env()?;
let started = Instant::now();
thread::sleep(RECEIVER_IDLE);
let mut received = false;
while let Some(event) = events.recv() {
if matches!(event, Event::Input { ref id, .. } if id.as_str() == "commit") {
received = true;
break;
}
if started.elapsed() > RECEIVER_DEADLINE {
break;
}
}
fs::write(
std::env::var("DORA_PRESSURE_RESULT")?,
if received { "received" } else { "lost" },
)?;
Ok(())
}