Skip to content

Commit 6e6d9e9

Browse files
authored
test: add helper for parameterized outcome tests (#2476)
Add an abstract class, `test.until.outcome.OutcomeChecker`, and implementations that make it easier to check for specific outcomes in parameterized tests. Also changed existing tests that use similar patterns to use `OutcomeChecker` instead.
1 parent abd9208 commit 6e6d9e9

12 files changed

+355
-246
lines changed

test/test_graph/test_graph.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# -*- coding: utf-8 -*-
22
import logging
33
import os
4-
from contextlib import ExitStack
54
from pathlib import Path
65
from test.data import TEST_DATA_DIR, bob, cheese, hates, likes, michel, pizza, tarek
76
from test.utils import GraphHelper, get_unique_plugin_names
8-
from test.utils.exceptions import ExceptionChecker
97
from test.utils.httpfileserver import HTTPFileServer, ProtoFileResource
10-
from typing import Callable, Optional, Set, Tuple, Union
8+
from test.utils.outcome import ExceptionChecker, OutcomeChecker, OutcomePrimitive
9+
from typing import Callable, Optional, Set, Tuple
1110
from urllib.error import HTTPError, URLError
1211

1312
import pytest
@@ -373,7 +372,7 @@ def test_guess_format_for_parse_http(
373372
http_file_server: HTTPFileServer,
374373
file: Path,
375374
content_type: Optional[str],
376-
expected_result: Union[int, ExceptionChecker],
375+
expected_result: OutcomePrimitive[int],
377376
) -> None:
378377
graph = make_graph()
379378
headers: Tuple[Tuple[str, str], ...] = tuple()
@@ -384,21 +383,11 @@ def test_guess_format_for_parse_http(
384383
ProtoFileResource(headers, file),
385384
suffix=f"/{file.name}",
386385
)
387-
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
388-
386+
checker = OutcomeChecker.from_primitive(expected_result)
389387
assert 0 == len(graph)
390-
with ExitStack() as exit_stack:
391-
if isinstance(expected_result, ExceptionChecker):
392-
catcher = exit_stack.enter_context(pytest.raises(expected_result.type))
388+
with checker.context():
393389
graph.parse(location=file_info.request_url)
394-
395-
if catcher is not None:
396-
# assert catcher.value is not None
397-
assert isinstance(expected_result, ExceptionChecker)
398-
logging.debug("graph = %s", list(graph.triples((None, None, None))))
399-
else:
400-
assert isinstance(expected_result, int)
401-
assert expected_result == len(graph)
390+
checker.check(len(graph))
402391

403392

404393
def test_parse_file_uri(make_graph: GraphFactory):

test/test_literal/test_literal.py

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99

1010
import datetime
1111
import logging
12-
from contextlib import ExitStack
1312
from decimal import Decimal
1413
from test.utils import affix_tuples
1514
from test.utils.literal import LiteralChecker
16-
from typing import Any, Callable, Generator, Iterable, Optional, Type, Union
15+
from test.utils.outcome import OutcomeChecker, OutcomePrimitive, OutcomePrimitives
16+
from typing import Any, Callable, Generator, Optional, Type, Union
1717

1818
import isodate
1919
import pytest
@@ -614,16 +614,10 @@ def test_literal_addsub(
614614
a: Literal,
615615
b: Literal,
616616
op: str,
617-
expected_result: Union[Literal, Type[Exception], Exception],
617+
expected_result: OutcomePrimitive[Literal],
618618
) -> None:
619-
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
620-
expected_exception: Optional[Exception] = None
621-
with ExitStack() as xstack:
622-
if isinstance(expected_result, type) and issubclass(expected_result, Exception):
623-
catcher = xstack.enter_context(pytest.raises(expected_result))
624-
elif isinstance(expected_result, Exception):
625-
expected_exception = expected_result
626-
catcher = xstack.enter_context(pytest.raises(type(expected_exception)))
619+
checker = OutcomeChecker[Literal].from_primitive(expected_result)
620+
with checker.context():
627621
if op == "aplusb":
628622
result = a + b
629623

@@ -636,14 +630,7 @@ def test_literal_addsub(
636630
else:
637631
raise ValueError(f"invalid operation {op}")
638632
logging.debug("result = %r", result)
639-
if catcher is not None or expected_exception is not None:
640-
assert catcher is not None
641-
assert catcher.value is not None
642-
if expected_exception is not None:
643-
assert catcher.match(expected_exception.args[0])
644-
else:
645-
assert isinstance(expected_result, Literal)
646-
assert expected_result == result
633+
checker.check(result)
647634

648635

649636
@pytest.mark.parametrize(
@@ -930,7 +917,7 @@ def unlexify(s: str) -> str:
930917

931918

932919
@pytest.mark.parametrize(
933-
["literal_maker", "checks"],
920+
["literal_maker", "outcome"],
934921
[
935922
(
936923
lambda: Literal("foo"),
@@ -969,32 +956,9 @@ def unlexify(s: str) -> str:
969956
)
970957
def test_literal_construction(
971958
literal_maker: Callable[[], Literal],
972-
checks: Union[
973-
Iterable[Union[LiteralChecker, Literal]],
974-
LiteralChecker,
975-
Literal,
976-
Type[Exception],
977-
],
959+
outcome: OutcomePrimitives[Literal],
978960
) -> None:
979-
check_error: Optional[Type[Exception]] = None
980-
if isinstance(checks, type) and issubclass(checks, Exception):
981-
check_error = checks
982-
checks = []
983-
elif not isinstance(checks, Iterable):
984-
checks = [checks]
985-
986-
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
987-
with ExitStack() as xstack:
988-
if check_error is not None:
989-
catcher = xstack.enter_context(pytest.raises(check_error))
990-
literal = literal_maker()
991-
992-
if check_error is not None:
993-
assert catcher is not None
994-
assert catcher.value is not None
995-
996-
for check in checks:
997-
if isinstance(check, LiteralChecker):
998-
check.check(literal)
999-
else:
1000-
check = literal
961+
checker = OutcomeChecker[Literal].from_primitives(outcome)
962+
with checker.context():
963+
actual_outcome = literal_maker()
964+
checker.check(actual_outcome)

test/test_misc/test_input_source.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@
77
import re
88
from contextlib import ExitStack, contextmanager
99
from dataclasses import dataclass
10-
11-
# from itertools import product
1210
from pathlib import Path
1311
from test.utils import GraphHelper
14-
from test.utils.exceptions import ExceptionChecker
1512
from test.utils.httpfileserver import (
1613
HTTPFileInfo,
1714
HTTPFileServer,
1815
LocationType,
1916
ProtoFileResource,
2017
ProtoRedirectResource,
2118
)
19+
from test.utils.outcome import ExceptionChecker
2220
from typing import ( # Callable,
2321
IO,
2422
BinaryIO,
@@ -648,9 +646,7 @@ def test_create_input_source(
648646
input_source: Optional[InputSource] = None
649647
with ExitStack() as xstack:
650648
if isinstance(test_params.expected_result, ExceptionChecker):
651-
catcher = xstack.enter_context(
652-
pytest.raises(test_params.expected_result.type)
653-
)
649+
catcher = xstack.enter_context(test_params.expected_result.context())
654650

655651
input_source = xstack.enter_context(
656652
call_create_input_source(
@@ -670,8 +666,3 @@ def test_create_input_source(
670666
)
671667

672668
logging.debug("input_source = %s, catcher = %s", input_source, catcher)
673-
674-
if isinstance(test_params.expected_result, ExceptionChecker):
675-
assert catcher is not None
676-
assert input_source is None
677-
test_params.expected_result.check(catcher.value)

test/test_misc/test_networking_redirect.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from contextlib import ExitStack
22
from copy import deepcopy
3-
from test.utils.exceptions import ExceptionChecker
43
from test.utils.http import headers_as_message as headers_as_message
4+
from test.utils.outcome import ExceptionChecker
55
from typing import Any, Dict, Iterable, Optional, Type, TypeVar, Union
66
from urllib.error import HTTPError
77
from urllib.request import HTTPRedirectHandler, Request
@@ -197,14 +197,13 @@ def test_make_redirect_request(
197197
result: Optional[Request] = None
198198
with ExitStack() as stack:
199199
if isinstance(expected_result, ExceptionChecker):
200-
catcher = stack.enter_context(pytest.raises(expected_result.type))
200+
catcher = stack.enter_context(expected_result.context())
201201
elif expected_result is RaisesIdentity:
202202
catcher = stack.enter_context(pytest.raises(HTTPError))
203203
result = _make_redirect_request(http_request, http_error)
204204

205205
if isinstance(expected_result, ExceptionChecker):
206206
assert catcher is not None
207-
expected_result.check(catcher.value)
208207
elif isinstance(expected_result, type):
209208
assert catcher is not None
210209
assert http_error is catcher.value

test/test_namespace/test_namespace.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from contextlib import ExitStack
2-
from typing import Any, Optional, Type, Union
1+
from test.utils.outcome import OutcomeChecker, OutcomePrimitive
2+
from typing import Any, Optional
33
from warnings import warn
44

55
import pytest
@@ -306,22 +306,15 @@ def test_expand_curie_exception_messages(self) -> None:
306306
],
307307
)
308308
def test_expand_curie(
309-
self, curie: Any, expected_result: Union[Type[Exception], URIRef, None]
309+
self, curie: Any, expected_result: OutcomePrimitive[URIRef]
310310
) -> None:
311311
g = Graph(bind_namespaces="none")
312312
nsm = g.namespace_manager
313313
nsm.bind("ex", "urn:example:")
314+
315+
checker = OutcomeChecker.from_primitive(expected_result)
316+
314317
result: Optional[URIRef] = None
315-
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
316-
with ExitStack() as xstack:
317-
if isinstance(expected_result, type) and issubclass(
318-
expected_result, Exception
319-
):
320-
catcher = xstack.enter_context(pytest.raises(expected_result))
318+
with checker.context():
321319
result = g.namespace_manager.expand_curie(curie)
322-
323-
if catcher is not None:
324-
assert result is None
325-
assert catcher.value is not None
326-
else:
327-
assert expected_result == result
320+
checker.check(result)

test/test_namespace/test_namespacemanager.py

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from __future__ import annotations
22

33
import logging
4-
import re
54
import sys
65
from contextlib import ExitStack
76
from pathlib import Path
8-
from test.utils.exceptions import ExceptionChecker
7+
from test.utils.outcome import ExceptionChecker, OutcomeChecker, OutcomePrimitive
98
from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Set, Tuple, Type, Union
109

1110
import pytest
@@ -374,7 +373,7 @@ def test_compute_qname(
374373
manager_prefixes: Optional[Mapping[str, Namespace]],
375374
graph_prefixes: Optional[Mapping[str, Namespace]],
376375
store_prefixes: Optional[Mapping[str, Namespace]],
377-
expected_result: Union[Tuple[str, URIRef, str], Type[Exception], Exception],
376+
expected_result: OutcomePrimitive[Tuple[str, URIRef, str]],
378377
) -> None:
379378
"""
380379
:param uri: argument to compute_qname()
@@ -403,25 +402,13 @@ def test_compute_qname(
403402
nm.bind(prefix, ns)
404403

405404
def check() -> None:
406-
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
407-
with ExitStack() as xstack:
408-
if isinstance(expected_result, type) and issubclass(
409-
expected_result, Exception
410-
):
411-
catcher = xstack.enter_context(pytest.raises(expected_result))
412-
if isinstance(expected_result, Exception):
413-
catcher = xstack.enter_context(pytest.raises(type(expected_result)))
405+
checker = OutcomeChecker[Tuple[str, URIRef, str]].from_primitive(
406+
expected_result
407+
)
408+
with checker.context():
414409
actual_result = nm.compute_qname(uri, generate)
415410
logging.debug("actual_result = %s", actual_result)
416-
if catcher is not None:
417-
assert catcher is not None
418-
assert catcher.value is not None
419-
if isinstance(expected_result, Exception):
420-
assert re.match(expected_result.args[0], f"{catcher.value}")
421-
else:
422-
assert isinstance(expected_result, tuple)
423-
assert isinstance(actual_result, tuple)
424-
assert actual_result == expected_result
411+
checker.check(actual_result)
425412

426413
check()
427414
# Run a second time to check caching
@@ -452,7 +439,7 @@ def test_compute_qname_strict(
452439
generate: bool,
453440
bind_namespaces: _NamespaceSetString,
454441
additional_prefixes: Optional[Mapping[str, Namespace]],
455-
expected_result: Union[Tuple[str, URIRef, str], Type[Exception], Exception],
442+
expected_result: OutcomePrimitive[Tuple[str, str, str]],
456443
) -> None:
457444
graph = Graph(bind_namespaces=bind_namespaces)
458445
nm = graph.namespace_manager
@@ -462,25 +449,11 @@ def test_compute_qname_strict(
462449
nm.bind(prefix, ns)
463450

464451
def check() -> None:
465-
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
466-
with ExitStack() as xstack:
467-
if isinstance(expected_result, type) and issubclass(
468-
expected_result, Exception
469-
):
470-
catcher = xstack.enter_context(pytest.raises(expected_result))
471-
if isinstance(expected_result, Exception):
472-
catcher = xstack.enter_context(pytest.raises(type(expected_result)))
452+
checker = OutcomeChecker[Tuple[str, str, str]].from_primitive(expected_result)
453+
with checker.context():
473454
actual_result = nm.compute_qname_strict(uri, generate)
474455
logging.debug("actual_result = %s", actual_result)
475-
if catcher is not None:
476-
assert catcher is not None
477-
assert catcher.value is not None
478-
if isinstance(expected_result, Exception):
479-
assert re.match(expected_result.args[0], f"{catcher.value}")
480-
else:
481-
assert isinstance(expected_result, tuple)
482-
assert isinstance(actual_result, tuple)
483-
assert actual_result == expected_result
456+
checker.check(actual_result)
484457

485458
check()
486459
# Run a second time to check caching
@@ -538,16 +511,15 @@ def test_nsm_function() -> NamespaceManager:
538511
def test_expand_curie(
539512
test_nsm_session: NamespaceManager,
540513
curie: str,
541-
expected_result: Union[ExceptionChecker, str],
514+
expected_result: OutcomePrimitive[str],
542515
) -> None:
543516
nsm = test_nsm_session
544-
with ExitStack() as xstack:
545-
if isinstance(expected_result, ExceptionChecker):
546-
xstack.enter_context(expected_result)
547-
result = nsm.expand_curie(curie)
548-
549-
if not isinstance(expected_result, ExceptionChecker):
550-
assert URIRef(expected_result) == result
517+
if isinstance(expected_result, str):
518+
expected_result = URIRef(expected_result)
519+
checker = OutcomeChecker[str].from_primitive(expected_result)
520+
with checker.context():
521+
actual_result = nsm.expand_curie(curie)
522+
checker.check(actual_result)
551523

552524

553525
@pytest.mark.parametrize(
@@ -578,7 +550,7 @@ def test_generate_curie(
578550
test_nsm_function: NamespaceManager,
579551
uri: str,
580552
generate: Optional[bool],
581-
expected_result: Union[ExceptionChecker, str],
553+
expected_result: OutcomePrimitive[str],
582554
) -> None:
583555
"""
584556
.. note::
@@ -587,13 +559,10 @@ def test_generate_curie(
587559
effects and will modify the namespace manager.
588560
"""
589561
nsm = test_nsm_function
590-
with ExitStack() as xstack:
591-
if isinstance(expected_result, ExceptionChecker):
592-
xstack.enter_context(expected_result)
562+
checker = OutcomeChecker[str].from_primitive(expected_result)
563+
with checker.context():
593564
if generate is None:
594-
result = nsm.curie(uri)
565+
actual_result = nsm.curie(uri)
595566
else:
596-
result = nsm.curie(uri, generate=generate)
597-
598-
if not isinstance(expected_result, ExceptionChecker):
599-
assert expected_result == result
567+
actual_result = nsm.curie(uri, generate=generate)
568+
checker.check(actual_result)

0 commit comments

Comments
 (0)