__main__.py 26.7 KB
Newer Older
1 2
#!/usr/bin/env python3

3
import collections
4 5 6 7 8
import slixmpp
import asyncio
import logging
import signal
import atexit
9
import lxml.etree
10
import sys
11
import io
louiz’'s avatar
louiz’ committed
12
import os
13
from functools import partial
louiz’'s avatar
louiz’ committed
14
from slixmpp.xmlstream.matcher.base import MatcherBase
15 16


louiz’'s avatar
louiz’ committed
17
class MatchAll(MatcherBase):
18 19 20 21 22 23
    """match everything"""

    def match(self, xml):
        return True


24
class StanzaError(Exception):
25 26 27 28 29 30 31 32 33 34 35
    """
    Raised when a step fails.
    """
    pass


class SkipStepError(Exception):
    """
    Raised by a step when it needs to be skiped, by running
    the next available step immediately.
    """
36 37 38
    pass


39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
class XMPPComponent(slixmpp.BaseXMPP):
    """
    XMPPComponent sending a “scenario” of stanzas, checking that the responses
    match the expected results.
    """

    def __init__(self, scenario, biboumi):
        super().__init__(jid="biboumi.localhost", default_ns="jabber:component:accept")
        self.is_component = True
        self.stream_header = '<stream:stream %s %s from="%s" id="%s">' % (
            'xmlns="jabber:component:accept"',
            'xmlns:stream="%s"' % self.stream_ns,
            self.boundjid, self.get_id())
        self.stream_footer = "</stream:stream>"

        self.register_handler(slixmpp.Callback('Match All',
                                               MatchAll(None),
                                               self.handle_incoming_stanza))

        self.add_event_handler("session_end", self.on_end_session)

        asyncio.async(self.accept_routine())

        self.scenario = scenario
        self.biboumi = biboumi
64 65 66
        # A callable, taking a stanza as argument and raising a StanzaError
        # exception if the test should fail.
        self.stanza_checker = None
67 68 69 70 71 72 73 74 75 76 77 78
        self.failed = False
        self.accepting_server = None

    def error(self, message):
        print("Failure: %s" % (message,))
        self.scenario.steps = []
        self.failed = True

    def on_end_session(self, event):
        self.loop.stop()

    def handle_incoming_stanza(self, stanza):
79 80 81 82 83
        if self.stanza_checker:
            try:
                self.stanza_checker(stanza)
            except StanzaError as e:
                self.error(e)
84 85 86 87
            except SkipStepError:
                # Run the next step and then re-handle this same stanza
                self.run_scenario()
                return self.handle_incoming_stanza(stanza)
88
            self.stanza_checker = None
89 90 91 92 93 94 95 96 97 98 99 100 101 102
        self.run_scenario()

    def run_scenario(self):
        if scenario.steps:
            step = scenario.steps.pop(0)
            step(self, self.biboumi)
        else:
            self.biboumi.stop()

    @asyncio.coroutine
    def accept_routine(self):
        self.accepting_server = yield from self.loop.create_server(lambda: self,
                                                                   "127.0.0.1", "8811", reuse_address=True)

103 104
    def check_stanza_against_all_expected_xpaths(self):
        pass
louiz’'s avatar
louiz’ committed
105

louiz’'s avatar
louiz’ committed
106

107 108 109 110
def check_xpath(xpaths, stanza):
    for xpath in xpaths:
        tree = lxml.etree.parse(io.StringIO(str(stanza)))
        matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions',
111 112
                                                'muc_user': 'http://jabber.org/protocol/muc#user',
                                                'disco_items': 'http://jabber.org/protocol/disco#items'})
113 114
        if not matched:
            raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath))
115

louiz’'s avatar
louiz’ committed
116

117 118 119 120 121 122 123
def check_xpath_optional(xpaths, stanza):
    try:
        check_xpath(xpaths, stanza)
    except StanzaError:
        raise SkipStepError()


124 125 126 127 128 129 130 131
class Scenario:
    """Defines a list of actions that are executed in sequence, until one of
    them throws an exception, or until the end.  An action can be something
    like “send a stanza”, “receive the next stanza and check that it matches
    the given XPath”, “send a signal”, “wait for the end of the process”,
    etc
    """

132
    def __init__(self, name, steps, conf="basic"):
133 134 135 136 137
        """
        Steps is a list of 2-tuple:
        [(action, answer), (action, answer)]
        """
        self.name = name
138
        self.steps = []
139
        self.conf = conf
