Skip to content

Commit e248081

Browse files
committed
Conformance testing docs
Also minor refactor test_validation
1 parent 2ac4b93 commit e248081

3 files changed

Lines changed: 106 additions & 47 deletions

File tree

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,64 @@ address issues of scalability and interoperability.
55

66
This repository contains the central [specification text](./ngff_spec/specification.md),
77
a comprehensive list of [metadata examples](./ngff_spec/examples)
8-
as well as [json schemas](./ngff_spec/schemas) to validate written ome-zarr image data.
8+
as well as [json schemas](./ngff_spec/schemas) to validate written ome-zarr image data.
99

1010
The built documentation including contribution hints can be found **[here](https://ngff-spec.readthedocs.io/en/latest/specification.html)**.
11+
12+
## Conformance tests
13+
14+
Conformance can be tested at several levels.
15+
16+
1. Validating a single zarr attributes object (i.e. containing OME-Zarr metadata)
17+
18+
- Validates that correct data can be represented, and that internally inconsistent data can be caught
19+
- Cannot validate references to other objects in the zarr hierarchy
20+
- Cannot validate conformance to other zarr metadata e.g. array data type, dimensionality
21+
22+
2. Validating a metadata-only zarr hierarchy
23+
24+
- Can validate references to other objects and other zarr metadata
25+
- Cannot validate values e.g. the invertibility of an affine matrix defined as a zarr array
26+
27+
3. Validating a zarr hierarchy with data
28+
29+
This repository contains a set of test cases for levels
30+
1 ([`./tests/attributes`](./tests/attributes/)) and
31+
2 ([`./tests/zarr`](./tests/zarr/)),
32+
as well as a tool for feeding these test cases into an external validator.
33+
34+
### Testing tool
35+
36+
See [`ome_zarr_conformance.py`](./ome_zarr_conformance.py).
37+
38+
Run it in either `attributes` or `zarr` mode, optionally with filters for test names or types.
39+
40+
Wrap your own OME-Zarr implementation in a
41+
["dingus"](https://talk.commonmark.org/t/origin-of-the-usage-for-dingus/1226) CLI,
42+
which takes as its last argument the path to either
43+
44+
- attributes mode: a JSON file representing zarr attributes (i.e. containing OME-Zarr metadata)
45+
- zarr mode: a zarr hierarchy root on the file system (i.e. a directory containing `zarr.json`)
46+
47+
The dingus should print to STDOUT a JSON object with the keys:
48+
49+
- `"valid"`: boolean, whether this is valid
50+
- optionally `"message"`: str, free text describing the success/ failure
51+
52+
Call the tool like
53+
54+
```sh
55+
python3 ./ome_zarr_conformance.py attributes -- path/to/my/dingus -dingusArg +argValue 10
56+
```
57+
58+
Each call to the dingus will then look like
59+
60+
```sh
61+
>>> path/to/my/dingus -dingusArg +argValue 10 /home/you/ngff-spec/tests/attributes/spec/valid/custom_type_axes.json
62+
63+
{"valid": true}
64+
```
65+
66+
`ome_zarr_conformance.py` will parse the JSON output and format the results of all requested tests in a tab-separated table.
67+
68+
Full usage information is available with `./ome_zarr_conformance.py --help`.

ngff_spec/schemas/strict_ome_zarr.schema

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"$id": "https://ngff.openmicroscopy.org/0.6dev2/schemas/ome_zarr.schema",
3+
"$id": "https://ngff.openmicroscopy.org/0.6dev2/schemas/strict_ome_zarr.schema",
44
"anyOf": [
55
{
66
"$ref": "https://ngff.openmicroscopy.org/0.6dev2/schemas/bf2raw.schema"

tests/test_validation.py

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
GENERIC_SCHEMA = schema_store[
2020
"https://ngff.openmicroscopy.org/0.6dev2/schemas/ome_zarr.schema"
2121
]
22-
23-
print(schema_store)
22+
GENERIC_STRICT_SCHEMA = schema_store[
23+
"https://ngff.openmicroscopy.org/0.6dev2/schemas/strict_ome_zarr.schema"
24+
]
2425

2526

2627
@dataclass
@@ -57,60 +58,60 @@ def pytest_generate_tests(metafunc):
5758
styled JSON tests. Metadata in each test defines which schema is used
5859
and whether or not the block is considered valid.
5960
"""
60-
if "suite" in metafunc.fixturenames:
61-
suites: List[Schema] = []
62-
ids: List[str] = []
63-
schema_store = {}
64-
for filename in glob.glob("ngff_spec/schemas/*.schema"):
65-
with open(filename) as o:
66-
schema = json.load(o)
67-
schema_store[schema["$id"]] = schema
68-
69-
# Validation
70-
for filename in glob.glob("tests/*.json"):
71-
with open(filename) as o:
72-
suite = json.load(o)
73-
schema = suite["schema"]
74-
with open(schema["id"]) as f:
75-
schema = json.load(f)
76-
for test in suite["tests"]:
77-
ids.append("validate_" + str(test["formerly"]).split("/")[-1][0:-5])
78-
suites.append(Suite(schema, test["data"], test["valid"]))
79-
80-
# Examples
81-
for config_filename in glob.glob("examples/*/.config.json"):
82-
with open(config_filename) as o:
83-
data = json.load(o)
84-
schema = data["schema"]
85-
with open(schema) as f:
86-
schema = json.load(f)
87-
example_folder = os.path.dirname(config_filename)
88-
for filename in glob.glob(f"{example_folder}/*.json"):
89-
with open(filename) as f:
90-
# Strip comments
91-
data = "".join(
92-
line for line in f if not line.lstrip().startswith("//")
93-
)
94-
data = json.loads(data)
95-
data = data["attributes"] # Only validate the attributes object
96-
ids.append("example_" + str(filename).split("/")[-1][0:-5])
97-
suites.append(Suite(schema, data, True)) # Assume true
98-
99-
metafunc.parametrize("suite", suites, ids=ids, indirect=True)
61+
if "suite" not in metafunc.fixturenames:
62+
return
63+
64+
suites: List[Suite] = []
65+
ids: List[str] = []
66+
schema_store = {}
67+
for filename in glob.glob("ngff_spec/schemas/*.schema"):
68+
with open(filename) as o:
69+
schema = json.load(o)
70+
schema_store[schema["$id"]] = schema
71+
72+
# Validation
73+
for filename in glob.glob("tests/*.json"):
74+
with open(filename) as o:
75+
suite = json.load(o)
76+
schema_path = suite["schema"]
77+
with open(schema_path["id"]) as f:
78+
schema = json.load(f)
79+
for test in suite["tests"]:
80+
ids.append("validate_" + str(test["formerly"]).split("/")[-1][0:-5])
81+
suites.append(Suite(schema, test["data"], test["valid"]))
82+
83+
# Examples
84+
for config_filename in glob.glob("examples/*/.config.json"):
85+
with open(config_filename) as o:
86+
data = json.load(o)
87+
schema_path = data["schema"]
88+
with open(schema_path) as f:
89+
schema = json.load(f)
90+
example_folder = os.path.dirname(config_filename)
91+
for filename in glob.glob(f"{example_folder}/*.json"):
92+
with open(filename) as f:
93+
# Strip comments
94+
data = "".join(line for line in f if not line.lstrip().startswith("//"))
95+
data = json.loads(data)
96+
data = data["attributes"] # Only validate the attributes object
97+
ids.append("example_" + str(filename).split("/")[-1][0:-5])
98+
suites.append(Suite(schema, data, True)) # Assume true
99+
100+
metafunc.parametrize("suite", suites, ids=ids, indirect=True)
100101

101102

102103
@pytest.fixture
103-
def suite(request):
104+
def suite(request) -> Suite:
104105
return request.param
105106

106107

107-
def test_run(suite):
108+
def test_run(suite: Suite):
108109
resolver = RefResolver.from_schema(suite.schema, store=schema_store)
109110
validator = Validator(suite.schema, resolver=resolver)
110111
suite.validate(validator)
111112

112113

113-
def test_generic_run(suite):
114+
def test_generic_run(suite: Suite):
114115
resolver = RefResolver.from_schema(GENERIC_SCHEMA, store=schema_store)
115116
validator = Validator(GENERIC_SCHEMA, resolver=resolver)
116117
suite.maybe_validate(validator)

0 commit comments

Comments
 (0)