215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
'''
|
|
Offline regressions for endpoint configuration and service startup.
|
|
|
|
'''
|
|
from contextlib import asynccontextmanager as acm
|
|
from pathlib import Path
|
|
import sys
|
|
import tomllib
|
|
from types import ModuleType
|
|
|
|
from click.testing import CliRunner
|
|
import pytest
|
|
import tractor
|
|
import trio
|
|
|
|
from piker import config
|
|
from piker.cli import load_trans_eps, pikerd
|
|
from piker.service import _daemon, _actor_runtime
|
|
|
|
|
|
@pytest.mark.parametrize('repeat', [False, True])
|
|
def test_maddrs_click_collection(repeat):
|
|
'''
|
|
Click previously produced a string for `--maddrs`, which endpoint
|
|
loading split into individual characters. Parse one or repeated
|
|
options through Click and then the real Tractor parser, without
|
|
entering the indefinitely running daemon callback. Every address
|
|
must survive intact and in the declared order.
|
|
|
|
'''
|
|
addresses = ['/ip4/127.0.0.1/tcp/6116']
|
|
if repeat:
|
|
addresses.append('/ip4/127.0.0.1/tcp/6117')
|
|
args = [
|
|
arg for address in addresses for arg in ['--maddrs', address]
|
|
]
|
|
with pikerd.make_context('pikerd', args) as ctx:
|
|
endpoints = load_trans_eps(maddrs=ctx.params['maddrs'])
|
|
assert [addr.unwrap() for addr in endpoints['pikerd']] == [
|
|
('tcp', '127.0.0.1', 6116 + index)
|
|
for index in range(len(addresses))
|
|
]
|
|
|
|
|
|
def test_single_string_multiaddr():
|
|
'''
|
|
Direct endpoint callers may provide one string, independently of
|
|
Click's collection conversion. That input must denote a single
|
|
endpoint rather than a sequence of invalid character endpoints.
|
|
|
|
'''
|
|
endpoints = load_trans_eps(maddrs='/ip4/127.0.0.1/tcp/6116')
|
|
assert endpoints['pikerd'][0].unwrap() == (
|
|
'tcp', '127.0.0.1', 6116,
|
|
)
|
|
|
|
|
|
def test_template_single_transport():
|
|
'''
|
|
The shipped template enabled TCP and UDS together although the
|
|
locked Tractor runtime permits only one transport per actor.
|
|
Parse the real template and ensure both daemon and chart defaults
|
|
use the TCP transport selected by their startup paths.
|
|
|
|
'''
|
|
path = Path(__file__).parents[1] / 'config' / 'conf.toml'
|
|
conf = tomllib.loads(path.read_text())
|
|
endpoints = load_trans_eps(conf['network'])
|
|
for name in ['pikerd', 'chart']:
|
|
assert endpoints[name]
|
|
assert {addr.proto_key for addr in endpoints[name]} == {'tcp'}
|
|
|
|
|
|
@pytest.mark.parametrize('network', [None, {}])
|
|
def test_chart_without_network(monkeypatch, network):
|
|
'''
|
|
Existing configs without a populated network table left chart
|
|
bind endpoints unbound, crashing before the UI entrypoint. Invoke
|
|
the actual Click callback with absent and empty tables, replacing
|
|
only config IO and the UI entrypoint. Automatic binding must be
|
|
passed as None while preserving the configured registrar address.
|
|
|
|
'''
|
|
from piker.ui.cli import chart
|
|
|
|
conf = {} if network is None else {'network': network}
|
|
monkeypatch.setattr(config, 'load', lambda **_: (conf, None))
|
|
app = ModuleType('piker.ui._app')
|
|
calls = []
|
|
app._main = lambda **kwargs: calls.append(kwargs)
|
|
monkeypatch.setitem(sys.modules, 'piker.ui._app', app)
|
|
result = CliRunner().invoke(
|
|
chart,
|
|
['btcusdt.binance'],
|
|
obj={
|
|
'pdb': False,
|
|
'brokermods': [object()],
|
|
'tractorloglevel': 'warning',
|
|
'loglevel': 'warning',
|
|
'registry_addrs': [('127.0.0.1', 6116)],
|
|
},
|
|
)
|
|
assert result.exit_code == 0, result.exception
|
|
runtime = calls[0]['tractor_kwargs']
|
|
assert runtime['tpt_bind_addrs'] is None
|
|
assert runtime['registry_addrs'] == [('127.0.0.1', 6116)]
|
|
|
|
|
|
@pytest.mark.parametrize('explicit_registry', [False, True])
|
|
def test_daemon_options_resolve_registrars(
|
|
monkeypatch,
|
|
explicit_registry,
|
|
):
|
|
'''
|
|
Any nonempty pikerd kwargs previously skipped registrar address
|
|
initialization. Exercise the full discovery and spawn path with
|
|
runtime options, using protocol-boundary fakes instead of remote
|
|
actors. An explicit override must reach discovery and startup
|
|
exactly once; otherwise the inherited registrar list is used.
|
|
The service lock must be released after leaving the context.
|
|
|
|
'''
|
|
inherited = [('tcp', '127.0.0.1', 6116)]
|
|
override = [('tcp', '127.0.0.1', 6117)]
|
|
expected = override if explicit_registry else inherited
|
|
calls = []
|
|
portal = object()
|
|
|
|
@acm
|
|
async def find(name, registry_addrs):
|
|
calls.append(('find', registry_addrs))
|
|
yield None
|
|
|
|
@acm
|
|
async def open_root(**kwargs):
|
|
calls.append(('open', kwargs))
|
|
yield None
|
|
|
|
@acm
|
|
async def wait(name):
|
|
yield portal
|
|
|
|
async def start(**kwargs):
|
|
return True
|
|
|
|
monkeypatch.setattr(_daemon, 'find_service', find)
|
|
monkeypatch.setattr(_daemon, 'maybe_open_pikerd', open_root)
|
|
monkeypatch.setattr(tractor, 'wait_for_actor', wait)
|
|
monkeypatch.setattr(
|
|
tractor,
|
|
'get_runtime_vars',
|
|
lambda: {'_registry_addrs': inherited},
|
|
)
|
|
|
|
async def main():
|
|
lock = trio.Lock()
|
|
monkeypatch.setitem(_daemon.Services.locks, 'probe', lock)
|
|
kwargs = {'debug_mode': False}
|
|
if explicit_registry:
|
|
kwargs['registry_addrs'] = override
|
|
async with _daemon.maybe_spawn_daemon(
|
|
'probe', start, {}, **kwargs,
|
|
) as found:
|
|
assert found is portal
|
|
assert not lock.locked()
|
|
|
|
trio.run(main)
|
|
assert calls[0] == ('find', expected)
|
|
assert calls[1][1]['registry_addrs'] == expected
|
|
assert calls[1][1]['debug_mode'] is False
|
|
|
|
|
|
async def _check_distinct_daemon_bind():
|
|
'''
|
|
Open Piker's service context on a non-registrar child actor.
|
|
|
|
'''
|
|
actor = tractor.current_actor()
|
|
assert not actor.is_registrar
|
|
assert actor.accept_addrs != actor.reg_addrs
|
|
async with _actor_runtime.open_pikerd(
|
|
registry_addrs=actor.reg_addrs,
|
|
tpt_bind_addrs=[('127.0.0.1', 0)],
|
|
):
|
|
return actor.accept_addrs
|
|
|
|
|
|
def test_daemon_bind_distinct_from_registrar(reg_addr):
|
|
'''
|
|
Separate daemon and registrar endpoints were rejected because
|
|
startup checked registrar addresses against daemon listeners.
|
|
Run a real TCP registrar and a distinct child daemon with an
|
|
ephemeral listener. Its service context must start and unwind
|
|
without taking ownership of the registrar's listening address.
|
|
|
|
'''
|
|
async def main():
|
|
async with (
|
|
tractor.open_root_actor(
|
|
registry_addrs=[reg_addr],
|
|
enable_transports=['tcp'],
|
|
),
|
|
tractor.open_nursery() as nursery,
|
|
):
|
|
portal = await nursery.start_actor(
|
|
'daemon_probe',
|
|
enable_modules=[__name__],
|
|
bind_addrs=[('127.0.0.1', 0)],
|
|
)
|
|
addresses = await portal.run(_check_distinct_daemon_bind)
|
|
assert addresses
|
|
await nursery.cancel()
|
|
|
|
trio.run(main)
|