File: test_wrapper.py

package info (click to toggle)
zarr 3.1.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,068 kB
  • sloc: python: 31,589; makefile: 10
file content (165 lines) | stat: -rw-r--r-- 8,119 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any, ClassVar

import pytest

from zarr.core.dtype.common import DTypeSpec_V2, DTypeSpec_V3, HasItemSize

if TYPE_CHECKING:
    from zarr.core.dtype.wrapper import TBaseDType, TBaseScalar, ZDType


"""
class _TestZDTypeSchema:
    # subclasses define the URL for the schema, if available
    schema_url: ClassVar[str] = ""

    @pytest.fixture(scope="class")
    def get_schema(self) -> object:
        response = requests.get(self.schema_url)
        response.raise_for_status()
        return json_schema.loads(response.text)

    def test_schema(self, schema: json_schema.Schema) -> None:
        assert schema.is_valid(self.test_cls.to_json(zarr_format=2))
"""


class BaseTestZDType:
    """
    A base class for testing ZDType subclasses. This class works in conjunction with the custom
    pytest collection function ``pytest_generate_tests`` defined in conftest.py, which applies the
    following procedure when generating tests:

    At test generation time, for each test fixture referenced by a method on this class
    pytest will look for an attribute with the same name as that fixture. Pytest will assume that
    this class attribute is a tuple of values to be used for generating a parametrized test fixture.

    This means that child classes can, by using different values for these class attributes, have
    customized test parametrization.

    Attributes
    ----------
    test_cls : type[ZDType[TBaseDType, TBaseScalar]]
        The ZDType subclass being tested.
    scalar_type : ClassVar[type[TBaseScalar]]
        The expected scalar type for the ZDType.
    valid_dtype : ClassVar[tuple[TBaseDType, ...]]
        A tuple of valid numpy dtypes for the ZDType.
    invalid_dtype : ClassVar[tuple[TBaseDType, ...]]
        A tuple of invalid numpy dtypes for the ZDType.
    valid_json_v2 : ClassVar[tuple[str | dict[str, object] | list[object], ...]]
        A tuple of valid JSON representations for Zarr format version 2.
    invalid_json_v2 : ClassVar[tuple[str | dict[str, object] | list[object], ...]]
        A tuple of invalid JSON representations for Zarr format version 2.
    valid_json_v3 : ClassVar[tuple[str | dict[str, object], ...]]
        A tuple of valid JSON representations for Zarr format version 3.
    invalid_json_v3 : ClassVar[tuple[str | dict[str, object], ...]]
        A tuple of invalid JSON representations for Zarr format version 3.
    cast_value_params : ClassVar[tuple[tuple[Any, Any, Any], ...]]
        A tuple of (dtype, value, expected) tuples for testing ZDType.cast_value.
    scalar_v2_params : ClassVar[tuple[Any, ...]]
        A tuple of (dtype, scalar json) tuples for testing
        ZDType.from_json_scalar / ZDType.to_json_scalar for zarr v2
    scalar_v3_params : ClassVar[tuple[Any, ...]]
        A tuple of (dtype, scalar json) tuples for testing
        ZDType.from_json_scalar / ZDType.to_json_scalar for zarr v3
    invalid_scalar_params : ClassVar[tuple[Any, ...]]
        A tuple of (dtype, value) tuples, where each value is expected to fail ZDType.cast_value.
    item_size_params : ClassVar[tuple[Any, ...]]
        A tuple of (dtype, expected) tuples for testing ZDType.item_size
    """

    test_cls: type[ZDType[TBaseDType, TBaseScalar]]
    scalar_type: ClassVar[type[TBaseScalar]]
    valid_dtype: ClassVar[tuple[TBaseDType, ...]] = ()
    invalid_dtype: ClassVar[tuple[TBaseDType, ...]] = ()

    valid_json_v2: ClassVar[tuple[DTypeSpec_V2, ...]] = ()
    invalid_json_v2: ClassVar[tuple[str | dict[str, object] | list[object], ...]] = ()

    valid_json_v3: ClassVar[tuple[DTypeSpec_V3, ...]] = ()
    invalid_json_v3: ClassVar[tuple[str | dict[str, object], ...]] = ()

    # for testing scalar round-trip serialization, we need a tuple of (data type json, scalar json)
    # pairs. the first element of the pair is used to create a dtype instance, and the second
    # element is the json serialization of the scalar that we want to round-trip.

    scalar_v2_params: ClassVar[tuple[tuple[ZDType[Any, Any], Any], ...]] = ()
    scalar_v3_params: ClassVar[tuple[tuple[Any, Any], ...]] = ()
    cast_value_params: ClassVar[tuple[tuple[ZDType[Any, Any], Any, Any], ...]] = ()
    # Some data types, like bool and string, can consume any python object as a scalar.
    # So we allow passing None in to this test to indicate that it should be skipped.
    invalid_scalar_params: ClassVar[tuple[tuple[ZDType[Any, Any], Any], ...] | tuple[None]] = ()
    item_size_params: ClassVar[tuple[ZDType[Any, Any], ...]] = ()

    def json_scalar_equals(self, scalar1: object, scalar2: object) -> bool:
        # An equality check for json-encoded scalars. This defaults to regular equality,
        # but some classes may need to override this for special cases
        return scalar1 == scalar2

    def scalar_equals(self, scalar1: object, scalar2: object) -> bool:
        # An equality check for scalars. This defaults to regular equality,
        # but some classes may need to override this for special cases
        return scalar1 == scalar2

    def test_check_dtype_valid(self, valid_dtype: TBaseDType) -> None:
        assert self.test_cls._check_native_dtype(valid_dtype)

    def test_check_dtype_invalid(self, invalid_dtype: object) -> None:
        assert not self.test_cls._check_native_dtype(invalid_dtype)  # type: ignore[arg-type]

    def test_from_dtype_roundtrip(self, valid_dtype: Any) -> None:
        zdtype = self.test_cls.from_native_dtype(valid_dtype)
        assert zdtype.to_native_dtype() == valid_dtype

    def test_from_json_roundtrip_v2(self, valid_json_v2: DTypeSpec_V2) -> None:
        zdtype = self.test_cls.from_json(valid_json_v2, zarr_format=2)
        assert zdtype.to_json(zarr_format=2) == valid_json_v2

    @pytest.mark.filterwarnings("ignore::zarr.core.dtype.common.UnstableSpecificationWarning")
    def test_from_json_roundtrip_v3(self, valid_json_v3: DTypeSpec_V3) -> None:
        zdtype = self.test_cls.from_json(valid_json_v3, zarr_format=3)
        assert zdtype.to_json(zarr_format=3) == valid_json_v3

    def test_scalar_roundtrip_v2(self, scalar_v2_params: tuple[ZDType[Any, Any], Any]) -> None:
        zdtype, scalar_json = scalar_v2_params
        scalar = zdtype.from_json_scalar(scalar_json, zarr_format=2)
        assert self.json_scalar_equals(scalar_json, zdtype.to_json_scalar(scalar, zarr_format=2))

    def test_scalar_roundtrip_v3(self, scalar_v3_params: tuple[ZDType[Any, Any], Any]) -> None:
        zdtype, scalar_json = scalar_v3_params
        scalar = zdtype.from_json_scalar(scalar_json, zarr_format=3)
        assert self.json_scalar_equals(scalar_json, zdtype.to_json_scalar(scalar, zarr_format=3))

    def test_cast_value(self, cast_value_params: tuple[ZDType[Any, Any], Any, Any]) -> None:
        zdtype, value, expected = cast_value_params
        observed = zdtype.cast_scalar(value)
        assert self.scalar_equals(expected, observed)
        # check that casting is idempotent
        assert self.scalar_equals(zdtype.cast_scalar(observed), observed)

    def test_invalid_scalar(
        self, invalid_scalar_params: tuple[ZDType[Any, Any], Any] | None
    ) -> None:
        if invalid_scalar_params is None:
            pytest.skip(f"No test data provided for {self}.{__name__}")
        zdtype, data = invalid_scalar_params
        msg = (
            f"Cannot convert object {data!r} with type {type(data)} to a scalar compatible with the "
            f"data type {zdtype}."
        )
        with pytest.raises(TypeError, match=re.escape(msg)):
            zdtype.cast_scalar(data)

    def test_item_size(self, item_size_params: ZDType[Any, Any]) -> None:
        """
        Test that the item_size attribute matches the numpy dtype itemsize attribute, for dtypes
        with a fixed scalar size.
        """
        if isinstance(item_size_params, HasItemSize):
            assert item_size_params.item_size == item_size_params.to_native_dtype().itemsize
        else:
            pytest.skip(f"Data type {item_size_params} does not implement HasItemSize")