Skip to content

Commit

Permalink
Merge pull request #1079 from guzman-raphael/pytest
Browse files Browse the repository at this point in the history
Initialize `pytest` and convert `test_connection`
  • Loading branch information
jverswijver authored May 26, 2023
2 parents 40a6794 + 7a41e8b commit a55e8d6
Show file tree
Hide file tree
Showing 72 changed files with 451 additions and 247 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ FROM mcr.microsoft.com/devcontainers/python:3.7-bullseye
RUN \
apt update && \
apt-get install bash-completion graphviz default-mysql-client -y && \
pip install flake8 black faker ipykernel nose nose-cov datajoint && \
pip install flake8 black faker ipykernel pytest pytest-cov nose nose-cov datajoint && \
pip uninstall datajoint -y

ENV DJ_HOST fakeservices.datajoint.io
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"ghcr.io/guiyomh/features/vim:0": {}
},
"onCreateCommand": "pip install -e .",
"postStartCommand": "MYSQL_VER=8.0 MINIO_VER=RELEASE.2022-08-11T04-37-28Z docker compose -f local-docker-compose.yml down && MYSQL_VER=8.0 MINIO_VER=RELEASE.2022-08-11T04-37-28Z docker compose -f local-docker-compose.yml up --build --wait",
"postStartCommand": "MYSQL_VER=8.0 MINIO_VER=RELEASE.2022-08-11T04-37-28Z docker compose -f local-docker-compose.yml down && docker volume prune -f && MYSQL_VER=8.0 MINIO_VER=RELEASE.2022-08-11T04-37-28Z docker compose -f local-docker-compose.yml up --build --wait",
"forwardPorts": [
80,
443,
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:
--count --max-complexity=62 --max-line-length=127 --statistics
black datajoint --check -v
black tests --check -v
black tests_old --check -v
publish-docs:
if: |
github.event_name == 'push' &&
Expand Down
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@
"[markdown]": {
"editor.defaultFormatter": "disable"
},
"[yaml]": {
"editor.defaultFormatter": "disable"
},
"[dockercompose]": {
"editor.defaultFormatter": "disable"
},
"files.autoSave": "off"
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Changed - `.data` method to `.stream` in the `get()` method for S3 (external) objects PR [#1085](https://github.com/datajoint/datajoint-python/pull/1085)
- Fixed - Docs to rename `create_virtual_module` to `VirtualModule`
- Added - Skeleton from `datajoint-company/datajoint-docs` repository for docs migration
- Added - Initial `pytest` for `test_connection`

### 0.14.0 -- Feb 13, 2023
- Added - `json` data type ([#245](https://github.com/datajoint/datajoint-python/issues/245)) PR [#1051](https://github.com/datajoint/datajoint-python/pull/1051)
Expand Down
3 changes: 2 additions & 1 deletion LNX-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ services:
pip install --user nose nose-cov
pip install -e .
pip list --format=freeze | grep datajoint
nosetests -vsw tests --with-coverage --cover-package=datajoint
pytest -sv --cov-report term-missing --cov=datajoint tests
nosetests -vsw tests_old --with-coverage --cover-package=datajoint
# ports:
# - "8888:8888"
user: ${HOST_UID}:anaconda
Expand Down
6 changes: 2 additions & 4 deletions datajoint/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
logger = logging.getLogger(__name__.split(".")[0])


def set_password(
new_password=None, connection=None, update_config=None
): # pragma: no cover
def set_password(new_password=None, connection=None, update_config=None):
connection = conn() if connection is None else connection
if new_password is None:
new_password = getpass("New password: ")
Expand All @@ -28,7 +26,7 @@ def set_password(
config.save_local(verbose=True)


def kill(restriction=None, connection=None, order_by=None): # pragma: no cover
def kill(restriction=None, connection=None, order_by=None):
"""
view and kill database connections.
Expand Down
4 changes: 2 additions & 2 deletions datajoint/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ def conn(
host = host if host is not None else config["database.host"]
user = user if user is not None else config["database.user"]
password = password if password is not None else config["database.password"]
if user is None: # pragma: no cover
if user is None:
user = input("Please enter DataJoint username: ")
if password is None: # pragma: no cover
if password is None:
password = getpass(prompt="Please enter DataJoint password: ")
init_fun = (
init_fun if init_fun is not None else config["connection.init_function"]
Expand Down
2 changes: 1 addition & 1 deletion datajoint/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def load(self, force=True):
self.add_edge(fk["referenced_table"], alias_node, **props)
self.add_edge(alias_node, fk["referencing_table"], **props)

if not nx.is_directed_acyclic_graph(self): # pragma: no cover
if not nx.is_directed_acyclic_graph(self):
raise DataJointError("DataJoint can only work with acyclic dependencies")
self._loaded = True

Expand Down
15 changes: 12 additions & 3 deletions docs/src/develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,24 @@ The following will verify there are no regression errors by running our test sui

- Entire test suite:
```
nosetests -vw tests
nosetests -vw tests_old
```
> Note: We are in the process of upgrading to `pytest` tests. To run those, use:
> ```
> pytest -sv --cov-report term-missing --cov=datajoint tests
> ```
- A single functional test:
```
nosetests -vs --tests=tests.test_external_class:test_insert_and_fetch
nosetests -vs --tests=tests_old.test_external_class:test_insert_and_fetch
```
> Note: We are in the process of upgrading to `pytest` tests. To run those, use:
> ```
> pytest -sv tests/test_connection.py::test_dj_conn
> ```
- A single class test:
```
nosetests -vs --tests=tests.test_fetch:TestFetch.test_getattribute_for_fetch1
nosetests -vs --tests=tests_old.test_fetch:TestFetch.test_getattribute_for_fetch1
```
### Style Tests
Expand Down
185 changes: 47 additions & 138 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,160 +1,69 @@
"""
Package for testing datajoint. Setup fixture will be run
to ensure that proper database connection and access privilege
exists. The content of the test database will be destroyed
after the test.
"""

import logging
from os import environ, remove
import datajoint as dj
from distutils.version import LooseVersion
from packaging import version
import pytest
import os
from pathlib import Path
import minio
import urllib3
import certifi
import shutil
from datajoint.utils import parse_sql

__author__ = "Edgar Walker, Fabian Sinz, Dimitri Yatsenko, Raphael Guzman"

# turn on verbose logging
logging.basicConfig(level=logging.DEBUG)

__all__ = ["__author__", "PREFIX", "CONN_INFO"]

# Connection for testing
CONN_INFO = dict(
host=environ.get("DJ_TEST_HOST", "fakeservices.datajoint.io"),
user=environ.get("DJ_TEST_USER", "datajoint"),
password=environ.get("DJ_TEST_PASSWORD", "datajoint"),
)
PREFIX = "djtest"

CONN_INFO_ROOT = dict(
host=environ.get("DJ_HOST", "fakeservices.datajoint.io"),
user=environ.get("DJ_USER", "root"),
password=environ.get("DJ_PASS", "simple"),
)

S3_CONN_INFO = dict(
endpoint=environ.get("S3_ENDPOINT", "fakeservices.datajoint.io"),
access_key=environ.get("S3_ACCESS_KEY", "datajoint"),
secret_key=environ.get("S3_SECRET_KEY", "datajoint"),
bucket=environ.get("S3_BUCKET", "datajoint.test"),
)

# Prefix for all databases used during testing
PREFIX = environ.get("DJ_TEST_DB_PREFIX", "djtest")
conn_root = dj.conn(**CONN_INFO_ROOT)

# Initialize httpClient with relevant timeout.
httpClient = urllib3.PoolManager(
timeout=30,
cert_reqs="CERT_REQUIRED",
ca_certs=certifi.where(),
retries=urllib3.Retry(
total=3, backoff_factor=0.2, status_forcelist=[500, 502, 503, 504]
),
)

# Initialize minioClient with an endpoint and access/secret keys.
minioClient = minio.Minio(
S3_CONN_INFO["endpoint"],
access_key=S3_CONN_INFO["access_key"],
secret_key=S3_CONN_INFO["secret_key"],
secure=True,
http_client=httpClient,
host=os.getenv("DJ_HOST"),
user=os.getenv("DJ_USER"),
password=os.getenv("DJ_PASS"),
)


def setup_package():
"""
Package-level unit test setup
Turns off safemode
"""
@pytest.fixture
def connection_root():
"""Root user database connection."""
dj.config["safemode"] = False
connection = dj.Connection(
host=os.getenv("DJ_HOST"),
user=os.getenv("DJ_USER"),
password=os.getenv("DJ_PASS"),
)
yield connection
dj.config["safemode"] = True
connection.close()


@pytest.fixture
def connection_test(connection_root):
"""Test user database connection."""
database = f"{PREFIX}%%"
credentials = dict(
host=os.getenv("DJ_HOST"), user="datajoint", password="datajoint"
)
permission = "ALL PRIVILEGES"

# Create MySQL users
if LooseVersion(conn_root.query("select @@version;").fetchone()[0]) >= LooseVersion(
"8.0.0"
):
if version.parse(
connection_root.query("select @@version;").fetchone()[0]
) >= version.parse("8.0.0"):
# create user if necessary on mysql8
conn_root.query(
connection_root.query(
f"""
CREATE USER IF NOT EXISTS '{credentials["user"]}'@'%%'
IDENTIFIED BY '{credentials["password"]}';
"""
CREATE USER IF NOT EXISTS 'datajoint'@'%%'
IDENTIFIED BY 'datajoint';
"""
)
conn_root.query(
connection_root.query(
f"""
GRANT {permission} ON `{database}`.*
TO '{credentials["user"]}'@'%%';
"""
CREATE USER IF NOT EXISTS 'djview'@'%%'
IDENTIFIED BY 'djview';
"""
)
conn_root.query(
"""
CREATE USER IF NOT EXISTS 'djssl'@'%%'
IDENTIFIED BY 'djssl'
REQUIRE SSL;
"""
)
conn_root.query("GRANT ALL PRIVILEGES ON `djtest%%`.* TO 'datajoint'@'%%';")
conn_root.query("GRANT SELECT ON `djtest%%`.* TO 'djview'@'%%';")
conn_root.query("GRANT SELECT ON `djtest%%`.* TO 'djssl'@'%%';")
else:
# grant permissions. For MySQL 5.7 this also automatically creates user
# if not exists
conn_root.query(
"""
GRANT ALL PRIVILEGES ON `djtest%%`.* TO 'datajoint'@'%%'
IDENTIFIED BY 'datajoint';
connection_root.query(
f"""
GRANT {permission} ON `{database}`.*
TO '{credentials["user"]}'@'%%'
IDENTIFIED BY '{credentials["password"]}';
"""
)
conn_root.query(
"GRANT SELECT ON `djtest%%`.* TO 'djview'@'%%' IDENTIFIED BY 'djview';"
)
conn_root.query(
"""
GRANT SELECT ON `djtest%%`.* TO 'djssl'@'%%'
IDENTIFIED BY 'djssl'
REQUIRE SSL;
"""
)

region = "us-east-1"
# Add S3
try:
minioClient.make_bucket(S3_CONN_INFO["bucket"], location=region)
except minio.error.S3Error as e:
if e.code != "BucketAlreadyOwnedByYou":
raise e


def teardown_package():
"""
Package-level unit test teardown.
Removes all databases with name starting with PREFIX.
To deal with possible foreign key constraints, it will unset
and then later reset FOREIGN_KEY_CHECKS flag
"""
conn_root.query("SET FOREIGN_KEY_CHECKS=0")
cur = conn_root.query('SHOW DATABASES LIKE "{}\_%%"'.format(PREFIX))
for db in cur.fetchall():
conn_root.query("DROP DATABASE `{}`".format(db[0]))
conn_root.query("SET FOREIGN_KEY_CHECKS=1")
if os.path.exists("dj_local_conf.json"):
remove("dj_local_conf.json")

# Remove created users
conn_root.query("DROP USER `datajoint`")
conn_root.query("DROP USER `djview`")
conn_root.query("DROP USER `djssl`")

# Remove S3
objs = list(minioClient.list_objects(S3_CONN_INFO["bucket"], recursive=True))
objs = [
minioClient.remove_object(S3_CONN_INFO["bucket"], o.object_name.encode("utf-8"))
for o in objs
]
minioClient.remove_bucket(S3_CONN_INFO["bucket"])
connection = dj.Connection(**credentials)
yield connection
connection_root.query(f"""DROP USER `{credentials["user"]}`""")
connection.close()
Loading

0 comments on commit a55e8d6

Please sign in to comment.