140 141 142 143 144 145
        for elem in steps:
            if isinstance(elem, collections.Iterable):
                for step in elem:
                    self.steps.append(step)
            else:
                self.steps.append(elem)
146

louiz’'s avatar
louiz’ committed
147

148 149
class ProcessRunner:
    def __init__(self):
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
        self.process = None
        self.signal_sent = False

    @asyncio.coroutine
    def start(self):
        self.process = yield from self.create

    @asyncio.coroutine
    def wait(self):
        code = yield from self.process.wait()
        return code

    def stop(self):
        if not self.signal_sent:
            self.signal_sent = True
            if self.process:
                self.process.send_signal(signal.SIGINT)

168 169 170
    def __del__(self):
        self.stop()

171

172 173 174 175 176 177 178 179 180 181 182 183 184 185
class BiboumiRunner(ProcessRunner):
    def __init__(self, name, with_valgrind):
        super().__init__()
        self.name = name
        self.fd = open("biboumi_%s_output.txt" % (name,), "w")
        if with_valgrind:
            self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all",
                                                         "--errors-for-leak-kinds=all", "--error-exitcode=16",
                                                         "./biboumi", "test.conf", stdin=None, stdout=self.fd,
                                                         stderr=self.fd, loop=None, limit=None)
        else:
            self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd,
                                                         stderr=self.fd, loop=None, limit=None)

louiz’'s avatar
louiz’ committed
186

187 188 189
class IrcServerRunner(ProcessRunner):
    def __init__(self):
        super().__init__()
louiz’'s avatar
louiz’ committed
190
        self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", os.getcwd() + "/../tests/end_to_end/ircd.conf",
191
                                                     stderr=asyncio.subprocess.PIPE)
192

louiz’'s avatar
louiz’ committed
193

194
def send_stanza(stanza, xmpp, biboumi):
195
    xmpp.send_raw(stanza.format_map(common_replacements))
196 197 198
    asyncio.get_event_loop().call_soon(xmpp.run_scenario)


199 200
def expect_stanza(xpaths, xmpp, biboumi, optional=False):
    check_func = check_xpath if not optional else check_xpath_optional
201
    if isinstance(xpaths, str):
202
        xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)])
203
    elif isinstance(xpaths, tuple):
204
        xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths])
205 206
    else:
        print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths)))
207

louiz’'s avatar
louiz’ committed
208

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
class BiboumiTest:
    """
    Spawns a biboumi process and a fake XMPP Component that will run a
    Scenario.  It redirects the outputs of the subprocess into separated
    files, and detects any failure in the running of the scenario.
    """

    def __init__(self, scenario, expected_code=0):
        self.scenario = scenario
        self.expected_code = 0

    def run(self, with_valgrind=True):
        print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else ''))
        # Redirect the slixmpp logging into a specific file
        output_filename = "slixmpp_%s_output.txt" % (self.scenario.name,)
        with open(output_filename, "w"):
            pass
        logging.basicConfig(level=logging.DEBUG,
                            format='%(levelname)-8s %(message)s',
                            filename=output_filename)

230
        with open("test.conf", "w") as fd:
231
            fd.write(confs[scenario.conf])
232

233 234 235 236 237 238 239 240 241 242
        # Start the XMPP component and biboumi
        biboumi = BiboumiRunner(scenario.name, with_valgrind)
        xmpp = XMPPComponent(self.scenario, biboumi)
        asyncio.get_event_loop().run_until_complete(biboumi.start())

        asyncio.get_event_loop().call_soon(xmpp.run_scenario)

        xmpp.process()

        code = asyncio.get_event_loop().run_until_complete(biboumi.wait())
louiz’'s avatar
louiz’ committed
243 244
        xmpp.biboumi = None
        scenario.steps.clear()
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        failed = False
        if not xmpp.failed:
            if code != self.expected_code:
                xmpp.error("Wrong return code from biboumi's process: %d" % (code,))
                failed = True
            else:
                print("Success!")
        else:
            failed = True

        if xmpp.server:
            xmpp.accepting_server.close()

        return not failed

louiz’'s avatar
louiz’ committed
260

261 262
confs = {
'basic':
263 264
"""hostname=biboumi.localhost
password=coucou
265
db_name=e2e_test.sqlite
266 267
port=8811
admin=admin@example.com""",
268 269 270 271

'fixed_server':
"""hostname=biboumi.localhost
password=coucou
272
db_name=e2e_test.sqlite
273 274
port=8811
fixed_irc_server=irc.localhost
275
admin=admin@example.com
276
"""}
277

