From d8661a0d6a03197bb5671e01d9e2f9f926c20526 Mon Sep 17 00:00:00 2001
From: Christopher Rhodes <christopher.rhodes@embl.de>
Date: Fri, 5 Jul 2024 15:42:47 +0200
Subject: [PATCH] Moved test configuration to its own module

---
 model_server/base/util.py          |  86 -----------------------
 model_server/conf/testing.py       | 109 +++++++++++++++++++++++++++++
 tests/base/conf.py                 |  83 ----------------------
 tests/base/test_accessors.py       |   4 +-
 tests/base/test_api.py             |   5 +-
 tests/base/test_model.py           |   3 +-
 tests/base/test_roiset.py          |   5 +-
 tests/base/test_workflow.py        |   4 +-
 tests/test_ilastik/_conf.py        |  12 ----
 tests/test_ilastik/test_ilastik.py |   2 +-
 10 files changed, 120 insertions(+), 193 deletions(-)
 create mode 100644 model_server/conf/testing.py
 delete mode 100644 tests/base/conf.py
 delete mode 100644 tests/test_ilastik/_conf.py

diff --git a/model_server/base/util.py b/model_server/base/util.py
index 6185ae21..9b95edca 100644
--- a/model_server/base/util.py
+++ b/model_server/base/util.py
@@ -1,16 +1,10 @@
-import json
-import unittest
 from math import ceil
-from multiprocessing import Process
 from pathlib import Path
-import os
 import re
 from time import localtime, strftime
 from typing import List
 
 import pandas as pd
-import requests
-from urllib3 import Retry
 
 from .accessors import InMemoryDataAccessor, write_accessor_data_to_file
 from .models import Model
