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