From dca06fb6518bc1e00bf7a3f54981496288c1005d Mon Sep 17 00:00:00 2001 From: David Huard Date: Wed, 11 May 2022 14:31:48 -0400 Subject: [PATCH 1/2] Make asynchronous process test run asynchronously. Fix #656. --- CONTRIBUTORS.md | 1 + tests/processes/__init__.py | 35 ++++++++++++++++++ tests/test_assync.py | 74 +++++++++++++++---------------------- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a7a19c359..47926e204 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,6 +10,7 @@ * @jonas-eberle Jonas Eberle * @cehbrecht Carsten Ehbrecht * @idanmiara Idan Miara +* @huard David Huard # Contributor to older versions of PyWPS (< 4.x) diff --git a/tests/processes/__init__.py b/tests/processes/__init__.py index 0b847ace4..5882a9ef1 100644 --- a/tests/processes/__init__.py +++ b/tests/processes/__init__.py @@ -99,3 +99,38 @@ def bbox(request, response): area = request.inputs['area'][0].data response.outputs['extent'].data = area return response + + +class Sleep(Process): + """A long running process, just sleeping.""" + def __init__(self): + inputs = [ + LiteralInput('seconds', title='Seconds', data_type='float') + ] + outputs = [ + LiteralOutput('finished', title='Finished', data_type='boolean') + ] + + super(Sleep, self).__init__( + self._handler, + identifier='sleep', + title='Sleep', + abstract='Wait for specified number of seconds.', + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + @staticmethod + def _handler(request, response): + import time + + seconds = request.inputs['seconds'][0].data + step = seconds / 3 + for i in range(3): + response.update_status('Sleep in progress...', i / 3 * 100) + time.sleep(step) + + response.outputs['finished'].data = "True" + return response diff --git a/tests/test_assync.py b/tests/test_assync.py index 2db2eb074..6f6af6d02 100644 --- a/tests/test_assync.py +++ b/tests/test_assync.py @@ -5,66 +5,50 @@ import unittest import time -from pywps import Service, Process, LiteralInput, LiteralOutput +from pywps import Service, configuration from pywps import get_ElementMakerForVersion from pywps.tests import client_for, assert_response_accepted +from .processes import Sleep +from owslib.wps import WPSExecution +from pathlib import Path VERSION = "1.0.0" WPS, OWS = get_ElementMakerForVersion(VERSION) -def create_sleep(): +class ExecuteTest(unittest.TestCase): + def setUp(self) -> None: + self.mode = configuration.CONFIG.get('processing', 'mode') + configuration.CONFIG.set('processing', 'mode', 'distributed') - def sleep(request, response): - seconds = request.inputs['seconds'][0].data - assert isinstance(seconds, float) + def tearDown(self) -> None: + configuration.CONFIG.set('processing', 'mode', self.mode) - step = seconds / 3 - for i in range(3): - # How is status working in version 4 ? - #self.status.set("Waiting...", i * 10) - time.sleep(step) + def test_assync(self): + client = client_for(Service(processes=[Sleep()])) + wps = WPSExecution() - response.outputs['finished'].data = "True" - return response + # Build an asynchronous request (requires specifying outputs and setting the mode). + doc = wps.buildRequest('sleep', + inputs=[('seconds', '.01')], + output=[('finished', None, None)], + mode='async') - return Process(handler=sleep, - identifier='sleep', - title='Sleep', - inputs=[ - LiteralInput('seconds', title='Seconds', data_type='float') - ], - outputs=[ - LiteralOutput('finished', title='Finished', data_type='boolean') - ] - ) + resp = client.post_xml(doc=doc) + wps.parseResponse(resp.xml) + assert_response_accepted(resp) + # Wait for process to complete. The test will fail otherwise, which confirms the process is asynchronous. + time.sleep(.5) -class ExecuteTest(unittest.TestCase): - - def test_assync(self): - client = client_for(Service(processes=[create_sleep()])) - request_doc = WPS.Execute( - OWS.Identifier('sleep'), - WPS.DataInputs( - WPS.Input( - OWS.Identifier('seconds'), - WPS.Data( - WPS.LiteralData( - "0.3" - ) - ) - ) - ), - version="1.0.0" - ) - resp = client.post_xml(doc=request_doc) - assert_response_accepted(resp) + # Parse response to extract the status file path + url = resp.xml.xpath("//@statusLocation")[0] - # TODO: - # . extract the status URL from the response - # . send a status request + # OWSlib only reads from URLs, not local files. So we need to read the response manually. + p = Path(url[6:]) + wps.checkStatus(response=p.read_bytes(), sleepSecs=0) + assert wps.status == 'ProcessSucceeded' def load_tests(loader=None, tests=None, pattern=None): From cbd1c29c1816f2ed21ee2bfe232e70a41328c448 Mon Sep 17 00:00:00 2001 From: David Huard Date: Wed, 11 May 2022 16:46:14 -0400 Subject: [PATCH 2/2] moved async test at the end of the suite. reset original config in test_storage --- pywps/response/__init__.py | 4 ++-- tests/__init__.py | 4 ++-- tests/test_dblog.py | 15 +++++++-------- tests/test_storage.py | 3 +++ tests/{test_assync.py => test_z_async.py} | 5 ++--- 5 files changed, 16 insertions(+), 15 deletions(-) rename tests/{test_assync.py => test_z_async.py} (92%) diff --git a/pywps/response/__init__.py b/pywps/response/__init__.py index f4c7cfc75..088b9d24d 100644 --- a/pywps/response/__init__.py +++ b/pywps/response/__init__.py @@ -54,9 +54,9 @@ def _update_status(self, status, message, status_percentage): Update status report of currently running process instance :param str message: Message you need to share with the client - :param int status_percentage: Percent done (number betwen <0-100>) + :param int status_percentage: Percent done (number between <0-100>) :param pywps.response.status.WPS_STATUS status: process status - user should usually - ommit this parameter + omit this parameter """ self.message = message self.status = status diff --git a/tests/__init__.py b/tests/__init__.py index 8ea1a2b2e..43973ca11 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -26,7 +26,7 @@ from tests import test_service from tests import test_process from tests import test_processing -from tests import test_assync +from tests import test_z_async from tests import test_grass_location from tests import test_storage from tests import test_filestorage @@ -90,7 +90,7 @@ def load_tests(loader=None, tests=None, pattern=None): test_service.load_tests(), test_process.load_tests(), test_processing.load_tests(), - test_assync.load_tests(), + test_z_async.load_tests(), test_grass_location.load_tests(), test_storage.load_tests(), test_filestorage.load_tests(), diff --git a/tests/test_dblog.py b/tests/test_dblog.py index 7e26a685c..e48cb03b9 100644 --- a/tests/test_dblog.py +++ b/tests/test_dblog.py @@ -16,9 +16,8 @@ class DBLogTest(unittest.TestCase): """DBGLog test cases""" - def setUp(self): - - self.database = configuration.get_config_value('logging', 'database') + def tearDown(self) -> None: + configuration.load_configuration() def test_0_dblog(self): """Test pywps.formats.Format class @@ -30,18 +29,18 @@ def test_db_content(self): session = get_session() null_time_end = session.query(ProcessInstance).filter(ProcessInstance.time_end == None) self.assertEqual(null_time_end.count(), 0, - 'There are no unfinished processes loged') + 'There are no unfinished processes logged') null_status = session.query(ProcessInstance).filter(ProcessInstance.status == None) self.assertEqual(null_status.count(), 0, - 'There are no processes without status loged') + 'There are no processes without status logged') null_percent = session.query(ProcessInstance).filter(ProcessInstance.percent_done == None) self.assertEqual(null_percent.count(), 0, - 'There are no processes without percent loged') + 'There are no processes without percent logged') - null_percent = session.query(ProcessInstance).filter(ProcessInstance.percent_done < 100) - self.assertEqual(null_percent.count(), 0, + unfinished = session.query(ProcessInstance).filter(ProcessInstance.percent_done < 100) + self.assertEqual(unfinished.count(), 0, 'There are no unfinished processes') def load_tests(loader=None, tests=None, pattern=None): diff --git a/tests/test_storage.py b/tests/test_storage.py index 309f65755..d9322f3f1 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -34,6 +34,9 @@ def _get_file(self): class TestStorageBuilder(): + def teardown_class(cls): + configuration.load_configuration() + def test_default_storage(self): storage = StorageBuilder.buildStorage() assert isinstance(storage, FileStorage) diff --git a/tests/test_assync.py b/tests/test_z_async.py similarity index 92% rename from tests/test_assync.py rename to tests/test_z_async.py index 6f6af6d02..8aca9dee4 100644 --- a/tests/test_assync.py +++ b/tests/test_z_async.py @@ -19,13 +19,12 @@ class ExecuteTest(unittest.TestCase): def setUp(self) -> None: - self.mode = configuration.CONFIG.get('processing', 'mode') configuration.CONFIG.set('processing', 'mode', 'distributed') def tearDown(self) -> None: - configuration.CONFIG.set('processing', 'mode', self.mode) + configuration.load_configuration() - def test_assync(self): + def test_async(self): client = client_for(Service(processes=[Sleep()])) wps = WPSExecution()