From 48192bd11a680e8f78be9e55d91b22ec348520dc Mon Sep 17 00:00:00 2001 From: zebra-lucky Date: Fri, 1 Nov 2024 23:58:52 +0200 Subject: [PATCH] test_irc_messaging.py: redo code to simplify testing --- .../tests/jmdaemon/test_irc_messaging.py | 279 ++++++++++-------- 1 file changed, 150 insertions(+), 129 deletions(-) diff --git a/electrum/plugins/joinmarket/tests/jmdaemon/test_irc_messaging.py b/electrum/plugins/joinmarket/tests/jmdaemon/test_irc_messaging.py index a4b6a7d1b..74166cd95 100644 --- a/electrum/plugins/joinmarket/tests/jmdaemon/test_irc_messaging.py +++ b/electrum/plugins/joinmarket/tests/jmdaemon/test_irc_messaging.py @@ -2,162 +2,183 @@ '''Tests of joinmarket bots end-to-end (including IRC and bitcoin) ''' +import asyncio import io -import time from electrum.plugins.joinmarket.jmbase import commands from electrum.plugins.joinmarket.jmdaemon import ( IRCMessageChannel, MessageChannelCollection) +from electrum.plugins.joinmarket.jmdaemon.irc import wlog, TxIRCFactory +from electrum.plugins.joinmarket.jmdaemon.irc_support import IRCClientService from electrum.plugins.joinmarket.tests import JMTestCase -si = 1 +class DummyTransport(asyncio.Transport): + def __init__(self): + super().__init__() + self._t = io.BytesIO(b'') -class DummyDaemon(object): + def write(self, data): + self._t.write(data) - def request_signature_verify(self, a, b, c, d, e, f, g, h): - return True + def close(self): + self._t.close() + + +class DummyIRCClientService(IRCClientService): + + async def _dummy_create_conn(self, sslc): + self.logger.debug(f'_dummy_create_conn: {self.factory.buildProtocol}, ' + f'{self.host}:{self.port}, {sslc}') + transport = DummyTransport() + protocol = self.factory.buildProtocol() + protocol.connection_made(transport) + return transport, protocol + + async def _create_conn(self, sslc): + self.logger.debug(f'_create_conn: {sslc}') + self.transport, self.protocol = await self._dummy_create_conn(sslc) + + async def _proxy_create_conn(self, sslc): + self.logger.debug(f'_proxy_create_conn: {sslc}') + self.transport, self.protocol = await self._create_conn(sslc) class DummyMC(IRCMessageChannel): def __init__(self, jmman, configdata, nick): super().__init__(jmman, configdata) - self.jmman = jmman self.set_nick(nick) - async def shutdown(self): - self.tx_irc_client.transport = io.BytesIO(b'') - self.tx_irc_client._queue = [] - self.tx_irc_client.quit() - self.give_up = True - if self.client_service: - self.client_service.stopService() - - -def on_connect(x): - print('simulated on-connect') - - -def on_welcome(mc): - print('simulated on-welcome') - mc.tx_irc_client.lineRate = 0.2 - if mc.nick == "irc_publisher": - d = commands.deferLater(3.0, junk_pubmsgs, mc) - d.addCallback(junk_longmsgs) - d.addCallback(junk_announce) - d.addCallback(junk_fill) - - -def on_disconnect(x): - print('simulated on-disconnect') - - -def on_order_seen(dummy, counterparty, oid, ordertype, minsize, - maxsize, txfee, cjfee): - global yg_name - yg_name = counterparty - - -def on_pubkey(pubkey): - print("received pubkey: " + pubkey) - - -async def junk_pubmsgs(mc): - # start a raw IRCMessageChannel instance in a thread; - # then call send_* on it with various errant messages - time.sleep(si) - await mc.request_orderbook() - time.sleep(si) - # now try directly - await mc.pubmsg("!orderbook") - time.sleep(si) - # should be ignored; can we check? - await mc.pubmsg("!orderbook!orderbook") - return mc - - -async def junk_longmsgs(mc): - # assuming MAX_PRIVMSG_LEN is not something crazy - # big like 550, this should fail - # with pytest.raises(AssertionError) as e_info: - await mc.pubmsg("junk and crap"*40) - time.sleep(si) - # assuming MAX_PRIVMSG_LEN is not something crazy - # small like 180, this should succeed - await mc.pubmsg("junk and crap"*15) - time.sleep(si) - return mc - - -async def junk_announce(mc): - # try a long order announcement in public - # because we don't want to build a real orderbook, - # call the underlying IRC announce function. - # TODO: how to test that the sent format was correct? - print('got here') - await mc._announce_orders(["!abc def gh 0001"]*30) - time.sleep(si) - return mc - - -async def junk_fill(mc): - cpname = "irc_receiver" - # send a fill with an invalid pubkey to the existing yg; - # this should trigger a NaclError but should NOT kill it. - await mc._privmsg(cpname, "fill", "0 10000000 abcdef") - # Try with ob flag - await mc._pubmsg("!reloffer stuff") - time.sleep(si) - # Trigger throttling with large messages - await mc._privmsg(cpname, "tx", "aa"*5000) - time.sleep(si) - # with pytest.raises(CJPeerError) as e_info: - await mc.send_error(cpname, "fly you fools!") - time.sleep(si) - return mc - - -def getmc(jmman, nick): - jmconf = jmman.jmconf - mchannels = list(jmconf.get_msg_channels().values()) - mc = DummyMC(jmman, mchannels[0], nick) - mc.register_orderbookwatch_callbacks(on_order_seen=on_order_seen) - mc.register_taker_callbacks(on_pubkey=on_pubkey) - mc.on_connect = on_connect - mc.on_disconnect = on_disconnect - mc.on_welcome = on_welcome - mcc = MessageChannelCollection([mc], jmman) - return mc, mcc - - -class TrialIRC(JMTestCase): + async def build_irc(self): + wlog(self.logger, 'building irc') + if self.tx_irc_client: + raise Exception('irc already built') + try: + self.irc_factory = TxIRCFactory(self) + wlog( + self.logger, + f'build_irc: host={self.host}, port={self.port}, ' + f'channel={self.channel}, usessl={self.usessl}, ' + f'socks5={self.socks5}, socks5_host={self.socks5_host}, ' + f'socks5_port={self.socks5_port}') + self.client_service = DummyIRCClientService( + self.irc_factory, host=self.host, port=self.port, + loop=self.loop, usessl=self.usessl, socks5=self.socks5, + socks5_host=self.socks5_host, socks5_port=self.socks5_port) + self.client_service.startService() + await self.client_service.srv_task + except Exception as e: + wlog(self.logger, 'error in buildirc: ' + repr(e)) + + +class IRCMessageChannelTestCase(JMTestCase): + + def on_connect(self, x): + print('simulated on-connect') + + def on_welcome(self, mc): + print('simulated on-welcome') + mc.tx_irc_client.lineRate = 0.2 + if mc.nick == "irc_publisher": + d = commands.deferLater(3.0, self.junk_pubmsgs, mc) + d.addCallback(self.junk_longmsgs) + d.addCallback(self.junk_announce) + d.addCallback(self.junk_fill) + + def on_disconnect(self, x): + print('simulated on-disconnect') + + def on_order_seen(self, dummy, counterparty, oid, ordertype, minsize, + maxsize, txfee, cjfee): + self.yg_name = counterparty + + def on_pubkey(self, pubkey): + print("received pubkey: " + pubkey) + + async def junk_pubmsgs(self, mc): + # start a raw IRCMessageChannel instance in a thread; + # then call send_* on it with various errant messages + await asyncio.sleep(1) + await mc.request_orderbook() + await asyncio.sleep(1) + # now try directly + await mc.pubmsg("!orderbook") + await asyncio.sleep(1) + # should be ignored; can we check? + await mc.pubmsg("!orderbook!orderbook") + return mc + + async def junk_longmsgs(self, mc): + # assuming MAX_PRIVMSG_LEN is not something crazy + # big like 550, this should fail + # with pytest.raises(AssertionError) as e_info: + await mc.pubmsg("junk and crap"*40) + await asyncio.sleep(1) + # assuming MAX_PRIVMSG_LEN is not something crazy + # small like 180, this should succeed + await mc.pubmsg("junk and crap"*15) + await asyncio.sleep(1) + return mc + + async def junk_announce(self, mc): + # try a long order announcement in public + # because we don't want to build a real orderbook, + # call the underlying IRC announce function. + # TODO: how to test that the sent format was correct? + print('got here') + await mc._announce_orders(["!abc def gh 0001"]*30) + await asyncio.sleep(1) + return mc + + async def junk_fill(self, mc): + cpname = "irc_receiver" + # send a fill with an invalid pubkey to the existing yg; + # this should trigger a NaclError but should NOT kill it. + await mc._privmsg(cpname, "fill", "0 10000000 abcdef") + # Try with ob flag + await mc._pubmsg("!reloffer stuff") + await asyncio.sleep(1) + # Trigger throttling with large messages + await mc._privmsg(cpname, "tx", "aa"*5000) + await asyncio.sleep(1) + # with pytest.raises(CJPeerError) as e_info: + await mc.send_error(cpname, "fly you fools!") + await asyncio.sleep(1) + return mc + + def getmc(self, nick): + jmman = self.jmman + jmconf = jmman.jmconf + mchannels = list(jmconf.get_msg_channels().values()) + mc = DummyMC(jmman, mchannels[0], nick) + mc.register_orderbookwatch_callbacks(on_order_seen=self.on_order_seen) + mc.register_taker_callbacks(on_pubkey=self.on_pubkey) + mc.on_connect = self.on_connect + mc.on_disconnect = self.on_disconnect + mc.on_welcome = self.on_welcome + mcc = MessageChannelCollection([mc], jmman) + return mc, mcc async def asyncSetUp(self): await super().asyncSetUp() self.jmman.jmconf.maker_timeout_sec = 1 - mc, mcc = getmc(self.jmman, "irc_publisher") - mc2, mcc2 = getmc(self.jmman, "irc_receiver") - await mcc.run() - mc.irc_factory.buildProtocol() - await mcc2.run() - mc2.irc_factory.buildProtocol() + self.mc, self.mcc = self.getmc("irc_publisher") + self.mc2, self.mcc2 = self.getmc("irc_receiver") + await self.mcc.run() + #self.mc.irc_factory.buildProtocol() + await self.mcc2.run() + #self.mc2.irc_factory.buildProtocol() async def cb(m): # don't try to reconnect m.give_up = True - await mc.shutdown() - - self.addCleanup(cb, mc) - self.addCleanup(cb, mc2) - # test_junk_messages() - print("Got here") + await m.shutdown() - async def test_waiter(self): - # commands.callLater(1.0, junk_messages, self.mcc) - return commands.deferLater(30, self._called_by_deffered) + self.addCleanup(cb, self.mc) + self.addCleanup(cb, self.mc2) - async def _called_by_deffered(self): - pass + async def test_setup(self): + assert 0