pytest-services/0000755000175000017500000000000014000615613012321 5ustar pebpebpytest-services/Makefile0000644000175000017500000000075213747000772014000 0ustar pebpeb# create virtual environment PATH := .env/bin:$(PATH) .env: virtualenv .env # install all needed for development develop: .env pip install -e . -r requirements-testing.txt tox coveralls coverage: develop coverage run --source=pytest_services .env/bin/py.test tests coverage report -m test: develop tox coveralls: coverage coveralls # clean the development envrironment clean: -rm -rf .env # debian dependencies dependencies: sudo apt-get install `grep -vh '#' DEPENDENCIES*` pytest-services/setup.cfg0000644000175000017500000000020013747000772014145 0ustar pebpeb[bdist_wheel] universal = 1 [options] # for compatibility with older installers setup_requires = setuptools_scm[toml] >= 3.4.1 pytest-services/.coveragerc0000644000175000017500000000002513747000772014452 0ustar pebpeb[run] omit = tests/* pytest-services/MANIFEST.in0000644000175000017500000000012013747000772014063 0ustar pebpebinclude *.rst include setup.py include requirements-testing.txt include tox.ini pytest-services/.travis.yml0000644000175000017500000000125413747000772014447 0ustar pebpebsudo: false language: python python: "2.7" matrix: include: - env: TOXENV=linters - env: TOXENV=py27-pytest39 - env: TOXENV=py27-pytest310 - env: TOXENV=py27-pytestlatest - env: TOXENV=py27-pytestlatest-xdist - env: TOXENV=py34-pytestlatest python: "3.4" - env: TOXENV=py35-pytestlatest python: "3.5" - env: TOXENV=py36-pytestlatest python: "3.6" - env: TOXENV=py27-pytestlatest-coveralls install: pip install tox # mysql_install_db will not work if it can't find the my-default.cnf file before_script: sudo cp /etc/mysql/my.cnf /usr/share/mysql/my-default.cnf script: tox branches: except: - /^\d/ notifications: email: - bubenkoff@gmail.com pytest-services/requirements-testing.txt0000644000175000017500000000017413747000772017275 0ustar pebpeb# apt-get install xvfb python-dev libmemcached-dev libmysqlclient-dev coverage mock mysqlclient pylibmc pytest-pep8 astroid pytest-services/tests/0000755000175000017500000000000013747000772013476 5ustar pebpebpytest-services/tests/test_plugin.py0000644000175000017500000000252513747000772016411 0ustar pebpeb"""Tests for pytest-services plugin.""" import os.path import socket import pylibmc import MySQLdb def test_memcached(request, memcached, memcached_socket): """Test memcached service.""" mc = pylibmc.Client([memcached_socket]) mc.set('some', 1) assert mc.get('some') == 1 # check memcached cleaner request.getfixturevalue('memcached_clean') assert mc.get('some') is None def test_mysql(mysql, mysql_connection, mysql_socket): """Test mysql service.""" conn = MySQLdb.connect(user='root', unix_socket=mysql_socket) assert conn def test_xvfb(xvfb, xvfb_display): """Test xvfb service.""" socket.create_connection(('127.0.0.1', 6000 + xvfb_display)) def test_port_getter(port_getter): """Test port getter utility.""" port1 = port_getter() sock1 = socket.socket(socket.AF_INET) sock1.bind(('127.0.0.1', port1)) assert port1 port2 = port_getter() sock2 = socket.socket(socket.AF_INET) sock2.bind(('127.0.0.1', port2)) assert port2 assert port1 != port2 def test_display_getter(display_getter): """Test display getter utility.""" display1 = display_getter() assert display1 display2 = display_getter() assert display2 assert display1 != display2 def test_temp_dir(temp_dir): """Test temp dir directory.""" assert os.path.isdir(temp_dir) pytest-services/tests/__init__.py0000644000175000017500000000003513747000772015605 0ustar pebpeb"""pytest-services tests.""" pytest-services/tests/conftest.py0000644000175000017500000000027413747000772015700 0ustar pebpeb"""Configuration for pytest runner.""" import pytest pytest_plugins = 'pytester' @pytest.fixture(scope='session') def run_services(): """Run services for tests.""" return True pytest-services/setup.py0000755000175000017500000000372513747000772014060 0ustar pebpeb"""Setuptools entry point.""" import codecs import os import sys import re from setuptools import setup dirname = os.path.dirname(__file__) long_description = ( codecs.open(os.path.join(dirname, 'README.rst'), encoding='utf-8').read() + '\n' + codecs.open(os.path.join(dirname, 'AUTHORS.rst'), encoding='utf-8').read() + '\n' + codecs.open(os.path.join(dirname, 'CHANGES.rst'), encoding='utf-8').read() ) install_requires = [ 'requests', 'psutil', 'pytest', 'zc.lockfile >= 2.0', ] PY2 = sys.version_info[0] < 3 if PY2: install_requires.append('subprocess32') with codecs.open(os.path.join(dirname, "pytest_services", "__init__.py"), encoding="utf-8") as fd: VERSION = re.compile(r".*__version__ = ['\"](.*?)['\"]", re.S).match(fd.read()).group(1) setup( name='pytest-services', description='Services plugin for pytest testing framework', long_description=long_description, author='Anatoly Bubenkov, Paylogic International and others', license='MIT license', author_email='bubenkoff@gmail.com', version=VERSION, url='https://github.com/pytest-dev/pytest-services', install_requires=install_requires, extras={ 'memcached': ['pylibmc'], }, classifiers=[ 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', ] + [('Programming Language :: Python :: %s' % x) for x in '2.7 3.4 3.5 3.6 3.7'.split()], tests_require=['tox'], entry_points={'pytest11': [ 'pytest-services=pytest_services.plugin', ]}, packages=['pytest_services'], ) pytest-services/tox.ini0000644000175000017500000000174013747000772013651 0ustar pebpeb[tox] distshare={homedir}/.tox/distshare envlist= linters py27-pytest{39,310,latest} py{34,35,36}-pytestlatest py27-pytestlatest-xdist py27-pytestlatest-coveralls skip_missing_interpreters = true [testenv] commands= py.test tests --junitxml={envlogdir}/junit-{envname}.xml {posargs} deps = pytestlatest: pytest pytest310: pytest~=3.10.0 pytest39: pytest~=3.9.0 xdist: pytest-xdist -r{toxinidir}/requirements-testing.txt passenv = DISPLAY COVERALLS_REPO_TOKEN USER TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH extras = memcache [testenv:linters] basepython=python2.7 commands={[testenv]commands} pytest_services --pep8 -m pep8 [testenv:py27-pytestlatest-coveralls] usedevelop = True deps = {[testenv]deps} coveralls commands= coverage run --source=pytest_services --branch -m pytest tests coverage report -m coveralls [testenv:py27-pytestlatest-xdist] commands={[testenv]commands} -n1 -rfsxX [pytest] pep8maxlinelength=120 addopts = -vvl pytest-services/pytest_services/0000755000175000017500000000000013747000772015567 5ustar pebpebpytest-services/pytest_services/process.py0000644000175000017500000000277213747000772017627 0ustar pebpeb"""Subprocess related functions.""" try: import subprocess32 as subprocess except ImportError: import subprocess def check_output(*popenargs, **kwargs): """Run command with arguments and return its output (both stdout and stderr) as a byte string. If the exit code was non-zero it raises a CalledProcessWithOutputError. """ if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') if 'stderr' in kwargs: raise ValueError('stderr argument not allowed, it will be overridden.') process = subprocess.Popen( stdout=subprocess.PIPE, stderr=subprocess.PIPE, *popenargs, **kwargs) output, err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] raise CalledProcessWithOutputError(retcode, cmd, output, err) return output, err class CalledProcessWithOutputError(subprocess.CalledProcessError): """An exception with the steout and stderr of a failed subprocess32.""" def __init__(self, returncode, cmd, output, err): """Assign output and error.""" super(CalledProcessWithOutputError, self).__init__(returncode, cmd) self.output = output self.err = err def __str__(self): """str representation.""" return super(CalledProcessWithOutputError, self).__str__() + ' with output: {0} and error: {1}'.format( self.output, self.err) pytest-services/pytest_services/plugin.py0000644000175000017500000000140613747000772017440 0ustar pebpeb"""Services plugin for pytest. Provides an easy way of running service processes for your tests. """ from .folders import * # NOQA from .log import * # NOQA from .locks import * # NOQA from .xvfb import * # NOQA from .memcached import * # NOQA from .mysql import * # NOQA from .service import * # NOQA def pytest_addoption(parser): """Add options for services plugin.""" group = parser.getgroup("services", "service processes for tests") group._addoption( '--run-services', action="store_true", dest="run_services", default=False, help="Run services automatically by pytest") group._addoption( '--xvfb-display', action="store", dest="display", default=None, help="X display to use") pytest-services/pytest_services/__init__.py0000644000175000017500000000006513747000772017701 0ustar pebpeb"""pytest-services package.""" __version__ = '2.2.1' pytest-services/pytest_services/memcached.py0000644000175000017500000000264513747000772020056 0ustar pebpeb"""Fixtures for memcache.""" import os import pytest @pytest.fixture(scope='session') def memcached_socket(run_dir, run_services): """The memcached socket location.""" if run_services: return os.path.join(run_dir, 'memcached.sock') @pytest.fixture(scope='session') def memcached(request, run_services, memcached_socket, watcher_getter): """The memcached instance which is ready to be used by the tests.""" if run_services: return watcher_getter( name='memcached', arguments=['-s', memcached_socket], checker=lambda: os.path.exists(memcached_socket), request=request, ) @pytest.fixture(scope='session') def memcached_connection(run_services, memcached_socket): """The connection string to the local memcached instance.""" if run_services: return 'unix:{0}'.format(memcached_socket) @pytest.fixture def do_memcached_clean(run_services): """Determine whether memcached should be clean on the start of every test.""" return run_services @pytest.fixture(scope='session') def memcached_client(memcached_socket, memcached): """Create client for memcached.""" mc = pytest.importorskip('pylibmc') return mc.Client([memcached_socket]) @pytest.fixture def memcached_clean(request, memcached_client, do_memcached_clean): """Clean memcached instance.""" if do_memcached_clean: memcached_client.flush_all() pytest-services/pytest_services/locks.py0000644000175000017500000001431313747000772017256 0ustar pebpeb"""Fixtures for supporting a distributed test run.""" import contextlib import json import os from random import random import socket import time import pytest import zc.lockfile marker = object() def try_remove(filename): try: os.unlink(filename) except OSError: pass @contextlib.contextmanager def file_lock(filename, remove=True, timeout=20): """A lock that is shared across processes. :param filename: the name of the file that will be locked. :param remove: whether or not to remove the file on context close :param timeout: Amount of time to retry the file lock if a :class:`zc.lockfile.LockError` is hit """ total_seconds_slept = 0 while True: try: with contextlib.closing(zc.lockfile.SimpleLockFile(filename)) as lockfile: yield lockfile._fp break except zc.lockfile.LockError as err: if total_seconds_slept >= timeout: raise err seconds_to_sleep = random() * 0.1 + 0.05 total_seconds_slept += seconds_to_sleep time.sleep(seconds_to_sleep) remove and try_remove(filename) def unlock_resource(name, resource, lock_dir, services_log): """Unlock previously locked resource. :param name: name to be used to separate various resources, eg. port, display :param resource: resource value which was previously locked :param lock_dir: directory for lockfiles to use. """ with locked_resources(name, lock_dir) as bound_resources: try: bound_resources.remove(resource) except ValueError: pass services_log.debug('resource freed {0}: {1}'.format(name, resource)) services_log.debug('bound resources {0}: {1}'.format(name, bound_resources)) @contextlib.contextmanager def locked_resources(name, lock_dir): """Contextmanager providing an access to locked shared resource list. :param name: name to be used to separate various resources, eg. port, display :param lock_dir: directory for lockfiles to use. """ with file_lock(os.path.join(lock_dir, name), remove=False) as fd: bound_resources = fd.read().strip() if bound_resources: try: bound_resources = json.loads(bound_resources) except ValueError: bound_resources = None if not isinstance(bound_resources, list): bound_resources = [] yield bound_resources fd.seek(0) fd.truncate() fd.write(json.dumps(bound_resources)) fd.flush() def unlock_port(port, lock_dir, services_log): """Unlock previously locked port.""" return unlock_resource('port', port, lock_dir, services_log) def unlock_display(display, lock_dir, services_log): """Unlock previously locked display.""" return unlock_resource('display', display, lock_dir, services_log) @pytest.fixture(scope='session') def lock_resource_timeout(): """Max number of seconds to obtain the lock.""" return 20 def lock_resource(name, resource_getter, lock_dir, services_log, lock_resource_timeout): """Issue a lock for given resource.""" total_seconds_slept = 0 while True: try: with locked_resources(name, lock_dir) as bound_resources: services_log.debug('bound_resources {0}: {1}'.format(name, bound_resources)) resource = resource_getter(bound_resources) while resource in bound_resources: # resource is already taken by someone, retry services_log.debug('bound resources {0}: {1}'.format(name, bound_resources)) resource = resource_getter(bound_resources) services_log.debug('free resource choosen {0}: {1}'.format(name, resource)) bound_resources.append(resource) services_log.debug('bound resources {0}: {1}'.format(name, bound_resources)) return resource except zc.lockfile.LockError as err: if total_seconds_slept >= lock_resource_timeout: raise err services_log.debug('lock resource failed: {0}'.format(err)) seconds_to_sleep = random() * 0.1 + 0.05 total_seconds_slept += seconds_to_sleep time.sleep(seconds_to_sleep) def get_free_port(lock_dir, services_log, lock_resource_timeout): """Get free port to listen on.""" def get_port(bound_resources): if bound_resources: port = max(bound_resources) + 1 else: port = 30000 while True: s = socket.socket() try: s.bind(('127.0.0.1', port)) s.close() return port except socket.error: pass port += 1 return lock_resource('port', get_port, lock_dir, services_log, lock_resource_timeout) def get_free_display(lock_dir, services_log, lock_resource_timeout): """Get free display to listen on.""" def get_display(bound_resources): display = 100 while True: if bound_resources: display = max(bound_resources) + 1 if os.path.exists('/tmp/.X{0}-lock'.format(display)): display += 1 continue return display return lock_resource('display', get_display, lock_dir, services_log, lock_resource_timeout) @pytest.fixture(scope='session') def port_getter(request, lock_dir, services_log, lock_resource_timeout): """Lock getter function.""" def get_port(): """Lock a free port and unlock it on finalizer.""" port = get_free_port(lock_dir, services_log, lock_resource_timeout) def finalize(): unlock_port(port, lock_dir, services_log) request.addfinalizer(finalize) return port return get_port @pytest.fixture(scope='session') def display_getter(request, lock_dir, services_log, lock_resource_timeout): """Display getter function.""" def get_display(): """Lock a free display and unlock it on finalizer.""" display = get_free_display(lock_dir, services_log, lock_resource_timeout) request.addfinalizer(lambda: unlock_display(display, lock_dir, services_log)) return display return get_display pytest-services/pytest_services/mysql.py0000644000175000017500000001242413747000772017311 0ustar pebpeb"""Fixtures for mysql.""" import os import shutil from distutils.spawn import find_executable # pylint: disable=E0611 import pytest from .process import ( CalledProcessWithOutputError, check_output, ) @pytest.fixture(scope='session') def mysql_defaults_file( run_services, tmp_path_factory, memory_temp_dir, request): """MySQL defaults file.""" if run_services: cfg = tmp_path_factory.mktemp(request.session.name) defaults_path = str(cfg / 'defaults.cnf') with open(defaults_path, 'w+') as fd: fd.write(""" [mysqld] user = {user} tmpdir = {tmpdir} default-time-zone = SYSTEM """.format(user=os.environ['USER'], tmpdir=memory_temp_dir)) return defaults_path @pytest.fixture(scope='session') def mysql_base_dir(): my_print_defaults = find_executable('my_print_defaults') assert my_print_defaults, 'You have to install my_print_defaults script.' return os.path.dirname(os.path.dirname(os.path.realpath(my_print_defaults))) @pytest.fixture(scope='session') def mysql_system_database( run_services, mysql_data_dir, mysql_base_dir, mysql_defaults_file, memory_temp_dir, lock_dir, services_log, ): """Install database to given path.""" if run_services: mysqld = find_executable('mysqld') assert mysqld, 'You have to install mysqld script.' try: services_log.debug('Starting mysqld.') check_output([ mysqld, '--defaults-file={0}'.format(mysql_defaults_file), '--initialize-insecure', '--datadir={0}'.format(mysql_data_dir), '--basedir={0}'.format(mysql_base_dir), '--user={0}'.format(os.environ['USER']) ]) except CalledProcessWithOutputError as e: services_log.error( '{e.cmd} failed with output:\n{e.output}\nand erorr:\n{e.err}. ' 'Please ensure you disabled apparmor for /run/shm/** or for whole mysql'.format(e=e)) raise finally: services_log.debug('mysqld was executed.') @pytest.fixture(scope='session') def mysql_data_dir( request, memory_base_dir, memory_temp_dir, lock_dir, session_id, services_log, run_services): """The root directory for the mysql instance. `mysql_install_db` is run in that directory. """ if run_services: path = os.path.join(memory_base_dir, 'mysql') services_log.debug('Making mysql base dir in {path}'.format(path=path)) def finalizer(): shutil.rmtree(path, ignore_errors=True) finalizer() request.addfinalizer(finalizer) os.mkdir(path) return path @pytest.fixture(scope='session') def mysql_socket(run_dir): """The mysqld socket location.""" return os.path.join(run_dir, 'mysql.sock') @pytest.fixture(scope='session') def mysql_pid(run_dir): """The pid file of the mysqld.""" return os.path.join(run_dir, 'mysql.pid') @pytest.fixture(scope='session') def mysql_connection(run_services, mysql_socket): """The connection string to the local mysql instance.""" if run_services: return 'mysql://root@localhost/?unix_socket={0}&charset=utf8'.format(mysql_socket) @pytest.fixture(scope='session') def mysql_watcher( request, run_services, watcher_getter, mysql_system_database, mysql_pid, mysql_socket, mysql_data_dir, mysql_defaults_file): """The mysqld process watcher.""" if run_services: return watcher_getter( 'mysqld', [ '--defaults-file={0}'.format(mysql_defaults_file), '--datadir={mysql_data_dir}'.format(mysql_data_dir=mysql_data_dir), '--pid-file={mysql_pid}'.format(mysql_pid=mysql_pid), '--socket={mysql_socket}'.format(mysql_socket=mysql_socket), '--skip-networking', ], checker=lambda: os.path.exists(mysql_socket), request=request, ) @pytest.fixture(scope='session') def mysql_database_name(): """Name of test database to be created.""" return 'pytest_services_test' @pytest.fixture(scope='session') def mysql_database_getter(run_services, mysql_watcher, mysql_socket): """Prepare new test database creation function.""" if run_services: def getter(database_name): check_output( [ 'mysql', '--user=root', '--socket={0}'.format(mysql_socket), '--execute=create database {0};'.format(database_name), ], ) check_output( 'mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql --user=root --socket={0} mysql'.format(mysql_socket), shell=True ) return getter @pytest.fixture(scope='session') def mysql_database(run_services, mysql_database_getter, mysql_database_name): """Prepare new test database creation function.""" if run_services: return mysql_database_getter(mysql_database_name) @pytest.fixture(scope='session') def mysql(request, run_services, mysql_watcher, mysql_database): """The mysql instance which is ready to be used by the tests.""" if run_services: return mysql_watcher pytest-services/pytest_services/log.py0000644000175000017500000000310613747000772016722 0ustar pebpeb"""Logging fixtures and functions.""" import contextlib import logging import logging.handlers import socket import pytest @pytest.fixture(scope='session') def services_log(worker_id): """A services_logger with the worker id.""" handler = None for kwargs in (dict(socktype=socket.SOCK_RAW), dict(socktype=socket.SOCK_STREAM), dict()): try: handler = logging.handlers.SysLogHandler( facility=logging.handlers.SysLogHandler.LOG_LOCAL7, address='/dev/log', **kwargs) break except (IOError, TypeError): pass logger = logging.getLogger('[{worker_id}] {name}'.format(name=__name__, worker_id=worker_id)) logger.setLevel(logging.DEBUG) if handler and workaround_issue_20(handler): logger.propagate = 0 logger.addHandler(handler) return logger def workaround_issue_20(handler): """ Workaround for https://github.com/pytest-dev/pytest-services/issues/20, disabling installation of a broken handler. """ return hasattr(handler, 'socket') @contextlib.contextmanager def dont_capture(request): """Suspend capturing of stdout by pytest.""" capman = request.config.pluginmanager.getplugin("capturemanager") capman.suspendcapture() try: yield finally: capman.resumecapture() def remove_handlers(): """Remove root services_logging handlers.""" handlers = [] for handler in logging.root.handlers: if not isinstance(handler, logging.StreamHandler): handlers.append(handler) logging.root.handlers = handlers pytest-services/pytest_services/django_settings.py0000644000175000017500000001013513747000772021323 0ustar pebpeb"""Django setttings helpers.""" import imp import os import sys from importlib import import_module from django.core.urlresolvers import clear_url_caches, set_urlconf from django.template import context, base, loader from django.utils import translation from django.utils.translation import trans_real def setup_django_settings(test_settings): """Override the enviroment variable and call the _setup method of the settings object to reload them.""" os.environ['DJANGO_SETTINGS_MODULE'] = test_settings from django.conf import settings as django_settings # (re)setup django settings django_settings._setup() # reload settings reload_settings(django_settings) def clean_django_settings(): """Clean current django settings.""" from django.conf import settings as django_settings del os.environ['DJANGO_SETTINGS_MODULE'] django_settings._wrapped = None def reload_settings(settings, databases=None): """Special routine to reload django settings. Including: urlconf module, context processor, templatetags settings, database settings. """ if databases: settings.DATABASES.update(databases) # check if there's settings to reload if hasattr(settings, 'ROOT_URLCONF'): if settings.ROOT_URLCONF in sys.modules: imp.reload(sys.modules[settings.ROOT_URLCONF]) import django if hasattr(django, 'setup'): django.setup() import_module(settings.ROOT_URLCONF) set_urlconf(settings.ROOT_URLCONF) settings.LANGUAGE_CODE = 'en' # all tests should be run with English by default # Make the ConnectionHandler use the new settings, otherwise the ConnectionHandler will have old configuraton. from django.db.utils import ConnectionHandler import django.db from django.db.utils import load_backend import django.db.transaction import django.db.models import django.db.models.sql.query import django.core.management.commands.syncdb import django.db.models.sql.compiler import django.db.backends import django.db.backends.mysql.base import django.core.management.commands.loaddata # all modules which imported django.db.connections should be changed to get new ConnectionHanlder django.db.models.sql.compiler.connections = django.db.models.connections = \ django.core.management.commands.loaddata.connections = \ django.db.backends.connections = django.db.backends.mysql.base.connections = \ django.core.management.commands.syncdb.connections = django.db.transaction.connections = \ django.db.connections = django.db.models.base.connections = django.db.models.sql.query.connections = \ ConnectionHandler(settings.DATABASES) # default django connection and backend should be also changed django.db.connection = django.db.connections[django.db.DEFAULT_DB_ALIAS] django.db.backend = load_backend(django.db.connection.settings_dict['ENGINE']) import django.core.cache django.core.cache.cache = django.core.cache._create_cache(django.core.cache.DEFAULT_CACHE_ALIAS) # clear django urls cache clear_url_caches() # clear django contextprocessors cache context._standard_context_processors = None # clear django templatetags cache base.templatetags_modules = None # reload translation files imp.reload(translation) imp.reload(trans_real) # clear django template loaders cache loader.template_source_loaders = None from django.template.loaders import app_directories imp.reload(app_directories) from django.template.base import get_templatetags_modules get_templatetags_modules.cache_clear() import django.apps import django import django.template django.template.engines.__dict__.pop('templates', None) django.template.engines._templates = None django.template.engines._engines = {} if django.apps.apps.ready: django.apps.apps.set_installed_apps(settings.INSTALLED_APPS) django.setup() pytest-services/pytest_services/service.py0000644000175000017500000000631513747000772017606 0ustar pebpeb"""Service fixtures.""" import time import re import warnings try: import subprocess32 as subprocess except ImportError: # pragma: no cover import subprocess import uuid # pylint: disable=C0411 from distutils.spawn import find_executable # pylint: disable=E0611 import pytest WRONG_FILE_NAME_CHARS_RE = re.compile(r'[^\w_-]') @pytest.fixture(scope='session') def run_services(request, worker_id): """Indicate whether the services should run or not.""" return worker_id != 'local' or request.config.option.run_services @pytest.fixture(scope='session') def worker_id(request): """ The id of the worker if tests are run using xdist. It is set to `'local'` if tests are not run using xdist. The id is unique for a test run. An id may clash if there are two workers that belong to different test sessions. """ return WRONG_FILE_NAME_CHARS_RE.sub('_', getattr(request.config, 'workerinput', {}).get('workerid', 'local')) @pytest.fixture(scope='session') def slave_id(request, worker_id): msg = "The `slave_id` fixture is deprecated; use `worker_id` instead." warnings.warn(msg, DeprecationWarning) return worker_id @pytest.fixture(scope='session') def session_id(request, worker_id, run_services): """The test session id. It is supposed to be globally unique. """ # UUID should be enough, other fields are added for the debugging purposes. session_id = '{random}-{worker_id}'.format( random=uuid.uuid4().hex, worker_id=worker_id, ) return session_id @pytest.fixture(scope='session') def watcher_getter(request, services_log): """Popen object of given executable name and it's arguments. Wait for the process to start. Add finalizer to properly stop it. """ orig_request = request def watcher_getter_function(name, arguments=None, kwargs=None, timeout=20, checker=None, request=None): if request is None: warnings.warn('Omitting the `request` parameter will result in an unstable order of finalizers.') request = orig_request executable = find_executable(name) assert executable, 'You have to install {0} executable.'.format(name) cmd = [name] + (arguments or []) services_log.debug('Starting {0}: {1}'.format(name, arguments)) watcher = subprocess.Popen( cmd, **(kwargs or {})) def finalize(): try: watcher.terminate() except OSError: pass if watcher.returncode is None: try: watcher.communicate(timeout=timeout / 2) except subprocess.TimeoutExpired: watcher.kill() watcher.communicate(timeout=timeout / 2) request.addfinalizer(finalize) # Wait for the service to start. times = 0 while not checker(): if watcher.returncode is not None: raise Exception("Error running {0}".format(name)) if times > timeout: raise Exception('The {0} service checked did not succeed!'.format(name)) time.sleep(1) times += 1 return watcher return watcher_getter_function pytest-services/pytest_services/xvfb.py0000644000175000017500000000503213747000772017106 0ustar pebpeb"""Fixtures for the GUI environment.""" import os import socket import re try: import subprocess32 as subprocess except ImportError: # pragma: no cover import subprocess import pytest from .locks import ( file_lock, ) def xvfb_supports_listen(): """Determine whether the '-listen' option is supported by Xvfb.""" p = subprocess.Popen( ['Xvfb', '-listen', 'TCP', '-__sentinel_parameter__'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) p.wait() _, stderr = p.communicate() match = re.search( br'^Unrecognized option: (?P