Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions testkit/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ ENV PYENV_ROOT /.pyenv
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH

# Set minimum supported Python version
RUN pyenv install 3.7.12
RUN pyenv install 3.7:latest
RUN pyenv install 3.8:latest
RUN pyenv install 3.9:latest
RUN pyenv install 3.10:latest
RUN pyenv rehash
RUN pyenv global 3.7.12
RUN pyenv global $(pyenv versions --bare --skip-aliases)

# Install Latest pip for each environment
# https://pip.pypa.io/en/stable/news/
RUN python -m pip install --upgrade pip

# Install Python Testing Tools
RUN python -m pip install coverage tox
RUN python -m pip install coverage tox tox-factor
10 changes: 9 additions & 1 deletion testkit/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@
# limitations under the License.


import subprocess


def run(args):
subprocess.run(
args, universal_newlines=True, stderr=subprocess.STDOUT, check=True)


if __name__ == "__main__":
pass
run(["python", "-m", "tox", "-f", "integration"])
2 changes: 1 addition & 1 deletion testkit/unittests.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ def run(args):


if __name__ == "__main__":
run(["python", "-m", "tox", "-c", "tox-unit.ini"])
run(["python", "-m", "tox", "-f", "unit"])
190 changes: 190 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# This file is part of Neo4j.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import asyncio
from functools import wraps
from os import environ
import warnings

import pytest
import pytest_asyncio

from neo4j import (
AsyncGraphDatabase,
ExperimentalWarning,
GraphDatabase,
)
from neo4j._exceptions import BoltHandshakeError
from neo4j._sync.io import Bolt
from neo4j.exceptions import ServiceUnavailable

from . import env


# from neo4j.debug import watch
#
# watch("neo4j")


@pytest.fixture(scope="session")
def uri():
return env.NEO4J_SERVER_URI


@pytest.fixture(scope="session")
def bolt_uri(uri):
if env.NEO4J_SCHEME != "bolt":
pytest.skip("Test requires bolt scheme")
return uri


@pytest.fixture(scope="session")
def _forced_bolt_uri():
return f"bolt://{env.NEO4J_HOST}:{env.NEO4J_PORT}"


@pytest.fixture(scope="session")
def neo4j_uri():
if env.NEO4J_SCHEME != "neo4j":
pytest.skip("Test requires neo4j scheme")
return uri


@pytest.fixture(scope="session")
def _forced_neo4j_uri():
return f"neo4j://{env.NEO4J_HOST}:{env.NEO4J_PORT}"


@pytest.fixture(scope="session")
def auth():
return env.NEO4J_USER, env.NEO4J_PASS


@pytest.fixture
def driver(uri, auth):
with GraphDatabase.driver(uri, auth=auth) as driver:
yield driver


@pytest.fixture
def bolt_driver(bolt_uri, auth):
with GraphDatabase.driver(bolt_uri, auth=auth) as driver:
yield driver


@pytest.fixture
def neo4j_driver(neo4j_uri, auth):
with GraphDatabase.driver(neo4j_uri, auth=auth) as driver:
yield driver


@wraps(AsyncGraphDatabase.driver)
def get_async_driver_no_warning(*args, **kwargs):
# with warnings.catch_warnings():
# warnings.filterwarnings("ignore", "neo4j async", ExperimentalWarning)
with pytest.warns(ExperimentalWarning, match="neo4j async"):
return AsyncGraphDatabase.driver(*args, **kwargs)


@pytest_asyncio.fixture
async def async_driver(uri, auth):
async with get_async_driver_no_warning(uri, auth=auth) as driver:
yield driver


@pytest_asyncio.fixture
async def async_bolt_driver(bolt_uri, auth):
async with get_async_driver_no_warning(bolt_uri, auth=auth) as driver:
yield driver


@pytest_asyncio.fixture
async def async_neo4j_driver(neo4j_uri, auth):
async with get_async_driver_no_warning(neo4j_uri, auth=auth) as driver:
yield driver


@pytest.fixture
def _forced_bolt_driver(_forced_bolt_uri):
with GraphDatabase.driver(_forced_bolt_uri, auth=auth) as driver:
yield driver


@pytest.fixture
def _forced_neo4j_driver(_forced_neo4j_uri):
with GraphDatabase.driver(_forced_neo4j_uri, auth=auth) as driver:
yield driver


@pytest.fixture(scope="session")
def server_info(_forced_bolt_driver):
return _forced_bolt_driver.get_server_info()


@pytest.fixture(scope="session")
def bolt_protocol_version(server_info):
return server_info.protocol_version


def mark_requires_min_bolt_version(version="3.5"):
return pytest.mark.skipif(
env.NEO4J_VERSION < version,
reason=f"requires server version '{version}' or higher, "
f"found '{env.NEO4J_VERSION}'"
)


