Repository: Migorithm/duva
Pull request #498: Integraton with tokio process, child instead of std
Author: login: DPS0340
Labels: name: testability
Created: 2025-04-25T04:39:40Z | Merged: 2025-04-27T11:53:04Z

# Explanation

This PR implements below:

- Fully integration with `tokio::process::{Child, Command}` and async tests



Diff:
diff --git a/duva/tests/client_ops/test_config_get_dir.rs b/duva/tests/client_ops/test_config_get_dir.rs
index d31bcec44..67f0a5434 100644
--- a/duva/tests/client_ops/test_config_get_dir.rs
+++ b/duva/tests/client_ops/test_config_get_dir.rs
@@ -7,17 +7,19 @@ use tokio::time::sleep;
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_config_get_dir() {
+async fn test_config_get_dir() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     sleep(Duration::from_millis(500)).await;
     let mut h = Client::new(process.port);
 
     // WHEN
-    let res = h.send_and_get("CONFIG get dir", 1);
+    let res = h.send_and_get("CONFIG get dir", 1).await;
 
     // THEN
-    assert_eq!(res.first().unwrap(), "dir .")
+    assert_eq!(res.first().unwrap(), "dir .");
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_decr.rs b/duva/tests/client_ops/test_decr.rs
index 9fb5ed68a..794722afb 100644
--- a/duva/tests/client_ops/test_decr.rs
+++ b/duva/tests/client_ops/test_decr.rs
@@ -1,37 +1,39 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
-#[test]
-fn test_decr() {
+#[tokio::test]
+async fn test_decr() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
 
     // WHEN
-    assert_eq!(h.send_and_get("DECR a", 1), vec!["(integer) -1"]);
-    assert_eq!(h.send_and_get("DECR a", 1), vec!["(integer) -2"]);
-    assert_eq!(h.send_and_get("DECR a", 1), vec!["(integer) -3"]);
+    assert_eq!(h.send_and_get("DECR a", 1).await, vec!["(integer) -1"]);
+    assert_eq!(h.send_and_get("DECR a", 1).await, vec!["(integer) -2"]);
+    assert_eq!(h.send_and_get("DECR a", 1).await, vec!["(integer) -3"]);
 
     // THEN
-    assert_eq!(h.send_and_get("GET a", 1), vec!["-3"]);
-    assert_eq!(h.send_and_get("INCR b", 1), vec!["(integer) 1"]);
-    assert_eq!(h.send_and_get("DECR b", 1), vec!["(integer) 0"]);
+    assert_eq!(h.send_and_get("GET a", 1).await, vec!["-3"]);
+    assert_eq!(h.send_and_get("INCR b", 1).await, vec!["(integer) 1"]);
+    assert_eq!(h.send_and_get("DECR b", 1).await, vec!["(integer) 0"]);
 
     // WHEN
-    assert_eq!(h.send_and_get("SET c adsds", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET c adsds", 1).await, vec!["OK"]);
 
     // THEN
     assert_eq!(
-        h.send_and_get("DECR c", 1),
+        h.send_and_get("DECR c", 1).await,
         vec!["(error) ERR value is not an integer or out of range"]
     );
 
     // WHEN - out of range
-    assert_eq!(h.send_and_get("SET d 92233720368547332375808", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET d 92233720368547332375808", 1).await, vec!["OK"]);
     // THEN
     assert_eq!(
-        h.send_and_get("DECR d", 1),
+        h.send_and_get("DECR d", 1).await,
         vec!["(error) ERR value is not an integer or out of range"]
     );
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_del.rs b/duva/tests/client_ops/test_del.rs
index 6e389a3ff..a580afa34 100644
--- a/duva/tests/client_ops/test_del.rs
+++ b/duva/tests/client_ops/test_del.rs
@@ -3,22 +3,25 @@
 /// Then we get the key and check if the value is returned
 /// After 300ms, we get the key again and check if the value is not returned (-1)
 use crate::common::{Client, ServerEnv, spawn_server_process};
+
 #[tokio::test]
-async fn test_del() {
+async fn test_del() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
-    assert_eq!(h.send_and_get("SET a b", 1), vec!["OK"]);
-    assert_eq!(h.send_and_get("SET c d", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET a b", 1).await, vec!["OK"]);
+    assert_eq!(h.send_and_get("SET c d", 1).await, vec!["OK"]);
 
     // WHEN - set key with expiry
-    assert_eq!(h.send_and_get("del a c d", 1), vec!["(integer) 2"]);
+    assert_eq!(h.send_and_get("del a c d", 1).await, vec!["(integer) 2"]);
 
-    assert_eq!(h.send_and_get("get a", 1), vec!["(nil)"]);
+    assert_eq!(h.send_and_get("get a", 1).await, vec!["(nil)"]);
 
     // THEN
-    let res = h.send_and_get("GET somanyrand", 1);
+    let res = h.send_and_get("GET somanyrand", 1).await;
     assert_eq!(res, vec!["(nil)"]);
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_exists.rs b/duva/tests/client_ops/test_exists.rs
index a810cfbf0..e7b562371 100644
--- a/duva/tests/client_ops/test_exists.rs
+++ b/duva/tests/client_ops/test_exists.rs
@@ -3,19 +3,22 @@
 /// Then we get the key and check if the value is returned
 /// After 300ms, we get the key again and check if the value is not returned (-1)
 use crate::common::{Client, ServerEnv, spawn_server_process};
+
 #[tokio::test]
-async fn test_exists() {
+async fn test_exists() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
-    assert_eq!(h.send_and_get("SET a b", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET a b", 1).await, vec!["OK"]);
 
-    assert_eq!(h.send_and_get("SET c d", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET c d", 1).await, vec!["OK"]);
 
     // WHEN & THEN
-    assert_eq!(h.send_and_get("exists a c d", 1), vec!["(integer) 2"]);
+    assert_eq!(h.send_and_get("exists a c d", 1).await, vec!["(integer) 2"]);
+
+    assert_eq!(h.send_and_get("exists x", 1).await, vec!["(integer) 0"]);
 
-    assert_eq!(h.send_and_get("exists x", 1), vec!["(integer) 0"]);
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_incr.rs b/duva/tests/client_ops/test_incr.rs
index 522f51a24..650368a13 100644
--- a/duva/tests/client_ops/test_incr.rs
+++ b/duva/tests/client_ops/test_incr.rs
@@ -1,37 +1,39 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
-#[test]
-fn test_incr() {
+#[tokio::test]
+async fn test_incr() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
 
     // WHEN
-    assert_eq!(h.send_and_get("INCR a", 1), vec!["(integer) 1"]);
-    assert_eq!(h.send_and_get("INCR a", 1), vec!["(integer) 2"]);
-    assert_eq!(h.send_and_get("INCR a", 1), vec!["(integer) 3"]);
+    assert_eq!(h.send_and_get("INCR a", 1).await, vec!["(integer) 1"]);
+    assert_eq!(h.send_and_get("INCR a", 1).await, vec!["(integer) 2"]);
+    assert_eq!(h.send_and_get("INCR a", 1).await, vec!["(integer) 3"]);
 
     // THEN
-    assert_eq!(h.send_and_get("GET a", 1), vec!["3"]);
-    assert_eq!(h.send_and_get("INCR b", 1), vec!["(integer) 1"]);
-    assert_eq!(h.send_and_get("INCR b", 1), vec!["(integer) 2"]);
+    assert_eq!(h.send_and_get("GET a", 1).await, vec!["3"]);
+    assert_eq!(h.send_and_get("INCR b", 1).await, vec!["(integer) 1"]);
+    assert_eq!(h.send_and_get("INCR b", 1).await, vec!["(integer) 2"]);
 
     // WHEN
-    assert_eq!(h.send_and_get("SET c adsds", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET c adsds", 1).await, vec!["OK"]);
 
     // THEN
     assert_eq!(
-        h.send_and_get("INCR c", 1),
+        h.send_and_get("INCR c", 1).await,
         vec!["(error) ERR value is not an integer or out of range"]
     );
 
     // WHEN - out of range
-    assert_eq!(h.send_and_get("SET d 92233720368547332375808", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET d 92233720368547332375808", 1).await, vec!["OK"]);
     // THEN
     assert_eq!(
-        h.send_and_get("INCR d", 1),
+        h.send_and_get("INCR d", 1).await,
         vec!["(error) ERR value is not an integer or out of range"]
     );
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_keys.rs b/duva/tests/client_ops/test_keys.rs
index 8b94f0473..3f28a930b 100644
--- a/duva/tests/client_ops/test_keys.rs
+++ b/duva/tests/client_ops/test_keys.rs
@@ -1,10 +1,10 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_keys() {
+async fn test_keys() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
 
@@ -12,11 +12,13 @@ async fn test_keys() {
 
     // WHEN set 500 keys with the value `bar`.
     for key in 0..num_keys_to_store {
-        assert_eq!(h.send_and_get(format!("SET {} bar", key), 1), vec!["OK"]);
+        assert_eq!(h.send_and_get(format!("SET {} bar", key), 1).await, vec!["OK"]);
     }
 
     // Fire keys command
-    let res = h.send_and_get("KEYS *", 500);
+    let res = h.send_and_get("KEYS *", 500).await;
 
     assert!(res.len() >= num_keys_to_store as usize);
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_replication_info.rs b/duva/tests/client_ops/test_replication_info.rs
index d41a69663..da1bb0259 100644
--- a/duva/tests/client_ops/test_replication_info.rs
+++ b/duva/tests/client_ops/test_replication_info.rs
@@ -6,18 +6,20 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_replication_info() {
+async fn test_replication_info() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
     let mut h = Client::new(process.port);
 
     // WHEN
-    let res = h.send_and_get("INFO replication", 4);
+    let res = h.send_and_get("INFO replication", 4).await;
 
     // THEN
     assert_eq!(res[0], "role:leader");
     assert!(res[1].starts_with("leader_repl_id:"));
     assert_eq!(res[2], "high_watermark:0");
     assert_eq!(res[3], format!("self_identifier:127.0.0.1:{}", env.port));
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_set_get.rs b/duva/tests/client_ops/test_set_get.rs
index 9acf9e3d1..8d60b9b3e 100644
--- a/duva/tests/client_ops/test_set_get.rs
+++ b/duva/tests/client_ops/test_set_get.rs
@@ -5,21 +5,23 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_set_get() {
+async fn test_set_get() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
 
     // WHEN - set key with expiry
-    assert_eq!(h.send_and_get("SET somanyrand bar PX 300", 1), vec!["OK"]);
-    let res = h.send_and_get("GET somanyrand", 1);
+    assert_eq!(h.send_and_get("SET somanyrand bar PX 300", 1).await, vec!["OK"]);
+    let res = h.send_and_get("GET somanyrand", 1).await;
     assert_eq!(res, vec!["bar"]);
 
     tokio::time::sleep(tokio::time::Duration::from_millis(300)).await;
     // THEN
 
-    let res = h.send_and_get("GET somanyrand", 1);
+    let res = h.send_and_get("GET somanyrand", 1).await;
     assert_eq!(res, vec!["(nil)"]);
+
+    Ok(())
 }
diff --git a/duva/tests/client_ops/test_snapshot_persists_and_recovers_state.rs b/duva/tests/client_ops/test_snapshot_persists_and_recovers_state.rs
index 419133440..f8a83eaec 100644
--- a/duva/tests/client_ops/test_snapshot_persists_and_recovers_state.rs
+++ b/duva/tests/client_ops/test_snapshot_persists_and_recovers_state.rs
@@ -5,50 +5,52 @@ use std::time::UNIX_EPOCH;
 
 // TODO response cannot be deterministic!
 #[tokio::test]
-async fn test_snapshot_persists_and_recovers_state() {
+async fn test_snapshot_persists_and_recovers_state() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default().with_file_name(create_unique_file_name("test_save_dump"));
-    let leader_process = spawn_server_process(&env);
+    let mut leader_process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(leader_process.port);
 
     // WHEN
     // set without expiry time
-    let res = h.send_and_get("SET foo bar", 1);
+    let res = h.send_and_get("SET foo bar", 1).await;
     assert_eq!(res, vec!["OK"]);
 
     // set with expiry time
-    assert_eq!(h.send_and_get("SET foo2 bar2 PX 9999999999", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET foo2 bar2 PX 9999999999", 1).await, vec!["OK"]);
 
     // check keys
-    assert_eq!(h.send_and_get("KEYS *", 2), vec!["0) \"foo2\"", "1) \"foo\""]);
+    assert_eq!(h.send_and_get("KEYS *", 2).await, vec!["0) \"foo2\"", "1) \"foo\""]);
 
     // check replication info
-    let info = h.send_and_get("INFO replication", 4);
+    let info = h.send_and_get("INFO replication", 4).await;
 
     // WHEN
-    assert_eq!(h.send_and_get("SAVE", 1), vec!["(nil)"]);
+    assert_eq!(h.send_and_get("SAVE", 1).await, vec!["(nil)"]);
 
     // wait for the file to be created
     tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
     // kill leader process
-    drop(leader_process);
+    let _ = leader_process.terminate().await;
 
     // run server with the same file name
-    let new_process = spawn_server_process(&env);
+    let new_process = spawn_server_process(&env).await?;
 
     // wait for the server to start
     tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
 
     let mut client = Client::new(new_process.port);
 
-    assert_eq!(client.send_and_get("KEYS *", 2), vec!["0) \"foo2\"", "1) \"foo\""]);
+    assert_eq!(client.send_and_get("KEYS *", 2).await, vec!["0) \"foo2\"", "1) \"foo\""]);
 
     // replication info
-    let info2 = client.send_and_get("INFO replication", 4);
+    let info2 = client.send_and_get("INFO replication", 4).await;
 
     // THEN
     assert_eq!(info, info2);
+
+    Ok(())
 }
 
 fn create_unique_file_name(function_name: &str) -> String {
diff --git a/duva/tests/client_ops/test_ttl.rs b/duva/tests/client_ops/test_ttl.rs
index 701a2c200..fc46bae37 100644
--- a/duva/tests/client_ops/test_ttl.rs
+++ b/duva/tests/client_ops/test_ttl.rs
@@ -1,19 +1,21 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_ttl() {
+async fn test_ttl() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let process = spawn_server_process(&env);
+    let process = spawn_server_process(&env).await?;
 
     let mut h = Client::new(process.port);
 
     // WHEN - set key with expiry
-    assert_eq!(h.send_and_get("SET somanyrand bar PX 5000", 1), vec!["OK"]);
+    assert_eq!(h.send_and_get("SET somanyrand bar PX 5000", 1).await, vec!["OK"]);
     tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; // slight delay so seconds gets floored
-    let res = h.send_and_get("TTL somanyrand", 1);
+    let res = h.send_and_get("TTL somanyrand", 1).await;
 
     // THEN
     assert_eq!(res, vec!["(integer) 4"]);
-    assert_eq!(h.send_and_get("TTL non_existing_key", 1), vec!["(integer) -1"]);
+    assert_eq!(h.send_and_get("TTL non_existing_key", 1).await, vec!["(integer) -1"]);
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_cluster_forget_makes_all_nodes_forget_target_node.rs b/duva/tests/cluster_ops/test_cluster_forget_makes_all_nodes_forget_target_node.rs
index 5b8032fb6..de17f8a3b 100644
--- a/duva/tests/cluster_ops/test_cluster_forget_makes_all_nodes_forget_target_node.rs
+++ b/duva/tests/cluster_ops/test_cluster_forget_makes_all_nodes_forget_target_node.rs
@@ -3,42 +3,44 @@ use duva::domains::cluster_actors::heartbeats::scheduler::LEADER_HEARTBEAT_INTER
 use crate::common::{Client, ServerEnv, check_internodes_communication, spawn_server_process};
 
 #[tokio::test]
-async fn test_cluster_forget_makes_all_nodes_forget_target_node() {
+async fn test_cluster_forget_makes_all_nodes_forget_target_node() -> anyhow::Result<()> {
     // GIVEN
     const HOP_COUNT: usize = 0;
 
     let env = ServerEnv::default().with_ttl(500).with_hf(2);
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
 
     let repl_env =
         ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().clone()).with_hf(10);
-    let mut repl_p = spawn_server_process(&repl_env);
+    let mut repl_p = spawn_server_process(&repl_env).await?;
 
     let repl_env2 =
         ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().clone()).with_hf(10);
-    let mut repl_p2 = spawn_server_process(&repl_env2);
+    let mut repl_p2 = spawn_server_process(&repl_env2).await?;
 
     check_internodes_communication(
         &mut [&mut leader_p, &mut repl_p, &mut repl_p2],
         HOP_COUNT,
         1000,
     )
-    .unwrap();
+    .await?;
 
     // WHEN
     let mut client_handler = Client::new(leader_p.port);
     let replica_id = repl_p.bind_addr();
-    let response1 = client_handler.send_and_get(format!("cluster forget {}", &replica_id), 1);
+    let response1 = client_handler.send_and_get(format!("cluster forget {}", &replica_id), 1).await;
     assert_eq!(response1, vec!["OK"]);
 
     // THEN
-    let response2 = client_handler.send_and_get("cluster info", 1);
+    let response2 = client_handler.send_and_get("cluster info", 1).await;
     assert_eq!(response2.first().unwrap(), "cluster_known_nodes:1");
 
     let mut repl_cli = Client::new(repl_p2.port);
 
-    // ! time buffer for the heartbeat message to be sent
     tokio::time::sleep(std::time::Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX + 1)).await;
-    let response2 = repl_cli.send_and_get("cluster info", 1);
+
+    let response2 = repl_cli.send_and_get("cluster info", 1).await;
     assert_eq!(response2.first().unwrap(), "cluster_known_nodes:1");
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_cluster_forget_when_wrong_id_given.rs b/duva/tests/cluster_ops/test_cluster_forget_when_wrong_id_given.rs
index e5bacad0a..5a456a854 100644
--- a/duva/tests/cluster_ops/test_cluster_forget_when_wrong_id_given.rs
+++ b/duva/tests/cluster_ops/test_cluster_forget_when_wrong_id_given.rs
@@ -3,24 +3,26 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_cluster_forget_node_return_error_when_wrong_id_given() {
+async fn test_cluster_forget_node_return_error_when_wrong_id_given() -> anyhow::Result<()> {
     // GIVEN
 
     let env = ServerEnv::default();
-    let leader_p = spawn_server_process(&env);
+    let leader_p = spawn_server_process(&env).await?;
 
     // WHEN
     let mut client_handler = Client::new(leader_p.port);
     let replica_id = "localhost:19933";
     let cmd = format!("cluster forget {}", &replica_id);
-    let cluster_info = client_handler.send_and_get(&cmd, 1);
+    let cluster_info = client_handler.send_and_get(&cmd, 1).await;
 
     // THEN
     assert_eq!(cluster_info.first().unwrap(), "(error) No such peer");
 
     // WHEN
-    let cluster_info = client_handler.send_and_get("cluster info", 1);
+    let cluster_info = client_handler.send_and_get("cluster info", 1).await;
 
     // THEN
     assert_eq!(cluster_info.first().unwrap(), "cluster_known_nodes:0");
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_cluster_known_nodes_increase_when_new_replica_is_added.rs b/duva/tests/cluster_ops/test_cluster_known_nodes_increase_when_new_replica_is_added.rs
index 83dea00aa..57ca064a6 100644
--- a/duva/tests/cluster_ops/test_cluster_known_nodes_increase_when_new_replica_is_added.rs
+++ b/duva/tests/cluster_ops/test_cluster_known_nodes_increase_when_new_replica_is_added.rs
@@ -1,32 +1,32 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_cluster_topology_change_when_new_node_added() {
+async fn test_cluster_topology_change_when_new_node_added() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
 
     let cmd = "cluster info";
 
     let repl_env = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut repl_p = spawn_server_process(&repl_env);
-    repl_p.wait_for_message(&leader_p.heartbeat_msg(0), 1).unwrap();
-    leader_p.wait_for_message(&repl_p.heartbeat_msg(0), 1).unwrap();
+    let mut repl_p = spawn_server_process(&repl_env).await?;
+    repl_p.wait_for_message(&leader_p.heartbeat_msg(0)).await?;
+    leader_p.wait_for_message(&repl_p.heartbeat_msg(0)).await?;
 
     let mut client_handler = Client::new(leader_p.port);
-    let cluster_info = client_handler.send_and_get(cmd.as_bytes(), 1);
+    let cluster_info = client_handler.send_and_get(cmd.as_bytes(), 1).await;
     assert_eq!(cluster_info, vec!["cluster_known_nodes:1".to_string()]);
 
     // // WHEN -- new replica is added
     let repl_env2 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut new_repl_p = spawn_server_process(&repl_env2);
-    new_repl_p.wait_for_message(&leader_p.heartbeat_msg(0), 1).unwrap();
+    let mut new_repl_p = spawn_server_process(&repl_env2).await?;
+    new_repl_p.wait_for_message(&leader_p.heartbeat_msg(0)).await?;
 
     //THEN
-    let cluster_info = client_handler.send_and_get(cmd.as_bytes(), 1);
+    let cluster_info = client_handler.send_and_get(cmd.as_bytes(), 1).await;
     assert_eq!(cluster_info, vec!["cluster_known_nodes:2".to_string()]);
 
-    let nodes = client_handler.send_and_get("cluster nodes".as_bytes(), 3);
+    let nodes = client_handler.send_and_get("cluster nodes".as_bytes(), 3).await;
     assert_eq!(nodes.len(), 3);
 
     let mut leader_nodes = Vec::new();
@@ -39,4 +39,6 @@ async fn test_cluster_topology_change_when_new_node_added() {
     for node in leader_nodes {
         assert!(nodes.contains(&node));
     }
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_heartbeat.rs b/duva/tests/cluster_ops/test_heartbeat.rs
index b74c8353c..d767d0b7a 100644
--- a/duva/tests/cluster_ops/test_heartbeat.rs
+++ b/duva/tests/cluster_ops/test_heartbeat.rs
@@ -2,56 +2,65 @@
 //! Any interconnected system should have a heartbeat mechanism to ensure that the connection is still alive
 //! In this case, the server will send PING message to the follower and the follower will respond with PONG message
 use crate::common::{ServerEnv, check_internodes_communication, spawn_server_process};
+
 #[tokio::test]
-async fn test_heartbeat_hop_count_decreases_over_time() {
+async fn test_heartbeat_hop_count_decreases_over_time() -> anyhow::Result<()> {
     const DEFAULT_HOP_COUNT: usize = 0;
     const TIMEOUT_IN_MILLIS: u128 = 2000;
     // GIVEN
 
     let env = ServerEnv::default();
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
     let leader_bind_addr = leader_p.bind_addr().clone();
     let repl_env = ServerEnv::default().with_leader_bind_addr(leader_bind_addr.clone().into());
-    let mut follower_p1 = spawn_server_process(&repl_env);
+    let mut follower_p1 = spawn_server_process(&repl_env).await?;
 
     let repl_env2 = ServerEnv::default().with_leader_bind_addr(leader_bind_addr.clone().into());
-    let mut follower_p2 = spawn_server_process(&repl_env2);
+    let mut follower_p2 = spawn_server_process(&repl_env2).await?;
     let mut processes = vec![&mut leader_p, &mut follower_p1, &mut follower_p2];
 
-    check_internodes_communication(&mut processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).unwrap();
+    check_internodes_communication(&mut processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS)
+        .await
+        .unwrap();
 
     // WHEN run Third follower
     let repl_env3 = ServerEnv::default().with_leader_bind_addr(leader_bind_addr.clone().into());
-    let mut follower_p3 = spawn_server_process(&repl_env3);
+    let mut follower_p3 = spawn_server_process(&repl_env3).await?;
     processes.push(&mut follower_p3);
 
     // THEN - some of the followers will have hop_count 1 and some will have hop_count 0
     let res =
-        check_internodes_communication(&mut processes, DEFAULT_HOP_COUNT + 1, TIMEOUT_IN_MILLIS);
+        check_internodes_communication(&mut processes, DEFAULT_HOP_COUNT + 1, TIMEOUT_IN_MILLIS)
+            .await;
     assert!(res.is_ok());
+
+    Ok(())
 }
 
 #[tokio::test]
-async fn test_heartbeat_hop_count_starts_with_0() {
+async fn test_heartbeat_hop_count_starts_with_0() -> anyhow::Result<()> {
     const DEFAULT_HOP_COUNT: usize = 0;
     const TIMEOUT_IN_MILLIS: u128 = 2000;
 
     // GIVEN
     let env = ServerEnv::default();
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
 
     // WHEN
     let repl_env = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p1 = spawn_server_process(&repl_env);
+    let mut follower_p1 = spawn_server_process(&repl_env).await?;
     let repl_env2 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p2 = spawn_server_process(&repl_env2);
+    let mut follower_p2 = spawn_server_process(&repl_env2).await?;
 
     let processes = &mut [&mut leader_p, &mut follower_p1, &mut follower_p2];
     // THEN
 
-    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).unwrap();
+    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).await?;
 
     // no node should have hop_count 1
-    let res = check_internodes_communication(processes, DEFAULT_HOP_COUNT + 1, TIMEOUT_IN_MILLIS);
+    let res =
+        check_internodes_communication(processes, DEFAULT_HOP_COUNT + 1, TIMEOUT_IN_MILLIS).await;
     assert!(res.is_err());
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_lazy_discovery.rs b/duva/tests/cluster_ops/test_lazy_discovery.rs
index effe7632b..c5545317d 100644
--- a/duva/tests/cluster_ops/test_lazy_discovery.rs
+++ b/duva/tests/cluster_ops/test_lazy_discovery.rs
@@ -1,44 +1,54 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_lazy_discovery_of_leader() {
+async fn test_lazy_discovery_of_leader() -> anyhow::Result<()> {
     // GIVEN
     let target_env = ServerEnv::default();
-    let leader_p = spawn_server_process(&target_env);
+    let leader_p = spawn_server_process(&target_env).await?;
 
     let mut target_h = Client::new(leader_p.port);
 
-    target_h.send_and_get("SET key value".as_bytes(), 1);
-    target_h.send_and_get("SET key2 value2".as_bytes(), 1);
-    assert_eq!(target_h.send_and_get("KEYS *".as_bytes(), 2), vec!["0) \"key\"", "1) \"key2\""]);
+    target_h.send_and_get("SET key value".as_bytes(), 1).await;
+    target_h.send_and_get("SET key2 value2".as_bytes(), 1).await;
+    assert_eq!(
+        target_h.send_and_get("KEYS *".as_bytes(), 2).await,
+        vec!["0) \"key\"", "1) \"key2\""]
+    );
 
     let replica_env = ServerEnv::default();
-    let replica_p = spawn_server_process(&replica_env);
+    let replica_p = spawn_server_process(&replica_env).await?;
     let mut other_h = Client::new(replica_p.port);
 
-    other_h.send_and_get("SET other value".as_bytes(), 1);
-    other_h.send_and_get("SET other2 value2".as_bytes(), 1);
+    other_h.send_and_get("SET other value".as_bytes(), 1).await;
+    other_h.send_and_get("SET other2 value2".as_bytes(), 1).await;
 
-    let cluster_info = other_h.send_and_get("CLUSTER INFO".as_bytes(), 1);
+    let cluster_info = other_h.send_and_get("CLUSTER INFO".as_bytes(), 1).await;
     assert_eq!(cluster_info.first().unwrap(), "cluster_known_nodes:0");
 
     // TODO can be flaky?
-    assert_eq!(other_h.send_and_get("KEYS *".as_bytes(), 2), vec!["0) \"other2\"", "1) \"other\""]);
+    assert_eq!(
+        other_h.send_and_get("KEYS *".as_bytes(), 2).await,
+        vec!["0) \"other2\"", "1) \"other\""]
+    );
 
     // WHEN
     assert_eq!(
-        target_h.send_and_get(format!("REPLICAOF 127.0.0.1 {}", &replica_env.port).as_bytes(), 1),
+        target_h
+            .send_and_get(format!("REPLICAOF 127.0.0.1 {}", &replica_env.port).as_bytes(), 1)
+            .await,
         ["OK".to_string(),]
     );
 
     // THEN
     assert_eq!(
-        other_h.send_and_get("CLUSTER INFO".as_bytes(), 1),
+        other_h.send_and_get("CLUSTER INFO".as_bytes(), 1).await,
         vec!["cluster_known_nodes:1".to_string()]
     );
 
     assert_eq!(
-        target_h.send_and_get("KEYS *".as_bytes(), 2),
+        target_h.send_and_get("KEYS *".as_bytes(), 2).await,
         vec!["0) \"other2\"", "1) \"other\""]
     );
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_reconnection_on_reboot.rs b/duva/tests/cluster_ops/test_reconnection_on_reboot.rs
index 11ac4db40..a2f767c11 100644
--- a/duva/tests/cluster_ops/test_reconnection_on_reboot.rs
+++ b/duva/tests/cluster_ops/test_reconnection_on_reboot.rs
@@ -2,33 +2,35 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test() {
+async fn test_reconnection_on_reboot() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
 
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
 
     let repl_env = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut repl_p = spawn_server_process(&repl_env);
+    let mut repl_p = spawn_server_process(&repl_env).await?;
 
-    repl_p.wait_for_message(&leader_p.heartbeat_msg(0), 1).unwrap();
-    leader_p.wait_for_message(&repl_p.heartbeat_msg(0), 1).unwrap();
+    repl_p.wait_for_message(&leader_p.heartbeat_msg(0)).await?;
+    leader_p.wait_for_message(&repl_p.heartbeat_msg(0)).await?;
 
-    repl_p.kill().unwrap();
+    repl_p.kill().await?;
 
     // WHEN running repl without leader bind address
     let repl_env = ServerEnv::default().with_topology_path(repl_env.topology_path.0.clone());
-    let mut repl_p = spawn_server_process(&repl_env);
+    let mut repl_p = spawn_server_process(&repl_env).await?;
 
     //THEN
-    repl_p.wait_for_message(&leader_p.heartbeat_msg(0), 1).unwrap();
-    leader_p.wait_for_message(&repl_p.heartbeat_msg(0), 1).unwrap();
+    repl_p.wait_for_message(&leader_p.heartbeat_msg(0)).await?;
+    leader_p.wait_for_message(&repl_p.heartbeat_msg(0)).await?;
 
     let mut cli_to_follower = Client::new(repl_env.port);
-    let role = cli_to_follower.send_and_get("ROLE".as_bytes(), 1);
+    let role = cli_to_follower.send_and_get("ROLE".as_bytes(), 1).await;
     assert_eq!(role, vec!["follower".to_string()]);
 
     let mut cli_to_leader = Client::new(leader_p.port);
-    let role = cli_to_leader.send_and_get("ROLE".as_bytes(), 1);
+    let role = cli_to_leader.send_and_get("ROLE".as_bytes(), 1).await;
     assert_eq!(role, vec!["leader".to_string()]);
+
+    Ok(())
 }
diff --git a/duva/tests/cluster_ops/test_removes_node_when_heartbeat_is_not_received_for_certain_time.rs b/duva/tests/cluster_ops/test_removes_node_when_heartbeat_is_not_received_for_certain_time.rs
index b09864f56..b7a8f81f6 100644
--- a/duva/tests/cluster_ops/test_removes_node_when_heartbeat_is_not_received_for_certain_time.rs
+++ b/duva/tests/cluster_ops/test_removes_node_when_heartbeat_is_not_received_for_certain_time.rs
@@ -1,28 +1,30 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_removes_node_when_heartbeat_is_not_received_for_certain_time() {
+async fn test_removes_node_when_heartbeat_is_not_received_for_certain_time() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
 
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
 
     let repl_env = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut repl_p = spawn_server_process(&repl_env);
+    let mut repl_p = spawn_server_process(&repl_env).await?;
 
-    repl_p.wait_for_message(&leader_p.heartbeat_msg(0), 1).unwrap();
-    leader_p.wait_for_message(&repl_p.heartbeat_msg(0), 1).unwrap();
+    repl_p.wait_for_message(&leader_p.heartbeat_msg(0)).await?;
+    leader_p.wait_for_message(&repl_p.heartbeat_msg(0)).await?;
 
     let mut h = Client::new(leader_p.port);
     let cmd = "cluster info";
     let cluster_info = h.send_and_get(cmd, 1);
-    assert_eq!(cluster_info.first().unwrap(), "cluster_known_nodes:1");
+    assert_eq!(cluster_info.await.first().unwrap(), "cluster_known_nodes:1");
 
     // WHEN
-    repl_p.kill().unwrap();
+    repl_p.kill().await?;
     tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
     let cluster_info = h.send_and_get(cmd, 1);
 
     //THEN
-    assert_eq!(cluster_info.first().unwrap(), "cluster_known_nodes:0")
+    assert_eq!(cluster_info.await.first().unwrap(), "cluster_known_nodes:0");
+
+    Ok(())
 }
diff --git a/duva/tests/common.rs b/duva/tests/common.rs
index 68b7289c3..7630fc36a 100644
--- a/duva/tests/common.rs
+++ b/duva/tests/common.rs
@@ -3,11 +3,12 @@
 use bytes::Bytes;
 use duva::domains::query_parsers::query_io::QueryIO;
 use duva::make_smart_pointer;
-use std::io::{BufRead, BufReader, Read, Write};
 use std::net::TcpListener;
-use std::process::{Child, Command, Stdio};
-use std::thread::sleep;
-use std::time::{Duration, Instant};
+use std::process::Stdio;
+use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWriteExt, BufReader};
+use tokio::process::{Child, ChildStdout, Command};
+use tokio::time::sleep;
+use tokio::time::{Duration, Instant};
 use uuid::Uuid;
 
 pub struct ServerEnv {
@@ -96,25 +97,18 @@ impl Drop for TopologyPath {
     }
 }
 
-pub fn spawn_server_process(env: &ServerEnv) -> TestProcessChild {
+pub async fn spawn_server_process(env: &ServerEnv) -> anyhow::Result<TestProcessChild> {
     println!("Starting server on port {}", env.port);
     let mut process = run_server_process(&env);
 
     wait_for_message(
         process.process.stdout.as_mut().unwrap(),
         vec![format!("listening peer connection on 127.0.0.1:{}...", env.port + 10000).as_str()],
-        1,
-        Some(20000),
+        Some(10000),
     )
-    .unwrap();
+    .await?;
 
-    process
-}
-
-impl Drop for TestProcessChild {
-    fn drop(&mut self) {
-        let _ = self.terminate();
-    }
+    Ok(process)
 }
 
 impl TestProcessChild {
@@ -140,7 +134,12 @@ impl TestProcessChild {
     }
 
     /// Attempts to gracefully terminate the process, falling back to force kill if necessary
-    pub fn terminate(&mut self) -> std::io::Result<()> {
+    pub async fn terminate(&mut self) -> std::io::Result<()> {
+        if self.process.id().is_none() {
+            // Do nothing if already terminated
+            return Ok(());
+        }
+
         // First try graceful shutdown
         // Give the process some time to shutdown gracefully
         let timeout = Duration::from_secs(1);
@@ -149,32 +148,38 @@ impl TestProcessChild {
         while start.elapsed() < timeout {
             match self.process.try_wait()? {
                 Some(_) => return Ok(()),
-                None => sleep(Duration::from_millis(100)),
+                None => sleep(Duration::from_millis(100)).await,
             }
         }
 
         // Force kill if still running
-        self.process.kill()?;
-        self.process.wait()?;
+        self.process.kill().await?;
+        self.process.wait().await?;
 
         Ok(())
     }
 
-    pub fn wait_for_message(&mut self, target: &str, target_count: usize) -> anyhow::Result<()> {
+    pub async fn wait_for_message(&mut self, target: &str) -> anyhow::Result<()> {
         let read = self.process.stdout.as_mut().unwrap();
 
-        wait_for_message(read, vec![target], target_count, None)
+        wait_for_message(read, vec![target], None).await
     }
 
-    pub fn timed_wait_for_message(
+    pub async fn timed_wait_for_message(
         &mut self,
         target: Vec<&str>,
-        target_count: usize,
+
         wait_for: u128,
     ) -> anyhow::Result<()> {
         let read = self.process.stdout.as_mut().unwrap();
 
-        wait_for_message(read, target, target_count, Some(wait_for))
+        wait_for_message(read, target, Some(wait_for)).await
+    }
+}
+
+impl Drop for TestProcessChild {
+    fn drop(&mut self) {
+        let _ = self.terminate();
     }
 }
 
@@ -216,27 +221,30 @@ pub fn run_server_process(env: &ServerEnv) -> TestProcessChild {
     )
 }
 
-fn wait_for_message<T: Read>(
+async fn wait_for_message<T: AsyncRead + Unpin>(
     read: &mut T,
     mut target: Vec<&str>,
-    target_count: usize,
+
     timeout_in_millis: Option<u128>,
 ) -> anyhow::Result<()> {
     let internal_count = Instant::now();
     let mut buf = BufReader::new(read).lines();
-    let mut cnt = 1;
+    let mut cnt = target.len();
 
     let mut current_target = target.remove(0);
-    while let Some(Ok(line)) = buf.next() {
+    while let Some(line) = buf.next_line().await? {
         if line.starts_with(current_target) {
-            if cnt == target_count {
+            cnt -= 1;
+
+            if cnt == 0 {
                 if target.is_empty() {
-                    break;
+                    return Ok(());
+                } else {
+                    return Err(anyhow::anyhow!("Targets remain after target_count exhausted"));
                 }
-                current_target = target.remove(0);
-            } else {
-                cnt += 1;
             }
+
+            current_target = target.remove(0);
         }
 
         if let Some(timeout) = timeout_in_millis {
@@ -246,7 +254,7 @@ fn wait_for_message<T: Read>(
         }
     }
 
-    Ok(())
+    return Err(anyhow::anyhow!("Error was found until reading nextline"));
 }
 
 pub fn array(arr: Vec<&str>) -> Bytes {
@@ -263,7 +271,7 @@ pub fn session_request(request_id: u64, arr: Vec<&str>) -> Bytes {
 }
 
 /// Check if all processes can communicate with each other
-pub fn check_internodes_communication(
+pub async fn check_internodes_communication(
     processes: &mut [&mut TestProcessChild],
     hop_count: usize,
     time_out: u128,
@@ -285,7 +293,7 @@ pub fn check_internodes_communication(
 
         // Then wait for all messages
         for msg in messages {
-            processes[i].timed_wait_for_message(vec![&msg], 1, time_out)?;
+            processes[i].timed_wait_for_message(vec![&msg], time_out).await?;
         }
     }
     Ok(())
@@ -293,7 +301,7 @@ pub fn check_internodes_communication(
 
 pub struct Client {
     pub child: Child,
-    reader: Option<BufReader<std::process::ChildStdout>>,
+    reader: Option<BufReader<ChildStdout>>,
 }
 
 impl Client {
@@ -324,15 +332,15 @@ impl Client {
         }
     }
 
-    pub fn send(&mut self, command: &[u8]) -> anyhow::Result<()> {
+    pub async fn send(&mut self, command: &[u8]) -> anyhow::Result<()> {
         let stdin = self.child.stdin.as_mut().unwrap();
-        stdin.write_all(command)?;
-        stdin.write_all(b"\r\n")?;
-        stdin.flush()?;
+        stdin.write_all(command).await?;
+        stdin.write_all(b"\r\n").await?;
+        stdin.flush().await?;
         Ok(())
     }
 
-    pub fn read(&mut self) -> Result<String, ()> {
+    pub async fn read(&mut self) -> Result<String, ()> {
         // Initialize reader if it doesn't exist
         if self.reader.is_none() {
             self.reader = Some(BufReader::new(self.child.stdout.take().unwrap()));
@@ -340,27 +348,37 @@ impl Client {
 
         let reader = self.reader.as_mut().unwrap();
         let mut line = String::new();
-        reader.read_line(&mut line).map_err(|_| ())?;
+        reader.read_line(&mut line).await.map_err(|_| ())?;
         Ok(line.trim().to_string())
     }
 
-    pub fn send_and_get(&mut self, command: impl AsRef<[u8]>, mut cnt: u16) -> Vec<String> {
-        self.send(command.as_ref()).unwrap();
+    pub async fn send_and_get(&mut self, command: impl AsRef<[u8]>, mut cnt: u16) -> Vec<String> {
+        self.send(command.as_ref()).await.unwrap();
 
         let mut res = vec![];
         while cnt > 0 {
             cnt -= 1;
-            if let Ok(line) = self.read() {
+            if let Ok(line) = self.read().await {
                 res.push(line);
             }
         }
         res
     }
+
+    pub async fn terminate(&mut self) -> std::io::Result<()> {
+        if self.child.id().is_none() {
+            // Do nothing if already terminated
+            return Ok(());
+        }
+        let _ = self.child.kill().await?;
+        let _ = self.child.wait().await?;
+
+        Ok(())
+    }
 }
 
 impl Drop for Client {
     fn drop(&mut self) {
-        let _ = self.child.kill();
-        let _ = self.child.wait();
+        let _ = self.terminate();
     }
 }
diff --git a/duva/tests/replication_ops/test_leader_election.rs b/duva/tests/replication_ops/test_leader_election.rs
index 4b95cf914..de3c3da2e 100644
--- a/duva/tests/replication_ops/test_leader_election.rs
+++ b/duva/tests/replication_ops/test_leader_election.rs
@@ -1,120 +1,124 @@
-use std::{thread::sleep, time::Duration};
+use tokio::time::{Duration, sleep};
 
 use crate::common::{Client, ServerEnv, check_internodes_communication, spawn_server_process};
 use duva::domains::cluster_actors::heartbeats::scheduler::LEADER_HEARTBEAT_INTERVAL_MAX;
 
 #[tokio::test]
-async fn test_leader_election() {
+async fn test_leader_election() -> anyhow::Result<()> {
     // GIVEN
     let leaver_env = ServerEnv::default();
-    let mut leader_p = spawn_server_process(&leaver_env);
+    let mut leader_p = spawn_server_process(&leaver_env).await?;
 
     let follower_env1 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p1 = spawn_server_process(&follower_env1);
+    let mut follower_p1 = spawn_server_process(&follower_env1).await?;
 
     let follower_env2 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
 
-    let mut follower_p2 = spawn_server_process(&follower_env2);
+    let mut follower_p2 = spawn_server_process(&follower_env2).await?;
     const DEFAULT_HOP_COUNT: usize = 0;
     const TIMEOUT_IN_MILLIS: u128 = 2000;
     let processes = &mut [&mut leader_p, &mut follower_p1, &mut follower_p2];
-    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).unwrap();
+    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).await?;
 
     // WHEN
-    leader_p.kill().unwrap();
-    sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX));
+    leader_p.kill().await?;
+    sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX)).await;
 
     // THEN
     let mut flag = false;
     for f in [&follower_p1, &follower_p2] {
         let mut handler = Client::new(f.port);
-        let response1 = handler.send_and_get("info replication", 4);
+        let response1 = handler.send_and_get("info replication", 4).await;
         if response1.contains(&"role:leader".to_string()) {
             flag = true;
             break;
         }
     }
     assert!(flag, "No leader found after the first leader was killed");
+
+    Ok(())
 }
 
 // ! EDGE case : when last_log_term is not updated, after the election, first write operation succeeds but second one doesn't
 // ! This is because the leader doesn't have the last_log_term of the first write operation
 // ! This test is to see if the leader can set the value twice after the election
 #[tokio::test]
-async fn test_set_twice_after_election() {
+async fn test_set_twice_after_election() -> anyhow::Result<()> {
     // GIVEN
     let leaver_env = ServerEnv::default();
-    let mut leader_p = spawn_server_process(&leaver_env);
+    let mut leader_p = spawn_server_process(&leaver_env).await?;
 
     let follower_env1 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p1 = spawn_server_process(&follower_env1);
+    let mut follower_p1 = spawn_server_process(&follower_env1).await?;
 
     let follower_env2 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p2 = spawn_server_process(&follower_env2);
+    let mut follower_p2 = spawn_server_process(&follower_env2).await?;
     const DEFAULT_HOP_COUNT: usize = 0;
     const TIMEOUT_IN_MILLIS: u128 = 2000;
     let processes = &mut [&mut leader_p, &mut follower_p1, &mut follower_p2];
-    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).unwrap();
+    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).await?;
 
     // WHEN
-    leader_p.kill().unwrap();
-    sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX));
+    leader_p.kill().await?;
+    sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX)).await;
 
     let mut flag = false;
     for f in [&follower_p1, &follower_p2] {
         let mut handler = Client::new(f.port);
-        let res = handler.send_and_get("info replication", 4);
+        let res = handler.send_and_get("info replication", 4).await;
         if res.contains(&"role:leader".to_string()) {
             // THEN - one of the replicas should become the leader
-            assert_eq!(handler.send_and_get("set 1 2", 1).first().unwrap(), "OK");
-            assert_eq!(handler.send_and_get("set 2 3", 1).first().unwrap(), "OK");
+            assert_eq!(handler.send_and_get("set 1 2", 1).await.first().unwrap(), "OK");
+            assert_eq!(handler.send_and_get("set 2 3", 1).await.first().unwrap(), "OK");
 
             flag = true;
             break;
         }
     }
     assert!(flag, "No leader found after the first leader was killed");
+
+    Ok(())
 }
 
 /// following test is to see if election works even after the first election.
 #[tokio::test]
-async fn test_leader_election_twice() {
+async fn test_leader_election_twice() -> anyhow::Result<()> {
     // GIVEN
     let leaver_env = ServerEnv::default();
-    let mut leader_p = spawn_server_process(&leaver_env);
+    let mut leader_p = spawn_server_process(&leaver_env).await?;
 
     let follower_env1 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p1 = spawn_server_process(&follower_env1);
+    let mut follower_p1 = spawn_server_process(&follower_env1).await?;
 
     let follower_env2 = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
-    let mut follower_p2 = spawn_server_process(&follower_env2);
+    let mut follower_p2 = spawn_server_process(&follower_env2).await?;
     const DEFAULT_HOP_COUNT: usize = 0;
     const TIMEOUT_IN_MILLIS: u128 = 2000;
     let processes = &mut [&mut leader_p, &mut follower_p1, &mut follower_p2];
-    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).unwrap();
+    check_internodes_communication(processes, DEFAULT_HOP_COUNT, TIMEOUT_IN_MILLIS).await?;
 
     // !first leader is killed -> election happens
-    leader_p.kill().unwrap();
-    sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX));
+    leader_p.kill().await?;
+    sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX)).await;
 
     let mut processes = vec![];
 
     for mut f in [follower_p1, follower_p2] {
         let mut handler = Client::new(f.port);
-        let res = handler.send_and_get("info replication", 4);
+        let res = handler.send_and_get("info replication", 4).await;
         if !res.contains(&"role:leader".to_string()) {
             processes.push(f);
             continue;
         }
 
         let follower_env3 = ServerEnv::default().with_leader_bind_addr(f.bind_addr().into());
-        let new_process = spawn_server_process(&follower_env3);
-        sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX));
+        let new_process = spawn_server_process(&follower_env3).await?;
+        sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX)).await;
 
         // WHEN
         // ! second leader is killed -> election happens
-        f.kill().unwrap();
-        sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX));
+        f.kill().await?;
+        sleep(Duration::from_millis(LEADER_HEARTBEAT_INTERVAL_MAX)).await;
         processes.push(new_process);
     }
     assert_eq!(processes.len(), 2);
@@ -122,11 +126,13 @@ async fn test_leader_election_twice() {
     let mut flag = false;
     for f in processes.iter() {
         let mut handler = Client::new(f.port);
-        let res = handler.send_and_get("info replication", 4);
+        let res = handler.send_and_get("info replication", 4).await;
         if res.contains(&"role:leader".to_string()) {
             flag = true;
             break;
         }
     }
     assert!(flag, "No leader found after the second leader was killed");
+
+    Ok(())
 }
diff --git a/duva/tests/replication_ops/test_raft_happy_case.rs b/duva/tests/replication_ops/test_raft_happy_case.rs
index bb86c35fe..9848ffee9 100644
--- a/duva/tests/replication_ops/test_raft_happy_case.rs
+++ b/duva/tests/replication_ops/test_raft_happy_case.rs
@@ -1,47 +1,40 @@
 use crate::common::{Client, ServerEnv, spawn_server_process};
 
 #[tokio::test]
-async fn test_set_operation_reaches_to_all_replicas() {
+async fn test_set_operation_reaches_to_all_replicas() -> anyhow::Result<()> {
     // GIVEN
 
     let env = ServerEnv::default();
 
     // loads the leader/follower processes
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
     let mut client_handler = Client::new(leader_p.port);
 
     let repl_env = ServerEnv::default()
         .with_leader_bind_addr(leader_p.bind_addr().into())
         .with_file_name("follower_dbfilename");
 
-    let mut repl_p = spawn_server_process(&repl_env);
+    let mut repl_p = spawn_server_process(&repl_env).await?;
 
-    repl_p.wait_for_message(&leader_p.heartbeat_msg(0), 1).unwrap();
-    leader_p.wait_for_message(&repl_p.heartbeat_msg(0), 1).unwrap();
+    repl_p.wait_for_message(&leader_p.heartbeat_msg(0)).await?;
+    leader_p.wait_for_message(&repl_p.heartbeat_msg(0)).await?;
 
     // WHEN -- set operation is made
-    client_handler.send_and_get("SET foo bar", 1);
+    client_handler.send_and_get("SET foo bar", 1).await;
 
     //THEN - run the following together
-    let h = std::thread::spawn(move || {
-        repl_p.timed_wait_for_message(
-            vec![
-                "[INFO] Received log entry with log index up to 1",
-                "[INFO] Received commit offset 1",
-            ],
-            1,
-            2000,
-        )
-    });
-
-    let h2 = std::thread::spawn(move || {
-        leader_p.timed_wait_for_message(
-            vec!["[INFO] Received acks for log index num: 1", "[INFO] log 1 commited"],
-            1,
-            2000,
-        )
-    });
-
-    h.join().unwrap().unwrap();
-    h2.join().unwrap().unwrap();
+    let f1 = repl_p.timed_wait_for_message(
+        vec!["[INFO] Received log entry with log index up to 1", "[INFO] Received commit offset 1"],
+        2000,
+    );
+
+    let f2 = leader_p.timed_wait_for_message(
+        vec!["[INFO] Received acks for log index num: 1", "[INFO] log 1 commited"],
+        2000,
+    );
+
+    f1.await?;
+    f2.await?;
+
+    Ok(())
 }
diff --git a/duva/tests/replication_ops/test_sync.rs b/duva/tests/replication_ops/test_sync.rs
index d137fa1f5..f74e3d57b 100644
--- a/duva/tests/replication_ops/test_sync.rs
+++ b/duva/tests/replication_ops/test_sync.rs
@@ -1,22 +1,24 @@
 use crate::common::{Client, ServerEnv, check_internodes_communication, spawn_server_process};
 
 #[tokio::test]
-async fn test_full_sync_on_newly_added_replica() {
+async fn test_full_sync_on_newly_added_replica() -> anyhow::Result<()> {
     // GIVEN
     let env = ServerEnv::default();
     // Start the leader server as a child process
-    let mut leader_p = spawn_server_process(&env);
+    let mut leader_p = spawn_server_process(&env).await?;
     let mut h = Client::new(leader_p.port);
 
-    h.send_and_get("SET foo bar", 1);
+    h.send_and_get("SET foo bar", 1).await;
 
     // WHEN run replica
     let repl_env = ServerEnv::default().with_leader_bind_addr(leader_p.bind_addr().into());
 
-    let mut replica_process = spawn_server_process(&repl_env);
-    check_internodes_communication(&mut [&mut leader_p, &mut replica_process], 0, 1000).unwrap();
+    let mut replica_process = spawn_server_process(&repl_env).await?;
+    check_internodes_communication(&mut [&mut leader_p, &mut replica_process], 0, 1000).await?;
 
     // THEN
     let mut client_to_repl = Client::new(replica_process.port);
-    assert_eq!(client_to_repl.send_and_get("KEYS *", 1), vec!["0) \"foo\""]);
+    assert_eq!(client_to_repl.send_and_get("KEYS *", 1).await, vec!["0) \"foo\""]);
+
+    Ok(())
 }


Reviews:
user: login: Jeffr-K
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T04:44:13Z
user: login: Jeffr-K
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T04:44:33Z
user: login: DPS0340
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T05:21:31Z
user: login: Jeffr-K
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T06:00:29Z

Review comments:
diff_hunk: @@ -10,13 +10,13 @@ use crate::common::{Client, ServerEnv, spawn_server_process};
 async fn test_config_get_dir() {
     // GIVEN
path: duva/tests/client_ops/test_config_get_dir.rs
user: login: Jeffr-K
body: @DPS0340 이거 마커 같은걸로 지정하면 안됨?

```rust
macro_rules! given {
    ($desc:expr, $($body:tt)*) => {
        println!("GIVEN: {}", $desc);
        $($body)*
    };
}

macro_rules! when {
    ($desc:expr, $($body:tt)*) => {
        println!("WHEN: {}", $desc);
        $($body)*
    };
}

macro_rules! then {
    ($desc:expr, $($body:tt)*) => {
        println!("THEN: {}", $desc);
        $($body)*
    };
}
```
굳이,, 겠지만 그냥 한번 달아봄
created_at: 2025-04-25T04:44:13Z
author_association: NONE
diff_hunk: @@ -10,13 +10,13 @@ use crate::common::{Client, ServerEnv, spawn_server_process};
 async fn test_config_get_dir() {
     // GIVEN
path: duva/tests/client_ops/test_config_get_dir.rs
user: login: Jeffr-K
body: ```rust
#[tokio::test]
async fn test_config_get_dir() {
    given!("a server environment", {
        let env = ServerEnv::default();
        let process = spawn_server_process(&env).await;
        sleep(Duration::from_millis(500)).await;
        let mut h = Client::new(process.port);
    });
    
    when!("sending CONFIG get dir command", {
        let res = h.send_and_get("CONFIG get dir", 1).await;
    });
    
    then!("dir should be set to current directory", {
        assert_eq!(res.first().unwrap(), "dir .");
    });
}
```

이런식?
created_at: 2025-04-25T04:44:32Z
author_association: NONE
diff_hunk: @@ -10,13 +10,13 @@ use crate::common::{Client, ServerEnv, spawn_server_process};
 async fn test_config_get_dir() {
     // GIVEN
path: duva/tests/client_ops/test_config_get_dir.rs
user: login: DPS0340
body: @Jeffr-K 

For Handling BDD definitions, I found that [cocumber](https://cucumber-rs.github.io/cucumber/main/) provides procedural macros.
The key point of this library is separating BDD definitions (including feature, scenario) and tests (one-to-one corresponding on a given or when or then).
created_at: 2025-04-25T05:21:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,13 +10,13 @@ use crate::common::{Client, ServerEnv, spawn_server_process};
 async fn test_config_get_dir() {
     // GIVEN
path: duva/tests/client_ops/test_config_get_dir.rs
user: login: Jeffr-K
body: @DPS0340 

Based on the maintainer's reply in kakao chat, I think it would be appropriate to just leave this as a comment. The BDD style seems suitable for usage scenarios but doesn't appear to be a good specification for creating modules or libraries. The file maintainability requirements suggest that this wouldn't be a good implementation at the current time about using cocumber macros.
created_at: 2025-04-25T06:00:28Z
author_association: NONE

PR comments:
user: login: Migorithm
created_at: 2025-04-25T21:00:14Z
body: @DPS0340 

Thanks for taking this initiative to make the testing better!

I think this will help especially in resource-constrained situation as things will be run on tokio runtime in managed way 

One suggestion:

Make test signature return `anyhow::Result` and propagate err to make it easier to read

author_association: OWNER
user: login: DPS0340
created_at: 2025-04-27T05:34:44Z
body: @Migorithm 

I applied a mentioned suggestion into test codes. :)
author_association: CONTRIBUTOR