278 279 280
common_replacements = {
    'irc_server_one': 'irc.localhost@biboumi.localhost',
    'irc_host_one': 'irc.localhost',
281
    'biboumi_host': 'biboumi.localhost',
282 283 284 285
    'resource_one': 'resource1',
    'nick_one': 'Nick',
    'jid_one': 'first@example.com',
    'jid_two': 'second@example.com',
286
    'jid_admin': 'admin@example.com',
287 288 289 290 291 292
    'nick_two': 'Bobby',
}


def handshake_sequence():
    return (partial(expect_stanza, "//handshake"),
293
            partial(send_stanza, "<handshake xmlns='jabber:component:accept'/>"))
294 295


296
def connection_begin_sequence(irc_host, jid):
297 298 299 300 301
    jid = jid.format_map(common_replacements)
    xpath    = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']"
    xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]"
    return (
    partial(expect_stanza,
302
            xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)),
303
    partial(expect_stanza,
304
            xpath % 'Connection failed: Connection refused'),
305
    partial(expect_stanza,
306
            xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)),
307
    partial(expect_stanza,
308
            xpath % 'Connection failed: Connection refused'),
309
    partial(expect_stanza,
310
            xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)),
311
    partial(expect_stanza,
312 313
            xpath % 'Connected to IRC server.'),
    # These two messages can be receive in any order
314
    partial(expect_stanza,
315
            xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)),
316
    partial(expect_stanza,
317
            xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)),
318 319
    # These three messages can be received in any order
    partial(expect_stanza,
320
            xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)),
321
    partial(expect_stanza,
322
            xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)),
323
    partial(expect_stanza,
324 325 326 327 328 329 330 331 332
            xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)),
    )


def connection_end_sequence(irc_host, jid):
    jid = jid.format_map(common_replacements)
    xpath    = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']"
    xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]"
    return (
333
    partial(expect_stanza,
334
            xpath_re % (r'^%s: Your host is .*$' % irc_host)),
335
    partial(expect_stanza,
336
            xpath_re % (r'^%s: This server was created .*$' % irc_host)),
337
    partial(expect_stanza,
338
            xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)),
339
    partial(expect_stanza,
340
            xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True),
341
    partial(expect_stanza,
342
            xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)),
343
    partial(expect_stanza,
344
            xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)),
345
    partial(expect_stanza,
346
            xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)),
347
    partial(expect_stanza,
348
            xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)),
349 350
    partial(expect_stanza,
            xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"),
351
    partial(expect_stanza,
352
            xpath_re % r'^User mode for \w+ is \[\+i\]$'),
353 354 355
    )


356 357 358 359
def connection_sequence(irc_host, jid):
    return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid)


