-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathtest_meta.py
145 lines (118 loc) · 4.62 KB
/
test_meta.py
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
"""Test meta-requirements of the implementations."""
import typing as t
import pytest
from safetywrap._interface import _Option, _Result
from safetywrap import Some, Nothing, Option, Ok, Err, Result
class TestInterfaceConformance:
"""Ensure the implementations implement and do not extend the interfaces.
This is a bit of a unique situation, where the usual open-closed
principle does not apply. We want our implementations to conform
EXACTLY to the interface, and not to extend it, since the whole
idea here is that you can treat an Ok() the same as an Err(),
or a Some() the same as a Nothing.
"""
@staticmethod
def _public_method_names(obj: object) -> t.Tuple[str, ...]:
"""Return public method names from an object."""
return tuple(
sorted(
map(
lambda i: i[0],
filter(
lambda i: not i[0].startswith("_") and callable(i[1]),
obj.__dict__.items(),
),
)
)
)
def test_ok_interface(self) -> None:
""""The Ok interface matches Result."""
assert self._public_method_names(Ok) == self._public_method_names(
_Result
)
def test_err_interface(self) -> None:
"""The Err interface matches Result."""
assert self._public_method_names(Err) == self._public_method_names(
_Result
)
def test_some_interface(self) -> None:
"""The Some interface matches Option."""
assert self._public_method_names(Some) == self._public_method_names(
_Option
)
def test_nothing_interface(self) -> None:
"""The Nothing interface matches Option."""
assert self._public_method_names(Nothing) == self._public_method_names(
_Option
)
class TestNoBaseInstantiations:
"""Base types are not instantiable"""
def test_result_cannot_be_instantiated(self) -> None:
"""Result cannot be instantiated"""
with pytest.raises(NotImplementedError):
r: Result[str, str] = Result("a")
assert r
def test_option_cannot_be_instantiated(self) -> None:
"""Option cannot be instantiated"""
with pytest.raises(NotImplementedError):
Option("a")
class TestNoConcretesInInterfaces:
"""Interfaces contain only abstract methods."""
@staticmethod
def assert_not_concrete(kls: t.Type, meth: str) -> None:
"""Assert the method on the class is not concrete."""
with pytest.raises(NotImplementedError):
for num_args in range(10):
try:
getattr(kls, meth)(*map(str, range(num_args)))
except TypeError:
continue
else:
break
@staticmethod
def filter_meths(cls: t.Type, meth: str) -> bool:
if not callable(getattr(cls, meth)):
return False
if not meth.startswith("_"):
return True
check_magic_methods = ("eq", "init", "iter", "ne", "repr", "str")
if any(map(lambda m: meth == "__%s__" % m, check_magic_methods)):
return True
return False
@pytest.mark.parametrize(
"meth",
filter(
lambda m: TestNoConcretesInInterfaces.filter_meths(
# No idea why it thinks `m` is "object", not "str"
_Result,
m, # type: ignore
),
_Result.__dict__,
),
)
def test_no_concrete_result_methods(self, meth: str) -> None:
"""The result interface contains no implementations."""
self.assert_not_concrete(_Result, meth)
@pytest.mark.parametrize(
"meth",
filter(
lambda m: TestNoConcretesInInterfaces.filter_meths(
# No idea why it thinks `m` is "object", not "str"
_Option,
m, # type: ignore
),
_Option.__dict__.keys(),
),
)
def test_no_concrete_option_methods(self, meth: str) -> None:
"""The option interface contains no implementations."""
self.assert_not_concrete(_Option, meth)
class TestImplementationDetails:
"""Some implementation details need to be tested."""
def test_nothing_singleton(self) -> None:
"""Ensure Nothing() is a singleton."""
assert Nothing() is Nothing() is Nothing()
@pytest.mark.parametrize("obj", (Some(1), Nothing(), Ok(1), Err(1)))
def test_all_slotted(self, obj: t.Any) -> None:
"""All implementations use __slots__."""
assert not hasattr(obj, "__dict__")