From fae597ad9d2f06bd835ce0434e8ec9bfc65265e3 Mon Sep 17 00:00:00 2001
From: Valentin Syrovatskiy <v.syrovatskiy@picodata.io>
Date: Tue, 26 Jul 2022 11:33:58 +0300
Subject: [PATCH] test: randomized testing

---
 test/conftest.py             | 11 ++++++
 test/rand/test_randomized.py | 65 ++++++++++++++++++++++++------------
 2 files changed, 54 insertions(+), 22 deletions(-)

diff --git a/test/conftest.py b/test/conftest.py
index ac1dda25ef..90945ac8a2 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -35,6 +35,12 @@ def pytest_addoption(parser):
     parser.addoption(
         "--seed", action="store", default=None, help="Seed for randomized tests"
     )
+    parser.addoption(
+        "--delay",
+        action="store",
+        default=None,
+        help="Delay between steps for fandomized tests",
+    )
 
 
 @pytest.fixture(scope="session")
@@ -42,6 +48,11 @@ def seed(pytestconfig):
     return pytestconfig.getoption("seed")
 
 
+@pytest.fixture(scope="session")
+def delay(pytestconfig):
+    return pytestconfig.getoption("delay")
+
+
 @pytest.fixture(scope="session")
 def xdist_worker_number(worker_id: str) -> int:
     """
diff --git a/test/rand/test_randomized.py b/test/rand/test_randomized.py
index 546415ed59..f6a9ad0f5d 100644
--- a/test/rand/test_randomized.py
+++ b/test/rand/test_randomized.py
@@ -1,12 +1,14 @@
-import sys
+import math
 import time
 import random
 import os
-import signal
-from conftest import Cluster, Instance, ReturnError
+from conftest import Cluster, Instance
 import pathlib
 
 
+SEED_CAP = 14  # 19 max
+
+
 def int_to_base_62(num: int):
     base_62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
     BASE = len(base_62)
@@ -24,10 +26,11 @@ def create(c: Cluster, istate):
 
 
 def stop(i: Instance, istate):
-    assert i.process, f"Instance {i.instance_id} expected has process"
-    assert i.process.pid, f"Instance {i.instance_id} expected process to be alive"
+    # assert i.process, f"Instance {i.instance_id} expected has process"
+    # assert i.process.pid, f"Instance {i.instance_id} expected process to be alive"
     istate[i.instance_id]["started"] = False
-    return os.kill(i.process.pid, signal.SIGTERM), istate
+    return i.terminate(), istate
+    # return os.kill(i.process.pid, signal.SIGTERM), istate
 
 
 def start(i: Instance, istate):
@@ -64,14 +67,22 @@ SEEDLOG = "seeds.txt"
 def possible_actions(c: Cluster, istate):
     actions = []
     actions.append([ADD, None])
+    stopping_allowed = stop_allowed(istate)
     for i in c.instances:
-        if istate[i.instance_id]["started"]:
-            actions.append([STOP, i])
-        else:
-            actions.append([START, i])
+        if istate[i.instance_id]["started"] and i.process is not None:
+            if stopping_allowed:
+                actions.append([STOP, i])  # type: ignore
+        elif not istate[i.instance_id]["started"] and i.process is None:
+            actions.append([START, i])  # type: ignore
     return actions
 
 
+def stop_allowed(istate):
+    return len(list(filter(lambda i: i[1]["started"], istate.items()))) > math.trunc(
+        len(istate) / 2 + 1
+    )
+
+
 def choose_action(c: Cluster, istate):
     actions = possible_actions(c, istate)
     a = actions[random.randint(0, len(actions) - 1)]
@@ -83,32 +94,42 @@ def step_msg(step: int, action, i: Instance):
     return f"Step {step}: {msg}"
 
 
-def get_seed():
-    return int_to_base_62(time.time_ns())
+def generate_seed():
+    return int_to_base_62(time.time_ns() % pow(10, SEED_CAP))
 
 
-def log_seed(seed):
+def log_params(seed, delay):
     dir = pathlib.Path(__file__).parent.absolute()
     with open(os.path.join(dir, SEEDLOG), "a") as f:
-        f.write(seed + "\n")
+        f.write(f"{seed} {delay}\n")
+
+
+def initial_istate(cluster: Cluster):
+    istate = {}
+    for i in cluster.instances:
+        istate[i.instance_id] = {"instance": i, "started": True}
+    return istate
 
 
-def test_randomized(cluster: Cluster, seed: int, capsys):
+def test_randomized(cluster: Cluster, seed: int, delay: int, capsys):
     cluster.deploy(instance_count=3)
 
-    seed = seed if seed else get_seed()
-    log_seed(seed)
+    seed = seed if seed else generate_seed()
+
+    # delay should be generated by random even if given as CLI param
+    generated_delay = random.randint(100, 500)
+    delay = int(delay) if delay else generated_delay
+
+    log_params(seed, delay)
     with capsys.disabled():
-        print(f"Seed: {seed}")
+        print(f"Seed: {seed} , step delay: {delay} ms")
 
     random.seed(seed)
 
     steps_count = random.randint(5, 10)
     print(f"Do {steps_count} steps...")
 
-    istate = {}
-    for i in cluster.instances:
-        istate[i.instance_id] = {"instance": i, "started": True}
+    istate = initial_istate(cluster)
 
     for step in range(steps_count):
         a, i = choose_action(cluster, istate)
@@ -116,7 +137,7 @@ def test_randomized(cluster: Cluster, seed: int, capsys):
             i, istate = a["pre_fn"](cluster, istate)
         print(step_msg(step + 1, a, i))
         _, istate = a["exec_fn"](i, istate)
-        time.sleep(0.1)
+        time.sleep(delay / 1000)
 
     for instance_id in istate:
         ist = istate[instance_id]
-- 
GitLab