2023-11-01 14:02:29 +02:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2023-11-21 09:29:48 +02:00
|
|
|
|
import inspect
|
2023-11-01 16:44:42 +02:00
|
|
|
|
import glob
|
2026-05-08 14:59:20 +08:00
|
|
|
|
import random
|
|
|
|
|
|
import string
|
2023-11-17 08:47:22 +02:00
|
|
|
|
from src.libs.custom_logger import get_custom_logger
|
2023-11-01 16:44:42 +02:00
|
|
|
|
import os
|
2023-11-01 14:02:29 +02:00
|
|
|
|
import pytest
|
2023-11-01 16:44:42 +02:00
|
|
|
|
from datetime import datetime
|
2023-11-17 08:47:22 +02:00
|
|
|
|
from time import time
|
2023-11-01 16:44:42 +02:00
|
|
|
|
from uuid import uuid4
|
2026-05-08 14:59:20 +08:00
|
|
|
|
from src.libs.common import attach_allure_file, gen_step_id
|
2023-11-01 16:44:42 +02:00
|
|
|
|
import src.env_vars as env_vars
|
2026-05-08 14:59:20 +08:00
|
|
|
|
from src.env_vars import FLEET_PRIMARY_MULTIADDR, FLEET_DNS_DISCOVERY_URL, FLEET_N1_MULTIADDR, FLEET_N2_MULTIADDR
|
2023-11-01 14:02:29 +02:00
|
|
|
|
from src.data_storage import DS
|
2024-05-28 16:50:14 +03:00
|
|
|
|
from src.postgres_setup import start_postgres, stop_postgres
|
2026-05-08 14:59:20 +08:00
|
|
|
|
from src.test_data import FLEET_CLUSTER_ID, FLEET_PUBSUB_TOPICS, PUBSUB_TOPICS_RLN, VALID_PUBSUB_TOPICS
|
|
|
|
|
|
from src.test_config import PubsubConfig
|
2023-11-01 14:02:29 +02:00
|
|
|
|
|
2023-11-17 08:47:22 +02:00
|
|
|
|
logger = get_custom_logger(__name__)
|
2023-11-01 14:02:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-08 14:59:20 +08:00
|
|
|
|
def pytest_addoption(parser):
|
|
|
|
|
|
"""Register the --fleet command-line option."""
|
|
|
|
|
|
parser.addoption(
|
|
|
|
|
|
"--fleet",
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
default=False,
|
|
|
|
|
|
help=(
|
|
|
|
|
|
"Bootstrap every local nwaku Docker node against the live waku.test "
|
|
|
|
|
|
"fleet (node-01.do-ams3 / gc-us-central1-a / ac-cn-hongkong-c). "
|
|
|
|
|
|
"Also activatable via FLEET_BOOTSTRAP=true env var."
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fleet_bootstrap_enabled(config) -> bool:
|
|
|
|
|
|
"""Return True when fleet bootstrap should be activated."""
|
|
|
|
|
|
|
|
|
|
|
|
if config.getoption("--fleet", default=False):
|
|
|
|
|
|
return True
|
|
|
|
|
|
return os.getenv("FLEET_BOOTSTRAP", "false").lower() == "true"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
|
|
|
def fleet_rln_state(request):
|
|
|
|
|
|
"""Register 2 RLN memberships once per test session when ``--fleet`` is active."""
|
|
|
|
|
|
if not _fleet_bootstrap_enabled(request.config):
|
|
|
|
|
|
yield {"keystore_prefixes": [], "rln_membership_indexes": []}
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
from src.node.waku_node import WakuNode
|
|
|
|
|
|
from src.env_vars import RLN_CREDENTIALS, DEFAULT_NWAKU
|
|
|
|
|
|
|
|
|
|
|
|
if not RLN_CREDENTIALS:
|
|
|
|
|
|
logger.info("Fleet RLN: RLN_CREDENTIALS not set – nodes will start without RLN")
|
|
|
|
|
|
yield {"keystore_prefixes": [], "rln_membership_indexes": []}
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
state: dict = {"keystore_prefixes": [], "rln_membership_indexes": []}
|
|
|
|
|
|
try:
|
|
|
|
|
|
for i in range(2):
|
|
|
|
|
|
prefix = "".join(random.choices(string.ascii_lowercase, k=4))
|
|
|
|
|
|
node = WakuNode(DEFAULT_NWAKU, f"rln_reg_{i + 1}_{gen_step_id()}")
|
|
|
|
|
|
membership_index = node.register_rln(
|
|
|
|
|
|
rln_keystore_prefix=prefix,
|
|
|
|
|
|
rln_creds_source=RLN_CREDENTIALS,
|
|
|
|
|
|
rln_creds_id=str(i + 1),
|
|
|
|
|
|
)
|
|
|
|
|
|
state["keystore_prefixes"].append(prefix)
|
|
|
|
|
|
state["rln_membership_indexes"].append(membership_index)
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"Fleet RLN: registered %d memberships – indexes=%s prefixes=%s",
|
|
|
|
|
|
len(state["rln_membership_indexes"]),
|
|
|
|
|
|
state["rln_membership_indexes"],
|
|
|
|
|
|
state["keystore_prefixes"],
|
|
|
|
|
|
)
|
|
|
|
|
|
except BaseException as ex:
|
|
|
|
|
|
logger.error("Fleet RLN: registration failed – aborting test session: %s", ex)
|
|
|
|
|
|
pytest.exit(f"Fleet RLN registration failed – aborting session: {ex}", returncode=1)
|
|
|
|
|
|
|
|
|
|
|
|
yield state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
|
|
|
def pubsub_cfg(request) -> PubsubConfig:
|
|
|
|
|
|
"""Return the pubsub-topic configuration for the current session."""
|
|
|
|
|
|
|
|
|
|
|
|
if _fleet_bootstrap_enabled(request.config):
|
|
|
|
|
|
return PubsubConfig(
|
|
|
|
|
|
relay_test_topic=FLEET_PUBSUB_TOPICS[1],
|
|
|
|
|
|
filter_test_topic=FLEET_PUBSUB_TOPICS[1],
|
|
|
|
|
|
filter_second_topic=FLEET_PUBSUB_TOPICS[2],
|
|
|
|
|
|
lightpush_test_topic=FLEET_PUBSUB_TOPICS[0],
|
|
|
|
|
|
store_test_topic=FLEET_PUBSUB_TOPICS[0],
|
|
|
|
|
|
rln_test_topic=FLEET_PUBSUB_TOPICS[0],
|
|
|
|
|
|
all_topics=FLEET_PUBSUB_TOPICS,
|
|
|
|
|
|
)
|
|
|
|
|
|
return PubsubConfig(
|
|
|
|
|
|
relay_test_topic=VALID_PUBSUB_TOPICS[1],
|
|
|
|
|
|
filter_test_topic=VALID_PUBSUB_TOPICS[1],
|
|
|
|
|
|
filter_second_topic=VALID_PUBSUB_TOPICS[2],
|
|
|
|
|
|
lightpush_test_topic=VALID_PUBSUB_TOPICS[0],
|
|
|
|
|
|
store_test_topic=VALID_PUBSUB_TOPICS[0],
|
|
|
|
|
|
rln_test_topic=PUBSUB_TOPICS_RLN[0],
|
|
|
|
|
|
all_topics=VALID_PUBSUB_TOPICS,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
|
|
|
|
def configure_fleet_bootstrap(request, fleet_rln_state):
|
|
|
|
|
|
"""Register ``FleetBootstrapConfig`` as ``WakuNode._pre_start_hook`` for the session."""
|
|
|
|
|
|
|
|
|
|
|
|
if not _fleet_bootstrap_enabled(request.config):
|
|
|
|
|
|
logger.info("Fleet bootstrap inactive – pass --fleet (or set FLEET_BOOTSTRAP=true) " "to connect local nodes to the waku.test fleet")
|
|
|
|
|
|
yield
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
os.environ["FLEET_BOOTSTRAP"] = "true"
|
|
|
|
|
|
|
|
|
|
|
|
from src.node.fleet_waku_node import FleetBootstrapConfig
|
|
|
|
|
|
from src.node.waku_node import WakuNode
|
|
|
|
|
|
|
|
|
|
|
|
cfg = FleetBootstrapConfig(fleet_rln_state=fleet_rln_state)
|
|
|
|
|
|
WakuNode._pre_start_hook = cfg.prepare_start_kwargs
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"Fleet bootstrap active – NODE1→%s NODE2→%s (additional nodes→%s) dns_discovery_url=%s",
|
|
|
|
|
|
FLEET_N1_MULTIADDR,
|
|
|
|
|
|
FLEET_N2_MULTIADDR,
|
|
|
|
|
|
FLEET_PRIMARY_MULTIADDR,
|
|
|
|
|
|
FLEET_DNS_DISCOVERY_URL,
|
|
|
|
|
|
)
|
|
|
|
|
|
yield
|
|
|
|
|
|
WakuNode._pre_start_hook = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
|
|
|
|
def skip_fleet_test_without_rln(request, fleet_rln_state):
|
|
|
|
|
|
"""Skip tests marked @pytest.mark.waku_test_fleet when no RLN keystore is
|
|
|
|
|
|
available for the current session.
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not _fleet_bootstrap_enabled(request.config):
|
|
|
|
|
|
return
|
|
|
|
|
|
if not request.node.get_closest_marker("waku_test_fleet"):
|
|
|
|
|
|
return
|
|
|
|
|
|
if not fleet_rln_state.get("keystore_prefixes"):
|
|
|
|
|
|
pytest.fail("Failing fleet tests: RLN keystore not available " "(RLN_CREDENTIALS not set or on-chain registration failed)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
|
|
|
|
def configure_fleet_cluster(request, pubsub_cfg):
|
|
|
|
|
|
"""Apply fleet cluster configuration to step classes when ``--fleet`` is active."""
|
|
|
|
|
|
|
|
|
|
|
|
if not _fleet_bootstrap_enabled(request.config):
|
|
|
|
|
|
yield
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
from src.steps.relay import StepsRelay
|
|
|
|
|
|
from src.steps.filter import StepsFilter
|
|
|
|
|
|
from src.steps.light_push import StepsLightPush
|
|
|
|
|
|
from src.steps.store import StepsStore
|
|
|
|
|
|
from src.steps.rln import StepsRLN
|
|
|
|
|
|
import tests.relay.test_publish as _relay_publish_mod
|
|
|
|
|
|
|
|
|
|
|
|
# Override step-class topic attributes with fleet cluster-1 topics.
|
|
|
|
|
|
StepsRelay.test_pubsub_topic = pubsub_cfg.relay_test_topic
|
|
|
|
|
|
StepsFilter.test_pubsub_topic = pubsub_cfg.filter_test_topic
|
|
|
|
|
|
StepsFilter.second_pubsub_topic = pubsub_cfg.filter_second_topic
|
|
|
|
|
|
StepsLightPush.test_pubsub_topic = pubsub_cfg.lightpush_test_topic
|
|
|
|
|
|
StepsStore.test_pubsub_topic = pubsub_cfg.store_test_topic
|
|
|
|
|
|
StepsRLN.test_pubsub_topic = pubsub_cfg.rln_test_topic
|
|
|
|
|
|
|
|
|
|
|
|
# tests/relay/test_publish.py::test_publish_on_multiple_pubsub_topics iterates
|
|
|
|
|
|
# over the module-level VALID_PUBSUB_TOPICS import directly; rebind it.
|
|
|
|
|
|
_relay_publish_mod.VALID_PUBSUB_TOPICS = pubsub_cfg.all_topics
|
|
|
|
|
|
|
|
|
|
|
|
def _fleet_setup_lightpush_node(self, image, node_index, **kwargs):
|
|
|
|
|
|
from src.node.waku_node import WakuNode
|
|
|
|
|
|
|
|
|
|
|
|
node = WakuNode(image, f"lightpush_node{node_index}_{self.test_id}")
|
|
|
|
|
|
fleet_kwargs = dict(kwargs)
|
|
|
|
|
|
fleet_kwargs["relay"] = "false"
|
|
|
|
|
|
fleet_kwargs["lightpush"] = "false"
|
|
|
|
|
|
fleet_kwargs["skip_fleet_peering"] = True
|
|
|
|
|
|
fleet_kwargs.setdefault("cluster_id", FLEET_CLUSTER_ID)
|
|
|
|
|
|
fleet_kwargs.setdefault("shard", list(range(8)))
|
|
|
|
|
|
|
|
|
|
|
|
lightpush_service_addr = self.multiaddr_list[0]
|
|
|
|
|
|
node.start(lightpushnode=lightpush_service_addr, **fleet_kwargs)
|
|
|
|
|
|
self.add_node_peer(node, self.multiaddr_list)
|
|
|
|
|
|
logger.debug(
|
|
|
|
|
|
"fleet _fleet_setup_lightpush_node: node %d started with relay=false, " "skip_fleet_peering=True, lightpushnode=%s",
|
|
|
|
|
|
node_index,
|
|
|
|
|
|
lightpush_service_addr,
|
|
|
|
|
|
)
|
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
|
|
StepsLightPush.setup_lightpush_node = _fleet_setup_lightpush_node
|
|
|
|
|
|
StepsLightPush.default_message_propagation_delay = 0.5
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"Fleet cluster config active – pubsub topics overridden to cluster-id=%s "
|
|
|
|
|
|
"(shards 0-7, e.g. relay_test_topic=%s rln_test_topic=%s); "
|
|
|
|
|
|
"StepsLightPush.setup_lightpush_node overridden to use receiving_node1 as "
|
|
|
|
|
|
"lightpush service (fleet-peered with RLN membership #1; messages relay "
|
|
|
|
|
|
"through fleet mesh to receiving_node2 peered with %s)",
|
|
|
|
|
|
FLEET_CLUSTER_ID,
|
|
|
|
|
|
pubsub_cfg.relay_test_topic,
|
|
|
|
|
|
pubsub_cfg.rln_test_topic,
|
|
|
|
|
|
FLEET_N2_MULTIADDR,
|
|
|
|
|
|
)
|
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-01 14:02:29 +02:00
|
|
|
|
# See https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
|
|
|
|
|
|
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
|
|
|
|
|
def pytest_runtest_makereport(item):
|
|
|
|
|
|
outcome = yield
|
|
|
|
|
|
rep = outcome.get_result()
|
|
|
|
|
|
if rep.when == "call":
|
|
|
|
|
|
setattr(item, "rep_call", rep)
|
|
|
|
|
|
return rep
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-01 16:44:42 +02:00
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
|
|
|
|
def set_allure_env_variables():
|
|
|
|
|
|
yield
|
|
|
|
|
|
if os.path.isdir("allure-results") and not os.path.isfile(os.path.join("allure-results", "environment.properties")):
|
2023-11-21 09:29:48 +02:00
|
|
|
|
logger.debug(f"Running fixture teardown: {inspect.currentframe().f_code.co_name}")
|
2023-11-01 16:44:42 +02:00
|
|
|
|
with open(os.path.join("allure-results", "environment.properties"), "w") as outfile:
|
|
|
|
|
|
for attribute_name in dir(env_vars):
|
|
|
|
|
|
if attribute_name.isupper():
|
|
|
|
|
|
attribute_value = getattr(env_vars, attribute_name)
|
|
|
|
|
|
outfile.write(f"{attribute_name}={attribute_value}\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-11 17:16:45 +02:00
|
|
|
|
@pytest.fixture(scope="function", autouse=False)
|
2024-05-28 16:50:14 +03:00
|
|
|
|
def start_postgres_container():
|
|
|
|
|
|
pg_container = start_postgres()
|
|
|
|
|
|
yield
|
|
|
|
|
|
stop_postgres(pg_container)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-01 16:44:42 +02:00
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
|
|
|
|
def test_id(request):
|
|
|
|
|
|
# setting up an unique test id to be used where needed
|
2023-11-21 09:29:48 +02:00
|
|
|
|
logger.debug(f"Running fixture setup: {inspect.currentframe().f_code.co_name}")
|
2023-11-01 16:44:42 +02:00
|
|
|
|
request.cls.test_id = f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}__{str(uuid4())}"
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-01 14:02:29 +02:00
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
2023-11-01 16:44:42 +02:00
|
|
|
|
def test_setup(request, test_id):
|
2023-11-17 08:47:22 +02:00
|
|
|
|
logger.debug(f"Running test: {request.node.name} with id: {request.cls.test_id}")
|
|
|
|
|
|
yield
|
2023-11-21 09:29:48 +02:00
|
|
|
|
logger.debug(f"Running fixture teardown: {inspect.currentframe().f_code.co_name}")
|
2023-11-17 08:47:22 +02:00
|
|
|
|
for file in glob.glob(os.path.join(env_vars.DOCKER_LOG_DIR, "*")):
|
|
|
|
|
|
if os.path.getmtime(file) < time() - 3600:
|
|
|
|
|
|
logger.debug(f"Deleting old log file: {file}")
|
|
|
|
|
|
try:
|
|
|
|
|
|
os.remove(file)
|
|
|
|
|
|
except:
|
|
|
|
|
|
logger.error("Could not delete file")
|
2023-11-01 16:44:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
|
|
|
|
def attach_logs_on_fail(request):
|
|
|
|
|
|
yield
|
2023-11-17 08:47:22 +02:00
|
|
|
|
if env_vars.RUNNING_IN_CI and hasattr(request.node, "rep_call") and request.node.rep_call.failed:
|
2023-11-21 09:29:48 +02:00
|
|
|
|
logger.debug(f"Running fixture teardown: {inspect.currentframe().f_code.co_name}")
|
2023-11-01 16:44:42 +02:00
|
|
|
|
logger.debug("Test failed, attempting to attach logs to the allure reports")
|
2023-11-17 08:47:22 +02:00
|
|
|
|
for file in glob.glob(os.path.join(env_vars.DOCKER_LOG_DIR, "*" + request.cls.test_id + "*")):
|
2023-11-01 16:44:42 +02:00
|
|
|
|
attach_allure_file(file)
|
2023-11-01 14:02:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
2023-11-17 08:47:22 +02:00
|
|
|
|
def close_open_nodes(attach_logs_on_fail):
|
2023-11-01 14:02:29 +02:00
|
|
|
|
DS.waku_nodes = []
|
|
|
|
|
|
yield
|
2023-11-21 09:29:48 +02:00
|
|
|
|
logger.debug(f"Running fixture teardown: {inspect.currentframe().f_code.co_name}")
|
2023-11-17 08:47:22 +02:00
|
|
|
|
crashed_containers = []
|
2023-11-01 14:02:29 +02:00
|
|
|
|
for node in DS.waku_nodes:
|
2023-11-17 08:47:22 +02:00
|
|
|
|
try:
|
|
|
|
|
|
node.stop()
|
|
|
|
|
|
except Exception as ex:
|
|
|
|
|
|
if "No such container" in str(ex):
|
|
|
|
|
|
crashed_containers.append(node.image)
|
|
|
|
|
|
logger.error(f"Failed to stop container because of error {ex}")
|
|
|
|
|
|
assert not crashed_containers, f"Containers {crashed_containers} crashed during the test!!!"
|
2024-08-27 11:56:25 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
|
|
|
|
def check_waku_log_errors():
|
|
|
|
|
|
yield
|
|
|
|
|
|
logger.debug(f"Running fixture teardown: {inspect.currentframe().f_code.co_name}")
|
|
|
|
|
|
for node in DS.waku_nodes:
|
|
|
|
|
|
node.check_waku_log_errors()
|