Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 27 additions & 23 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
version: 2
version: 2.1
jobs:
build:
# Test mocknet
docker:
- image: qrledger/qrl-docker-ci:bionic
environment:
PYTHONPATH: /root/project
- image: qrledger/qrl-docker-ci:noble
# environment:
# PYTHONPATH: /root/project
steps:
- checkout
- run: pip3 install -U -r requirements.txt
- run: apt install -y python3-venv
# chmod and chown scripts in mocknet folder to current user
- run: chmod +x mocknet/*.sh
- run: chown -R $(whoami) mocknet/
- run:
name: Mocknet Tests
command: pytest tests/python/mocknet
Expand All @@ -18,20 +20,22 @@ jobs:
name: Basic Tests
command: pytest tests/python/basic

tests_bionic:
# Run all tests in bionic
tests_noble:
# Run all tests in noble
docker:
- image: qrledger/qrl-docker-ci:bionic
environment:
PYTHONPATH: /root/project
- image: qrledger/qrl-docker-ci:noble
# environment:
# PYTHONPATH: /root/project
steps:
- checkout
- run: apt install -y python3-venv
- run: chmod +x mocknet/*.sh
- run: chown -R $(whoami) mocknet/*.sh
- run: chmod +x tests/python/fork_recovery/scripts/prepare_data.sh
- run: chown -R $(whoami) tests/python/fork_recovery/scripts/prepare_data.sh
- run: pip install -U -r requirements.txt
- run:
name: Mocknet Tests
command: pytest tests/python/mocknet
- run: pip3 install -U -r requirements.txt
- run:
name: Basic Tests
command: pytest tests/python/basic
Expand Down Expand Up @@ -63,11 +67,13 @@ jobs:

tests_fastnet_long:
docker:
- image: qrledger/qrl-docker-ci:bionic
environment:
PYTHONPATH: /root/project
- image: qrledger/qrl-docker-ci:noble
# environment:
# PYTHONPATH: /root/project
steps:
- checkout
- run: chmod +x mocknet/*.sh
- run: chown -R $(whoami) mocknet/
- run: git submodule update --init --recursive --remote
- run: python3 -m pip install -U -r requirements.txt
- run:
Expand Down Expand Up @@ -96,11 +102,10 @@ jobs:
tests_webstack:
# Run full webstack integration tests (wallet and explorer)
machine: true
pre:
- cat /etc/*release
- pyenv global 2.7.11 3.5.1
steps:
- checkout
- run: chmod +x mocknet/*.sh
- run: chown -R $(whoami) mocknet/
- run: curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
- run: sudo apt update -y
- run: sudo apt install -y locales default-jre default-jdk python3 python3-pip netcat cpulimit bsdtar nodejs
Expand Down Expand Up @@ -143,13 +148,12 @@ jobs:
# debugging job - this can be customize locally but it wont run in circleci
# to run this, execute circleci build --job debug
docker:
- image: qrledger/qrl-docker-ci:bionic
environment:
PYTHONPATH: /root/project
- image: qrledger/qrl-docker-ci:noble
# environment:
# PYTHONPATH: /root/project
steps:
- checkout
- run: pip install -U -r requirements.txt
- run: apt install -y python3-venv
- run:
name: Debugging - Adjust accordingly
command: pytest tests/python/fastnet
Expand All @@ -159,7 +163,7 @@ workflows:
build_all:
jobs:
- build
- tests_bionic
- tests_noble
# - tests_js
# - tests_webstack
# - tests_fuzzing
Expand Down
64 changes: 56 additions & 8 deletions mocknet/MockNet.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ def __init__(self,
def prepare_source(self):
cmd = "{}/prepare_source.sh".format(self.this_dir)
p = subprocess.Popen(cmd, shell=True)
p.wait()
return_code = p.wait()
if return_code != 0:
raise RuntimeError(f"prepare_source.sh failed with return code {return_code}")

@property
def running(self):
Expand Down Expand Up @@ -201,19 +203,56 @@ def start_node(self, node_idx: int, stop_event: multiprocessing.Event):
yaml.dump(config, stream=f, Dumper=yaml.Dumper)

if not stop_event.is_set():
p = subprocess.Popen("{}/{} --qrldir {} {}".format(
node_cmd = "{}/{} --qrldir {} {}".format(
self.this_dir,
self.run_script,
node_data_dir,
self.node_args),
self.node_args)

p = subprocess.Popen(node_cmd,
shell=True,
preexec_fn=os.setsid,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stderr=subprocess.PIPE) # Separate stderr for better error handling

self.nodes_pids.put(p.pid)

# Enqueue any output

# Check if the process started successfully
# Wait a brief moment to see if it immediately exits due to permission/other errors
time.sleep(0.2)
if p.poll() is not None:
# Process has already exited, this is likely a failure
return_code = p.returncode
# Capture any error output
try:
stdout, stderr = p.communicate(timeout=1)
error_msg = f"Node {node_idx} failed to start. Command: {node_cmd}, Return code: {return_code}"
if stderr:
error_msg += f", stderr: {stderr.decode('utf-8', errors='ignore').strip()}"
if stdout:
error_msg += f", stdout: {stdout.decode('utf-8', errors='ignore').strip()}"
raise RuntimeError(error_msg)
except subprocess.TimeoutExpired:
p.kill()
raise RuntimeError(f"Node {node_idx} failed to start. Command: {node_cmd}, Return code: {return_code}")

# Enqueue any output (stdout and stderr)
import threading
def read_stderr():
try:
for line in io.TextIOWrapper(p.stderr, encoding="utf-8"):
s = "Node{:2} | [STDERR] {}".format(node_idx, line)
if stop_event.is_set():
break
self.log_queue.put(s, block=False)
except:
pass # Ignore errors in stderr reading thread

# Start stderr reading thread
stderr_thread = threading.Thread(target=read_stderr, daemon=True)
stderr_thread.start()

# Read stdout in main thread
for line in io.TextIOWrapper(p.stdout, encoding="utf-8"):
s = "Node{:2} | {}".format(node_idx, line)
if stop_event.is_set():
Expand All @@ -234,15 +273,24 @@ def run(self):
for node_idx in range(self.node_count):
if test_future.running():
MockNet.writeout('[Mocknet] launch node %d' % node_idx)
self.nodes.append(self.pool.submit(self.start_node, node_idx, self.stop_event))
node_future = self.pool.submit(self.start_node, node_idx, self.stop_event)
self.nodes.append(node_future)
sleep(1)

# Check if the node launch failed immediately
if node_future.done():
try:
node_future.result() # This will raise an exception if the node failed
except Exception as e:
self.writeout_error(f"Node {node_idx} failed to launch: {e}")
raise RuntimeError(f"Node {node_idx} failed to launch") from e
try:
remaining_time = self.timeout_secs - self.uptime
MockNet.writeout('[Mocknet] remaining time: %d' % remaining_time)
result = test_future.result(remaining_time)
except concurrent.futures.TimeoutError:
self.writeout_error("TIMEOUT")
raise TimeoutError
raise
except Exception:
self.writeout_error("Exception detected")
raise
Expand Down
16 changes: 10 additions & 6 deletions mocknet/NodeTracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@

class NodeLogTracker(object):
MAX_IDLE_TIME = 60
MAX_NO_ADDITION_TIME = 60
MAX_NO_ADDITION_TIME = 120 # Increased to 2 minutes for CI stability

def __init__(self, mocknet):
def __init__(self, mocknet, max_idle_time=None, max_no_addition_time=None):
self.node_status = {}
self.node_last_event = {}
self.node_last_addition = {}
self.mocknet = mocknet

# Allow configurable timeouts, with defaults
self.max_idle_time = max_idle_time if max_idle_time is not None else self.MAX_IDLE_TIME
self.max_no_addition_time = max_no_addition_time if max_no_addition_time is not None else self.MAX_NO_ADDITION_TIME

self.abort_triggers = [
"<_Rendezvous of RPC that terminated with (StatusCode.UNKNOWN",
Expand Down Expand Up @@ -63,13 +67,13 @@ def track(self, output=True):

def check_idle_nodes(self):
for k, v in self.node_last_event.items():
if time.time() - v > self.MAX_IDLE_TIME:
raise Exception("{} - no event for more than {} secs".format(k, self.MAX_IDLE_TIME))
if time.time() - v > self.max_idle_time:
raise Exception("{} - no event for more than {} secs".format(k, self.max_idle_time))

def check_last_addition(self):
for k, v in self.node_last_addition.items():
if time.time() - v > self.MAX_NO_ADDITION_TIME:
raise Exception("{} - no addition for more than {} secs".format(k, self.MAX_NO_ADDITION_TIME))
if time.time() - v > self.max_no_addition_time:
raise Exception("{} - no addition for more than {} secs".format(k, self.max_no_addition_time))

def parse(self, msg):
parts = msg.split('|')
Expand Down
12 changes: 2 additions & 10 deletions mocknet/prepare_source.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@ source ${BASH_SOURCE%/*}/set_env.sh
###############

echo
export REPO_SLUG='theQRL/QRL'
export REPO_BRANCH='master'

# Clean up
rm -rf ${VENV_PATH}
rm -rf ${SOURCE_PATH}
export REPO_SLUG='jplomas/QRL'
export REPO_BRANCH='noble'

if [ ! -z ${TESTINPLACE:-} ]; then
rsync -qar . ${SOURCE_PATH} --exclude tests_integration # > /dev/null
Expand All @@ -20,10 +16,6 @@ else
cp ${SOURCE_PATH}/src/qrl/generated/* qrl/generated/
fi

# Prepare clean virtual environment to run the tests
python3 -m venv ${VENV_PATH} --system-site-packages
source ${VENV_PATH}/bin/activate

# Install dependencies
pip install -U setuptools
pip install -U mock
Expand Down
4 changes: 2 additions & 2 deletions mocknet/run_mining_node.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ source ${BASH_SOURCE%/*}/set_env.sh
###############

echo
source ${VENV_PATH}/bin/activate
# source ${VENV_PATH}/bin/activate
export PYTHONPATH=${SOURCE_PATH}/src
${SOURCE_PATH}/start_qrl.py --mocknet -l DEBUG "$@"
python ${SOURCE_PATH}/start_qrl.py --mocknet -l DEBUG "$@"

3 changes: 1 addition & 2 deletions mocknet/run_node.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ source ${BASH_SOURCE%/*}/set_env.sh
###############

echo
source ${VENV_PATH}/bin/activate
export PYTHONPATH=${SOURCE_PATH}/src
${SOURCE_PATH}/start_qrl.py -l DEBUG "$@"
python ${SOURCE_PATH}/start_qrl.py -l DEBUG "$@"

2 changes: 1 addition & 1 deletion mocknet/set_env.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
TMP_PATH="$( cd ${SCRIPT_PATH}/../tmp ; pwd -P )"
SOURCE_PATH=${TMP_PATH}/src
VENV_PATH=${TMP_PATH}/venv
# VENV_PATH=${TMP_PATH}/venv

echo "SCRIPT_PATH ${SCRIPT_PATH}"
echo "TMP_PATH ${TMP_PATH}"
Expand Down
Loading