Skip to content

Commit 5e46ebd

Browse files
committed
refactor(side-dag): refactor node processes management
1 parent 53fa539 commit 5e46ebd

File tree

9 files changed

+443
-260
lines changed

9 files changed

+443
-260
lines changed

Makefile

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
py_sources = hathor/ tests/
1+
py_sources = hathor/ tests/ extras/custom_tests/
22

33
.PHONY: all
44
all: check tests
@@ -49,8 +49,12 @@ tests-genesis:
4949
tests-ci:
5050
pytest $(tests_ci)
5151

52+
.PHONY: tests-custom
53+
tests-custom:
54+
bash ./extras/custom_tests.sh
55+
5256
.PHONY: tests
53-
tests: tests-cli tests-lib tests-genesis tests-ci
57+
tests: tests-cli tests-lib tests-genesis tests-custom tests-ci
5458

5559
.PHONY: tests-full
5660
tests-full:
@@ -60,11 +64,11 @@ tests-full:
6064

6165
.PHONY: mypy
6266
mypy:
63-
mypy -p hathor -p tests
67+
mypy -p hathor -p tests -p extras.custom_tests
6468

6569
.PHONY: dmypy
6670
dmypy:
67-
dmypy run --timeout 86400 -- -p hathor -p tests
71+
dmypy run --timeout 86400 -- -p hathor -p tests -p extras.custom_tests
6872

6973
.PHONY: flake8
7074
flake8:

extras/custom_tests.sh

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
3+
# Define colors
4+
RED='\033[0;31m'
5+
GREEN='\033[0;32m'
6+
BLUE='\033[0;34m'
7+
NC='\033[0m' # No Color
8+
9+
TESTS_DIR="extras/custom_tests"
10+
11+
# List of test scripts to be executed
12+
tests=(
13+
/side_dag/test_one_fails.py
14+
/side_dag/test_both_fail.py
15+
)
16+
17+
# Initialize a variable to track if any test fails
18+
any_test_failed=0
19+
20+
# Loop over all tests
21+
for test in "${tests[@]}"; do
22+
echo -e "${BLUE}Testing $test${NC}"
23+
PYTHONPATH=$TESTS_DIR python $TESTS_DIR/$test
24+
result=$?
25+
if [ $result -ne 0 ]; then
26+
echo -e "${RED}Test $test FAILED${NC}"
27+
any_test_failed=1
28+
else
29+
echo -e "${GREEN}Test $test PASSED${NC}"
30+
fi
31+
done
32+
33+
# Exit with code 0 if no test failed, otherwise exit with code 1
34+
if [ $any_test_failed -eq 0 ]; then
35+
echo -e "${GREEN}All tests PASSED${NC}"
36+
exit 0
37+
else
38+
echo -e "${RED}Some tests FAILED${NC}"
39+
exit 1
40+
fi

extras/custom_tests/__init__.py

Whitespace-only changes.