@@ -170,83 +164,3 @@ def loop_workflow(
 
     if len(failures) > 0:
         pd.DataFrame(failures).to_csv(Path(output_folder_path) / 'failures.csv')
-
-
-class TestServerBaseClass(unittest.TestCase):
-    """
-    Base class for unittests of API functionality.  Implements both server and clients for testing.
-    """
-
-    app_name = 'model_server.base.api:app'
-
-    def setUp(self) -> None:
-        import uvicorn
-        host = '127.0.0.1'
-        port = 5001
-
-        self.server_process = Process(
-            target=uvicorn.run,
-            args=(self.app_name, ),
-            kwargs={'host': host, 'port': port, 'log_level': 'critical'},
-            daemon=True
-        )
-        self.uri = f'http://{host}:{port}/'
-        self.server_process.start()
-
-    def _get_sesh(self):
-        sesh = requests.Session()
-        retries = Retry(
-            total=5,
-            backoff_factor=0.1,
-        )
-        sesh.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
-        return sesh
-
-    def _get(self, endpoint):
-        return self._get_sesh().get(self.uri + endpoint)
-
-    def _put(self, endpoint, query=None, body=None):
-        return self._get_sesh().put(
-            self.uri + endpoint,
-            params=query,
-            data=json.dumps(body)
-        )
-
-    def tearDown(self) -> None:
-        self.server_process.terminate()
-        self.server_process.join()
-
-def setup_test_data():
-    """
-    Look for test data, create test output directory, parse and return meta information
-    :return:
-        meta (dict) of test data and paths
-    """
-    # places to look for test data
-    data_paths = [
-        os.environ.get('UNITTEST_DATA_ROOT'),
-        Path.home() / 'model_server' / 'testing',
-        os.getcwd(),
-    ]
-    root = None
-
-    # look for first instance of summary.json
-    for dp in data_paths:
-        if dp is None:
-            continue
-        sf = (Path(dp) / 'summary.json')
-        if sf.exists():
-            with open(sf, 'r') as fh:
-                meta = json.load(fh)
-                root = Path(dp)
-                break
-
-    if root is None:
-        raise Exception('Could not find test data, try setting environmental variable UNITTEST_DATA_ROOT.')
-
-    op_ev = os.environ.get('UNITTEST_OUTPUT', (root / 'test_output').__str__())
-    output_path = Path(op_ev)
-    output_path.mkdir(parents=True, exist_ok=True)
-    meta['root'] = root.__str__()
-    meta['output_path'] = output_path.__str__()
-    return meta
\ No newline at end of file
diff --git a/model_server/conf/testing.py b/model_server/conf/testing.py
new file mode 100644
index 00000000..f982ec8b
--- /dev/null
+++ b/model_server/conf/testing.py
@@ -0,0 +1,109 @@
+import json
+import os
+import unittest
+from multiprocessing import Process
+from pathlib import Path
+
+import requests
+from urllib3 import Retry
+
+
+class TestServerBaseClass(unittest.TestCase):
+    """
+    Base class for unittests of API functionality.  Implements both server and clients for testing.
+    """
+
+    app_name = 'model_server.base.api:app'
+
+    def setUp(self) -> None:
+        import uvicorn
+        host = '127.0.0.1'
+        port = 5001
+
+        self.server_process = Process(
+            target=uvicorn.run,
+            args=(self.app_name, ),
+            kwargs={'host': host, 'port': port, 'log_level': 'critical'},
+            daemon=True
+        )
+        self.uri = f'http://{host}:{port}/'
+        self.server_process.start()
+
+    def _get_sesh(self):
+        sesh = requests.Session()
+        retries = Retry(
+            total=5,
+            backoff_factor=0.1,
+        )
+        sesh.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
+        return sesh
+
+    def _get(self, endpoint):
+        return self._get_sesh().get(self.uri + endpoint)
+
+    def _put(self, endpoint, query=None, body=None):
+        return self._get_sesh().put(
+            self.uri + endpoint,
+            params=query,
+            data=json.dumps(body)
+        )
+
+    def tearDown(self) -> None:
+        self.server_process.terminate()
+        self.server_process.join()
+
+
+def setup_test_data():
+    """
+    Look for test data, create test output directory, parse and return meta information
+    :return:
+        meta (dict) of test data and paths
+    """
+    # places to look for test data
+    data_paths = [
+        os.environ.get('UNITTEST_DATA_ROOT'),
+        Path.home() / 'model_server' / 'testing',
+        os.getcwd(),
+    ]
+    root = None
+
+    # look for first instance of summary.json
+    for dp in data_paths:
+        if dp is None:
+            continue
+        sf = (Path(dp) / 'summary.json')
+        if sf.exists():
+            with open(sf, 'r') as fh:
+                meta = json.load(fh)
+                root = dp
+                break
+
+    if root is None:
+        raise Exception('Could not find test data, try setting environmental variable UNITTEST_DATA_ROOT.')
+
+    op_ev = os.environ.get('UNITTEST_OUTPUT', (root / 'test_output'))
+    meta['output_path'] = Path(op_ev)
+    meta['root'] = Path(root)
+    meta['output_path'].mkdir(parents=True, exist_ok=True)
+
+
+    # resolve relative paths
+    def _resolve_paths(d):
+        keys = list(d.keys())
+        for k in keys:
+            if k == 'name':
+                d['path'] = root / d['name']
+            elif isinstance(d[k], dict):
+                _resolve_paths(d[k])
+    _resolve_paths(meta)
+
+    return meta
+
+meta = setup_test_data()
+# root = Path(meta['root'])
+# output_path = Path(meta['output_path'])
+
+# # resolve paths in test image files
+# imgs = meta['image_files']
+# for k in imgs.keys():
+#     imgs[k]['path'] = root / imgs[k]['name']
\ No newline at end of file
diff --git a/tests/base/conf.py b/tests/base/conf.py
deleted file mode 100644
index 18d49871..00000000
--- a/tests/base/conf.py
+++ /dev/null
@@ -1,83 +0,0 @@
-from pathlib import Path
-from model_server.base.util import setup_test_data
-
-meta = setup_test_data()
-
-root = Path(meta['root'])
-output_path = Path(meta['output_path'])
-
-# resolve paths in test image files
-imgs = meta['image_files']
-for k in imgs.keys():
-    imgs[k]['path'] = root / imgs[k]['name']
-
-# filename = 'D3-selection-01.czi'
-# czifile = {
-#     'filename': filename,
-#     'path': root / filename,
-#     'w': 1274,
-#     'h': 1274,
-#     'c': 5,
-#     'z': 1,
-#     'um_per_pixel': 1/3.9881,
-# }
-#
-# filename = 'rgb.png'
-# rgbpngfile = {
-#     'filename': filename,
-#     'path': root / filename,
-#     'w': 64,
-#     'h': 128,
-#     'c': 3,
-#     'z': 1
-# }
-#
-# filename = 'mono.png'
-# monopngfile = {
-#     'filename': filename,
-#     'path': root / filename,
-#     'w': 64,
-#     'h': 128,
-#     'c': 1,
-#     'z': 1
-# }
-#
-# filename = 'zmask-test-stack.tif'
-# tifffile = {
-#     'filename': filename,
-#     'path': root / filename,
-#     'w': 512,
-#     'h': 512,
-#     'c': 2,
-#     'z': 7,
-# }
-#
-# filename = 'mono_zstack_mask.tif'
-# monozstackmask = {
-#     'filename': filename,
-#     'path': root / filename,
-#     'w': 256,
-#     'h': 256,
-#     'c': 1,
-#     'z': 85
-# }
-#
-# roiset_test_data = {
-#     'multichannel_zstack': {
-#         'path': root / 'zmask-test-stack-chlorophyl.tif',
-#         'w': 512,
-#         'h': 512,
-#         'c': 5,
-#         'z': 7,
-#         'mask_path': root / 'zmask-test-stack-mask.tif',
-#         'mask_path_3d': root / 'zmask-test-stack-mask-3d.tif',
-#     },
-#     'pipeline_params': {
-#         'segmentation_channel': 0,
-#         'patches_channel': 4,
-#         'pxmap_channel': 0,
-#         'pxmap_threshold': 0.6,
-#     },
-#     'pixel_classifier': root / 'zmask' / 'AF405-bodies_boundaries.ilp',
-# }
-
diff --git a/tests/base/test_accessors.py b/tests/base/test_accessors.py
index 540f2121..a8a879f2 100644
--- a/tests/base/test_accessors.py
+++ b/tests/base/test_accessors.py
@@ -4,11 +4,11 @@ import numpy as np
 
 from model_server.base.accessors import PatchStack, make_patch_stack_from_file, FileNotFoundError
 
-from tests.base import conf
 from model_server.base.accessors import CziImageFileAccessor, DataShapeError, generate_file_accessor, InMemoryDataAccessor, PngFileAccessor, write_accessor_data_to_file, TifSingleSeriesFileAccessor
+import model_server.conf.testing as conf
 
 data = conf.meta['image_files']
-output_path = conf.output_path
+output_path = conf.meta['output_path']
 
 def _random_int(*args):
     return np.random.randint(0, 2 ** 8, size=args, dtype='uint8')
diff --git a/tests/base/test_api.py b/tests/base/test_api.py
index b8d406e8..46f614be 100644
--- a/tests/base/test_api.py
+++ b/tests/base/test_api.py
@@ -1,11 +1,10 @@
 from pathlib import Path
 
-from base.util import TestServerBaseClass
-from tests.base import conf
+import model_server.conf.testing as conf
 
 czifile = conf.meta['image_files']['czifile']
 
-class TestApiFromAutomatedClient(TestServerBaseClass):
+class TestApiFromAutomatedClient(conf.TestServerBaseClass):
 
     def copy_input_file_to_server(self):
         from shutil import copyfile
diff --git a/tests/base/test_model.py b/tests/base/test_model.py
index ce0159e7..d5b25c17 100644
--- a/tests/base/test_model.py
+++ b/tests/base/test_model.py
@@ -1,7 +1,8 @@
 import unittest
-from tests.base import conf
+
 from model_server.base.accessors import CziImageFileAccessor
 from model_server.base.models import DummySemanticSegmentationModel, DummyInstanceSegmentationModel, CouldNotLoadModelError
+import model_server.conf.testing as conf
 
 czifile = conf.meta['image_files']['czifile']
 
diff --git a/tests/base/test_roiset.py b/tests/base/test_roiset.py
index c7bf2752..f993d2a0 100644
--- a/tests/base/test_roiset.py
+++ b/tests/base/test_roiset.py
@@ -6,15 +6,14 @@ from pathlib import Path
 
 import pandas as pd
 
-from tests.base import conf
-
 from model_server.base.roiset import RoiSetExportParams, RoiSetMetaParams
 from model_server.base.roiset import RoiSet
 from model_server.base.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file, PatchStack
 from model_server.base.models import DummyInstanceSegmentationModel
+import model_server.conf.testing as conf
 
 data = conf.meta['image_files']
-output_path = conf.output_path
+output_path = conf.meta['output_path']
 params = conf.meta['roiset']
 
 class BaseTestRoiSetMonoProducts(object):
diff --git a/tests/base/test_workflow.py b/tests/base/test_workflow.py
index c0f857ef..54b83268 100644
--- a/tests/base/test_workflow.py
+++ b/tests/base/test_workflow.py
@@ -1,11 +1,11 @@
 import unittest
 
-from tests.base import conf
 from model_server.base.models import DummySemanticSegmentationModel
 from model_server.base.workflows import classify_pixels
+import model_server.conf.testing as conf
 
 czifile = conf.meta['image_files']['czifile']
-output_path = conf.output_path
+output_path = conf.meta['output_path']
 
 class TestGetSessionObject(unittest.TestCase):
     def setUp(self) -> None:
diff --git a/tests/test_ilastik/_conf.py b/tests/test_ilastik/_conf.py
deleted file mode 100644
index 8fb8fc5e..00000000
--- a/tests/test_ilastik/_conf.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from tests.base import _conf
-root = _conf.root / 'ilastik'
-
-
-ilastik_classifiers = {
-    'px': root / 'demo_px.ilp',
-    'pxmap_to_obj': root / 'demo_obj.ilp',
-    'seg_to_obj': root / 'demo_obj_seg.ilp',
-    'px_color_zstack': root / 'px-3d-color.ilp',
-    'ob_pxmap_color_zstack': root / 'ob-pxmap-color-zstack.ilp',
-    'ob_seg_color_zstack': root / 'ob-seg-color-zstack.ilp',
-}
\ No newline at end of file
diff --git a/tests/test_ilastik/test_ilastik.py b/tests/test_ilastik/test_ilastik.py
index 5153e854..3214d04f 100644
--- a/tests/test_ilastik/test_ilastik.py
+++ b/tests/test_ilastik/test_ilastik.py
@@ -7,7 +7,7 @@ from model_server.extensions.ilastik import models as ilm
 from model_server.extensions.ilastik.workflows import infer_px_then_ob_model
 from model_server.base.roiset import _get_label_ids, RoiSet, RoiSetMetaParams
 from model_server.base.workflows import classify_pixels
-from model_server.base.util import TestServerBaseClass
+from conf.testing import TestServerBaseClass
 
 from tests.test_ilastik import conf
 
-- 
GitLab