From 7492ff87a5660072e1293167f351ad3b6cd1e855 Mon Sep 17 00:00:00 2001 From: jluethi Date: Fri, 14 Jul 2023 13:02:25 +0200 Subject: [PATCH] Update manifest and add test that manifest matches function signature --- .github/workflows/ci.yml | 3 + .../__FRACTAL_MANIFEST__.json | 14 +++- tests/test_valid_args_schemas.py | 74 +++++++++++++++++++ tests/test_valid_manifest.py | 33 +++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tests/test_valid_args_schemas.py create mode 100644 tests/test_valid_manifest.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 110bd4d..0c775d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,9 @@ jobs: python -m pip install -U pip python -m pip install .[test] + - name: Install some testing dependencies (hard-coded) + run: python -m pip install pytest devtools jsonschema requests wget + - name: 🧪 Run Tests run: pytest --color=yes --cov --cov-report=xml --cov-report=term-missing diff --git a/src/fractal_faim_hcs/__FRACTAL_MANIFEST__.json b/src/fractal_faim_hcs/__FRACTAL_MANIFEST__.json index d8397a5..134ecf6 100644 --- a/src/fractal_faim_hcs/__FRACTAL_MANIFEST__.json +++ b/src/fractal_faim_hcs/__FRACTAL_MANIFEST__.json @@ -28,7 +28,7 @@ }, "mode": { "default": "all", - "description": "Mode can be 3 values: \"z-steps\" (only parse the 3D data), \"top-level\" (only parse the 2D data), \"all\" (parse both)", + "description": "Mode can be 4 values: \"z-steps\" (only parse the 3D data), \"top-level\" (only parse the 2D data), \"all\" (parse both), \"zmb\" (zmb-parser, detect mode automatically)", "title": "Mode", "type": "string" }, @@ -54,6 +54,12 @@ "title": "Overwrite", "type": "boolean" }, + "query": { + "default": "", + "description": "Pandas query to filter intput-filenames", + "title": "Query", + "type": "string" + }, "zarr_name": { "default": "Plate", "description": "Name of the zarr plate file that will be created", @@ -90,6 +96,12 @@ "title": "Component", "type": "string" }, + "grid_montage": { + "default": true, + "description": "Force FOVs into closest grid cells", + "title": "Grid Montage", + "type": "boolean" + }, "input_paths": { "description": "List of paths to the input files (Fractal managed)", "items": { diff --git a/tests/test_valid_args_schemas.py b/tests/test_valid_args_schemas.py new file mode 100644 index 0000000..5648672 --- /dev/null +++ b/tests/test_valid_args_schemas.py @@ -0,0 +1,74 @@ +import json +from pathlib import Path + +import fractal_faim_hcs +import pytest +from devtools import debug +from fractal_tasks_core.dev.lib_args_schemas import ( + create_schema_for_single_task, +) +from fractal_tasks_core.dev.lib_signature_constraints import ( + _extract_function, + _validate_function_signature, +) +from jsonschema.validators import ( + Draft7Validator, + Draft201909Validator, + Draft202012Validator, +) + +FRACTAL_TASKS_CORE_DIR = Path(fractal_faim_hcs.__file__).parent +with (FRACTAL_TASKS_CORE_DIR / "__FRACTAL_MANIFEST__.json").open("r") as f: + MANIFEST = json.load(f) +TASK_LIST = MANIFEST["task_list"] +PACKAGE = "fractal_faim_hcs" + + +def test_manifest_has_args_schemas_is_true(): + debug(MANIFEST) + assert MANIFEST["has_args_schemas"] + + +def test_task_functions_have_valid_signatures(): + """ + Test that task functions have valid signatures. + """ + for _ind_task, task in enumerate(TASK_LIST): + function_name = Path(task["executable"]).with_suffix("").name + task_function = _extract_function( + task["executable"], function_name, package_name=PACKAGE + ) + _validate_function_signature(task_function) + + +def test_args_schemas_are_up_to_date(): + """ + Test that args_schema attributes in the manifest are up-to-date + """ + for ind_task, task in enumerate(TASK_LIST): + print(f"Now handling {task['executable']}") + old_schema = TASK_LIST[ind_task]["args_schema"] + new_schema = create_schema_for_single_task(task["executable"], package=PACKAGE) + # The following step is required because some arguments may have a + # default which has a non-JSON type (e.g. a tuple), which we need to + # convert to JSON type (i.e. an array) before comparison. + new_schema = json.loads(json.dumps(new_schema)) + assert new_schema == old_schema + + +@pytest.mark.parametrize( + "jsonschema_validator", + [Draft7Validator, Draft201909Validator, Draft202012Validator], +) +def test_args_schema_comply_with_jsonschema_specs(jsonschema_validator): + """ + FIXME: it is not clear whether this test is actually useful + """ + for ind_task, task in enumerate(TASK_LIST): + schema = TASK_LIST[ind_task]["args_schema"] + my_validator = jsonschema_validator(schema=schema) + my_validator.check_schema(my_validator.schema) + print( + f"Schema for task {task['executable']} is valid for " + f"{jsonschema_validator}." + ) diff --git a/tests/test_valid_manifest.py b/tests/test_valid_manifest.py new file mode 100644 index 0000000..884ddee --- /dev/null +++ b/tests/test_valid_manifest.py @@ -0,0 +1,33 @@ +import json +import sys +from pathlib import Path + +import fractal_tasks_core +import requests +from devtools import debug + + +def test_valid_manifest(tmp_path): + """ + NOTE: to avoid adding a fractal-server dependency, we simply download the + relevant file. In the future we may have a fractal-common package, and that + one could be easily added as a `dev` dependency. + """ + + url = ( + "https://raw.githubusercontent.com/fractal-analytics-platform/" + "fractal-common/main/schemas/manifest.py" + ) + r = requests.get(url) + debug(tmp_path) + with (tmp_path / "fractal_manifest.py").open("wb") as fout: + fout.write(r.content) + + sys.path.append(str(tmp_path)) + from fractal_manifest import ManifestV1 + + module_dir = Path(fractal_tasks_core.__file__).parent + with (module_dir / "__FRACTAL_MANIFEST__.json").open("r") as fin: + manifest_dict = json.load(fin) + manifest = ManifestV1(**manifest_dict) + debug(manifest)