360 361 362 363 364 365 366 367 368
if __name__ == '__main__':

    atexit.register(asyncio.get_event_loop().close)

    # Start the test component, accepting connections on the configured
    # port.
    scenarios = (
        Scenario("basic_handshake_success",
                 [
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
                     handshake_sequence()
                 ]),
        Scenario("irc_server_connection",
                 [
                     handshake_sequence(),
                     partial(send_stanza,
                             "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
                     connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                 ]),
        Scenario("simple_channel_join",
                 [
                     handshake_sequence(),
                     partial(send_stanza,
                             "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
                     connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                     partial(expect_stanza,
385 386 387
                             "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
                     partial(expect_stanza,
                             ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
388 389 390
                             "/presence/muc_user:x/muc_user:status[@code='110']")
                             ),
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
391
                 ]),
louiz’'s avatar
louiz’ committed
392 393 394 395 396 397 398 399 400 401 402 403 404
        Scenario("virtual_channel_join",
                 [
                     handshake_sequence(),
                     partial(send_stanza,
                             "<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"),
                     connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                     partial(expect_stanza,
                             ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",
                              "/presence/muc_user:x/muc_user:status[@code='110']")
                             ),
                     partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"),
                     connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                 ]),
405
        Scenario("channel_join_with_two_users",
406
                 [
407 408
                     handshake_sequence(),
                     # First user joins
louiz’'s avatar
louiz’ committed
409
                     partial(send_stanza,
410 411 412
                             "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
                     connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                     partial(expect_stanza,
413 414 415
                             "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
                     partial(expect_stanza,
                             ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']",
416 417 418 419 420 421 422
                             "/presence/muc_user:x/muc_user:status[@code='110']")
                             ),
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),

                     # Second user joins
                     partial(send_stanza,
                             "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
louiz’'s avatar
louiz’ committed
423 424 425 426 427 428
                     connection_sequence("irc.localhost", '{jid_two}/{resource_one}'),
                     # Our presence, sent to the other user
                     partial(expect_stanza,
                             ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)),
                     # The other user presence
                     partial(expect_stanza,
429
                             "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"),
louiz’'s avatar
louiz’ committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
                     # Our own presence
                     partial(expect_stanza,
                             ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",
                              "/presence/muc_user:x/muc_user:status[@code='110']")
                             ),
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
                 ]),
        Scenario("channel_custom_topic",
                 [
                     handshake_sequence(),
                     # First user joins
                     partial(send_stanza,
                             "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
                     connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                     partial(expect_stanza,
445 446 447 448
                             "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
                     partial(expect_stanza,
                             ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']",
                             "/presence/muc_user:x/muc_user:status[@code='110']")
louiz’'s avatar
louiz’ committed
449 450 451 452 453
                             ),
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),

                     # First user sets the topic
                     partial(send_stanza,
454
                             "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>TOPIC TEST</subject></message>"),
455
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"),
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471

                     # Second user joins
                     partial(send_stanza,
                             "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
                     connection_sequence("irc.localhost", '{jid_two}/{resource_one}'),
                     # Our presence, sent to the other user
                     partial(expect_stanza,
                             ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)),
                     # The other user presence
                     partial(expect_stanza,
                             "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']"),
                     # Our own presence
                     partial(expect_stanza,
                             ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",
                              "/presence/muc_user:x/muc_user:status[@code='110']")
                             ),
472
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"),
473
                 ]),
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
        Scenario("channel_basic_join_on_fixed_irc_server",
                 [
                     handshake_sequence(),
                     partial(send_stanza,
                     "<presence from='{jid_one}/{resource_one}' to='#zgeg@{biboumi_host}/{nick_one}' />"),
                     connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                     partial(expect_stanza,
                     "/message/body[text()='Mode #zgeg [+nt] by {irc_host_one}']"),
                     partial(expect_stanza,
                     ("/presence[@to='{jid_one}/{resource_one}'][@from='#zgeg@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
                     "/presence/muc_user:x/muc_user:status[@code='110']")
                     ),
                     partial(expect_stanza, "/message[@from='#zgeg@{biboumi_host}'][@type='groupchat']/subject[not(text())]"),
                 ], conf='fixed_server'
                 ),
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
        Scenario("list_adhoc",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[3]")),
                 ]),
        Scenario("list_admin_adhoc",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[5]")),
                 ]),
        Scenario("list_adhoc_fixed_server",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[3]")),
                 ], conf='fixed_server'),
        Scenario("list_admin_adhoc_fixed_server",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[5]")),
                 ], conf='fixed_server'),
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539


        Scenario("list_adhoc_irc",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{irc_host_one}@{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[1]")),
                 ]),
        Scenario("list_adhoc_irc_fixed_server",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[4]")),
                 ], conf='fixed_server'),
        Scenario("list_admin_adhoc_irc_fixed_server",
                 [
                     handshake_sequence(),
                     partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"),
                     partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
                                             "/iq/disco_items:query/disco_items:item[6]")),
                 ], conf='fixed_server'),
540 541 542 543
    )

    failures = 0

louiz’'s avatar
louiz’ committed
544
    irc_output = open("irc_output.txt", "w")
545
    irc = IrcServerRunner()
louiz’'s avatar
louiz’ committed
546
    print("Starting irc server…")
547 548 549
    asyncio.get_event_loop().run_until_complete(irc.start())
    while True:
        res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline())
louiz’'s avatar
louiz’ committed
550
        irc_output.write(res.decode())
551
        if not res:
louiz’'s avatar
louiz’ committed
552
            print("IRC server failed to start, see irc_output.txt for more details. Exiting…")
553
            sys.exit(1)
louiz’'s avatar
louiz’ committed
554
        if b"now running in foreground mode" in res:
555
            break
louiz’'s avatar
louiz’ committed
556
    print("irc server started.")
557 558 559 560 561 562 563 564 565
    print("Running %s checks for biboumi." % (len(scenarios)))

    for scenario in scenarios:
        test = BiboumiTest(scenario)
        if not test.run(False):
            print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." %
                  (scenario.name, scenario.name))
            failures += 1

louiz’'s avatar
louiz’ committed
566
    print("Waiting for irc server to exit…")
567
    irc.stop()
louiz’'s avatar
louiz’ committed
568
    asyncio.get_event_loop().run_until_complete(irc.wait())
569

570 571 572 573 574 575
    if failures:
        print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '',
                                                    'them' if failures > 1 else 'it'))
        sys.exit(1)
    else:
        print("All tests passed successfully")