#![cfg(unix)]
use futures::StreamExt;
use nix::{
sys::signal::{kill, Signal},
unistd::Pid,
};
use rusty_fork::{fork, rusty_fork_id, ChildWrapper};
use tokio::{
runtime::Runtime,
signal::unix::{signal, SignalKind},
};
use std::{
env,
fs::File,
io::{BufRead, BufReader, Seek, SeekFrom},
thread,
time::Duration,
};
pub mod common;
use crate::common::{run_nodes, Options};
fn check_child_start(child: &mut ChildWrapper) {
let maybe_status = child
.wait_timeout(Duration::from_secs(5))
.expect("Failed to wait for node to function");
if let Some(status) = maybe_status {
panic!(
"Node exited unexpectedly with this exit status: {:?}",
status
);
}
}
fn check_child_exit(child: &mut ChildWrapper, output: &mut File) {
let exit_status = child
.wait_timeout(Duration::from_secs(2))
.expect("Failed to wait for node exit")
.unwrap_or_else(|| {
child.kill().ok();
panic!("Node did not exit in 2 secs after being sent the signal");
});
assert!(
exit_status.success(),
"Node exited with unexpected status: {:?}",
exit_status
);
output.seek(SeekFrom::Start(0)).unwrap();
let reader = BufReader::new(&*output);
for line_res in reader.lines() {
if let Ok(line) = line_res {
if line.contains("Shutting down node handler") {
return;
}
}
}
panic!("Node did not shut down properly");
}
fn check_child(child: &mut ChildWrapper, output: &mut File, signal: Signal) {
check_child_start(child);
let pid = Pid::from_raw(child.id() as i32);
kill(pid, signal).unwrap();
check_child_exit(child, output);
}
async fn start_node(start_port: u16, with_http: bool) {
env::set_var("RUST_LOG", "exonum_node=info");
exonum::helpers::init_logger().ok();
let mut options = Options::default();
if with_http {
options.http_start_port = Some(start_port + 1);
}
let (mut nodes, _) = run_nodes(1, start_port, options);
let node = nodes.pop().unwrap();
node.run().await
}
fn check_child_with_custom_handler(
child: &mut ChildWrapper,
output: &mut File,
http_port: Option<u16>,
) {
check_child_start(child);
let pid = Pid::from_raw(child.id() as i32);
for _ in 0..2 {
kill(pid, Signal::SIGTERM).unwrap();
thread::sleep(Duration::from_secs(1));
let maybe_status = child.try_wait().expect("Failed to query child exit status");
if let Some(status) = maybe_status {
panic!(
"Node exited unexpectedly with this exit status: {:?}",
status
);
}
}
if let Some(http_port) = http_port {
let url = format!(
"http://127.0.0.1:{}/api/runtimes/rust/proto-sources?type=core",
http_port
);
let response = reqwest::blocking::get(&url).unwrap();
assert!(response.status().is_success(), "{:?}", response);
}
kill(pid, Signal::SIGTERM).unwrap();
check_child_exit(child, output);
}
async fn start_node_without_signals(start_port: u16, with_http: bool) {
let mut options = Options::default();
options.disable_signals = true;
if with_http {
options.http_start_port = Some(start_port + 1);
}
let (mut nodes, _) = run_nodes(1, start_port, options);
let node = nodes.pop().unwrap();
let mut signal = signal(SignalKind::terminate()).unwrap().skip(2);
let shutdown_handle = node.shutdown_handle();
tokio::spawn(async move {
signal.next().await;
println!("Shutting down node handler");
shutdown_handle.shutdown().await.unwrap();
});
node.run().await
}
#[test]
fn interrupt_node_without_http() {
fork(
"interrupt_node_without_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child(child, output, Signal::SIGINT),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node(16_450, false));
},
)
.unwrap();
}
#[test]
fn interrupt_node_with_http() {
fork(
"interrupt_node_with_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child(child, output, Signal::SIGINT),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node(16_460, true));
},
)
.unwrap();
}
#[test]
fn terminate_node_without_http() {
fork(
"terminate_node_without_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child(child, output, Signal::SIGTERM),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node(16_470, false));
},
)
.unwrap();
}
#[test]
fn terminate_node_with_http() {
fork(
"terminate_node_with_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child(child, output, Signal::SIGTERM),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node(16_480, true));
},
)
.unwrap();
}
#[test]
fn quit_node_without_http() {
fork(
"quit_node_without_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child(child, output, Signal::SIGQUIT),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node(16_490, false));
},
)
.unwrap();
}
#[test]
fn quit_node_with_http() {
fork(
"quit_node_with_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child(child, output, Signal::SIGQUIT),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node(16_500, true));
},
)
.unwrap();
}
#[test]
fn term_node_with_custom_handling_and_http() {
fork(
"term_node_with_custom_handling_and_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child_with_custom_handler(child, output, Some(16_511)),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node_without_signals(16_510, true));
},
)
.unwrap();
}
#[test]
fn term_node_with_custom_handling_and_no_http() {
fork(
"term_node_with_custom_handling_and_no_http",
rusty_fork_id!(),
|_| {},
|child, output| check_child_with_custom_handler(child, output, None),
|| {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(start_node_without_signals(16_520, true));
},
)
.unwrap();
}