Merge branch 'first-integration-tests' into 'master'

First integration tests

See merge request poezio/slixmpp!75
This commit is contained in:
mathieui 2020-12-05 17:37:50 +01:00
commit 73cc2a4008
7 changed files with 269 additions and 1 deletions

View file

@ -13,6 +13,21 @@ test:
- pip3 install emoji aiohttp
- ./run_tests.py
test_integration:
stage: test
tags:
- docker
image: ubuntu:latest
only:
variables:
- $CI_ACCOUNT1
- $CI_ACCOUNT2
script:
- apt update
- apt install -y python3 python3-pip cython3 gpg
- pip3 install emoji aiohttp aiodns
- ./run_integration_tests.py
trigger_poezio:
stage: trigger
tags:

0
itests/__init__.py Normal file
View file

View file

@ -0,0 +1,28 @@
import unittest
from slixmpp.test.integration import SlixIntegration
class TestConnect(SlixIntegration):
async def asyncSetUp(self):
await super().asyncSetUp()
self.add_client(
self.envjid('CI_ACCOUNT1'),
self.envstr('CI_ACCOUNT1_PASSWORD'),
)
self.add_client(
self.envjid('CI_ACCOUNT2'),
self.envstr('CI_ACCOUNT2_PASSWORD'),
)
await self.connect_clients()
async def test_send_message(self):
"""Make sure we can send and receive messages"""
msg = self.clients[0].make_message(
mto=self.clients[1].boundjid, mbody='Msg body',
)
msg.send()
message = await self.clients[1].wait_until('message')
self.assertEqual(message['body'], msg['body'])
suite = unittest.TestLoader().loadTestsFromTestCase(TestConnect)

78
itests/test_muc.py Normal file
View file

@ -0,0 +1,78 @@
import asyncio
import unittest
from uuid import uuid4
from slixmpp import JID
from slixmpp.test.integration import SlixIntegration
UNIQUE = uuid4().hex
class TestConnect(SlixIntegration):
async def asyncSetUp(self):
self.mucserver = self.envjid('CI_MUC_SERVER')
self.muc = JID('%s@%s' % (UNIQUE, self.mucserver))
self.add_client(
self.envjid('CI_ACCOUNT1'),
self.envstr('CI_ACCOUNT1_PASSWORD'),
)
self.add_client(
self.envjid('CI_ACCOUNT2'),
self.envstr('CI_ACCOUNT2_PASSWORD'),
)
self.register_plugins(['xep_0045'])
await self.connect_clients()
async def test_initial_join(self):
"""Check that we can connect to a new muc"""
self.clients[0]['xep_0045'].join_muc(self.muc, 'client1')
presence = await self.clients[0].wait_until('muc::%s::got_online' % self.muc)
self.assertEqual(presence['muc']['affiliation'], 'owner')
async def test_setup_muc(self):
"""Check that sending the initial room config and affiliation list works"""
self.clients[0]['xep_0045'].join_muc(self.muc, 'client1')
presence = await self.clients[0].wait_until('muc::%s::got_online' % self.muc)
self.assertEqual(presence['muc']['affiliation'], 'owner')
# Send initial configuration
config = await self.clients[0]['xep_0045'].get_room_config(self.muc)
values = config.get_values()
values['muc#roomconfig_persistentroom'] = False
values['muc#roomconfig_membersonly'] = True
config['values'] = values
config.reply()
config = await self.clients[0]['xep_0045'].set_room_config(self.muc, config)
# Send affiliation list including client 2
await self.clients[0]['xep_0045'].send_affiliation_list(
self.muc,
[
(self.clients[1].boundjid.bare, 'member'),
],
)
async def test_join_after_config(self):
"""Join a room after being added to the affiliation list"""
await self.test_setup_muc()
self.clients[1]['xep_0045'].join_muc(self.muc, 'client2')
await self.clients[1].wait_until('muc::%s::got_online' % self.muc)
async def test_leave(self):
"""Check that we leave properly"""
await self.test_join_after_config()
self.clients[0]['xep_0045'].leave_muc(self.muc, 'client1', 'boooring')
pres = await self.clients[1].wait_until('muc::%s::got_offline' % self.muc)
self.assertEqual(pres['status'], 'boooring')
self.assertEqual(pres['type'], 'unavailable')
async def test_kick(self):
"""Test kicking a user"""
await self.test_join_after_config()
await asyncio.gather(
self.clients[0].wait_until('muc::%s::got_offline' % self.muc),
self.clients[0]['xep_0045'].set_role(self.muc, 'client2', 'none')
)
suite = unittest.TestLoader().loadTestsFromTestCase(TestConnect)

