diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4580a39..0db2e0b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,20 +1,20 @@ [bumpversion] -current_version = 1.4.0 +current_version = 1.4.1 commit = True tag = True message = "Release {new_version}" [bumpversion:file:setup.py] search = version='{current_version}' replace = version='{new_version}' [bumpversion:file:src/pytest_postgresql/__init__.py] [bumpversion:file:README.rst] [bumpversion:file:CHANGES.rst] search = unreleased ------- replace = {new_version} ------- diff --git a/CHANGES.rst b/CHANGES.rst index c4cd88b..eaac360 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,72 +1,77 @@ CHANGELOG ========= +1.4.1 +------- + +- [bugfix] Allow creating test databse with hyphens + 1.4.0 ------- - [enhancements] Ability to configure additional options for postgresql process and connection - [bugfix] - removed hard dependency on ``psycopg2``, allowing any of its alternative packages, like ``psycopg2-binary``, to be used. - [maintenance] Drop support for python 3.4 and use 3.7 instead 1.3.4 ------- - [bugfix] properly detect if executor running and clean after executor is being stopped .. note:: Previously if a test failed, there was a possibility of the executor being removed when python was closing, causing it to print ignored errors on already unloaded modules. 1.3.3 ------- - [enhancement] use executor's context manager to start/stop postrgesql server in a fixture 1.3.2 ------- - [bugfix] version regexp to correctly catch postgresql 10 1.3.1 ------- - [enhancement] explicitly turn off logging_collector 1.3.0 ------- - [feature] pypy compatibility 1.2.0 ------- - [bugfix] - disallow connection to database before it gets dropped. .. note:: Otherwise it caused random test subprocess to connect again and this the drop was unsucessfull which resulted in many more test failes on setup. - [cleanup] - removed path.py dependency 1.1.1 ------- - [bugfix] - Fixing the default pg_ctl path creation 1.1.0 ------- - [feature] - migrate usage of getfuncargvalue to getfixturevalue. require at least pytest 3.0.0 1.0.0 ------- - create command line and pytest.ini configuration options for postgresql starting parameters - create command line and pytest.ini configuration options for postgresql username - make the port random by default - create command line and pytest.ini configuration options for executable - create command line and pytest.ini configuration options for host - create command line and pytest.ini configuration options for port - Extracted code from pytest-dbfixtures diff --git a/README.rst b/README.rst index a11681c..8dc7836 100644 --- a/README.rst +++ b/README.rst @@ -1,138 +1,138 @@ pytest-postgresql ================= .. image:: https://img.shields.io/pypi/v/pytest-postgresql.svg :target: https://pypi.python.org/pypi/pytest-postgresql/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/wheel/pytest-postgresql.svg :target: https://pypi.python.org/pypi/pytest-postgresql/ :alt: Wheel Status .. image:: https://img.shields.io/pypi/pyversions/pytest-postgresql.svg :target: https://pypi.python.org/pypi/pytest-postgresql/ :alt: Supported Python Versions .. image:: https://img.shields.io/pypi/l/pytest-postgresql.svg :target: https://pypi.python.org/pypi/pytest-postgresql/ :alt: License Package status -------------- -.. image:: https://travis-ci.org/ClearcodeHQ/pytest-postgresql.svg?branch=v1.4.0 +.. image:: https://travis-ci.org/ClearcodeHQ/pytest-postgresql.svg?branch=v1.4.1 :target: https://travis-ci.org/ClearcodeHQ/pytest-postgresql :alt: Tests -.. image:: https://coveralls.io/repos/ClearcodeHQ/pytest-postgresql/badge.png?branch=v1.4.0 - :target: https://coveralls.io/r/ClearcodeHQ/pytest-postgresql?branch=v1.4.0 +.. image:: https://coveralls.io/repos/ClearcodeHQ/pytest-postgresql/badge.png?branch=v1.4.1 + :target: https://coveralls.io/r/ClearcodeHQ/pytest-postgresql?branch=v1.4.1 :alt: Coverage Status -.. image:: https://requires.io/github/ClearcodeHQ/pytest-postgresql/requirements.svg?tag=v1.4.0 - :target: https://requires.io/github/ClearcodeHQ/pytest-postgresql/requirements/?tag=v1.4.0 +.. image:: https://requires.io/github/ClearcodeHQ/pytest-postgresql/requirements.svg?tag=v1.4.1 + :target: https://requires.io/github/ClearcodeHQ/pytest-postgresql/requirements/?tag=v1.4.1 :alt: Requirements Status What is this? ============= This is a pytest plugin, that enables you to test your code that relies on a running PostgreSQL Database. It allows you to specify fixtures for PostgreSQL process and client. How to use ========== .. warning:: Tested on PostgreSQL versions > 9.x. See tests for more details. Install with: .. code-block:: sh pip install pytest-postgresql You will also need to install ``psycopg2``, or one of its alternative packagings such as ``psycopg2-binary`` (pre-compiled wheels) or ``psycopg2cffi`` (CFFI based, useful on PyPy). Plugin contains two fixtures: * **postgresql** - it's a client fixture that has functional scope. After each test it ends all leftover connections, and drops test database from PostgreSQL ensuring repeatability. * **postgresql_proc** - session scoped fixture, that starts PostgreSQL instance at it's first use and stops at the end of the tests. Simply include one of these fixtures into your tests fixture list. You can also create additional postgresql client and process fixtures if you'd need to: .. code-block:: python from pytest_postgresql import factories postgresql_my_proc = factories.postgresql_proc( port=None, logsdir='/tmp') postgresql_my = factories.postgresql('postgresql_my_proc') .. note:: Each PostgreSQL process fixture can be configured in a different way than the others through the fixture factory arguments. Configuration ============= You can define your settings in three ways, it's fixture factory argument, command line option and pytest.ini configuration option. You can pick which you prefer, but remember that these settings are handled in the following order: * ``Fixture factory argument`` * ``Command line option`` * ``Configuration option in your pytest.ini file`` +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | PostgreSQL option | Fixture factory argument | Command line option | pytest.ini option | Default | +==========================+==========================+============================+==========================+====================================+ | Path to executable | executable | --postgresql-exec | postgresql_exec | /usr/lib/postgresql/9.1/bin/pg_ctl | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | host | host | --postgresql-host | postgresql_host | 127.0.0.1 | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | port | port | --postgresql-port | postgresql_port | random | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | postgresql user | user | --postgresql-user | postgresql_user | postgres | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | Starting parameters | startparams | --postgresql-startparams | postgresql_startparams | -w | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | Log directory location | logsdir | --postgresql-logsdir | postgresql_logsdir | $TMPDIR | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | Log filename's prefix | logsprefix | --postgresql-logsprefix | postgresql_logsprefix | | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ | Location for unixsockets | unixsocket | --postgresql-unixsocketdir | postgresql_unixsocketdir | $TMPDIR | +--------------------------+--------------------------+----------------------------+--------------------------+------------------------------------+ Example usage: * pass it as an argument in your own fixture .. code-block:: python postgresql_proc = factories.postgresql_proc( port=8888) * use ``--postgresql-port`` command line option when you run your tests .. code-block:: py.test tests --postgresql-port=8888 * specify your port as ``postgresql_port`` in your ``pytest.ini`` file. To do so, put a line like the following under the ``[pytest]`` section of your ``pytest.ini``: .. code-block:: ini [pytest] postgresql_port = 8888 Package resources ----------------- * Bug tracker: https://github.com/ClearcodeHQ/pytest-postgresql/issues diff --git a/requirements-test.txt b/requirements-test.txt index 3167391..3c6db37 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,9 +1,9 @@ # test runs requirements (versions we'll be testing against) - automatically updated by requires.io pip>=9 # minimum installation requirements setuptools>=21 # minimum installation requirements coverage==4.5.3 # pytest-cov -pytest==4.4.0 -psycopg2-binary==2.8.1; platform_python_implementation != "PyPy" +pytest==4.4.1 +psycopg2-binary==2.8.2; platform_python_implementation != "PyPy" psycopg2cffi==2.8.1; platform_python_implementation == "PyPy" port-for==0.4 mirakuru==1.1.0 diff --git a/setup.py b/setup.py index 0583104..f5c86da 100644 --- a/setup.py +++ b/setup.py @@ -1,102 +1,102 @@ # -*- coding: utf-8 -*- # Copyright (C) 2016 by Clearcode # and associates (see AUTHORS). # This file is part of pytest-postgresql. # pytest-postgresql is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # pytest-postgresql is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with pytest-postgresql. If not, see . """pytest-postgresql setup.py module.""" import os from setuptools import setup, find_packages here = os.path.dirname(__file__) def read(fname): """ Read given file's content. :param str fname: file name :returns: file contents :rtype: str """ return open(os.path.join(here, fname)).read() requirements = [ 'pytest>=3.0.0', 'port-for', 'mirakuru' ] test_requires = [ 'pytest-cov==2.6.1', 'pytest-xdist==1.28.0', ] extras_require = { 'docs': ['sphinx'], 'tests': test_requires, } setup_requires = [ 'setuptools>=21', 'pip>=9' ] setup( name='pytest-postgresql', - version='1.4.0', + version='1.4.1', description='Postgresql fixtures and fixture factories for Pytest.', long_description=( read('README.rst') + '\n\n' + read('CHANGES.rst') ), keywords='tests py.test pytest fixture postgresql', author='Clearcode - The A Room', author_email='thearoom@clearcode.cc', url='https://github.com/ClearcodeHQ/pytest-postgresql', license='LGPLv3+', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: ' 'GNU Lesser General Public License v3 or later (LGPLv3+)', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries :: Python Modules', ], package_dir={'': 'src'}, packages=find_packages('src'), install_requires=requirements, tests_require=test_requires, setup_requires=setup_requires, test_suite='tests', entry_points={ 'pytest11': [ 'pytest_postgresql = pytest_postgresql.plugin' ]}, include_package_data=True, zip_safe=False, extras_require=extras_require, ) diff --git a/src/pytest_postgresql/__init__.py b/src/pytest_postgresql/__init__.py index 1ebb785..4e72ad2 100644 --- a/src/pytest_postgresql/__init__.py +++ b/src/pytest_postgresql/__init__.py @@ -1,27 +1,27 @@ # -*- coding: utf-8 -*- # Copyright (C) 2016 by Clearcode # and associates (see AUTHORS). # This file is part of pytest-postgresql. # pytest-postgresql is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # pytest-postgresql is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with pytest-postgresql. If not, see . """Main module for pytest-postgresql.""" try: import psycopg2cffi.compat # pylint:disable=import-error except ImportError: pass else: psycopg2cffi.compat.register() -__version__ = '1.4.0' +__version__ = '1.4.1' diff --git a/src/pytest_postgresql/factories.py b/src/pytest_postgresql/factories.py index 5b778f2..ac390da 100644 --- a/src/pytest_postgresql/factories.py +++ b/src/pytest_postgresql/factories.py @@ -1,301 +1,301 @@ # Copyright (C) 2013-2016 by Clearcode # and associates (see AUTHORS). # This file is part of pytest-dbfixtures. # pytest-dbfixtures is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # pytest-dbfixtures is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with pytest-dbfixtures. If not, see . """Fixture factories for postgresql fixtures.""" import os.path import time import shutil import platform import subprocess from tempfile import gettempdir import pytest try: import psycopg2 except ImportError: psycopg2 = False from pytest_postgresql.executor import PostgreSQLExecutor from pytest_postgresql.port import get_port def get_config(request): """Return a dictionary with config options.""" config = {} options = [ 'exec', 'host', 'port', 'user', 'options', 'startparams', 'logsdir', 'logsprefix', 'unixsocketdir' ] for option in options: option_name = 'postgresql_' + option conf = request.config.getoption(option_name) or \ request.config.getini(option_name) config[option] = conf return config START_INFO = 'database system is ready to accept connections' def wait_for_postgres(logfile, awaited_msg): """ Wait for postgresql being started. :param str logfile: logfile path :param str awaited_msg: awaited message """ # wait until logfile is created while not os.path.isfile(logfile): time.sleep(1) # wait for expected message. while 1: with open(logfile, 'r') as content_file: content = content_file.read() if awaited_msg in content: break time.sleep(1) def remove_postgresql_directory(datadir): """ Remove directory created for postgresql run. :param str datadir: datadir path """ if os.path.isdir(datadir): shutil.rmtree(datadir) def init_postgresql_directory(postgresql_ctl, user, datadir): """ Initialize postgresql data directory. See `Initialize postgresql data directory `_ :param str postgresql_ctl: ctl path :param str user: postgresql username :param str datadir: datadir path """ # remove old one if exists first. remove_postgresql_directory(datadir) init_directory = ( postgresql_ctl, 'initdb', '-o "--auth=trust --username=%s"' % user, '-D %s' % datadir, ) subprocess.check_output(' '.join(init_directory), shell=True) def init_postgresql_database(user, host, port, db_name): """ Create database in postgresql. :param str user: postgresql username :param str host: postgresql host :param str port: postgresql port :param str db_name: database name """ conn = psycopg2.connect(user=user, host=host, port=port) conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) cur = conn.cursor() - cur.execute('CREATE DATABASE {0};'.format(db_name)) + cur.execute('CREATE DATABASE "{0}";'.format(db_name)) cur.close() conn.close() def drop_postgresql_database(user, host, port, db_name, version): """ Drop databse in postgresql. :param str user: postgresql username :param str host: postgresql host :param str port: postgresql port :param str db_name: database name :param str version: postgresql version number """ conn = psycopg2.connect(user=user, host=host, port=port) conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) cur = conn.cursor() # We cannot drop the database while there are connections to it, so we # terminate all connections first while not allowing new connections. if float(version) >= 9.2: pid_column = 'pid' else: pid_column = 'procpid' cur.execute( 'UPDATE pg_database SET datallowconn=false WHERE datname = %s;', (db_name,)) cur.execute( 'SELECT pg_terminate_backend(pg_stat_activity.{0})' 'FROM pg_stat_activity WHERE pg_stat_activity.datname = %s;'.format( pid_column), (db_name,)) - cur.execute('DROP DATABASE IF EXISTS {0};'.format(db_name)) + cur.execute('DROP DATABASE IF EXISTS "{0}";'.format(db_name)) cur.close() conn.close() def postgresql_proc( executable=None, host=None, port=-1, user=None, options='', startparams=None, unixsocketdir=None, logsdir=None, logs_prefix='', ): """ Postgresql process factory. :param str executable: path to postgresql_ctl :param str host: hostname :param str|int|tuple|set|list port: exact port (e.g. '8000', 8000) randomly selected port (None) - any random available port -1 - command line or pytest.ini configured port [(2000,3000)] or (2000,3000) - random available port from a given range [{4002,4003}] or {4002,4003} - random of 4002 or 4003 ports [(2000,3000), {4002,4003}] - random of given range and set :param str user: postgresql username :param str startparams: postgresql starting parameters :param str unixsocketdir: directory to create postgresql's unixsockets :param str logsdir: location for logs :param str logs_prefix: prefix for log filename :rtype: func :returns: function which makes a postgresql process """ @pytest.fixture(scope='session') def postgresql_proc_fixture(request): """ Process fixture for PostgreSQL. :param FixtureRequest request: fixture request object :rtype: pytest_dbfixtures.executors.TCPExecutor :returns: tcp executor """ config = get_config(request) postgresql_ctl = executable or config['exec'] # check if that executable exists, as it's no on system PATH # only replace if executable isn't passed manually if not os.path.exists(postgresql_ctl) and executable is None: pg_bindir = subprocess.check_output( ['pg_config', '--bindir'], universal_newlines=True ).strip() postgresql_ctl = os.path.join(pg_bindir, 'pg_ctl') pg_host = host or config['host'] pg_port = get_port(port) or get_port(config['port']) datadir = os.path.join( gettempdir(), 'postgresqldata.{0}'.format(pg_port)) pg_user = user or config['user'] pg_options = options or config['options'] pg_unixsocketdir = unixsocketdir or config['unixsocketdir'] pg_startparams = startparams or config['startparams'] pg_logsdir = logsdir or config['logsdir'] logfile_path = os.path.join( pg_logsdir, '{prefix}postgresql.{port}.log'.format( prefix=logs_prefix, port=pg_port )) init_postgresql_directory( postgresql_ctl, pg_user, datadir ) if platform.system() == 'FreeBSD': with (datadir / 'pg_hba.conf').open(mode='a') as conf_file: conf_file.write('host all all 0.0.0.0/0 trust\n') postgresql_executor = PostgreSQLExecutor( executable=postgresql_ctl, host=pg_host, port=pg_port, user=pg_user, options=pg_options, datadir=datadir, unixsocketdir=pg_unixsocketdir, logfile=logfile_path, startparams=pg_startparams, ) # start server with postgresql_executor: if '-w' in pg_startparams: wait_for_postgres(logfile_path, START_INFO) yield postgresql_executor remove_postgresql_directory(datadir) return postgresql_proc_fixture def postgresql(process_fixture_name, db_name='tests'): """ Return connection fixture factory for PostgreSQL. :param str process_fixture_name: name of the process fixture :param str db_name: database name :rtype: func :returns: function which makes a connection to postgresql """ @pytest.fixture def postgresql_factory(request): """ Fixture factory for PostgreSQL. :param FixtureRequest request: fixture request object :rtype: psycopg2.connection :returns: postgresql client """ if not psycopg2: raise ImportError( 'No module named psycopg2. Please install either ' 'psycopg2 or psycopg2-binary package for CPython ' 'or psycopg2cffi for Pypy.' ) proc_fixture = request.getfixturevalue(process_fixture_name) # _, config = try_import('psycopg2', request) pg_host = proc_fixture.host pg_port = proc_fixture.port pg_user = proc_fixture.user pg_options = proc_fixture.options pg_db = db_name init_postgresql_database(pg_user, pg_host, pg_port, pg_db) connection = psycopg2.connect( dbname=pg_db, user=pg_user, host=pg_host, port=pg_port, options=pg_options ) def drop_database(): connection.close() drop_postgresql_database( pg_user, pg_host, pg_port, pg_db, proc_fixture.version ) request.addfinalizer(drop_database) return connection return postgresql_factory __all__ = ('postgresql', 'postgresql_proc') diff --git a/tests/conftest.py b/tests/conftest.py index 1c00ae2..87155e5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,27 +1,27 @@ """Tests main conftest file.""" import sys import warnings from pytest_postgresql import factories if not sys.version_info >= (3, 5): warnings.simplefilter("error", category=DeprecationWarning) PG_CTL = '/usr/lib/postgresql/{ver}/bin/pg_ctl' # pylint:disable=invalid-name postgresql92 = factories.postgresql_proc(PG_CTL.format(ver='9.2'), port=None) postgresql93 = factories.postgresql_proc(PG_CTL.format(ver='9.3'), port=None) postgresql94 = factories.postgresql_proc(PG_CTL.format(ver='9.4'), port=None) postgresql95 = factories.postgresql_proc(PG_CTL.format(ver='9.5'), port=None) postgresql96 = factories.postgresql_proc(PG_CTL.format(ver='9.6'), port=None) postgresql10 = factories.postgresql_proc(PG_CTL.format(ver='10'), port=None) postgresql101 = factories.postgresql_proc(PG_CTL.format(ver='10.1'), port=None) postgresql_proc2 = factories.postgresql_proc(port=9876) -postgresql2 = factories.postgresql('postgresql_proc2') +postgresql2 = factories.postgresql('postgresql_proc2', db_name='test-db') postgresql_rand_proc = factories.postgresql_proc(port=None) postgresql_rand = factories.postgresql('postgresql_rand_proc') # pylint:enable=invalid-name