extras/custom_tests/side_dag/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2024 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import shlex
17+
import signal
18+
import subprocess
19+
import sys
20+
21+
from utils import ( # type: ignore[import-not-found]
22+
COMMAND,
23+
HATHOR_PROCESS_NAME,
24+
KILL_WAIT_DELAY,
25+
MONITOR_PROCESS_NAME,
26+
SIDE_DAG_PROCESS_NAME,
27+
get_pid_by_name,
28+
is_alive,
29+
popen_is_alive,
30+
wait_seconds,
31+
)
32+
33+
if sys.platform == 'win32':
34+
print("test skipped on windows")
35+
sys.exit(0)
36+
37+
38+
def test_both_fail() -> None:
39+
# Assert that there are no existing processes
40+
assert get_pid_by_name(MONITOR_PROCESS_NAME) is None
41+
assert get_pid_by_name(HATHOR_PROCESS_NAME) is None
42+
assert get_pid_by_name(SIDE_DAG_PROCESS_NAME) is None
43+
44+
# Run the python command
45+
args = shlex.split(COMMAND)
46+
monitor_process = subprocess.Popen(args)
47+
print(f'running "run_node_with_side_dag" in the background with pid: {monitor_process.pid}')
48+
print('awaiting subprocesses initialization...')
49+
wait_seconds(5)
50+
51+
monitor_process_pid = get_pid_by_name(MONITOR_PROCESS_NAME)
52+
hathor_process_pid = get_pid_by_name(HATHOR_PROCESS_NAME)
53+
side_dag_process_pid = get_pid_by_name(SIDE_DAG_PROCESS_NAME)
54+
55+
# Assert that the processes exist and are alive
56+
assert monitor_process_pid == monitor_process.pid
57+
assert monitor_process_pid is not None
58+
assert hathor_process_pid is not None
59+
assert side_dag_process_pid is not None
60+
61+
assert is_alive(monitor_process_pid)
62+
assert is_alive(hathor_process_pid)
63+
assert is_alive(side_dag_process_pid)
64+
65+
print('processes are running:')
66+
print(f' "{MONITOR_PROCESS_NAME}" pid: {monitor_process_pid}')
67+
print(f' "{HATHOR_PROCESS_NAME}" pid: {hathor_process_pid}')
68+
print(f' "{SIDE_DAG_PROCESS_NAME}" pid: {side_dag_process_pid}')
69+
print('letting processes run for a while...')
70+
wait_seconds(10)
71+
72+
# Terminate both subprocess
73+
print('terminating subprocesses...')
74+
os.kill(hathor_process_pid, signal.SIGTERM)
75+
os.kill(side_dag_process_pid, signal.SIGTERM)
76+
print('awaiting processes termination...')
77+
wait_seconds(KILL_WAIT_DELAY, break_function=lambda: not popen_is_alive(monitor_process))
78+
79+
# Assert that all process are terminated
80+
assert not popen_is_alive(monitor_process)
81+
assert not is_alive(monitor_process_pid)
82+
assert not is_alive(hathor_process_pid)
83+
assert not is_alive(side_dag_process_pid)
84+
85+
print('all processes are dead. test succeeded!')
86+
87+
88+
try:
89+
test_both_fail()
90+
except Exception:
91+
if monitor_process_pid := get_pid_by_name(MONITOR_PROCESS_NAME):
92+
os.kill(monitor_process_pid, signal.SIGKILL)
93+
if hathor_process_pid := get_pid_by_name(HATHOR_PROCESS_NAME):
94+
os.kill(hathor_process_pid, signal.SIGKILL)
95+
if side_dag_process_pid := get_pid_by_name(SIDE_DAG_PROCESS_NAME):
96+
os.kill(side_dag_process_pid, signal.SIGKILL)
97+
98+
raise
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright 2024 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import shlex
17+
import signal
18+
import subprocess
19+
import sys
20+
21+
from utils import ( # type: ignore[import-not-found]
22+
COMMAND,
23+
HATHOR_PROCESS_NAME,
24+
KILL_WAIT_DELAY,
25+
MONITOR_PROCESS_NAME,
26+
SIDE_DAG_PROCESS_NAME,
27+
get_pid_by_name,
28+
is_alive,
29+
popen_is_alive,
30+
wait_seconds,
31+
)
32+
33+
if sys.platform == 'win32':
34+
print("test skipped on windows")
35+
sys.exit(0)
36+
37+
38+
def test_one_fails() -> None:
39+
# Assert that there are no existing processes
40+
assert get_pid_by_name(MONITOR_PROCESS_NAME) is None
41+
assert get_pid_by_name(HATHOR_PROCESS_NAME) is None
42+
assert get_pid_by_name(SIDE_DAG_PROCESS_NAME) is None
43+
44+
# Run the python command
45+
args = shlex.split(COMMAND)
46+
monitor_process = subprocess.Popen(args)
47+
print(f'running "run_node_with_side_dag" in the background with pid: {monitor_process.pid}')
48+
print('awaiting subprocesses initialization...')
49+
wait_seconds(5)
50+
51+
monitor_process_pid = get_pid_by_name(MONITOR_PROCESS_NAME)
52+
hathor_process_pid = get_pid_by_name(HATHOR_PROCESS_NAME)
53+
side_dag_process_pid = get_pid_by_name(SIDE_DAG_PROCESS_NAME)
54+
55+
# Assert that the processes exist and are alive
56+
assert monitor_process_pid == monitor_process.pid
57+
assert monitor_process_pid is not None
58+
assert hathor_process_pid is not None
59+
assert side_dag_process_pid is not None
60+
61+
assert is_alive(monitor_process_pid)
62+
assert is_alive(hathor_process_pid)
63+
assert is_alive(side_dag_process_pid)
64+
65+
print('processes are running:')
66+
print(f' "{MONITOR_PROCESS_NAME}" pid: {monitor_process_pid}')
67+
print(f' "{HATHOR_PROCESS_NAME}" pid: {hathor_process_pid}')
68+
print(f' "{SIDE_DAG_PROCESS_NAME}" pid: {side_dag_process_pid}')
69+
print('letting processes run for a while...')
70+
wait_seconds(10)
71+
72+
# Terminate one subprocess
73+
print('terminating side-dag process...')
74+
os.kill(side_dag_process_pid, signal.SIGTERM)
75+
print('awaiting process termination...')
76+
wait_seconds(KILL_WAIT_DELAY, break_function=lambda: not popen_is_alive(monitor_process))
77+
78+
# Assert that all process are terminated
79+
assert not popen_is_alive(monitor_process)
80+
assert not is_alive(monitor_process_pid)
81+
assert not is_alive(hathor_process_pid)
82+
assert not is_alive(side_dag_process_pid)
83+
84+
print('all processes are dead. test succeeded!')
85+
86+
87+
try:
88+
test_one_fails()
89+
except Exception:
90+
if monitor_process_pid := get_pid_by_name(MONITOR_PROCESS_NAME):
91+
os.kill(monitor_process_pid, signal.SIGKILL)
92+
if hathor_process_pid := get_pid_by_name(HATHOR_PROCESS_NAME):
93+
os.kill(hathor_process_pid, signal.SIGKILL)
94+
if side_dag_process_pid := get_pid_by_name(SIDE_DAG_PROCESS_NAME):
95+
os.kill(side_dag_process_pid, signal.SIGKILL)
96+
97+
raise