71
run_integration_tests.py Executable file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env python3
import sys
import logging
import unittest
from argparse import ArgumentParser
from distutils.core import Command
from importlib import import_module
from pathlib import Path
def run_tests(filenames=None):
"""
Find and run all tests in the tests/ directory.
Excludes live tests (tests/live_*).
"""
if sys.version_info < (3, 8):
raise ValueError('Your python version is too old to run these tests')
if not filenames:
filenames = [i for i in Path('itests').glob('test_*')]
else:
filenames = [Path(i) for i in filenames]
modules = ['.'.join(test.parts[:-1] + (test.stem,)) for test in filenames]
suites = []
for filename in modules:
module = import_module(filename)
suites.append(module.suite)
tests = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=2)
# Disable logging output
logging.basicConfig(level=100)
logging.disable(100)
result = runner.run(tests)
return result
# Add a 'test' command for setup.py
class TestCommand(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
run_tests()
if __name__ == '__main__':
parser = ArgumentParser(description='Run unit tests.')
parser.add_argument('tests', metavar='TEST', nargs='*', help='list of tests to run, or nothing to run them all')
args = parser.parse_args()
result = run_tests(args.tests)
print("<tests %s ran='%s' errors='%s' fails='%s' success='%s'/>" % (
"xmlns='http//andyet.net/protocol/tests'",
result.testsRun, len(result.errors),
len(result.failures), result.wasSuccessful()))
sys.exit(not result.wasSuccessful())

View file

@ -0,0 +1,61 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import asyncio
import os
try:
from unittest import IsolatedAsyncioTestCase
except ImportError:
# Python < 3.8
# just to make sure the imports do not break, but
# not usable.
from unittest import TestCase as IsolatedAsyncioTestCase
from typing import (
List,
)
from slixmpp import JID
from slixmpp.clientxmpp import ClientXMPP
class SlixIntegration(IsolatedAsyncioTestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.clients = []
self.addAsyncCleanup(self._destroy)
def envjid(self, name):
"""Get a JID from an env var"""
value = os.getenv(name)
return JID(value)
def envstr(self, name):
"""get a str from an env var"""
return os.getenv(name)
def register_plugins(self, plugins: List[str]):
"""Register plugins on all known clients"""
for plugin in plugins:
for client in self.clients:
client.register_plugin(plugin)
def add_client(self, jid: JID, password: str):
"""Register a new client"""
self.clients.append(ClientXMPP(jid, password))
async def connect_clients(self):
"""Connect all clients"""
for client in self.clients:
client.connect()
await client.wait_until('session_start')
async def _destroy(self):
"""Kill all clients"""
for client in self.clients:
client.abort()

View file

@ -12,7 +12,7 @@
:license: MIT, see LICENSE for more details
"""
from typing import Optional, Set, Callable
from typing import Optional, Set, Callable, Any
import functools
import logging
@ -1130,3 +1130,18 @@ class XMLStream(asyncio.BaseProtocol):
:param exception: An unhandled exception object.
"""
pass
async def wait_until(self, event: str, timeout=30) -> Any:
"""Utility method to wake on the next firing of an event.
(Registers a disposable handler on it)
:param str event: Event to wait on.
:param int timeout: Timeout
"""
fut = asyncio.Future()
self.add_event_handler(
event,
fut.set_result,
disposable=True,
)
return await asyncio.wait_for(fut, timeout)