diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..bae2e82 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,17 @@ +[bumpversion] +current_version = 3.1.3 +commit = True +tag = True +message = "Release {new_version}" + +[bumpversion:file:setup.cfg] +search = version = {current_version} +replace = version = {new_version} + +[bumpversion:file:src/pytest_postgresql/__init__.py] + +[bumpversion:file:CHANGES.rst] +search = unreleased + ---------- +replace = {new_version} + ---------- diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..1e01055 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +source = pytest_postgresql diff --git a/.github/ISSUE_TEMPLATE.rst b/.github/ISSUE_TEMPLATE.rst new file mode 100644 index 0000000..0946e37 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.rst @@ -0,0 +1,7 @@ +### What action do you want to perform + + +### What are the results + + +### What are the expected results \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.rst b/.github/PULL_REQUEST_TEMPLATE.rst new file mode 100644 index 0000000..8f849ce --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.rst @@ -0,0 +1,3 @@ +Fixes #[ISSUE_NUMBER_HERE]. + +Changes proposed. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ea7a070 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 2 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + time: "04:00" + open-pull-requests-limit: 2 diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..57da09f --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,37 @@ +name: Merge me test dependencies! + +on: + workflow_run: + types: + - completed + workflows: + # List all required workflow names here. + - 'Run tests' + - 'Run tests on macos' + - 'Test build package' + +jobs: + merge-me: + name: Merge me! + runs-on: ubuntu-latest + steps: + - # It is often a desired behavior to merge only when a workflow execution + # succeeds. This can be changed as needed. + if: ${{ github.event.workflow_run.conclusion == 'success' }} + name: Merge me! + uses: ridedott/merge-me-action@v2.9.50 + with: + # Depending on branch protection rules, a manually populated + # `GITHUB_TOKEN_WORKAROUND` secret with permissions to push to + # a protected branch must be used. This secret can have an arbitrary + # name, as an example, this repository uses `DOTTBOTT_TOKEN`. + # + # When using a custom token, it is recommended to leave the following + # comment for other developers to be aware of the reasoning behind it: + # + # This must be used as GitHub Actions token does not support pushing + # to protected branches. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MERGE_METHOD: MERGE + PRESET: DEPENDABOT_MINOR + ENABLED_FOR_MANUAL_CHANGES: 'true' diff --git a/.github/workflows/automergelint.yml b/.github/workflows/automergelint.yml new file mode 100644 index 0000000..edb2091 --- /dev/null +++ b/.github/workflows/automergelint.yml @@ -0,0 +1,37 @@ +name: Merge me linter dependencies! + +on: + workflow_run: + types: + - completed + workflows: + # List all required workflow names here. + - 'Run linters' + - 'Run tests' + - 'Test build package' + +jobs: + merge-me: + name: Merge me! + runs-on: ubuntu-latest + steps: + - # It is often a desired behavior to merge only when a workflow execution + # succeeds. This can be changed as needed. + if: ${{ github.event.workflow_run.conclusion == 'success' }} + name: Merge me! + uses: ridedott/merge-me-action@v2.9.50 + with: + # Depending on branch protection rules, a manually populated + # `GITHUB_TOKEN_WORKAROUND` secret with permissions to push to + # a protected branch must be used. This secret can have an arbitrary + # name, as an example, this repository uses `DOTTBOTT_TOKEN`. + # + # When using a custom token, it is recommended to leave the following + # comment for other developers to be aware of the reasoning behind it: + # + # This must be used as GitHub Actions token does not support pushing + # to protected branches. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MERGE_METHOD: MERGE + PRESET: DEPENDABOT_MINOR + ENABLED_FOR_MANUAL_CHANGES: 'true' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..89a9b4f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,11 @@ +name: Test build package + +on: + push: + branches: [ v3 ] + pull_request: + branches: [ v3 ] + +jobs: + build: + uses: fizyk/actions-reuse/.github/workflows/pypi.yml@v1.1.1 diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..466664a --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,97 @@ +name: Run linters + +on: + push: + branches: [ v3 ] + paths: + - '**.py' + - .github/workflows/linters.yml + - requirements-lint.txt + pull_request: + branches: [ v3 ] + +jobs: + pydocstyle: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-lint.txt + - name: Run pydocstyle + run: | + pydocstyle src/ tests/ + + flake8: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-lint.txt + - name: Run flake8 + run: | + flake8 src/ tests/ + black: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: psf/black@21.7b0 + + imports: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-test.txt + - name: Check if the code can be imported without psycopg2 + run: | + python -c "import pytest_postgresql" + python -c "import pytest_postgresql.plugin" + python -c "import pytest_postgresql.factories" + python -c "import pytest_postgresql.factories.client" + python -c "import pytest_postgresql.factories.noprocess" + python -c "import pytest_postgresql.factories.process" + python -c "import pytest_postgresql.janitor" + + mypy: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-lint.txt + - name: Run mypy check + run: | + mypy src tests diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..ba8128a --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,12 @@ +name: Package and publish +on: + push: + tags: + - v* +jobs: + build-n-publish: + uses: fizyk/actions-reuse/.github/workflows/pypi.yml@v1.1.1 + with: + publish: true + secrets: + pypi_token: ${{ secrets.pypi_password }} diff --git a/.github/workflows/tests-macos.yml b/.github/workflows/tests-macos.yml new file mode 100644 index 0000000..6a65dcd --- /dev/null +++ b/.github/workflows/tests-macos.yml @@ -0,0 +1,64 @@ +name: Run tests on macos + +on: + push: + branches: [ v3 ] + paths: + - '**.py' + - .github/workflows/tests-macos.yml + - requirements-test.txt + - requirements-test-withpsycopg.txt + pull_request: + branches: [ v3 ] + paths: + - '**.py' + - .github/workflows/tests-macos.yml + - requirements-test.txt + - requirements-test-withpsycopg.txt + +jobs: + macostests: + runs-on: macos-latest + strategy: + fail-fast: true + matrix: + python-version: [3.8, 3.9, "3.10", pypy-3.8-v7.3.7] + postgres-version: [13] + env: + OS: macos-latest + PYTHON: ${{ matrix.python-version }} + POSTGRES: ${{ matrix.postgres-version }} + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install postgresql + run: | + brew install postgresql@${{ matrix.postgres-version }} + - name: Check installed locales + run: | + locale -a + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-test-withpsycopg.txt + - name: Install dependencies + run: | + echo "TMPDIR=$TMPDIR" >> $GITHUB_ENV + - name: Run test + run: | + py.test -n 0 --postgresql-exec="/usr/lib/postgresql/${{ matrix.postgres-version }}/bin/pg_ctl" -k "not docker" --cov-report=xml + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: postgresql-${{ matrix.python-version }}-${{ matrix.postgres-version }} + path: /${{ env.TMPDIR }}/pytest-of-runner/** + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2.1.0 + with: + flags: macos + env_vars: OS, PYTHON, POSTGRES + fail_ci_if_error: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6e05583 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,88 @@ +name: Run tests + +on: + push: + branches: [ v3 ] + paths: + - '**.py' + - .github/workflows/tests.yml + - requirements-test.txt + - requirements-test-withpsycopg.txt + pull_request: + branches: [ v3 ] + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + python-version: [3.7, 3.8, 3.9, "3.10", pypy-3.8-v7.3.7] + postgres-version: [12, 13, 14] + env: + OS: ubuntu-latest + PYTHON: ${{ matrix.python-version }} + POSTGRES: ${{ matrix.postgres-version }} + # Service containers to run with `container-job` + services: + # Label used to access the service container + postgres: + # Docker Hub image: + image: postgres:${{ matrix.postgres-version }} + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5433:5432 + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install postgresql + run: | + sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg ${{ matrix.postgres-version }}" > /etc/apt/sources.list.d/pgdg.list' + sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo sh -c 'echo "Package: *" > /etc/apt/preferences.d/psql' + sudo sh -c 'echo "Pin: release c=${{ matrix.poxtgresql-version }}" >> /etc/apt/preferences.d/psql' + sudo sh -c 'echo "Pin-Priority: 1000" >> /etc/apt/preferences.d/psql' + sudo apt-get update -o Dir::Etc::sourcelist="/etc/apt/sources.list.d/pgdg.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0" + sudo apt install -y postgresql-${{ matrix.postgres-version }} postgresql-client-${{ matrix.postgres-version }} + - name: Check installed locales + run: | + locale -a + sudo locale-gen de_DE.UTF-8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-test-withpsycopg.txt + - name: Run test + run: | + py.test -n 0 --postgresql-exec="/usr/lib/postgresql/${{ matrix.postgres-version }}/bin/pg_ctl" -k "not docker" --cov-report=xml + - name: Run xdist test + run: | + py.test -n 1 --postgresql-exec="/usr/lib/postgresql/${{ matrix.postgres-version }}/bin/pg_ctl" -k "not docker" --cov-report=xml:coverage-xdist.xml + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: postgresql-${{ matrix.python-version }}-${{ matrix.postgres-version }} + path: /tmp/pytest-of-runner/** + - name: Run test noproc fixture on docker + run: | + pytest -n 0 -k docker --postgresql-host=localhost --postgresql-port 5433 --postgresql-password=postgres --cov-report=xml:coverage-docker.xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2.1.0 + with: + flags: linux + env_vars: OS, PYTHON, POSTGRES + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfc76f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +.cache +.pytest_cache +venv/ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject +.idea diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6aef5b3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +--- +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + # We use locally installed linters so all version numbers are kept in + # requirements-lint.txt. + - repo: local + hooks: + - id: black + name: black + entry: black + language: system + types: + - python + require_serial: true + - id: flake8 + name: flake8 + entry: flake8 + language: system + types_or: + - python + - pyi + - id: pydocstyle + name: pydocstyle + entry: pydocstyle + language: system + types: + - python + - id: mypy + name: mypy + entry: mypy + language: system + types: + - python + # Workaround . + require_serial: true diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 7ece1fe..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,787 +0,0 @@ -Metadata-Version: 2.1 -Name: pytest-postgresql -Version: 3.1.3 -Summary: Postgresql fixtures and fixture factories for Pytest. -Home-page: https://github.com/ClearcodeHQ/pytest-postgresql -Maintainer: Grzegorz Śliwiński -Maintainer-email: fizyk+pypi@fizyk.net.pl -License: LGPLv3+ -Keywords: tests,py.test,pytest,fixture,postgresql -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Software Development :: Testing -Classifier: Framework :: Pytest -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst -Provides-Extra: tests -License-File: COPYING -License-File: COPYING.lesser -License-File: AUTHORS.rst - -.. image:: https://raw.githubusercontent.com/ClearcodeHQ/pytest-postgresql/master/logo.png - :width: 100px - :height: 100px - -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 - -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.6. See tests for more details. - -Install with: - -.. code-block:: sh - - pip install pytest-postgresql - -You will also need to install ``psycopg2`` (2.9 or newer), or one of its alternative packagings such as ``psycopg2-binary`` -(pre-compiled wheels) or ``psycopg2cffi`` (CFFI based, useful on PyPy). - -Plugin contains three 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. - This fixture returns already connected psycopg2 connection. - -* **postgresql_proc** - session scoped fixture, that starts PostgreSQL instance - at it's first use and stops at the end of the tests. -* **postgresql_noproc** - a noprocess fixture, that's connecting to already - running postgresql instance. - For example on dockerized test environments, or CI providing postgresql services - -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, unixsocketdir='/var/run') - 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. - -Sample test - -.. code-block:: python - - def test_example_postgres(postgresql): - """Check main postgresql fixture.""" - cur = postgresql.cursor() - cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);") - postgresql.commit() - cur.close() - -If you want the database fixture to be automatically populated with your schema there are two ways: - -#. client fixture specific -#. process fixture specific - -Both are accepting same set of possible loaders: - -* sql file path -* loading function import path (string) -* actual loading function - -That function will receive **host**, **port**, **user**, **dbname** and **password** kwargs and will have to perform -connection to the database inside. However, you'll be able to run SQL files or even trigger programmatically database -migrations you have. - -Client specific loads the database each test - -.. code-block:: python - - postgresql_my_with_schema = factories.postgresql( - 'postgresql_my_proc', - load=["schemafile.sql", "otherschema.sql", "import.path.to.function", "import.path.to:otherfunction", load_this] - ) - -.. warning:: - - This way, the database will still be dropped each time. - - -The process fixture performs the load once per test session, and loads the data into the template database. -Client fixture then creates test database out of the template database each test, which significantly speeds up the tests. - -.. code-block:: python - - postgresql_my_proc = factories.postgresql_proc( - load=["schemafile.sql", "otherschema.sql", "import.path.to.function", "import.path.to:otherfunction", load_this] - ) - - -.. code-block:: bash - - pytest --postgresql-populate-template=path.to.loading_function --postgresql-populate-template=path.to.other:loading_function --postgresql-populate-template=path/to/file.sql - - -The loading_function from example will receive , and have to commit that. -Connecting to already existing postgresql database --------------------------------------------------- - -Some projects are using already running postgresql servers (ie on docker instances). -In order to connect to them, one would be using the ``postgresql_noproc`` fixture. - -.. code-block:: python - - postgresql_external = factories.postgresql('postgresql_noproc') - -By default the ``postgresql_noproc`` fixture would connect to postgresql instance using **5432** port. Standard configuration options apply to it. - -These are the configuration options that are working on all levels with the ``postgresql_noproc`` fixture: - -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`` - - -.. list-table:: Configuration options - :header-rows: 1 - - * - PostgreSQL option - - Fixture factory argument - - Command line option - - pytest.ini option - - Noop process fixture - - Default - * - Path to executable - - executable - - --postgresql-exec - - postgresql_exec - - - - - /usr/lib/postgresql/9.6/bin/pg_ctl - * - host - - host - - --postgresql-host - - postgresql_host - - yes - - 127.0.0.1 - * - port - - port - - --postgresql-port - - postgresql_port - - yes (5432) - - random - * - postgresql user - - user - - --postgresql-user - - postgresql_user - - yes - - postgres - * - password - - password - - --postgresql-password - - postgresql_password - - yes - - - * - Starting parameters (extra pg_ctl arguments) - - startparams - - --postgresql-startparams - - postgresql_startparams - - - - - -w - * - Postgres exe extra arguments (passed via pg_ctl's -o argument) - - postgres_options - - --postgresql-postgres-options - - postgresql_postgres_options - - - - - - * - Log filename's prefix - - logsprefix - - --postgresql-logsprefix - - postgresql_logsprefix - - - - - - * - Location for unixsockets - - unixsocket - - --postgresql-unixsocketdir - - postgresql_unixsocketdir - - - - - $TMPDIR - * - Database name - - dbname - - --postgresql-dbname - - postgresql_dbname - - - - - test - * - Default Schema either in sql files or import path to function that will load it (list of values for each) - - load - - --postgresql-load - - postgresql_load - - yes - - - * - PostgreSQL connection options - - options - - --postgresql-options - - postgresql_options - - yes - - - - -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 - -Examples -======== - -Populating database for tests ------------------------------ - -With SQLAlchemy -+++++++++++++++ - -This example shows how to populate database and create an SQLAlchemy's ORM connection: - -Sample below is simplified session fixture from -`pyramid_fullauth `_ tests: - -.. code-block:: python - - from sqlalchemy import create_engine - from sqlalchemy.orm import scoped_session, sessionmaker - from sqlalchemy.pool import NullPool - from zope.sqlalchemy import register - - - @pytest.fixture - def db_session(postgresql): - """Session for SQLAlchemy.""" - from pyramid_fullauth.models import Base - - connection = f'postgresql+psycopg2://{postgresql.info.user}:@{postgresql.info.host}:{postgresql.info.port}/{postgresql.info.dbname}' - - engine = create_engine(connection, echo=False, poolclass=NullPool) - pyramid_basemodel.Session = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) - pyramid_basemodel.bind_engine( - engine, pyramid_basemodel.Session, should_create=True, should_drop=True) - - yield pyramid_basemodel.Session - - transaction.commit() - Base.metadata.drop_all(engine) - - - @pytest.fixture - def user(db_session): - """Test user fixture.""" - from pyramid_fullauth.models import User - from tests.tools import DEFAULT_USER - - new_user = User(**DEFAULT_USER) - db_session.add(new_user) - transaction.commit() - return new_user - - - def test_remove_last_admin(db_session, user): - """ - Sample test checks internal login, but shows usage in tests with SQLAlchemy - """ - user = db_session.merge(user) - user.is_admin = True - transaction.commit() - user = db_session.merge(user) - - with pytest.raises(AttributeError): - user.is_admin = False -.. note:: - - See the original code at `pyramid_fullauth's conftest file `_. - Depending on your needs, that in between code can fire alembic migrations in case of sqlalchemy stack or any other code - -Maintaining database state outside of the fixtures --------------------------------------------------- - -It is possible and appears it's used in other libraries for tests, -to maintain database state with the use of the ``pytest-postgresql`` database -managing functionality: - -For this import DatabaseJanitor and use its init and drop methods: - - -.. code-block:: python - - import pytest - from pytest_postgresql.janitor import DatabaseJanitor - - @pytest.fixture - def database(postgresql_proc): - # variable definition - - janitor = DatabaseJanitor( - postgresql_proc.user, - postgresql_proc.host, - postgresql_proc.port, - "my_test_database", - postgresql_proc.version, - password="secret_password, - ): - janitor.init() - yield psycopg2.connect( - dbname="my_test_database", - user=postgresql_proc.user, - password="secret_password", - host=postgresql_proc.host, - port=postgresql_proc.port, - ) - janitor.drop() - -or use it as a context manager: - -.. code-block:: python - - import pytest - from pytest_postgresql.janitor import DatabaseJanitor - - @pytest.fixture - def database(postgresql_proc): - # variable definition - - with DatabaseJanitor( - postgresql_proc.user, - postgresql_proc.host, - postgresql_proc.port, - "my_test_database", - postgresql_proc.version, - password="secret_password, - ): - yield psycopg2.connect( - dbname="my_test_database", - user=postgresql_proc.user, - password="secret_password", - host=postgresql_proc.host, - port=postgresql_proc.port, - ) - -.. note:: - - DatabaseJanitor manages the state of the database, but you'll have to create - connection to use in test code yourself. - - You can optionally pass in a recognized postgresql ISOLATION_LEVEL for - additional control. - -.. note:: - - See DatabaseJanitor usage in python's warehouse test code https://github.com/pypa/warehouse/blob/5d15bfe/tests/conftest.py#L127 - -Connecting to Postgresql (in a docker) --------------------------------------- - -To connect to a docker run postgresql and run test on it, use noproc fixtures. - -.. code-block:: sh - - docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres - -This will start postgresql in a docker container, however using a postgresql installed locally is not much different. - -In tests, make sure that all your tests are using **postgresql_noproc** fixture like that: - -.. code-block:: python - - postgresql_in_docker = factories.postgresql_noproc() - postresql = factories.postgresql("postgresql_in_docker", db_name="test") - - - def test_postgres_docker(postresql): - """Run test.""" - cur = postgresql.cursor() - cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);") - postgresql.commit() - cur.close() - -And run tests: - -.. code-block:: sh - - pytest --postgresql-host=172.17.0.2 --postgresql-password=mysecretpassword - -Using a common database initialisation between tests ----------------------------------------------------- - -If you've got several tests that require common initialisation, you need to define a `load` and pass it to -your custom postgresql process fixture: - -.. code-block:: python - - import pytest_postgresql.factories - def load_database(**kwargs): - db_connection: connection = psycopg2.connect(**kwargs) - with db_connection.cursor() as cur: - cur.execute("CREATE TABLE stories (id serial PRIMARY KEY, name varchar);") - cur.execute( - "INSERT INTO stories (name) VALUES" - "('Silmarillion'), ('Star Wars'), ('The Expanse'), ('Battlestar Galactica')" - ) - db_connection.commit() - - postgresql_proc = factories.postgresql_proc( - load=[load_database], - ) - - postgresql = factories.postgresql( - "postgresql_proc", - ) - -You can also define your own database name by passing same dbname value -to **both** factories. - -The way this will work is that the process fixture will populate template database, -which in turn will be used automatically by client fixture to create a test database from scratch. -Fast, clean and no dangling transactions, that could be accidentally rolled back. - -Same approach will work with noproces fixture, while connecting to already running postgresql instance whether -it'll be on a docker machine or running remotely or locally. - -CHANGELOG -========= - -3.1.3 ----------- - -Cherry picked from v4.x - -Misc -++++ - -- Import FixtureRequest from pytest, not private _pytest. - Require at least pytest 6.2 -- Replace tmpdir_factory with tmp_path_factory -- Add Postgresql 14 to the CI - -3.1.2 ----------- - -Bugfix -++++++ - -- Database can be created by DatabaseJanitor or the client fixture when an isolation - level is specified. - -3.1.1 ----------- - -Misc -++++ - -- rely on `get_port` functionality delivered by `port_for` - -3.1.0 ----------- - -Features -++++++++ - -- Added type annotations and compatibitlity with PEP 561 - -Misc -++++ - -- pre-commit configuration - -3.0.2 ----------- - -Bugfix -++++++ - -- Changed `UPDATE pg_database SET` to `ALTER`. System tables should not be updated. - -3.0.1 ----------- - -Bugfix -++++++ - -- Fixed DatabaseJanitor port type hint to int from str -- Changed retry definition to not fail if psycopg2 is not installed. - Now the default is Exception. - -Misc -++++ - -- Support python 3.7 and up - -3.0.0 ----------- - -Features -++++++++ - -- Ability to create template database once for the process fixture and - re-recreate a clean database out of it every test. Not only it does provide some - common db initialisation between tests but also can speed up tests significantly, - especially if the initialisation has lots of operations to perform. -- DatabaseJanitor can now define a `connection_timeout` parameter. - How long will it try to connect to database before raising a TimeoutError -- Updated supported python versions -- Unified temporary directory handling in fixture. Settled on tmpdir_factory. -- Fully moved to the Github Actions as CI/CD pipeline - -Deprecations -++++++++++++ - -- Deprecated support for `logs_prefix` process fixture factory argument, - `--postgresql-logsprefix` pytest command line option and `postgresql_logsprefix` - ini configuration option. tmpdir_factory now builds pretty unique temporary directory structure. - -Backward Incompatibilities -++++++++++++++++++++++++++ - -- Dropped support for postgresql 9.5 and down -- Removed init_postgresql_database and drop_postgresql_database functions. - They were long deprecated and their role perfectly covered by DatabaseJanitor class. -- `pytest_postgresql.factories.get_config` was moved to `pytest_postgresql.config.get_config` -- all `db_name` keywords and attributes were renamed to `dbname` -- postgresql_nooproc fixture was renamed to postgresql_noproc - -Bugfix -++++++ - -- Use `postgresql_logsprefix` and `--postgresql-logsprefix` again. - They were stopped being used somewhere along the way. -- Sometimes pytest-postrgesql would fail to start postgresql with - "FATAL: the database system is starting up" message. It's not really a fatal error, - but a message indicating that the process still starts. Now pytest-postgresql will wait properly in this cases. - -2.6.1 ----------- - -- [bugfix] To not fail loading code if no postgresql version is installed. - Fallback for janitor and process fixture only, if called upon. - -2.6.0 ----------- - -- [enhancement] add ability to pass options to pg_ctl's -o flag to send arguments to the underlying postgres executable - Use `postgres_options` as fixture argument, `--postgresql-postgres-options` as pytest starting option or - `postgresql_postgres_options` as pytest.ini configuration option - -2.5.3 ----------- - -- [enhancement] Add ability to set up isolation level for fixture and janitor - -2.5.2 ----------- - -- [fix] Status checks for running postgres depend on pg_ctl status code, - not on pg_ctl log language. Fixes starting on systems without C locale. - Thanks @Martin Meyries. - - -2.5.1 ----------- - -- [fix] Added LC_* env vars to running initdb and other utilities. - Now all tools and server are using same, C locale - - -2.5.0 ----------- - -- [feature] Ability to define default schema to initialize database with -- [docs] Added more examples to readme on how to use the plugin - - -2.4.1 ----------- - -- [enhancement] extract NoopExecutor into it's own submodule -- [bugfix] Ignore occasional `ProcessFinishedWithError` error on executor exit. -- [bugfix] Fixed setting custom password for process fixture -- [bugfix] Fix version detection, to allow for two-digit minor version part - -2.4.0 ----------- - -- [feature] Drop support for pyhon 3.5 -- [enhancement] require at least mirakuru 2.3.0 (executor's stop method parameter's change) -- [bug] pass password to DatabaseJanitor in client's factory - -2.3.0 ----------- - -- [feature] Allow to set password for postgresql. Use it throughout the flow. -- [bugfix] Default Janitor's connections to postgres database. When using custom users, - postgres attempts to use user's database and it might not exist. -- [bugfix] NoopExecutor connects to read version by context manager to properly handle cases - where it can't connect to the server. - -2.2.1 ----------- - -- [bugfix] Fix drop_postgresql_database to actually use DatabaseJanitor.drop instead of an init - -2.2.0 ----------- - -- [feature] ability to properly connect to already existing postgresql server using ``postgresql_nooproc`` fixture. - -2.1.0 ----------- - -- [enhancement] Gather helper functions maintaining postgresql database in DatabaseJanitor class. -- [deprecate] Deprecate ``init_postgresql_database`` in favour of ``DatabaseJanitor.init`` -- [deprecate] Deprecate ``drop_postgresql_database`` in favour of ``DatabaseJanitor.drop`` - -2.0.0 ----------- - -- [feature] Drop support for python 2.7. From now on, only support python 3.5 and up -- [feature] Ability to configure database name through plugin options -- [enhancement] Use tmpdir_factory. Drop ``logsdir`` parameter -- [ehnancement] Support only Postgresql 9.0 and up -- [bugfix] Always start postgresql with LC_ALL, LC_TYPE and LANG set to C.UTF-8. - It makes postgresql start in english. - -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/logo.png b/logo.png new file mode 100644 index 0000000..f667660 Binary files /dev/null and b/logo.png differ diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..fc9a109 --- /dev/null +++ b/logo.svg @@ -0,0 +1,238 @@ + + + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..e85938c --- /dev/null +++ b/mypy.ini @@ -0,0 +1,29 @@ +[mypy] +allow_redefinition = False +allow_untyped_globals = False +check_untyped_defs = True +disallow_incomplete_defs = True +disallow_subclassing_any = True +disallow_untyped_calls = True +disallow_untyped_decorators = True +disallow_untyped_defs = True +follow_imports = silent +ignore_missing_imports = False +implicit_reexport = False +no_implicit_optional = True +pretty = True +show_error_codes = True +strict_equality = True +warn_no_return = True +warn_return_any = True +warn_unreachable = True +warn_unused_ignores = True + +[mypy-psycopg2.*] +ignore_missing_imports = True + +[mypy-psycopg2cffi.*] +ignore_missing_imports = True + +[mypy-setuptools.*] +ignore_missing_imports = True diff --git a/requirements-lint.txt b/requirements-lint.txt new file mode 100644 index 0000000..afa145f --- /dev/null +++ b/requirements-lint.txt @@ -0,0 +1,10 @@ +# linters +pycodestyle==2.8.0 +flake8==4.0.1 +mccabe==0.6.1 +pyflakes==2.4.0 +pydocstyle==6.1.1 +black==21.9b0 +mypy==0.910 +types-pkg-resources==0.1.3 +-r requirements-test-withpsycopg.txt diff --git a/requirements-test-withpsycopg.txt b/requirements-test-withpsycopg.txt new file mode 100644 index 0000000..b08dbce --- /dev/null +++ b/requirements-test-withpsycopg.txt @@ -0,0 +1,4 @@ +# test runs requirements (versions we'll be testing against) - automatically updated by requires.io +psycopg2-binary==2.9.1; platform_python_implementation != "PyPy" +psycopg2cffi==2.9.0; platform_python_implementation == "PyPy" +-r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..c2f6178 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,10 @@ +# 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==6.0.2 # pytest-cov +pytest==7.0.1 +pytest-cov==3.0.0 +pytest-xdist==2.4.0 +port-for==0.6.1 +mirakuru==2.4.1 +-e .[tests] diff --git a/setup.cfg b/setup.cfg index fc0ab9f..2180843 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,74 +1,69 @@ [metadata] name = pytest-postgresql version = 3.1.3 url = https://github.com/ClearcodeHQ/pytest-postgresql description = Postgresql fixtures and fixture factories for Pytest. long_description = file: README.rst, CHANGES.rst long_description_content_type = text/x-rst keywords = tests, py.test, pytest, fixture, postgresql license = LGPLv3+ maintainer = Grzegorz Śliwiński maintainer_email = fizyk+pypi@fizyk.net.pl -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 :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3 :: Only - Topic :: Software Development :: Libraries :: Python Modules - Topic :: Software Development :: Testing - Framework :: Pytest +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 :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3 :: Only + Topic :: Software Development :: Libraries :: Python Modules + Topic :: Software Development :: Testing + Framework :: Pytest [options] zip_safe = False include_package_data = True python_requires = >= 3.7 packages = find: -package_dir = - =src -install_requires = - pytest>=6.2.0 - port-for - mirakuru>=2.3.0 +package_dir = + =src +install_requires = + pytest>=6.2.0 + port-for + mirakuru>=2.3.0 [options.entry_points] -pytest11 = - pytest_postgresql = pytest_postgresql.plugin +pytest11 = + pytest_postgresql = pytest_postgresql.plugin [options.packages.find] where = src [options.package_data] pytest_postgresql = py.typed [options.extras_require] -tests = - pytest-cov - pytest-xdist +tests = + pytest-cov + pytest-xdist [flake8] max-line-length = 100 exclude = docs/*,build/*,venv/* [pydocstyle] ignore = D203,D212 match = '(?!docs|build|venv).*\.py' [tool:pytest] addopts = --max-worker-restart=0 --showlocals --verbose --cov src/pytest_postgresql --cov tests testpaths = tests xfail_strict = true pytester_example_dir = tests/examples -norecursedirs = examples - -[egg_info] -tag_build = -tag_date = 0 - +norecursedirs=examples diff --git a/src/pytest_postgresql.egg-info/PKG-INFO b/src/pytest_postgresql.egg-info/PKG-INFO deleted file mode 100644 index 7ece1fe..0000000 --- a/src/pytest_postgresql.egg-info/PKG-INFO +++ /dev/null @@ -1,787 +0,0 @@ -Metadata-Version: 2.1 -Name: pytest-postgresql -Version: 3.1.3 -Summary: Postgresql fixtures and fixture factories for Pytest. -Home-page: https://github.com/ClearcodeHQ/pytest-postgresql -Maintainer: Grzegorz Śliwiński -Maintainer-email: fizyk+pypi@fizyk.net.pl -License: LGPLv3+ -Keywords: tests,py.test,pytest,fixture,postgresql -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Software Development :: Testing -Classifier: Framework :: Pytest -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst -Provides-Extra: tests -License-File: COPYING -License-File: COPYING.lesser -License-File: AUTHORS.rst - -.. image:: https://raw.githubusercontent.com/ClearcodeHQ/pytest-postgresql/master/logo.png - :width: 100px - :height: 100px - -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 - -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.6. See tests for more details. - -Install with: - -.. code-block:: sh - - pip install pytest-postgresql - -You will also need to install ``psycopg2`` (2.9 or newer), or one of its alternative packagings such as ``psycopg2-binary`` -(pre-compiled wheels) or ``psycopg2cffi`` (CFFI based, useful on PyPy). - -Plugin contains three 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. - This fixture returns already connected psycopg2 connection. - -* **postgresql_proc** - session scoped fixture, that starts PostgreSQL instance - at it's first use and stops at the end of the tests. -* **postgresql_noproc** - a noprocess fixture, that's connecting to already - running postgresql instance. - For example on dockerized test environments, or CI providing postgresql services - -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, unixsocketdir='/var/run') - 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. - -Sample test - -.. code-block:: python - - def test_example_postgres(postgresql): - """Check main postgresql fixture.""" - cur = postgresql.cursor() - cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);") - postgresql.commit() - cur.close() - -If you want the database fixture to be automatically populated with your schema there are two ways: - -#. client fixture specific -#. process fixture specific - -Both are accepting same set of possible loaders: - -* sql file path -* loading function import path (string) -* actual loading function - -That function will receive **host**, **port**, **user**, **dbname** and **password** kwargs and will have to perform -connection to the database inside. However, you'll be able to run SQL files or even trigger programmatically database -migrations you have. - -Client specific loads the database each test - -.. code-block:: python - - postgresql_my_with_schema = factories.postgresql( - 'postgresql_my_proc', - load=["schemafile.sql", "otherschema.sql", "import.path.to.function", "import.path.to:otherfunction", load_this] - ) - -.. warning:: - - This way, the database will still be dropped each time. - - -The process fixture performs the load once per test session, and loads the data into the template database. -Client fixture then creates test database out of the template database each test, which significantly speeds up the tests. - -.. code-block:: python - - postgresql_my_proc = factories.postgresql_proc( - load=["schemafile.sql", "otherschema.sql", "import.path.to.function", "import.path.to:otherfunction", load_this] - ) - - -.. code-block:: bash - - pytest --postgresql-populate-template=path.to.loading_function --postgresql-populate-template=path.to.other:loading_function --postgresql-populate-template=path/to/file.sql - - -The loading_function from example will receive , and have to commit that. -Connecting to already existing postgresql database --------------------------------------------------- - -Some projects are using already running postgresql servers (ie on docker instances). -In order to connect to them, one would be using the ``postgresql_noproc`` fixture. - -.. code-block:: python - - postgresql_external = factories.postgresql('postgresql_noproc') - -By default the ``postgresql_noproc`` fixture would connect to postgresql instance using **5432** port. Standard configuration options apply to it. - -These are the configuration options that are working on all levels with the ``postgresql_noproc`` fixture: - -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`` - - -.. list-table:: Configuration options - :header-rows: 1 - - * - PostgreSQL option - - Fixture factory argument - - Command line option - - pytest.ini option - - Noop process fixture - - Default - * - Path to executable - - executable - - --postgresql-exec - - postgresql_exec - - - - - /usr/lib/postgresql/9.6/bin/pg_ctl - * - host - - host - - --postgresql-host - - postgresql_host - - yes - - 127.0.0.1 - * - port - - port - - --postgresql-port - - postgresql_port - - yes (5432) - - random - * - postgresql user - - user - - --postgresql-user - - postgresql_user - - yes - - postgres - * - password - - password - - --postgresql-password - - postgresql_password - - yes - - - * - Starting parameters (extra pg_ctl arguments) - - startparams - - --postgresql-startparams - - postgresql_startparams - - - - - -w - * - Postgres exe extra arguments (passed via pg_ctl's -o argument) - - postgres_options - - --postgresql-postgres-options - - postgresql_postgres_options - - - - - - * - Log filename's prefix - - logsprefix - - --postgresql-logsprefix - - postgresql_logsprefix - - - - - - * - Location for unixsockets - - unixsocket - - --postgresql-unixsocketdir - - postgresql_unixsocketdir - - - - - $TMPDIR - * - Database name - - dbname - - --postgresql-dbname - - postgresql_dbname - - - - - test - * - Default Schema either in sql files or import path to function that will load it (list of values for each) - - load - - --postgresql-load - - postgresql_load - - yes - - - * - PostgreSQL connection options - - options - - --postgresql-options - - postgresql_options - - yes - - - - -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 - -Examples -======== - -Populating database for tests ------------------------------ - -With SQLAlchemy -+++++++++++++++ - -This example shows how to populate database and create an SQLAlchemy's ORM connection: - -Sample below is simplified session fixture from -`pyramid_fullauth `_ tests: - -.. code-block:: python - - from sqlalchemy import create_engine - from sqlalchemy.orm import scoped_session, sessionmaker - from sqlalchemy.pool import NullPool - from zope.sqlalchemy import register - - - @pytest.fixture - def db_session(postgresql): - """Session for SQLAlchemy.""" - from pyramid_fullauth.models import Base - - connection = f'postgresql+psycopg2://{postgresql.info.user}:@{postgresql.info.host}:{postgresql.info.port}/{postgresql.info.dbname}' - - engine = create_engine(connection, echo=False, poolclass=NullPool) - pyramid_basemodel.Session = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) - pyramid_basemodel.bind_engine( - engine, pyramid_basemodel.Session, should_create=True, should_drop=True) - - yield pyramid_basemodel.Session - - transaction.commit() - Base.metadata.drop_all(engine) - - - @pytest.fixture - def user(db_session): - """Test user fixture.""" - from pyramid_fullauth.models import User - from tests.tools import DEFAULT_USER - - new_user = User(**DEFAULT_USER) - db_session.add(new_user) - transaction.commit() - return new_user - - - def test_remove_last_admin(db_session, user): - """ - Sample test checks internal login, but shows usage in tests with SQLAlchemy - """ - user = db_session.merge(user) - user.is_admin = True - transaction.commit() - user = db_session.merge(user) - - with pytest.raises(AttributeError): - user.is_admin = False -.. note:: - - See the original code at `pyramid_fullauth's conftest file `_. - Depending on your needs, that in between code can fire alembic migrations in case of sqlalchemy stack or any other code - -Maintaining database state outside of the fixtures --------------------------------------------------- - -It is possible and appears it's used in other libraries for tests, -to maintain database state with the use of the ``pytest-postgresql`` database -managing functionality: - -For this import DatabaseJanitor and use its init and drop methods: - - -.. code-block:: python - - import pytest - from pytest_postgresql.janitor import DatabaseJanitor - - @pytest.fixture - def database(postgresql_proc): - # variable definition - - janitor = DatabaseJanitor( - postgresql_proc.user, - postgresql_proc.host, - postgresql_proc.port, - "my_test_database", - postgresql_proc.version, - password="secret_password, - ): - janitor.init() - yield psycopg2.connect( - dbname="my_test_database", - user=postgresql_proc.user, - password="secret_password", - host=postgresql_proc.host, - port=postgresql_proc.port, - ) - janitor.drop() - -or use it as a context manager: - -.. code-block:: python - - import pytest - from pytest_postgresql.janitor import DatabaseJanitor - - @pytest.fixture - def database(postgresql_proc): - # variable definition - - with DatabaseJanitor( - postgresql_proc.user, - postgresql_proc.host, - postgresql_proc.port, - "my_test_database", - postgresql_proc.version, - password="secret_password, - ): - yield psycopg2.connect( - dbname="my_test_database", - user=postgresql_proc.user, - password="secret_password", - host=postgresql_proc.host, - port=postgresql_proc.port, - ) - -.. note:: - - DatabaseJanitor manages the state of the database, but you'll have to create - connection to use in test code yourself. - - You can optionally pass in a recognized postgresql ISOLATION_LEVEL for - additional control. - -.. note:: - - See DatabaseJanitor usage in python's warehouse test code https://github.com/pypa/warehouse/blob/5d15bfe/tests/conftest.py#L127 - -Connecting to Postgresql (in a docker) --------------------------------------- - -To connect to a docker run postgresql and run test on it, use noproc fixtures. - -.. code-block:: sh - - docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres - -This will start postgresql in a docker container, however using a postgresql installed locally is not much different. - -In tests, make sure that all your tests are using **postgresql_noproc** fixture like that: - -.. code-block:: python - - postgresql_in_docker = factories.postgresql_noproc() - postresql = factories.postgresql("postgresql_in_docker", db_name="test") - - - def test_postgres_docker(postresql): - """Run test.""" - cur = postgresql.cursor() - cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);") - postgresql.commit() - cur.close() - -And run tests: - -.. code-block:: sh - - pytest --postgresql-host=172.17.0.2 --postgresql-password=mysecretpassword - -Using a common database initialisation between tests ----------------------------------------------------- - -If you've got several tests that require common initialisation, you need to define a `load` and pass it to -your custom postgresql process fixture: - -.. code-block:: python - - import pytest_postgresql.factories - def load_database(**kwargs): - db_connection: connection = psycopg2.connect(**kwargs) - with db_connection.cursor() as cur: - cur.execute("CREATE TABLE stories (id serial PRIMARY KEY, name varchar);") - cur.execute( - "INSERT INTO stories (name) VALUES" - "('Silmarillion'), ('Star Wars'), ('The Expanse'), ('Battlestar Galactica')" - ) - db_connection.commit() - - postgresql_proc = factories.postgresql_proc( - load=[load_database], - ) - - postgresql = factories.postgresql( - "postgresql_proc", - ) - -You can also define your own database name by passing same dbname value -to **both** factories. - -The way this will work is that the process fixture will populate template database, -which in turn will be used automatically by client fixture to create a test database from scratch. -Fast, clean and no dangling transactions, that could be accidentally rolled back. - -Same approach will work with noproces fixture, while connecting to already running postgresql instance whether -it'll be on a docker machine or running remotely or locally. - -CHANGELOG -========= - -3.1.3 ----------- - -Cherry picked from v4.x - -Misc -++++ - -- Import FixtureRequest from pytest, not private _pytest. - Require at least pytest 6.2 -- Replace tmpdir_factory with tmp_path_factory -- Add Postgresql 14 to the CI - -3.1.2 ----------- - -Bugfix -++++++ - -- Database can be created by DatabaseJanitor or the client fixture when an isolation - level is specified. - -3.1.1 ----------- - -Misc -++++ - -- rely on `get_port` functionality delivered by `port_for` - -3.1.0 ----------- - -Features -++++++++ - -- Added type annotations and compatibitlity with PEP 561 - -Misc -++++ - -- pre-commit configuration - -3.0.2 ----------- - -Bugfix -++++++ - -- Changed `UPDATE pg_database SET` to `ALTER`. System tables should not be updated. - -3.0.1 ----------- - -Bugfix -++++++ - -- Fixed DatabaseJanitor port type hint to int from str -- Changed retry definition to not fail if psycopg2 is not installed. - Now the default is Exception. - -Misc -++++ - -- Support python 3.7 and up - -3.0.0 ----------- - -Features -++++++++ - -- Ability to create template database once for the process fixture and - re-recreate a clean database out of it every test. Not only it does provide some - common db initialisation between tests but also can speed up tests significantly, - especially if the initialisation has lots of operations to perform. -- DatabaseJanitor can now define a `connection_timeout` parameter. - How long will it try to connect to database before raising a TimeoutError -- Updated supported python versions -- Unified temporary directory handling in fixture. Settled on tmpdir_factory. -- Fully moved to the Github Actions as CI/CD pipeline - -Deprecations -++++++++++++ - -- Deprecated support for `logs_prefix` process fixture factory argument, - `--postgresql-logsprefix` pytest command line option and `postgresql_logsprefix` - ini configuration option. tmpdir_factory now builds pretty unique temporary directory structure. - -Backward Incompatibilities -++++++++++++++++++++++++++ - -- Dropped support for postgresql 9.5 and down -- Removed init_postgresql_database and drop_postgresql_database functions. - They were long deprecated and their role perfectly covered by DatabaseJanitor class. -- `pytest_postgresql.factories.get_config` was moved to `pytest_postgresql.config.get_config` -- all `db_name` keywords and attributes were renamed to `dbname` -- postgresql_nooproc fixture was renamed to postgresql_noproc - -Bugfix -++++++ - -- Use `postgresql_logsprefix` and `--postgresql-logsprefix` again. - They were stopped being used somewhere along the way. -- Sometimes pytest-postrgesql would fail to start postgresql with - "FATAL: the database system is starting up" message. It's not really a fatal error, - but a message indicating that the process still starts. Now pytest-postgresql will wait properly in this cases. - -2.6.1 ----------- - -- [bugfix] To not fail loading code if no postgresql version is installed. - Fallback for janitor and process fixture only, if called upon. - -2.6.0 ----------- - -- [enhancement] add ability to pass options to pg_ctl's -o flag to send arguments to the underlying postgres executable - Use `postgres_options` as fixture argument, `--postgresql-postgres-options` as pytest starting option or - `postgresql_postgres_options` as pytest.ini configuration option - -2.5.3 ----------- - -- [enhancement] Add ability to set up isolation level for fixture and janitor - -2.5.2 ----------- - -- [fix] Status checks for running postgres depend on pg_ctl status code, - not on pg_ctl log language. Fixes starting on systems without C locale. - Thanks @Martin Meyries. - - -2.5.1 ----------- - -- [fix] Added LC_* env vars to running initdb and other utilities. - Now all tools and server are using same, C locale - - -2.5.0 ----------- - -- [feature] Ability to define default schema to initialize database with -- [docs] Added more examples to readme on how to use the plugin - - -2.4.1 ----------- - -- [enhancement] extract NoopExecutor into it's own submodule -- [bugfix] Ignore occasional `ProcessFinishedWithError` error on executor exit. -- [bugfix] Fixed setting custom password for process fixture -- [bugfix] Fix version detection, to allow for two-digit minor version part - -2.4.0 ----------- - -- [feature] Drop support for pyhon 3.5 -- [enhancement] require at least mirakuru 2.3.0 (executor's stop method parameter's change) -- [bug] pass password to DatabaseJanitor in client's factory - -2.3.0 ----------- - -- [feature] Allow to set password for postgresql. Use it throughout the flow. -- [bugfix] Default Janitor's connections to postgres database. When using custom users, - postgres attempts to use user's database and it might not exist. -- [bugfix] NoopExecutor connects to read version by context manager to properly handle cases - where it can't connect to the server. - -2.2.1 ----------- - -- [bugfix] Fix drop_postgresql_database to actually use DatabaseJanitor.drop instead of an init - -2.2.0 ----------- - -- [feature] ability to properly connect to already existing postgresql server using ``postgresql_nooproc`` fixture. - -2.1.0 ----------- - -- [enhancement] Gather helper functions maintaining postgresql database in DatabaseJanitor class. -- [deprecate] Deprecate ``init_postgresql_database`` in favour of ``DatabaseJanitor.init`` -- [deprecate] Deprecate ``drop_postgresql_database`` in favour of ``DatabaseJanitor.drop`` - -2.0.0 ----------- - -- [feature] Drop support for python 2.7. From now on, only support python 3.5 and up -- [feature] Ability to configure database name through plugin options -- [enhancement] Use tmpdir_factory. Drop ``logsdir`` parameter -- [ehnancement] Support only Postgresql 9.0 and up -- [bugfix] Always start postgresql with LC_ALL, LC_TYPE and LANG set to C.UTF-8. - It makes postgresql start in english. - -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/src/pytest_postgresql.egg-info/SOURCES.txt b/src/pytest_postgresql.egg-info/SOURCES.txt deleted file mode 100644 index 0edc2c7..0000000 --- a/src/pytest_postgresql.egg-info/SOURCES.txt +++ /dev/null @@ -1,31 +0,0 @@ -AUTHORS.rst -CHANGES.rst -CONTRIBUTING.rst -COPYING -COPYING.lesser -MANIFEST.in -README.rst -pyproject.toml -setup.cfg -setup.py -src/pytest_postgresql/__init__.py -src/pytest_postgresql/compat.py -src/pytest_postgresql/config.py -src/pytest_postgresql/executor.py -src/pytest_postgresql/executor_noop.py -src/pytest_postgresql/janitor.py -src/pytest_postgresql/plugin.py -src/pytest_postgresql/py.typed -src/pytest_postgresql/retry.py -src/pytest_postgresql/sql.py -src/pytest_postgresql.egg-info/PKG-INFO -src/pytest_postgresql.egg-info/SOURCES.txt -src/pytest_postgresql.egg-info/dependency_links.txt -src/pytest_postgresql.egg-info/entry_points.txt -src/pytest_postgresql.egg-info/not-zip-safe -src/pytest_postgresql.egg-info/requires.txt -src/pytest_postgresql.egg-info/top_level.txt -src/pytest_postgresql/factories/__init__.py -src/pytest_postgresql/factories/client.py -src/pytest_postgresql/factories/noprocess.py -src/pytest_postgresql/factories/process.py \ No newline at end of file diff --git a/src/pytest_postgresql.egg-info/dependency_links.txt b/src/pytest_postgresql.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/src/pytest_postgresql.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/pytest_postgresql.egg-info/entry_points.txt b/src/pytest_postgresql.egg-info/entry_points.txt deleted file mode 100644 index 942a6b9..0000000 --- a/src/pytest_postgresql.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[pytest11] -pytest_postgresql = pytest_postgresql.plugin diff --git a/src/pytest_postgresql.egg-info/not-zip-safe b/src/pytest_postgresql.egg-info/not-zip-safe deleted file mode 100644 index 8b13789..0000000 --- a/src/pytest_postgresql.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/pytest_postgresql.egg-info/requires.txt b/src/pytest_postgresql.egg-info/requires.txt deleted file mode 100644 index 35fbf3f..0000000 --- a/src/pytest_postgresql.egg-info/requires.txt +++ /dev/null @@ -1,7 +0,0 @@ -pytest>=6.2.0 -port-for -mirakuru>=2.3.0 - -[tests] -pytest-cov -pytest-xdist diff --git a/src/pytest_postgresql.egg-info/top_level.txt b/src/pytest_postgresql.egg-info/top_level.txt deleted file mode 100644 index 906e435..0000000 --- a/src/pytest_postgresql.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -pytest_postgresql diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..3177959 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Main test module.""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..be0f6f4 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,25 @@ +"""Tests main conftest file.""" +import os + +from pytest_postgresql import factories + +pytest_plugins = ["pytester"] +POSTGRESQL_VERSION = os.environ.get("POSTGRES", "13") + + +TEST_SQL_DIR = os.path.dirname(os.path.abspath(__file__)) + "/test_sql/" + +postgresql_proc2 = factories.postgresql_proc(port=None) +postgresql2 = factories.postgresql("postgresql_proc2", dbname="test-db") +postgresql_load_1 = factories.postgresql( + "postgresql_proc2", + dbname="test-load-db", + load=[ + TEST_SQL_DIR + "test.sql", + ], +) +postgresql_load_2 = factories.postgresql( + "postgresql_proc2", + dbname="test-load-moredb", + load=[TEST_SQL_DIR + "test.sql", TEST_SQL_DIR + "test2.sql"], +) diff --git a/tests/docker/test_nooproc_docker.py b/tests/docker/test_nooproc_docker.py new file mode 100644 index 0000000..0a4d890 --- /dev/null +++ b/tests/docker/test_nooproc_docker.py @@ -0,0 +1,43 @@ +import pytest + +import pytest_postgresql.factories.client +import pytest_postgresql.factories.noprocess +from pytest_postgresql.compat import connection +from tests.loader import load_database + +postgresql_my_proc = pytest_postgresql.factories.noprocess.postgresql_noproc() +postgres_with_schema = pytest_postgresql.factories.client.postgresql( + "postgresql_my_proc", dbname="test", load=["tests/test_sql/eidastats.sql"] +) + +postgresql_my_proc_template = pytest_postgresql.factories.noprocess.postgresql_noproc( + dbname="stories_templated", load=[load_database] +) +postgres_with_template = pytest_postgresql.factories.client.postgresql( + "postgresql_my_proc_template", dbname="stories_templated" +) + + +def test_postgres_docker_load(postgres_with_schema: connection) -> None: + """ + Check main postgres fixture + """ + with postgres_with_schema.cursor() as cur: + # Query for public.tokens since the eidastats changes postgres' search_path to ''. + # The search path by default is public, but without it, + # every schema has to be written explicitly. + cur.execute("select * from public.tokens") + print(cur.fetchall()) + + +@pytest.mark.parametrize("_", range(5)) +def test_template_database(postgres_with_template: connection, _: int) -> None: + """Check that the database structure gets recreated out of a template.""" + with postgres_with_template.cursor() as cur: + cur.execute("SELECT * FROM stories") + res = cur.fetchall() + assert len(res) == 4 + cur.execute("TRUNCATE stories") + cur.execute("SELECT * FROM stories") + res = cur.fetchall() + assert len(res) == 0 diff --git a/tests/examples/test_postgres_options.py b/tests/examples/test_postgres_options.py new file mode 100644 index 0000000..ab0358c --- /dev/null +++ b/tests/examples/test_postgres_options.py @@ -0,0 +1,9 @@ +"""This is not called directly but is used in another test.""" + +from typing import Any + + +def test_postgres_options(postgresql: Any) -> None: + cur = postgresql.cursor() + cur.execute("SHOW max_connections") + assert cur.fetchone() == ("11",) diff --git a/tests/loader.py b/tests/loader.py new file mode 100644 index 0000000..a658629 --- /dev/null +++ b/tests/loader.py @@ -0,0 +1,14 @@ +"""Helping loader function.""" + +from pytest_postgresql.compat import connection, psycopg2 + + +def load_database(**kwargs: str) -> None: + db_connection: connection = psycopg2.connect(**kwargs) + with db_connection.cursor() as cur: + cur.execute("CREATE TABLE stories (id serial PRIMARY KEY, name varchar);") + cur.execute( + "INSERT INTO stories (name) VALUES" + "('Silmarillion'), ('Star Wars'), ('The Expanse'), ('Battlestar Galactica')" + ) + db_connection.commit() diff --git a/tests/test_executor.py b/tests/test_executor.py new file mode 100644 index 0000000..13c7684 --- /dev/null +++ b/tests/test_executor.py @@ -0,0 +1,148 @@ +"""Test various executor behaviours.""" +import sys +from typing import Any + +from pytest import FixtureRequest +from pkg_resources import parse_version + +import psycopg2 +import pytest +from port_for import get_port + +from pytest_postgresql.executor import PostgreSQLExecutor, PostgreSQLUnsupported +from pytest_postgresql.factories import postgresql_proc, postgresql +from pytest_postgresql.compat import connection +from pytest_postgresql.config import get_config +from pytest_postgresql.retry import retry + + +class PatchedPostgreSQLExecutor(PostgreSQLExecutor): + """PostgreSQLExecutor that always says it's 8.9 version.""" + + @property + def version(self) -> Any: + """Overwrite version, to always return highes unsupported version.""" + return parse_version("8.9") + + +def test_unsupported_version(request: FixtureRequest) -> None: + """Check that the error gets raised on unsupported postgres version.""" + config = get_config(request) + port = get_port(config["port"]) + assert port is not None + executor = PatchedPostgreSQLExecutor( + executable=config["exec"], + host=config["host"], + port=port, + datadir="/tmp/error", + unixsocketdir=config["unixsocketdir"], + logfile="/tmp/version.error.log", + startparams=config["startparams"], + ) + + with pytest.raises(PostgreSQLUnsupported): + executor.start() + + +@pytest.mark.skipif( + sys.platform == "darwin", reason="The default pg_ctl path is for linux, not macos" +) +@pytest.mark.parametrize("locale", ("en_US.UTF-8", "de_DE.UTF-8")) +def test_executor_init_with_password( + request: FixtureRequest, + monkeypatch: pytest.MonkeyPatch, + tmp_path_factory: pytest.TempPathFactory, + locale: str, +) -> None: + """Test whether the executor initializes properly.""" + config = get_config(request) + monkeypatch.setenv("LC_ALL", locale) + port = get_port(config["port"]) + assert port is not None + tmpdir = tmp_path_factory.mktemp(f"pytest-postgresql-{request.node.name}") + datadir = tmpdir / f"data-{port}" + datadir.mkdir() + logfile_path = tmpdir / f"postgresql.{port}.log" + executor = PostgreSQLExecutor( + executable=config["exec"], + host=config["host"], + port=port, + datadir=str(datadir), + unixsocketdir=config["unixsocketdir"], + logfile=str(logfile_path), + startparams=config["startparams"], + password="somepassword", + ) + with executor: + assert executor.running() + psycopg2.connect( + dbname=executor.user, + user=executor.user, + password=executor.password, + host=executor.host, + port=executor.port, + ) + with pytest.raises(psycopg2.OperationalError): + psycopg2.connect( + dbname=executor.user, + user=executor.user, + password="bogus", + host=executor.host, + port=executor.port, + ) + assert not executor.running() + + +postgres_with_password = postgresql_proc(password="hunter2") + + +def test_proc_with_password( + postgres_with_password: PostgreSQLExecutor, +) -> None: + """Check that password option to postgresql_proc factory is honored.""" + assert postgres_with_password.running() is True + + # no assertion necessary here; we just want to make sure it connects with + # the password + retry( + lambda: psycopg2.connect( # type: ignore[no-any-return] + dbname=postgres_with_password.user, + user=postgres_with_password.user, + password=postgres_with_password.password, + host=postgres_with_password.host, + port=postgres_with_password.port, + ), + possible_exception=psycopg2.OperationalError, + ) + + with pytest.raises(psycopg2.OperationalError): + psycopg2.connect( + dbname=postgres_with_password.user, + user=postgres_with_password.user, + password="bogus", + host=postgres_with_password.host, + port=postgres_with_password.port, + ) + + +postgresql_max_conns_proc = postgresql_proc(postgres_options="-N 42") +postgres_max_conns = postgresql("postgresql_max_conns_proc") + + +def test_postgres_options(postgres_max_conns: connection) -> None: + """Check that max connections (-N 42) is honored.""" + cur = postgres_max_conns.cursor() + cur.execute("SHOW max_connections") + assert cur.fetchone() == ("42",) + + +postgres_isolation_level = postgresql( + "postgresql_proc", isolation_level=psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE +) + + +def test_custom_isolation_level(postgres_isolation_level: connection) -> None: + """Check that a client fixture with a custom isolation level works.""" + cur = postgres_isolation_level.cursor() + cur.execute("SELECT 1") + assert cur.fetchone() == (1,) diff --git a/tests/test_janitor.py b/tests/test_janitor.py new file mode 100644 index 0000000..1955bad --- /dev/null +++ b/tests/test_janitor.py @@ -0,0 +1,63 @@ +"""Database Janitor tests.""" +import sys +from unittest.mock import patch, MagicMock +from typing import Any +import pytest +from pkg_resources import parse_version + +from pytest_postgresql.janitor import DatabaseJanitor + +VERSION = parse_version("9.6") + + +@pytest.mark.parametrize("version", (VERSION, 9.6, "9.6")) +def test_version_cast(version: Any) -> None: + """Test that version is cast to Version object.""" + janitor = DatabaseJanitor("user", "host", "1234", "database_name", version) + assert janitor.version == VERSION + + +@patch("pytest_postgresql.janitor.psycopg2.connect") +def test_cursor_selects_postgres_database(connect_mock: MagicMock) -> None: + """Test that the cursor requests the postgres database.""" + janitor = DatabaseJanitor("user", "host", "1234", "database_name", 9.6) + with janitor.cursor(): + connect_mock.assert_called_once_with( + dbname="postgres", user="user", password=None, host="host", port="1234" + ) + + +@patch("pytest_postgresql.janitor.psycopg2.connect") +def test_cursor_connects_with_password(connect_mock: MagicMock) -> None: + """Test that the cursor requests the postgres database.""" + janitor = DatabaseJanitor("user", "host", "1234", "database_name", 9.6, "some_password") + with janitor.cursor(): + connect_mock.assert_called_once_with( + dbname="postgres", user="user", password="some_password", host="host", port="1234" + ) + + +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="Unittest call_args.kwargs was introduced since python 3.8" +) +@pytest.mark.parametrize( + "load_database", ("tests.loader.load_database", "tests.loader:load_database") +) +@patch("pytest_postgresql.janitor.psycopg2.connect") +def test_janitor_populate(connect_mock: MagicMock, load_database: str) -> None: + """ + Test that the cursor requests the postgres database. + + load_database tries to connect to database, which triggers mocks. + """ + call_kwargs = { + "host": "host", + "port": "1234", + "user": "user", + "dbname": "database_name", + "password": "some_password", + } + janitor = DatabaseJanitor(version=9.6, **call_kwargs) # type: ignore[arg-type] + janitor.load(load_database) + assert connect_mock.called + assert connect_mock.call_args.kwargs == call_kwargs diff --git a/tests/test_noopexecutor.py b/tests/test_noopexecutor.py new file mode 100644 index 0000000..bb07b47 --- /dev/null +++ b/tests/test_noopexecutor.py @@ -0,0 +1,37 @@ +"""Test for NoopExecutor.""" +from pytest_postgresql.executor import PostgreSQLExecutor +from pytest_postgresql.compat import psycopg2 +from pytest_postgresql.executor_noop import NoopExecutor +from pytest_postgresql.retry import retry + + +def test_noproc_version(postgresql_proc: PostgreSQLExecutor) -> None: + """ + Test the way postgresql version is being read. + + Version behaves differently for postgresql >= 10 and differently for older ones + """ + postgresql_noproc = NoopExecutor( + postgresql_proc.host, + postgresql_proc.port, + postgresql_proc.user, + postgresql_proc.options, + ) + noproc_version = retry( + lambda: postgresql_noproc.version, # type: ignore[no-any-return] + possible_exception=psycopg2.OperationalError, + ) + assert postgresql_proc.version == noproc_version + + +def test_noproc_cached_version(postgresql_proc: PostgreSQLExecutor) -> None: + """Test that the version is being cached.""" + postgresql_noproc = NoopExecutor( + postgresql_proc.host, postgresql_proc.port, postgresql_proc.user, postgresql_proc.options + ) + ver = retry( + lambda: postgresql_noproc.version, # type: ignore[no-any-return] + possible_exception=psycopg2.OperationalError, + ) + with postgresql_proc.stopped(): + assert ver == postgresql_noproc.version diff --git a/tests/test_postgres_options_plugin.py b/tests/test_postgres_options_plugin.py new file mode 100644 index 0000000..3913108 --- /dev/null +++ b/tests/test_postgres_options_plugin.py @@ -0,0 +1,18 @@ +"""Test behavior of postgres_options passed in different ways.""" + +from pytest import Pytester + + +def test_postgres_options_config_in_cli(pytester: Pytester) -> None: + """Check that command line arguments are honored.""" + pytester.copy_example("test_postgres_options.py") + ret = pytester.runpytest("--postgresql-postgres-options", "-N 11", "test_postgres_options.py") + ret.assert_outcomes(passed=1) + + +def test_postgres_options_config_in_ini(pytester: Pytester) -> None: + """Check that pytest.ini arguments are honored.""" + pytester.copy_example("test_postgres_options.py") + pytester.makefile(".ini", pytest="[pytest]\npostgresql_postgres_options = -N 11\n") + ret = pytester.runpytest("test_postgres_options.py") + ret.assert_outcomes(passed=1) diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py new file mode 100644 index 0000000..9a8bd15 --- /dev/null +++ b/tests/test_postgresql.py @@ -0,0 +1,86 @@ +"""All tests for pytest-postgresql.""" +import decimal + +import psycopg2 +import pytest + +from pytest_postgresql.executor import PostgreSQLExecutor +from pytest_postgresql.retry import retry +from pytest_postgresql.compat import connection +from tests.conftest import POSTGRESQL_VERSION + +MAKE_Q = "CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);" +SELECT_Q = "SELECT * FROM test;" + + +def test_postgresql_proc(postgresql_proc: PostgreSQLExecutor) -> None: + """Test different postgresql versions.""" + assert postgresql_proc.running() is True + + +def test_main_postgres(postgresql: connection) -> None: + """Check main postgresql fixture.""" + cur = postgresql.cursor() + cur.execute(MAKE_Q) + postgresql.commit() + cur.close() + + +def test_two_postgreses(postgresql: connection, postgresql2: connection) -> None: + """Check two postgresql fixtures on one test.""" + cur = postgresql.cursor() + cur.execute(MAKE_Q) + postgresql.commit() + cur.close() + + cur = postgresql2.cursor() + cur.execute(MAKE_Q) + postgresql2.commit() + cur.close() + + +def test_postgres_load_one_file(postgresql_load_1: connection) -> None: + """Check postgresql fixture can load one file.""" + cur = postgresql_load_1.cursor() + cur.execute(SELECT_Q) + results = cur.fetchall() + assert len(results) == 1 + cur.close() + + +def test_postgres_load_two_files(postgresql_load_2: connection) -> None: + """Check postgresql fixture can load two files.""" + cur = postgresql_load_2.cursor() + cur.execute(SELECT_Q) + results = cur.fetchall() + assert len(results) == 2 + cur.close() + + +def test_rand_postgres_port(postgresql2: connection) -> None: + """Check if postgres fixture can be started on random port.""" + assert postgresql2.status == psycopg2.extensions.STATUS_READY + + +@pytest.mark.skipif( + decimal.Decimal(POSTGRESQL_VERSION) < 10, + reason="Test query not supported in those postgresql versions, and soon will not be supported.", +) +@pytest.mark.parametrize("_", range(2)) +def test_postgres_terminate_connection(postgresql2: connection, _: int) -> None: + """ + Test that connections are terminated between tests. + + And check that only one exists at a time. + """ + + with postgresql2.cursor() as cur: + + def check_if_one_connection() -> None: + cur.execute("SELECT * FROM pg_stat_activity " "WHERE backend_type = 'client backend';") + existing_connections = cur.fetchall() + assert ( + len(existing_connections) == 1 + ), f"there is always only one connection, {existing_connections}" + + retry(check_if_one_connection, timeout=120, possible_exception=AssertionError) diff --git a/tests/test_sql/eidastats.sql b/tests/test_sql/eidastats.sql new file mode 100644 index 0000000..6a1b653 --- /dev/null +++ b/tests/test_sql/eidastats.sql @@ -0,0 +1,118 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 13.2 (Debian 13.2-1.pgdg100+1) +-- Dumped by pg_dump version 13.2 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: hll; Type: EXTENSION; Schema: -; Owner: - +-- + +-- CREATE EXTENSION IF NOT EXISTS hll WITH SCHEMA public; + + +-- +-- Name: EXTENSION hll; Type: COMMENT; Schema: -; Owner: +-- + +-- COMMENT ON EXTENSION hll IS 'type for storing hyperloglog data'; + + +-- SET default_tablespace = ''; +-- +-- SET default_table_access_method = heap; + +-- +-- Name: dataselect_stats; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.dataselect_stats ( + node_id integer, + date date, + network character varying(6), + station character varying(5), + location character varying(2), + channel character varying(3), + country character varying(2), + bytes bigint, + nb_reqs integer, + nb_successful_reqs integer, + nb_failed_reqs integer, +-- clients public.hll, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone +); + + +ALTER TABLE public.dataselect_stats OWNER TO postgres; + +-- +-- Name: nodes; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.nodes ( + id serial, + name text, + contact text +); + + +ALTER TABLE public.nodes OWNER TO postgres; + +-- +-- Name: tokens; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.tokens ( + id serial, + node_id integer, + value character varying(32), + valid_from timestamp with time zone NOT NULL, + valid_until timestamp with time zone NOT NULL, + create_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.tokens OWNER TO postgres; + +-- +-- Name: nodes nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.nodes + ADD CONSTRAINT nodes_pkey PRIMARY KEY (id); + + +-- +-- Name: dataselect_stats fk_nodes; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.dataselect_stats + ADD CONSTRAINT fk_nodes FOREIGN KEY (node_id) REFERENCES public.nodes(id); + +ALTER TABLE ONLY public.dataselect_stats + ADD CONSTRAINT uniq_stat UNIQUE (date,network,station,location,channel,country); + +-- +-- Name: tokens fk_nodes; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.tokens + ADD CONSTRAINT fk_nodes FOREIGN KEY (node_id) REFERENCES public.nodes(id); + + +-- +-- PostgreSQL database dump complete +-- diff --git a/tests/test_sql/test.sql b/tests/test_sql/test.sql new file mode 100644 index 0000000..edd768c --- /dev/null +++ b/tests/test_sql/test.sql @@ -0,0 +1,2 @@ +CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar); +INSERT INTO test VALUES(1, 2, 'c'); diff --git a/tests/test_sql/test2.sql b/tests/test_sql/test2.sql new file mode 100644 index 0000000..fbda0f5 --- /dev/null +++ b/tests/test_sql/test2.sql @@ -0,0 +1 @@ +INSERT INTO test VALUES(2, 1, 'z'); diff --git a/tests/test_template_database.py b/tests/test_template_database.py new file mode 100644 index 0000000..ddd03d9 --- /dev/null +++ b/tests/test_template_database.py @@ -0,0 +1,30 @@ +"""Template database tests.""" +import pytest + +from pytest_postgresql.factories import postgresql, postgresql_proc +from pytest_postgresql.compat import connection +from tests.loader import load_database + +postgresql_proc_with_template = postgresql_proc( + port=21987, + dbname="stories_templated", + load=[load_database], +) + +postgresql_template = postgresql( + "postgresql_proc_with_template", + dbname="stories_templated", +) + + +@pytest.mark.parametrize("_", range(5)) +def test_template_database(postgresql_template: connection, _: int) -> None: + """Check that the database struture gets recreated out of a template.""" + with postgresql_template.cursor() as cur: + cur.execute("SELECT * FROM stories") + res = cur.fetchall() + assert len(res) == 4 + cur.execute("TRUNCATE stories") + cur.execute("SELECT * FROM stories") + res = cur.fetchall() + assert len(res) == 0 diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..cfde61c --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,23 @@ +"""Auxiliary tests.""" +import pytest + +from pytest_postgresql.executor import PostgreSQLExecutor + + +@pytest.mark.parametrize( + "ctl_input, version", + ( + ("pg_ctl (PostgreSQL) 9.6.21", "9.6"), + ("pg_ctl (PostgreSQL) 10.0", "10.0"), + ("pg_ctl (PostgreSQL) 10.1", "10.1"), + ("pg_ctl (PostgreSQL) 10.16", "10.16"), + ("pg_ctl (PostgreSQL) 11.11", "11.11"), + ("pg_ctl (PostgreSQL) 12.6", "12.6"), + ("pg_ctl (PostgreSQL) 13.2", "13.2"), + ), +) +def test_versions(ctl_input: str, version: str) -> None: + """Check correctness of the version regexp.""" + match = PostgreSQLExecutor.VERSION_RE.search(ctl_input) + assert match is not None + assert match.groupdict()["version"] == version