Skip to content

Commit 3c4572f

Browse files
authored
Try including tests in the coverage data (#500)
* Try including tests in the coverage data * Combine coverage on 3.12 * Test fixes * Improve test coverage * Simplify tests for coverage
1 parent 4f4a6e9 commit 3c4572f

8 files changed

+41
-76
lines changed

.github/workflows/main.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- uses: "actions/checkout@v3"
2222

23-
- uses: "actions/setup-python@v4"
23+
- uses: "actions/setup-python@v5"
2424
with:
2525
python-version: "${{ matrix.python-version }}"
2626
allow-prereleases: true
@@ -47,10 +47,10 @@ jobs:
4747
steps:
4848
- uses: "actions/checkout@v3"
4949

50-
- uses: "actions/setup-python@v4"
50+
- uses: "actions/setup-python@v5"
5151
with:
5252
cache: "pip"
53-
python-version: "3.11"
53+
python-version: "3.12"
5454

5555
- run: "python -Im pip install --upgrade coverage[toml]"
5656

@@ -65,7 +65,7 @@ jobs:
6565
python -Im coverage json
6666
6767
# Report and write to summary.
68-
python -Im coverage report | sed 's/^/ /' >> $GITHUB_STEP_SUMMARY
68+
python -Im coverage report --skip-covered --skip-empty | sed 's/^/ /' >> $GITHUB_STEP_SUMMARY
6969
7070
export TOTAL=$(python -c "import json;print(json.load(open('coverage.json'))['totals']['percent_covered_display'])")
7171
echo "total=$TOTAL" >> $GITHUB_ENV
@@ -100,9 +100,9 @@ jobs:
100100

101101
steps:
102102
- uses: "actions/checkout@v3"
103-
- uses: "actions/setup-python@v4"
103+
- uses: "actions/setup-python@v5"
104104
with:
105-
python-version: "3.11"
105+
python-version: "3.12"
106106

107107
- name: "Install pdm, check-wheel-content, and twine"
108108
run: "python -m pip install pdm twine check-wheel-contents"

pyproject.toml

+1-8
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,7 @@ addopts = "-l --benchmark-sort=fullname --benchmark-warmup=true --benchmark-warm
103103

104104
[tool.coverage.run]
105105
parallel = true
106-
source_pkgs = ["cattrs"]
107-
108-
[tool.coverage.paths]
109-
source = [
110-
"src",
111-
".tox/*/lib/python*/site-packages",
112-
".tox/pypy*/site-packages",
113-
]
106+
source_pkgs = ["cattrs", "tests"]
114107

115108
[tool.coverage.report]
116109
exclude_also = [

tests/test_converter.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,7 @@ def test_forbid_extra_keys(cls_and_vals):
127127
cl, vals, kwargs = cls_and_vals
128128
inst = cl(*vals, **kwargs)
129129
unstructured = converter.unstructure(inst)
130-
bad_key = next(iter(unstructured)) + "A" if unstructured else "Hyp"
131-
while bad_key in unstructured:
132-
bad_key += "A"
130+
bad_key = next(iter(unstructured)) + "_" if unstructured else "Hyp"
133131
unstructured[bad_key] = 1
134132
with pytest.raises(ClassValidationError) as cve:
135133
converter.structure(unstructured, cl)
@@ -417,12 +415,8 @@ def test_type_overrides(cl_and_vals):
417415
unstructured = converter.unstructure(inst)
418416

419417
for field, val in zip(fields(cl), vals):
420-
if field.type is int and field.default is not None:
421-
if isinstance(field.default, Factory):
422-
if not field.default.takes_self and field.default() == val:
423-
assert field.name not in unstructured
424-
elif field.default == val:
425-
assert field.name not in unstructured
418+
if field.type is int and field.default is not None and field.default == val:
419+
assert field.name not in unstructured
426420

427421

428422
def test_calling_back():

tests/test_converter_inheritance.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import collections
22
import typing
33

4-
import attr
54
import pytest
5+
from attrs import define
66

77
from cattrs import BaseConverter
88

99

1010
def test_inheritance(converter):
11-
@attr.define
11+
@define
1212
class A:
1313
i: int
1414

15-
@attr.define
15+
@define
1616
class B(A):
1717
j: int
1818

@@ -23,11 +23,11 @@ class B(A):
2323
def test_gen_hook_priority(converter: BaseConverter):
2424
"""Autogenerated hooks should not take priority over manual hooks."""
2525

26-
@attr.define
26+
@define
2727
class A:
2828
i: int
2929

30-
@attr.define
30+
@define
3131
class B(A):
3232
pass
3333

@@ -51,8 +51,8 @@ def test_inherit_typing(converter: BaseConverter, typing_cls):
5151
cattrs handles them correctly.
5252
"""
5353

54-
@attr.define
55-
class A(typing_cls):
54+
@define
55+
class A(typing_cls): # pragma: nocover
5656
i: int = 0
5757

5858
def __hash__(self):
@@ -74,8 +74,8 @@ def __reversed__(self):
7474
def test_inherit_collections_abc(converter: BaseConverter, collections_abc_cls):
7575
"""As extension of test_inherit_typing, check if collections.abc.* work."""
7676

77-
@attr.define
78-
class A(collections_abc_cls):
77+
@define
78+
class A(collections_abc_cls): # pragma: nocover
7979
i: int = 0
8080

8181
def __hash__(self):

tests/test_gen_dict.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,9 @@ def test_nodefs_generated_unstructuring_cl(
9393
else:
9494
# The default is a factory, but might take self.
9595
if attr.default.takes_self:
96-
if val == attr.default.factory(cl):
97-
assert attr.name not in res
98-
else:
99-
assert attr.name in res
96+
# Our strategies can only produce these for now.
97+
assert val == attr.default.factory(cl)
98+
assert attr.name not in res
10099
else:
101100
if val == attr.default.factory():
102101
assert attr.name not in res
@@ -151,10 +150,7 @@ def test_individual_overrides(converter_cls, cl_and_vals):
151150
assert attr.name in res
152151
else:
153152
if attr.default.takes_self:
154-
if val == attr.default.factory(inst):
155-
assert attr.name not in res
156-
else:
157-
assert attr.name in res
153+
assert attr.name not in res
158154
else:
159155
if val == attr.default.factory():
160156
assert attr.name not in res

tests/test_structure_attrs.py

+9-26
Original file line numberDiff line numberDiff line change
@@ -242,36 +242,19 @@ class ClassWithLiteral:
242242
converter.structure({"literal_field": 3}, ClassWithLiteral)
243243

244244

245-
@pytest.mark.parametrize("converter_type", [BaseConverter, Converter])
246-
def test_structure_fallback_to_attrib_converters(converter_type):
247-
attrib_converter = Mock()
248-
attrib_converter.side_effect = lambda val: str(val)
245+
def test_structure_fallback_to_attrib_converters(converter):
246+
"""`attrs` converters are called after cattrs processing."""
249247

250-
def called_after_default_converter(val):
251-
if not isinstance(val, int):
252-
raise ValueError(
253-
"The 'int' conversion should have happened first by the built-in hooks"
254-
)
255-
return 42
256-
257-
converter = converter_type()
258-
cl = make_class(
259-
"HasConverter",
260-
{
261-
# non-built-in type with custom converter
262-
"ip": field(type=Union[IPv4Address, IPv6Address], converter=ip_address),
263-
# attribute without type
264-
"x": field(converter=attrib_converter),
265-
# built-in types converters
266-
"z": field(type=int, converter=called_after_default_converter),
267-
},
268-
)
248+
@define
249+
class HasConverter:
250+
ip: Union[IPv4Address, IPv6Address] = field(converter=ip_address)
251+
x = field(converter=lambda v: v + 1)
252+
z: int = field(converter=lambda _: 42)
269253

270-
inst = converter.structure({"ip": "10.0.0.0", "x": 1, "z": "3"}, cl)
254+
inst = converter.structure({"ip": "10.0.0.0", "x": 1, "z": "3"}, HasConverter)
271255

272256
assert inst.ip == IPv4Address("10.0.0.0")
273-
assert inst.x == "1"
274-
attrib_converter.assert_any_call(1)
257+
assert inst.x == 2
275258
assert inst.z == 42
276259

277260

tests/typeddicts.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,11 @@ def simple_typeddicts_with_extra_keys(
212212

213213

214214
@composite
215-
def generic_typeddicts(
216-
draw: DrawFn, total: Optional[bool] = None
217-
) -> Tuple[TypedDictType, dict]:
215+
def generic_typeddicts(draw: DrawFn, total: bool = True) -> Tuple[TypedDictType, dict]:
218216
"""Generate generic typed dicts.
219217
220-
:param total: Generate the given totality dicts (default = random)
218+
:param total: Generate the given totality dicts
221219
"""
222-
if total is None:
223-
total = draw(booleans())
224-
225220
attrs = draw(
226221
lists(
227222
int_attributes(total)

tests/untyped.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
Sequence,
1717
Set,
1818
Tuple,
19+
Type,
1920
)
2021

2122
import attr
22-
from attr import NOTHING, make_class
2323
from attr._make import _CountingAttr
24+
from attrs import NOTHING, AttrsInstance, Factory, make_class
2425
from hypothesis import strategies as st
26+
from hypothesis.strategies import SearchStrategy
2527

2628
PosArg = Any
2729
PosArgs = Tuple[PosArg]
@@ -217,9 +219,11 @@ def just_class_with_type(tup):
217219
return _create_hyp_class(combined_attrs)
218220

219221

220-
def just_class_with_type_takes_self(tup):
222+
def just_class_with_type_takes_self(
223+
tup: Tuple[List[Tuple[_CountingAttr, SearchStrategy]], Tuple[Type[AttrsInstance]]]
224+
) -> SearchStrategy[Tuple[Type[AttrsInstance]]]:
221225
nested_cl = tup[1][0]
222-
default = attr.Factory(lambda _: nested_cl(), takes_self=True)
226+
default = Factory(lambda _: nested_cl(), takes_self=True)
223227
combined_attrs = list(tup[0])
224228
combined_attrs.append(
225229
(attr.ib(default=default, type=nested_cl), st.just(nested_cl()))

0 commit comments

Comments
 (0)