Files
core/test/rust/native/application/job.rs
T
2026-11-03 03:32:07 +00:00

106 lines
3.8 KiB
Rust

use super::http::Server;
use super::*;
use std::time::{Duration, Instant};
#[cfg(feature = "postgres")]
#[path = "job/heartbeat.rs"]
mod heartbeat;
#[test]
fn dever_jobs_persist_execute_and_rollback_with_bounded_retries() {
let directory = Directory::new();
verify_queue(
&directory,
serde_json::json!({"type":"sqlite","path":"data/jobs.db","max_connections":4}),
);
}
#[cfg(feature = "postgres")]
#[test]
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
fn postgres_dever_jobs_persist_execute_and_rollback_with_bounded_retries() {
let directory = Directory::new();
let schema = super::database::postgres::Schema::new(
&directory.0,
"dever_native_jobs",
"CREATE SCHEMA dever_native_jobs",
"DROP SCHEMA dever_native_jobs CASCADE",
);
let mut connection = schema.connection.clone();
connection["max_connections"] = 4.into();
verify_queue(&directory, connection);
}
// 两种驱动运行同一 Dever 源码,包含 worker 重启、失败回滚和活跃键幂等。
fn verify_queue(directory: &Directory, connection: serde_json::Value) {
let project = Project::load(&fixture().with_file_name("job")).unwrap();
let program = project.check().unwrap();
assert_eq!(program.jobs.len(), 1);
let binary = build::build(
&program,
&project.sources,
"main",
&directory.0.join("jobs"),
)
.unwrap();
std::fs::create_dir(directory.0.join("config")).unwrap();
std::fs::write(directory.0.join("config/setting.json"), serde_json::to_vec(&serde_json::json!({"database":{"default":connection},"job":{"workers":2,"poll_ms":10,"lease_ms":2000,"retry_base_ms":10,"retry_max_ms":100},"runtime":{"shutdown_ms":1000}})).unwrap()).unwrap();
let run = |name: &str, input: &str| {
let result = execute(&binary, &format!("demo.queue.{name}"), input);
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
serde_json::from_slice::<serde_json::Value>(&result.stdout).unwrap()["data"].clone()
};
let first = run("submit", r#"{"name":"first","key":"one"}"#);
assert_eq!(first, run("submit", r#"{"name":"ignored","key":"one"}"#));
run("submit", r#"{"name":"fail","key":"bad"}"#);
assert!(
!execute(
&binary,
"demo.queue.rollback",
r#"{"name":"rolled back","key":"rollback"}"#
)
.status
.success()
);
assert_eq!(run("count", "{}"), 0);
let mut worker = Server(Command::new(&binary).spawn().unwrap());
let deadline = Instant::now() + Duration::from_secs(10);
loop {
assert!(worker.0.try_wait().unwrap().is_none(), "worker exited");
if run("count", "{}") == 1 {
break;
}
assert!(
Instant::now() < deadline,
"worker did not execute queued Dever code"
);
std::thread::sleep(Duration::from_millis(20));
}
std::thread::sleep(Duration::from_millis(150));
assert_eq!(run("count", "{}"), 1);
assert!(
Command::new("sh")
.args(["-c", "kill -TERM \"$1\"", "--", &worker.0.id().to_string()])
.status()
.unwrap()
.success()
);
assert!(worker.0.wait().unwrap().success());
let next = run("submit", r#"{"name":"second","key":"one"}"#);
assert_ne!(first, next);
let mut worker = Server(Command::new(&binary).spawn().unwrap());
let deadline = Instant::now() + Duration::from_secs(10);
while run("count", "{}") != 2 {
assert!(
worker.0.try_wait().unwrap().is_none(),
"restarted worker exited"
);
assert!(Instant::now() < deadline);
std::thread::sleep(Duration::from_millis(20));
}
}