From 73efe7019a8bf2c9e6db0a0ffa2360d8690a48c7 Mon Sep 17 00:00:00 2001 From: Markus Scheidgen <markus.scheidgen@gmail.com> Date: Tue, 12 Feb 2019 17:05:23 +0100 Subject: [PATCH] Refined tests. --- nomad/infrastructure.py | 1 + nomad/processing/base.py | 4 +-- nomad/processing/data.py | 2 +- nomad/search.py | 48 +---------------------------------- tests/__init__.py | 13 +++++++--- tests/conftest.py | 31 +++++++++++++--------- tests/processing/test_data.py | 8 +++--- tests/test_api.py | 14 +++++----- tests/test_client.py | 6 ++--- tests/test_search.py | 2 +- 10 files changed, 49 insertions(+), 80 deletions(-) diff --git a/nomad/infrastructure.py b/nomad/infrastructure.py index de85924b40..25426731d1 100644 --- a/nomad/infrastructure.py +++ b/nomad/infrastructure.py @@ -78,6 +78,7 @@ def setup_mongo(): global mongo_client mongo_client = connect(db=config.mongo.db_name, host=config.mongo.host, port=config.mongo.port) logger.info('setup mongo connection') + return mongo_client def setup_elastic(): diff --git a/nomad/processing/base.py b/nomad/processing/base.py index e2d9f9c48a..9d24d9fb0f 100644 --- a/nomad/processing/base.py +++ b/nomad/processing/base.py @@ -194,7 +194,7 @@ class Proc(Document, metaclass=ProcMetaclass): def fail(self, *errors, log_level=logging.ERROR, **kwargs): """ Allows to fail the process. Takes strings or exceptions as args. """ - assert self.tasks_running, 'Cannot fail a completed process.' + assert self.process_running or self.tasks_running, 'Cannot fail a completed process.' failed_with_exception = False @@ -219,7 +219,7 @@ class Proc(Document, metaclass=ProcMetaclass): def warning(self, *warnings, log_level=logging.WARNING, **kwargs): """ Allows to save warnings. Takes strings or exceptions as args. """ - assert self.tasks_running + assert self.process_running or self.tasks_running logger = self.get_logger(**kwargs) diff --git a/nomad/processing/data.py b/nomad/processing/data.py index 11517ce755..81132da386 100644 --- a/nomad/processing/data.py +++ b/nomad/processing/data.py @@ -239,7 +239,7 @@ class Calc(Proc): # index in search with utils.timer(logger, 'indexed', step='persist'): - search.Entry.from_calc_with_metadata(calc_with_metadata, published=False).persist() + search.Entry.from_calc_with_metadata(calc_with_metadata, published=False).save() # persist the archive with utils.timer( diff --git a/nomad/search.py b/nomad/search.py index c30f4efc30..db5c79966b 100644 --- a/nomad/search.py +++ b/nomad/search.py @@ -16,9 +16,6 @@ This module represents calculations in elastic search. """ -from elasticsearch.exceptions import ConflictError, ConnectionTimeout -from datetime import datetime -import time from elasticsearch_dsl import Document, InnerDoc, Keyword, Text, Date, \ Nested, Boolean, Search @@ -127,39 +124,7 @@ class Entry(Document): @classmethod def add_upload(cls, source: datamodel.UploadWithMetadata): for calc in source.calcs: - cls.from_calc_with_metadata(calc).save(op_type='create') - - def persist(self, **kwargs): - """ - Persist this entry to elastic search. Kwargs are passed to elastic search. - - Raises: - AlreadyExists: If the calculation already exists in elastic search. We use - the elastic document lock here. The elastic document is IDed via the - ``calc_id``. - """ - try: - # In practive es operation might fail due to timeout under heavy loads/ - # bad configuration. Retries with a small delay is a pragmatic solution. - e_after_retries = None - for _ in range(0, 2): - try: - self.save(op_type='create', **kwargs) - e_after_retries = None - break - except ConnectionTimeout as e: - e_after_retries = e - time.sleep(1) - except ConflictError as e: # this should never happen, but happens - e_after_retries = e - time.sleep(1) - else: - raise e - if e_after_retries is not None: - # if we had and exception and could not fix with retries, throw it - raise e_after_retries # pylint: disable=E0702 - except ConflictError: - raise AlreadyExists('Calculation %s/%s does already exist.' % (self.upload_id, self.calc_id)) + cls.from_calc_with_metadata(calc).save() @classmethod def update_by_query(cls, upload_id, script): @@ -191,14 +156,3 @@ class Entry(Document): def es_search(body): """ Perform an elasticsearch and not elasticsearch_dsl search on the Calc index. """ return infrastructure.elastic_client.search(index=config.elastic.index_name, body=body) - - @property - def json_dict(self): - """ A json serializable dictionary representation. """ - data = self.to_dict() - - upload_time = data.get('upload_time', None) - if upload_time is not None and isinstance(upload_time, datetime): - data['upload_time'] = data['upload_time'].isoformat() - - return {key: value for key, value in data.items() if value is not None} diff --git a/tests/__init__.py b/tests/__init__.py index d0a8968a51..482bfbc29c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -24,10 +24,17 @@ Otherwise the test submodules follow the names of the nomad code modules. from nomad import config -# For convinience we test the api without path prefix. -# This should be setup with a fixture with in conftest.py, but it will be too late. -# After importing the api module, the config values have already been used and + +# This should be setup with fixtures with in conftest.py, but it will be too late. +# After importing the api/infrastructure module, the config values have already been used and # changing them afterwards does not change anything anymore. + +# For convinience we test the api without path prefix. services_config = config.services._asdict() services_config.update(api_base_path='') config.services = config.NomadServicesConfig(**services_config) + +# We use a mocked in memory mongo version. +mongo_config = config.mongo._asdict() +mongo_config.update(host='mongomock://localhost') +config.mongo = config.MongoConfig(**mongo_config) diff --git a/tests/conftest.py b/tests/conftest.py index dc01e9ec59..c8da398578 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,8 +16,6 @@ from typing import Tuple import pytest import logging from sqlalchemy.orm import Session -from mongoengine import connect -from mongoengine.connection import disconnect from contextlib import contextmanager from collections import namedtuple from smtpd import SMTPServer @@ -79,7 +77,7 @@ def raw_files(raw_files_infra): @pytest.fixture(scope='function') -def client(monkeysession): +def client(monkeysession, mongo): api.app.config['TESTING'] = True client = api.app.test_client() @@ -98,23 +96,32 @@ def celery_config(): } +@pytest.fixture(scope='session') +def purged_app(celery_session_app): + """ + Purges all pending tasks of the celery app before test. This is necessary to + remove tasks from the queue that might be 'left over' from prior tests. + """ + celery_session_app.control.purge() + yield celery_session_app + + @pytest.fixture(scope='session') def worker(celery_session_worker): """ Provides a clean worker (no old tasks) per function. Waits for all tasks to be completed. """ pass -@pytest.fixture(scope='function') -def mongo(monkeypatch): - """ Provides a cleaned mocked mongo per function. """ - - disconnect() - connection = connect('test_db', host='mongomock://localhost') - monkeypatch.setattr('nomad.infrastructure.setup_mongo', lambda **kwargs: None) +@pytest.fixture(scope='session') +def mongo_infra(monkeysession): + return infrastructure.setup_mongo() - yield - connection.drop_database('test_db') +@pytest.fixture(scope='function') +def mongo(mongo_infra): + """ Provides a cleaned mocked mongo per function. """ + mongo_infra.drop_database('test_db') + return mongo_infra @pytest.fixture(scope='session') diff --git a/tests/processing/test_data.py b/tests/processing/test_data.py index 770c122a26..21a62deb72 100644 --- a/tests/processing/test_data.py +++ b/tests/processing/test_data.py @@ -62,7 +62,7 @@ def run_processing(uploaded_id: str, test_user) -> Upload: assert upload.current_task == 'uploading' upload.process_upload() # pylint: disable=E1101 - upload.block_until_complete(interval=.1) + upload.block_until_complete(interval=.01) return upload @@ -105,7 +105,7 @@ def test_processing(processed, no_warn, mails): @pytest.mark.timeout(10) -def test_processing_with_warning(raw_files, worker, test_user, with_warn): +def test_processing_with_warning(proc_infra, test_user, with_warn): example_file = 'tests/data/proc/examples_with_warning_template.zip' example_upload_id = os.path.basename(example_file).replace('.zip', '') upload_files = ArchiveBasedStagingUploadFiles(example_upload_id, create=True) @@ -116,7 +116,7 @@ def test_processing_with_warning(raw_files, worker, test_user, with_warn): @pytest.mark.timeout(10) -def test_process_non_existing(worker, test_user, with_error): +def test_process_non_existing(proc_infra, test_user, with_error): upload = run_processing('__does_not_exist', test_user) assert not upload.tasks_running @@ -127,7 +127,7 @@ def test_process_non_existing(worker, test_user, with_error): @pytest.mark.parametrize('task', ['extracting', 'parse_all', 'cleanup', 'parsing']) @pytest.mark.timeout(10) -def test_task_failure(monkeypatch, uploaded, worker, task, test_user, with_error): +def test_task_failure(monkeypatch, uploaded, worker, task, proc_infra, test_user, with_error): # mock the task method to through exceptions if hasattr(Upload, task): cls = Upload diff --git a/tests/test_api.py b/tests/test_api.py index 782fa002b8..1d382784ed 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -236,7 +236,7 @@ class TestUploads: @pytest.mark.parametrize('mode', ['multipart', 'stream', 'local_path']) @pytest.mark.parametrize('name', [None, 'test_name']) - def test_put(self, client, test_user_auth, proc_infra, example_upload, mode, name): + def test_put(self, client, test_user_auth, proc_infra, example_upload, mode, name, no_warn): file = example_upload if name: url = '/uploads/?name=%s' % name @@ -281,7 +281,7 @@ class TestUploads: yield True monkeypatch.setattr('nomad.processing.data.Upload.cleanup', old_cleanup) - def test_delete_during_processing(self, client, test_user_auth, proc_infra, slow_processing): + def test_delete_during_processing(self, client, test_user_auth, proc_infra, slow_processing, no_warn): rv = client.put('/uploads/?local_path=%s' % example_file, headers=test_user_auth) upload = self.assert_upload(rv.data) assert upload['tasks_running'] @@ -289,7 +289,7 @@ class TestUploads: assert rv.status_code == 400 self.assert_processing(client, test_user_auth, upload['upload_id']) - def test_delete_unstaged(self, client, test_user_auth, proc_infra): + def test_delete_unstaged(self, client, test_user_auth, proc_infra, no_warn): rv = client.put('/uploads/?local_path=%s' % example_file, headers=test_user_auth) upload = self.assert_upload(rv.data) self.assert_processing(client, test_user_auth, upload['upload_id']) @@ -297,7 +297,7 @@ class TestUploads: rv = client.delete('/uploads/%s' % upload['upload_id'], headers=test_user_auth) assert rv.status_code == 404 - def test_delete(self, client, test_user_auth, proc_infra): + def test_delete(self, client, test_user_auth, proc_infra, no_warn): rv = client.put('/uploads/?local_path=%s' % example_file, headers=test_user_auth) upload = self.assert_upload(rv.data) self.assert_processing(client, test_user_auth, upload['upload_id']) @@ -305,7 +305,7 @@ class TestUploads: assert rv.status_code == 200 self.assert_upload_does_not_exist(client, upload['upload_id'], test_user_auth) - def test_post(self, client, test_user_auth, example_upload, proc_infra): + def test_post(self, client, test_user_auth, example_upload, proc_infra, no_warn): rv = client.put('/uploads/?local_path=%s' % example_upload, headers=test_user_auth) upload = self.assert_upload(rv.data) self.assert_processing(client, test_user_auth, upload['upload_id']) @@ -313,14 +313,14 @@ class TestUploads: def test_post_metadata( self, client, proc_infra, admin_user_auth, test_user_auth, test_user, - other_test_user): + other_test_user, no_warn): rv = client.put('/uploads/?local_path=%s' % example_file, headers=test_user_auth) upload = self.assert_upload(rv.data) self.assert_processing(client, test_user_auth, upload['upload_id']) metadata = dict(comment='test comment') self.assert_unstage(client, admin_user_auth, upload['upload_id'], proc_infra, metadata) - def test_post_metadata_forbidden(self, client, proc_infra, test_user_auth): + def test_post_metadata_forbidden(self, client, proc_infra, test_user_auth, no_warn): rv = client.put('/uploads/?local_path=%s' % example_file, headers=test_user_auth) upload = self.assert_upload(rv.data) self.assert_processing(client, test_user_auth, upload['upload_id']) diff --git a/tests/test_client.py b/tests/test_client.py index ad9a09f94c..d435be08ab 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -19,11 +19,11 @@ from nomad.processing import SUCCESS from tests.test_files import example_file, create_public_upload -def test_get_upload_command(bravado): +def test_get_upload_command(bravado, no_warn): assert bravado.uploads.get_upload_command().response().result.upload_command is not None -def test_upload(bravado, proc_infra): +def test_upload(bravado, proc_infra, no_warn): with open(example_file, 'rb') as f: upload = bravado.uploads.upload(file=f, name='test_upload').response().result @@ -34,7 +34,7 @@ def test_upload(bravado, proc_infra): assert upload.tasks_status == SUCCESS -def test_get_repo_calc(bravado, raw_files): +def test_get_repo_calc(bravado, raw_files, no_warn): create_public_upload('test_upload', 'pp') repo = bravado.repo.get_repo_calc(upload_id='test_upload', calc_id='0').response().result assert repo is not None diff --git a/tests/test_search.py b/tests/test_search.py index 8de0ef9e21..8594182adc 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -46,7 +46,7 @@ def test_index_upload(elastic, processed: processing.Upload): def create_entry(calc_with_metadata: datamodel.CalcWithMetadata): - search.Entry.from_calc_with_metadata(calc_with_metadata).save(op_type='create') + search.Entry.from_calc_with_metadata(calc_with_metadata).save() assert_entry(calc_with_metadata.calc_id) -- GitLab