Egor Rachkovskii 342a965370
test(e2e): port remaining wrapper tests from the interop repo (#4077)
* test(e2e): port remaining wrapper tests from the interop repo

The wrapper suite that moved into tests-e2e (#4027) was a reworked subset of
the one still living in logos-delivery-interop-tests. Comparing both sides
showed 21 tests here against 46 there, with no overlap in the delta: the 25
missing tests cover scenarios the reworked set never included.

Ports those 25 tests, bringing the in-repo suite to the full 46:
  - 12 send scenarios: s01 (nil/destroyed handle), s03, s04, s05, s11, s13,
    s16, s18 (both orderings), s25, s29
  - 7 channel lifecycle tests (rc01-rc04)
  - 6 wrapper corner cases: auto port allocation, MyBoundPorts, ENR

Supporting changes the ported tests need:
  - wrapper_helpers: get_node_tcp_port, get_node_bound_ports, enr_udp_port
  - WrapperManager: channel_create/send/close, destroy_keep_ctx
  - vendored binding refreshed to the revision exposing the channel API
    (additive only; cffi resolves symbols lazily, so nothing existing moves)

Two Edge senders were fixed while porting. build_node_config defaults relay
and store to True, and the flat-JSON config path applies mode=Edge before
explicit fields, so those defaults win: the Edge nodes in s11/s16/s25 came up
as relay and store servers and exercised the relay path instead of lightpush.
They now set relay=False and store=False, matching test_send_e2e_part2. s16
also dropped lightpush=True, which mounts the lightpush server and fails node
start once relay is off; the lightpush client mounts unconditionally.

Suite goes from 21 to 46 functions (53 collected). The docker subset grows
from 3 to 5 as s11 and s25 need a store peer. Local run against a freshly
built library: 45 passed, 2 skipped, 1 xfailed.

* test(e2e): enable autosharding in the channel lifecycle tests

channel_create subscribes to the channel's content topic since #4081, and
resolving that topic to a shard needs autosharding. build_node_config leaves
numShardsInNetwork at 0 and cluster 198 has no preset, so these nodes came up
with static sharding and every channel_create failed with "autosharding is not
configured; pass an explicit shard".

Adds numShardsInNetwork=1 to the six tests that create a channel, matching what
every other wrapper test that touches the send or channel API already does.
rc02 is left alone: channel_send rejects on the id lookup before any shard is
resolved.

Verified locally against a fresh build: the five tests that complete now pass
and the error string is gone from the run.
2026-08-10 08:36:28 +01:00

149 lines
4.4 KiB
Python

"""S01 helpers: invoke send() against an invalid handle in an isolated process.
Run via:
python -m tests.wrappers_tests.helpers.send_invalid_handle nil <marker>
python -m tests.wrappers_tests.helpers.send_invalid_handle destroyed <marker>
Prints a single line to stdout starting with <marker>, followed by a JSON
payload describing the outcome. Runs in its own process so that a missing
C-ABI guard (which can SIGSEGV) fails the parent test cleanly instead of
taking the pytest runner down.
Cases:
- nil: send() on a wrapper built with ctx=ffi.NULL.
- destroyed: send() after destroy_keep_ctx() — self.ctx stays non-nil so the
call reaches the C side with the original (now-stale) pointer.
"""
import json
import sys
from pathlib import Path
def _ensure_bindings_on_path() -> None:
# The wrapper module lives outside the project tree, under vendor/.
# Helper file is at <root>/tests/wrappers_tests/helpers/<this>.py.
project_root = Path(__file__).resolve().parents[3]
bindings_path = project_root / "vendor" / "logos-delivery-python-bindings" / "waku"
if str(bindings_path) not in sys.path:
sys.path.insert(0, str(bindings_path))
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
def _emit(marker: str, payload: dict) -> None:
print(marker + json.dumps(payload))
def _run_nil_handle(marker: str) -> None:
from wrapper import NodeWrapper, ffi # type: ignore[import-not-found]
from src.node.wrappers_manager import WrapperManager
from src.node.wrapper_helpers import create_message_bindings
sender = WrapperManager(NodeWrapper(ctx=ffi.NULL, config_buffer=None, event_cb_handler=None))
send_result = sender.send_message(message=create_message_bindings())
_emit(
marker,
{
"is_ok": send_result.is_ok(),
"ok": send_result.ok_value if send_result.is_ok() else None,
"err": send_result.err() if send_result.is_err() else None,
},
)
def _run_destroyed_handle(marker: str) -> None:
from src.node.wrappers_manager import WrapperManager
from src.node.wrapper_helpers import EventCollector, create_message_bindings
from tests.wrappers_tests.conftest import build_node_config
collector = EventCollector()
create_result = WrapperManager.create_and_start(
config=build_node_config(),
event_cb=collector.event_callback,
)
if create_result.is_err():
_emit(
marker,
{
"stage": "create_and_start",
"is_ok": False,
"ok": None,
"err": create_result.err(),
"events_after_send": [],
},
)
return
sender = create_result.ok_value
stop_result = sender.stop_node()
if stop_result.is_err():
_emit(
marker,
{
"stage": "stop_node",
"is_ok": False,
"ok": None,
"err": stop_result.err(),
"events_after_send": [],
},
)
return
destroy_result = sender.destroy_keep_ctx()
if destroy_result.is_err():
_emit(
marker,
{
"stage": "destroy_keep_ctx",
"is_ok": False,
"ok": None,
"err": destroy_result.err(),
"events_after_send": [],
},
)
return
events_before_send = len(collector.events)
send_result = sender.send_message(message=create_message_bindings())
new_events = collector.events[events_before_send:]
_emit(
marker,
{
"stage": "send_message",
"is_ok": send_result.is_ok(),
"ok": send_result.ok_value if send_result.is_ok() else None,
"err": send_result.err() if send_result.is_err() else None,
"events_after_send": [str(e) for e in new_events],
},
)
CASES = {
"nil": _run_nil_handle,
"destroyed": _run_destroyed_handle,
}
def main() -> int:
if len(sys.argv) != 3 or sys.argv[1] not in CASES:
cases = "|".join(CASES)
print(f"usage: send_invalid_handle <{cases}> <result_marker>", file=sys.stderr)
return 2
case, marker = sys.argv[1], sys.argv[2]
_ensure_bindings_on_path()
CASES[case](marker)
return 0
if __name__ == "__main__":
sys.exit(main())