use fs2::FileExt;
use serde_json::Value;
use std::{
net::TcpListener,
path::Path,
process::{Child, Command, Output, Stdio},
time::{Duration, Instant},
};
fn binary() -> std::path::PathBuf {
std::env::var_os("CYBER_TEST_BINARY")
.map(Into::into)
.unwrap_or_else(|| env!("CARGO_BIN_EXE_cyber").into())
}
fn cyber(home: &Path, args: &[&str]) -> Output {
Command::new(binary())
.arg("--home")
.arg(home)
.args(args)
.output()
.unwrap()
}
fn success(output: Output) -> String {
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap()
}
struct Server(Child);
impl Server {
fn start(home: &Path) -> Self {
Self(
Command::new(binary())
.arg("--home")
.arg(home)
.arg("node")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap(),
)
}
fn ready(&mut self, rpc: &str) {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
assert!(
self.0.try_wait().unwrap().is_none(),
"node exited before readiness"
);
if ureq::get(&format!("{rpc}/health"))
.timeout(Duration::from_millis(200))
.call()
.is_ok()
{
return;
}
assert!(Instant::now() < deadline, "node readiness timed out");
std::thread::sleep(Duration::from_millis(25));
}
}
}
impl Drop for Server {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
#[test]
fn cyb_wire_write_restart_and_exclusive_home() {
let home = tempfile::tempdir().unwrap();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let bind = listener.local_addr().unwrap().to_string();
drop(listener);
success(cyber(home.path(), &["init", "--bind", &bind]));
let descriptor: Value = serde_json::from_str(&success(cyber(home.path(), &["cyb"]))).unwrap();
let rpc = descriptor["rpc"].as_str().unwrap();
let mut server = Server::start(home.path());
server.ready(rpc);
let lock = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(home.path().join("node.lock"))
.unwrap();
assert!(lock.try_lock_exclusive().is_err());
let mut competing = Server::start(home.path());
let deadline = Instant::now() + Duration::from_secs(3);
loop {
if let Some(status) = competing.0.try_wait().unwrap() {
assert!(!status.success());
break;
}
assert!(
Instant::now() < deadline,
"second node accepted the same home"
);
std::thread::sleep(Duration::from_millis(25));
}
let receipt: Value = ureq::post(&format!("{rpc}/v1/link"))
.timeout(Duration::from_secs(5))
.send_json(serde_json::json!({"neuron":"alice", "from":"hello", "to":"cyber", "amount":1, "valence":0}))
.unwrap().into_json().unwrap();
assert_eq!(receipt["ok"], true);
assert_eq!(receipt["height"], 1);
let status: Value =
serde_json::from_str(&success(cyber(home.path(), &["status", "--json"]))).unwrap();
assert_eq!(status["height"], 1);
assert_eq!(status["bbg-root"], receipt["root"]);
let wire = success(cyber(home.path(), &["status"]));
assert!(wire.lines().any(|l| l == "height: 1"));
assert!(wire.contains(&format!("bbg-root: {}", receipt["root"].as_str().unwrap())));
for route in ["/blocks", "/block/1", "/stats", "/log"] {
assert!(ureq::get(&format!("{rpc}{route}"))
.timeout(Duration::from_secs(5))
.call()
.is_ok());
}
drop(server);
let mut restarted = Server::start(home.path());
restarted.ready(rpc);
let after: Value =
serde_json::from_str(&success(cyber(home.path(), &["status", "--json"]))).unwrap();
assert_eq!(after["height"], status["height"]);
assert_eq!(after["bbg-root"], status["bbg-root"]);
assert_eq!(after["signals"], status["signals"]);
}
#[test]
fn config_is_explicit_validated_and_never_overwritten() {
let home = tempfile::tempdir().unwrap();
assert!(!cyber(home.path(), &["node"]).status.success());
assert!(!cyber(home.path(), &["init", "--bind", "0.0.0.0:7780"])
.status
.success());
assert!(!home.path().join("config.toml").exists());
success(cyber(home.path(), &["init", "--moniker", "my node"]));
let original = std::fs::read(home.path().join("config.toml")).unwrap();
assert!(!cyber(home.path(), &["init"]).status.success());
assert_eq!(
std::fs::read(home.path().join("config.toml")).unwrap(),
original
);
assert!(!cyber(home.path(), &["init", "--bind"]).status.success());
assert!(success(cyber(home.path(), &["config"])).contains("my node"));
std::fs::write(home.path().join("config.toml"), "version=99").unwrap();
assert!(!cyber(home.path(), &["node"]).status.success());
}