def mark_requires_edition(edition):
return pytest.mark.skipif(
env.NEO4J_EDITION != edition,
reason=f"requires server edition '{edition}', "
f"found '{env.NEO4J_EDITION}'"
)


@pytest.fixture
def session(driver):
with driver.session() as session:
yield session


@pytest.fixture
def bolt_session(bolt_driver):
with bolt_driver.session() as session:
yield session


@pytest.fixture
def neo4j_session(neo4j_driver):
with neo4j_driver.session() as session:
yield session


# async support for pytest-benchmark
# https://github.com/ionelmc/pytest-benchmark/issues/66
@pytest_asyncio.fixture
async def aio_benchmark(benchmark, event_loop):
def _wrapper(func, *args, **kwargs):
if asyncio.iscoroutinefunction(func):
@benchmark
def _():
return event_loop.run_until_complete(func(*args, **kwargs))
else:
benchmark(func, *args, **kwargs)

return _wrapper
87 changes: 77 additions & 10 deletions tests/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,86 @@
# limitations under the License.


from os import getenv
import abc
from os import environ
import sys


# Full path of a server package to be used for integration testing
NEO4J_SERVER_PACKAGE = getenv("NEO4J_SERVER_PACKAGE")
class _LazyEval(abc.ABC):
@abc.abstractmethod
def eval(self):
pass

# An existing remote server at this URI
NEO4J_SERVER_URI = getenv("NEO4J_URI")

# Name of a user for the currently running server
NEO4J_USER = getenv("NEO4J_USER")
class _LazyEvalEnv(_LazyEval):
def __init__(self, env_key, type_=str, default=...):
self.env_key = env_key
self.type_ = type_
self.default = default

# Password for the currently running server
NEO4J_PASSWORD = getenv("NEO4J_PASSWORD")
def eval(self):
if self.default is not ...:
value = environ.get(self.env_key, default=self.default)
else:
try:
value = environ[self.env_key]
except KeyError as e:
raise Exception(
f"Missing environemnt variable {self.env_key}"
) from e
if self.type_ is bool:
return value.lower() in ("yes", "y", "1", "on", "true")
if self.type_ is not None:
return self.type_(value)

NEOCTRL_ARGS = getenv("NEOCTRL_ARGS", "3.4.1")

class _LazyEvalFunc(_LazyEval):
def __init__(self, func):
self.func = func

def eval(self):
return self.func()


class _Module:
def __init__(self, module):
self._moudle = module

def __getattr__(self, item):
val = getattr(self._moudle, item)
if isinstance(val, _LazyEval):
val = val.eval()
setattr(self._moudle, item, val)
return val


_module = _Module(sys.modules[__name__])

sys.modules[__name__] = _module


NEO4J_HOST = _LazyEvalEnv("TEST_NEO4J_HOST")
NEO4J_PORT = _LazyEvalEnv("TEST_NEO4J_PORT", int)
NEO4J_USER = _LazyEvalEnv("TEST_NEO4J_USER")
NEO4J_PASS = _LazyEvalEnv("TEST_NEO4J_PASS")
NEO4J_SCHEME = _LazyEvalEnv("TEST_NEO4J_SCHEME")
NEO4J_EDITION = _LazyEvalEnv("TEST_NEO4J_EDITION")
NEO4J_VERSION = _LazyEvalEnv("TEST_NEO4J_VERSION")
NEO4J_IS_CLUSTER = _LazyEvalEnv("TEST_NEO4J_IS_CLUSTER", bool)
NEO4J_SERVER_URI = _LazyEvalFunc(
lambda: f"{_module.NEO4J_SCHEME}://{_module.NEO4J_HOST}:"
f"{_module.NEO4J_PORT}"
)


__all__ = (
"NEO4J_HOST",
"NEO4J_PORT",
"NEO4J_USER",
"NEO4J_PASS",
"NEO4J_SCHEME",
"NEO4J_EDITION",
"NEO4J_VERSION",
"NEO4J_IS_CLUSTER",
"NEO4J_SERVER_URI",
)
6 changes: 3 additions & 3 deletions tests/integration/async_/test_custom_ssl_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


@mark_async_test
async def test_custom_ssl_context_wraps_connection(target, auth, mocker):
async def test_custom_ssl_context_wraps_connection(uri, auth, mocker):
# Test that the driver calls either `.wrap_socket` or `.wrap_bio` on the
# provided custom SSL context.

Expand All @@ -39,8 +39,8 @@ def wrap_fail(*_, **__):
fake_ssl_context.wrap_socket.side_effect = wrap_fail
fake_ssl_context.wrap_bio.side_effect = wrap_fail

driver = AsyncGraphDatabase.neo4j_driver(
target, auth=auth, ssl_context=fake_ssl_context
driver = AsyncGraphDatabase.driver(
uri, auth=auth, ssl_context=fake_ssl_context
)
async with driver:
async with driver.session() as session:
Expand Down
Loading