extras/custom_tests/side_dag/utils.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright 2024 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import subprocess
17+
import time
18+
from typing import Callable
19+
20+
MONITOR_PROCESS_NAME = 'hathor-core: monitor'
21+
PROCNAME_SUFFIX = 'hathor-core'
22+
HATHOR_PROCESS_PREFIX = 'hathor:'
23+
SIDE_DAG_PROCESS_PREFIX = 'side-dag:'
24+
HATHOR_PROCESS_NAME = HATHOR_PROCESS_PREFIX + PROCNAME_SUFFIX
25+
SIDE_DAG_PROCESS_NAME = SIDE_DAG_PROCESS_PREFIX + PROCNAME_SUFFIX
26+
KILL_WAIT_DELAY = 305
27+
28+
COMMAND = f"""
29+
python -m hathor run_node_with_side_dag
30+
--disable-logs
31+
--testnet
32+
--memory-storage
33+
--x-localhost-only
34+
--procname-prefix {HATHOR_PROCESS_PREFIX}
35+
--side-dag-testnet
36+
--side-dag-memory-storage
37+
--side-dag-x-localhost-only
38+
--side-dag-procname-prefix {SIDE_DAG_PROCESS_PREFIX}
39+
"""
40+
41+
42+
def wait_seconds(seconds: int, *, break_function: Callable[[], bool] | None = None) -> None:
43+
while seconds > 0:
44+
print(f'waiting {seconds} seconds...')
45+
time.sleep(1)
46+
seconds -= 1
47+
if break_function and break_function():
48+
break
49+
50+
51+
def get_pid_by_name(process_name: str) -> int | None:
52+
try:
53+
output = subprocess.check_output(['pgrep', '-f', process_name], text=True)
54+
except subprocess.CalledProcessError:
55+
return None
56+
pids = output.strip().split()
57+
assert len(pids) <= 1
58+
try:
59+
return int(pids[0])
60+
except IndexError:
61+
return None
62+
63+
64+
def is_alive(pid: int) -> bool:
65+
try:
66+
os.kill(pid, 0)
67+
except OSError:
68+
return False
69+
return True
70+
71+
72+
def popen_is_alive(popen: subprocess.Popen) -> bool:
73+
try:
74+
popen.wait(0)
75+
except subprocess.TimeoutExpired:
76+
return True
77+
assert popen.returncode is not None
78+
return False

0 commit comments

Comments
 (0)