use std::process::Command;
use std::time::Duration;
use chrono::Utc;
use netsky_db::Db;
const CHILD_ENV: &str = "NETSKY_DB_CONCURRENT_CHILD";
const PATH_ENV: &str = "NETSKY_DB_CONCURRENT_PATH";
const WRITES_ENV: &str = "NETSKY_DB_CONCURRENT_WRITES";
#[test]
fn eight_process_cli_writes_have_zero_lock_errors() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("meta.db");
let db = Db::open_path(&path).expect("open");
db.migrate().expect("migrate");
let exe = std::env::current_exe().expect("current exe");
let procs = 8;
let writes = 4;
let mut children = Vec::new();
for writer in 0..procs {
children.push(
Command::new(&exe)
.arg("--exact")
.arg("concurrent_child")
.arg("--nocapture")
.env(CHILD_ENV, writer.to_string())
.env(PATH_ENV, &path)
.env(WRITES_ENV, writes.to_string())
.output()
.expect("spawn child"),
);
}
for (idx, output) in children.into_iter().enumerate() {
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success(), "child {idx} failed: {stderr}");
assert!(
!stderr.contains("Database already open") && !stderr.contains("database is locked"),
"child {idx} lock stderr: {stderr}"
);
}
let db = Db::open_path(&path).expect("reopen");
let out = db
.query("SELECT COUNT(*) AS count FROM cli_invocations")
.expect("query count");
assert!(out.contains(&(procs * writes).to_string()), "{out}");
}
#[test]
fn concurrent_child() {
let Ok(writer) = std::env::var(CHILD_ENV) else {
return;
};
let path = std::env::var(PATH_ENV).expect("path env");
let writes = std::env::var(WRITES_ENV)
.expect("writes env")
.parse::<usize>()
.expect("writes");
let db = Db::open_path(path).expect("open child db");
db.migrate().expect("migrate child db");
for i in 0..writes {
let argv = format!(r#"["writer-{writer}", "{i}"]"#);
db.record_cli(
Utc::now(),
"netsky",
&argv,
Some(0),
Some(Duration::from_millis(1).as_millis() as i64),
"concurrent-test",
)
.expect("record cli");
}
}