summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIwan Aucamp <aucampia@gmail.com>2022-06-05 14:03:02 +0200
committerGitHub <noreply@github.com>2022-06-05 14:03:02 +0200
commit7d8cab669c37791aeb97337454d35f99d4e43d43 (patch)
tree4ea04726540a43d862dc84baa843bb904e190975
parente677274fab1a7ddbf3c7f71feae5cd9004b0f103 (diff)
downloadrdflib-7d8cab669c37791aeb97337454d35f99d4e43d43.tar.gz
Add more tests for graph_diff (#1983)
This is to confirm that `graph_diff` does not work with quads. Other changes: - Added support to `GraphHelper` for collapsing `BNode`s to a specific IRI when converting to triple sets, this is in addition to the current functionality for ignoring BNodes. This makes it possible to more accurately compare Graphs that contain blank nodes. - Added `assert_cgraph_isomorphic` to `GraphHelper`. This method adds some rudimentary support for checking if conjunctive graphs are isomorphic, it will ignore named graphs that have blank nodes as name.
-rw-r--r--test/test_graph/test_diff.py174
-rw-r--r--test/test_graph/test_graph_cbd.py4
-rw-r--r--test/test_roundtrip.py10
-rw-r--r--test/utils/__init__.py203
-rw-r--r--test/utils/test/test_testutils.py182
5 files changed, 488 insertions, 85 deletions
diff --git a/test/test_graph/test_diff.py b/test/test_graph/test_diff.py
index d9b48a70..58728187 100644
--- a/test/test_graph/test_diff.py
+++ b/test/test_graph/test_diff.py
@@ -1,12 +1,23 @@
-from test.utils import GraphHelper
-from typing import TYPE_CHECKING, Set
+from dataclasses import dataclass, field
+from test.utils import (
+ COLLAPSED_BNODE,
+ BNodeHandling,
+ GHQuad,
+ GHTriple,
+ GraphHelper,
+ MarksType,
+ MarkType,
+)
+from typing import TYPE_CHECKING, Collection, Set, Tuple, Type, Union, cast
import pytest
+from _pytest.mark.structures import ParameterSet
import rdflib
from rdflib import Graph
from rdflib.compare import graph_diff
-from rdflib.namespace import FOAF, RDF
+from rdflib.graph import ConjunctiveGraph, Dataset
+from rdflib.namespace import FOAF, RDF, Namespace
from rdflib.term import BNode, Literal
if TYPE_CHECKING:
@@ -15,7 +26,7 @@ if TYPE_CHECKING:
"""Test for graph_diff - much more extensive testing
would certainly be possible"""
-_TripleSetT = Set["_TripleType"]
+_TripleSetType = Set["_TripleType"]
class TestDiff:
@@ -48,7 +59,7 @@ class TestDiff:
triples only in `g0`, and that there are triples that occur in both
`g0` and `g1`, and that there are triples only in `g1`.
"""
- g0_ts: _TripleSetT = set()
+ g0_ts: _TripleSetType = set()
bnode = BNode()
g0_ts.update(
{
@@ -59,7 +70,7 @@ class TestDiff:
g0 = Graph()
g0 += g0_ts
- g1_ts: _TripleSetT = set()
+ g1_ts: _TripleSetType = set()
bnode = BNode()
g1_ts.update(
{
@@ -76,3 +87,154 @@ class TestDiff:
assert in_first == set()
assert len(in_second) > 0
assert len(in_both) > 0
+
+
+_ElementSetType = Union[Collection[GHTriple], Collection[GHQuad]]
+
+_ElementSetTypeOrStr = Union[_ElementSetType, str]
+
+
+@dataclass
+class GraphDiffCase:
+ graph_type: Type[Graph]
+ format: str
+ lhs: str
+ rhs: str
+ expected_result: Tuple[
+ _ElementSetTypeOrStr, _ElementSetTypeOrStr, _ElementSetTypeOrStr
+ ]
+ marks: MarkType = field(default_factory=lambda: cast(MarksType, list()))
+
+ def as_element_set(self, value: _ElementSetTypeOrStr) -> _ElementSetType:
+ if isinstance(value, str):
+ graph = self.graph_type()
+ graph.parse(data=value, format=self.format)
+ if isinstance(graph, ConjunctiveGraph):
+ return GraphHelper.quad_set(graph, BNodeHandling.COLLAPSE)
+ else:
+ return GraphHelper.triple_set(graph, BNodeHandling.COLLAPSE)
+ return value
+
+ def expected_in_both_set(self) -> _ElementSetType:
+ return self.as_element_set(self.expected_result[0])
+
+ def expected_in_lhs_set(self) -> _ElementSetType:
+ return self.as_element_set(self.expected_result[1])
+
+ def expected_in_rhs_set(self) -> _ElementSetType:
+ return self.as_element_set(self.expected_result[2])
+
+ def as_params(self) -> ParameterSet:
+ return pytest.param(self, marks=self.marks)
+
+
+EGSCHEME = Namespace("example:")
+
+
+@pytest.mark.parametrize(
+ "test_case",
+ [
+ GraphDiffCase(
+ Graph,
+ format="turtle",
+ lhs="""
+ @prefix eg: <example:> .
+ _:a _:b _:c .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ rhs="""
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ expected_result=(
+ """
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ {(COLLAPSED_BNODE, COLLAPSED_BNODE, COLLAPSED_BNODE)},
+ "",
+ ),
+ ),
+ GraphDiffCase(
+ Graph,
+ format="turtle",
+ lhs="""
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ rhs="""
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ expected_result=(
+ """
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ "",
+ "",
+ ),
+ ),
+ GraphDiffCase(
+ Dataset,
+ format="trig",
+ lhs="""
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ rhs="""
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ expected_result=(
+ """
+ @prefix eg: <example:> .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ "",
+ "",
+ ),
+ marks=pytest.mark.xfail(
+ reason="quads are not supported", raises=ValueError
+ ),
+ ).as_params(),
+ ],
+)
+def test_assert_sets_equal(test_case: GraphDiffCase):
+ """
+ GraphHelper.sets_equals and related functions work correctly in both
+ positive and negative cases.
+ """
+ lhs_graph: Graph = test_case.graph_type()
+ lhs_graph.parse(data=test_case.lhs, format=test_case.format)
+
+ rhs_graph: Graph = test_case.graph_type()
+ rhs_graph.parse(data=test_case.rhs, format=test_case.format)
+
+ in_both, in_lhs, in_rhs = graph_diff(lhs_graph, rhs_graph)
+ in_both_set = GraphHelper.triple_or_quad_set(in_both, BNodeHandling.COLLAPSE)
+ in_lhs_set = GraphHelper.triple_or_quad_set(in_lhs, BNodeHandling.COLLAPSE)
+ in_rhs_set = GraphHelper.triple_or_quad_set(in_rhs, BNodeHandling.COLLAPSE)
+
+ assert test_case.expected_in_both_set() == in_both_set
+ assert test_case.expected_in_lhs_set() == in_lhs_set
+ assert test_case.expected_in_rhs_set() == in_rhs_set
+
+ # Diff should be symetric
+ in_rboth, in_rlhs, in_rrhs = graph_diff(rhs_graph, lhs_graph)
+ in_rboth_set = GraphHelper.triple_or_quad_set(in_rboth, BNodeHandling.COLLAPSE)
+ in_rlhs_set = GraphHelper.triple_or_quad_set(in_rlhs, BNodeHandling.COLLAPSE)
+ in_rrhs_set = GraphHelper.triple_or_quad_set(in_rrhs, BNodeHandling.COLLAPSE)
+
+ assert test_case.expected_in_both_set() == in_rboth_set
+ assert test_case.expected_in_rhs_set() == in_rlhs_set
+ assert test_case.expected_in_lhs_set() == in_rrhs_set
diff --git a/test/test_graph/test_graph_cbd.py b/test/test_graph/test_graph_cbd.py
index 4b8985ed..66861241 100644
--- a/test/test_graph/test_graph_cbd.py
+++ b/test/test_graph/test_graph_cbd.py
@@ -1,5 +1,5 @@
from test.data import TEST_DATA_DIR
-from test.utils import GraphHelper
+from test.utils import BNodeHandling, GraphHelper
import pytest
@@ -130,7 +130,7 @@ def test_cbd_example():
query = "http://example.com/aReallyGreatBook"
GraphHelper.assert_isomorphic(g.cbd(URIRef(query)), g_cbd)
- GraphHelper.assert_sets_equals(g.cbd(URIRef(query)), g_cbd, exclude_blanks=True)
+ GraphHelper.assert_sets_equals(g.cbd(URIRef(query)), g_cbd, BNodeHandling.COLLAPSE)
assert len(g.cbd(URIRef(query))) == (
21
), "cbd() for aReallyGreatBook should return 21 triples"
diff --git a/test/test_roundtrip.py b/test/test_roundtrip.py
index 632b3add..58d7480c 100644
--- a/test/test_roundtrip.py
+++ b/test/test_roundtrip.py
@@ -3,7 +3,7 @@ import logging
import os.path
from pathlib import Path
from test.data import TEST_DATA_DIR
-from test.utils import GraphHelper
+from test.utils import BNodeHandling, GraphHelper
from typing import Callable, Iterable, List, Optional, Set, Tuple, Type, Union
from xml.sax import SAXParseException
@@ -285,9 +285,13 @@ def roundtrip(
GraphHelper.assert_isomorphic(g1, g2)
if checks is not None:
if Check.SET_EQUALS in checks:
- GraphHelper.assert_sets_equals(g1, g2, exclude_blanks=False)
+ GraphHelper.assert_sets_equals(
+ g1, g2, bnode_handling=BNodeHandling.COLLAPSE
+ )
if Check.SET_EQUALS_WITHOUT_BLANKS in checks:
- GraphHelper.assert_sets_equals(g1, g2, exclude_blanks=True)
+ GraphHelper.assert_sets_equals(
+ g1, g2, bnode_handling=BNodeHandling.COLLAPSE
+ )
if logger.isEnabledFor(logging.DEBUG):
logger.debug("OK")
diff --git a/test/utils/__init__.py b/test/utils/__init__.py
index 56af5e5f..67935cc7 100644
--- a/test/utils/__init__.py
+++ b/test/utils/__init__.py
@@ -7,18 +7,14 @@ The tests for test utilities should be placed inside `test.utils.test`
from __future__ import print_function
-import email.message
+import enum
import pprint
import random
-import unittest
-from contextlib import AbstractContextManager, contextmanager
-from http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler
+from contextlib import contextmanager
+from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import PurePath, PureWindowsPath
from threading import Thread
-from traceback import print_exc
-from types import TracebackType
from typing import (
- TYPE_CHECKING,
Any,
Callable,
Collection,
@@ -28,7 +24,6 @@ from typing import (
Iterable,
Iterator,
List,
- NamedTuple,
Optional,
Set,
Tuple,
@@ -37,10 +32,7 @@ from typing import (
Union,
cast,
)
-from unittest.mock import MagicMock, Mock
-from urllib.error import HTTPError
-from urllib.parse import ParseResult, parse_qs, unquote, urlparse
-from urllib.request import urlopen
+from urllib.parse import unquote, urlparse
import pytest
from _pytest.mark.structures import Mark, MarkDecorator, ParameterSet
@@ -105,6 +97,16 @@ GHQuad = Tuple[GHNode, GHNode, GHNode, Identifier]
GHQuadSet = Set[GHQuad]
GHQuadFrozenSet = FrozenSet[GHQuad]
+NodeT = TypeVar("NodeT", bound=GHNode)
+
+COLLAPSED_BNODE = URIRef("urn:fdc:rdflib.github.io:20220522:collapsed-bnode")
+
+
+class BNodeHandling(str, enum.Enum):
+ COMPARE = "compare" # Compare BNodes as normal
+ EXCLUDE = "exclude" # Exclude blanks from comparison
+ COLLAPSE = "collapse" # Collapse all blank nodes to one IRI
+
class GraphHelper:
"""
@@ -112,57 +114,102 @@ class GraphHelper:
"""
@classmethod
- def node(cls, node: Node, exclude_blanks: bool = False) -> GHNode:
+ def add_triples(
+ cls, graph: Graph, triples: Iterable[Tuple[Node, Node, Node]]
+ ) -> Graph:
+ for triple in triples:
+ graph.add(triple)
+ return graph
+
+ @classmethod
+ def node(
+ cls, node: Node, bnode_handling: BNodeHandling = BNodeHandling.COMPARE
+ ) -> GHNode:
"""
Return the identifier of the provided node.
"""
if isinstance(node, Graph):
- xset = cast(GHNode, cls.triple_or_quad_set(node, exclude_blanks))
+ xset = cast(GHNode, cls.triple_or_quad_set(node, bnode_handling))
return xset
return cast(Identifier, node)
@classmethod
def nodes(
- cls, nodes: Tuple[Node, ...], exclude_blanks: bool = False
+ cls,
+ nodes: Tuple[Node, ...],
+ bnode_handling: BNodeHandling = BNodeHandling.COMPARE,
) -> Tuple[GHNode, ...]:
"""
Return the identifiers of the provided nodes.
"""
result = []
for node in nodes:
- result.append(cls.node(node, exclude_blanks))
+ result.append(cls.node(node, bnode_handling))
+ return tuple(result)
+
+ @classmethod
+ def _contains_bnodes(cls, nodes: Tuple[GHNode, ...]) -> bool:
+ """
+ Return true if any of the nodes are BNodes.
+ """
+ for node in nodes:
+ if isinstance(node, BNode):
+ return True
+ return False
+
+ @classmethod
+ def _collapse_bnodes(cls, nodes: Tuple[NodeT, ...]) -> Tuple[NodeT, ...]:
+ """
+ Return BNodes as COLLAPSED_BNODE
+ """
+ result: List[NodeT] = []
+ for node in nodes:
+ if isinstance(node, BNode):
+ result.append(cast(NodeT, COLLAPSED_BNODE))
+ else:
+ result.append(node)
return tuple(result)
@classmethod
def triple_set(
- cls, graph: Graph, exclude_blanks: bool = False
+ cls, graph: Graph, bnode_handling: BNodeHandling = BNodeHandling.COMPARE
) -> GHTripleFrozenSet:
result: GHTripleSet = set()
for sn, pn, on in graph.triples((None, None, None)):
- s, p, o = cls.nodes((sn, pn, on), exclude_blanks)
- if exclude_blanks and (
- isinstance(s, BNode) or isinstance(p, BNode) or isinstance(o, BNode)
+ s, p, o = cls.nodes((sn, pn, on), bnode_handling)
+ if bnode_handling == BNodeHandling.EXCLUDE and cls._contains_bnodes(
+ (s, p, o)
):
continue
+ elif bnode_handling == BNodeHandling.COLLAPSE:
+ s, p, o = cls._collapse_bnodes((s, p, o))
+ # if bnode_handling == BNodeHandling.EXCLUDE (
+ # isinstance(s, BNode) or isinstance(p, BNode) or isinstance(o, BNode)
+ # ):
+ # continue
result.add((s, p, o))
return frozenset(result)
@classmethod
def triple_sets(
- cls, graphs: Iterable[Graph], exclude_blanks: bool = False
+ cls,
+ graphs: Iterable[Graph],
+ bnode_handling: BNodeHandling = BNodeHandling.COMPARE,
) -> List[GHTripleFrozenSet]:
"""
Extracts the set of all triples from the supplied Graph.
"""
result: List[GHTripleFrozenSet] = []
for graph in graphs:
- result.append(cls.triple_set(graph, exclude_blanks))
+ result.append(cls.triple_set(graph, bnode_handling))
return result
@classmethod
def quad_set(
- cls, graph: ConjunctiveGraph, exclude_blanks: bool = False
+ cls,
+ graph: ConjunctiveGraph,
+ bnode_handling: BNodeHandling = BNodeHandling.COMPARE,
) -> GHQuadFrozenSet:
"""
Extracts the set of all quads from the supplied ConjunctiveGraph.
@@ -180,73 +227,90 @@ class GraphHelper:
gn_id = gn.identifier # type: ignore[assignment]
else:
raise ValueError(f"invalid graph type {type(graph)}: {graph!r}")
- s, p, o = cls.nodes((sn, pn, on), exclude_blanks)
- if exclude_blanks and (
- isinstance(s, BNode) or isinstance(p, BNode) or isinstance(o, BNode)
+ s, p, o = cls.nodes((sn, pn, on), bnode_handling)
+ if bnode_handling == BNodeHandling.EXCLUDE and cls._contains_bnodes(
+ (s, p, o, gn_id)
):
continue
+ elif bnode_handling == BNodeHandling.COLLAPSE:
+ s, p, o, gn_id = cast(GHQuad, cls._collapse_bnodes((s, p, o, gn_id)))
quad: GHQuad = (s, p, o, gn_id)
result.add(quad)
return frozenset(result)
@classmethod
def triple_or_quad_set(
- cls, graph: Graph, exclude_blanks: bool = False
+ cls, graph: Graph, bnode_handling: BNodeHandling = BNodeHandling.COMPARE
) -> Union[GHQuadFrozenSet, GHTripleFrozenSet]:
"""
Extracts quad or triple sets depending on whether or not the graph is
ConjunctiveGraph or a normal Graph.
"""
if isinstance(graph, ConjunctiveGraph):
- return cls.quad_set(graph, exclude_blanks)
- return cls.triple_set(graph, exclude_blanks)
+ return cls.quad_set(graph, bnode_handling)
+ return cls.triple_set(graph, bnode_handling)
@classmethod
def assert_triple_sets_equals(
- cls, lhs: Graph, rhs: Graph, exclude_blanks: bool = False
+ cls,
+ lhs: Graph,
+ rhs: Graph,
+ bnode_handling: BNodeHandling = BNodeHandling.COMPARE,
+ negate: bool = False,
) -> None:
"""
Asserts that the triple sets in the two graphs are equal.
"""
- lhs_set = cls.triple_set(lhs, exclude_blanks) if isinstance(lhs, Graph) else lhs
- rhs_set = cls.triple_set(rhs, exclude_blanks) if isinstance(rhs, Graph) else rhs
- assert lhs_set == rhs_set
+ lhs_set = cls.triple_set(lhs, bnode_handling) if isinstance(lhs, Graph) else lhs
+ rhs_set = cls.triple_set(rhs, bnode_handling) if isinstance(rhs, Graph) else rhs
+ if not negate:
+ assert lhs_set == rhs_set
+ else:
+ assert lhs_set != rhs_set
@classmethod
def assert_quad_sets_equals(
cls,
lhs: Union[ConjunctiveGraph, GHQuadSet],
rhs: Union[ConjunctiveGraph, GHQuadSet],
- exclude_blanks: bool = False,
+ bnode_handling: BNodeHandling = BNodeHandling.COMPARE,
+ negate: bool = False,
) -> None:
"""
Asserts that the quads sets in the two graphs are equal.
"""
- lhs_set = cls.quad_set(lhs, exclude_blanks) if isinstance(lhs, Graph) else lhs
- rhs_set = cls.quad_set(rhs, exclude_blanks) if isinstance(rhs, Graph) else rhs
- assert lhs_set == rhs_set
+ lhs_set = cls.quad_set(lhs, bnode_handling) if isinstance(lhs, Graph) else lhs
+ rhs_set = cls.quad_set(rhs, bnode_handling) if isinstance(rhs, Graph) else rhs
+ if not negate:
+ assert lhs_set == rhs_set
+ else:
+ assert lhs_set != rhs_set
@classmethod
def assert_sets_equals(
cls,
lhs: Union[Graph, GHTripleSet, GHQuadSet],
rhs: Union[Graph, GHTripleSet, GHQuadSet],
- exclude_blanks: bool = False,
+ bnode_handling: BNodeHandling = BNodeHandling.COMPARE,
+ negate: bool = False,
) -> None:
"""
Asserts that that ther quad or triple sets from the two graphs are equal.
"""
lhs_set = (
- cls.triple_or_quad_set(lhs, exclude_blanks)
+ cls.triple_or_quad_set(lhs, bnode_handling)
if isinstance(lhs, Graph)
else lhs
)
rhs_set = (
- cls.triple_or_quad_set(rhs, exclude_blanks)
+ cls.triple_or_quad_set(rhs, bnode_handling)
if isinstance(rhs, Graph)
else rhs
)
- assert lhs_set == rhs_set
+ if not negate:
+ assert lhs_set == rhs_set
+ else:
+ assert lhs_set != rhs_set
@classmethod
def format_set(
@@ -281,7 +345,7 @@ class GraphHelper:
This asserts that the two graphs are isomorphic, providing a nicely
formatted error message if they are not.
"""
-
+ # TODO FIXME: This should possibly raise an error when used on a ConjunctiveGraph
def format_report(message: Optional[str] = None) -> str:
in_both, in_lhs, in_rhs = rdflib.compare.graph_diff(lhs, rhs)
preamle = "" if message is None else f"{message}\n"
@@ -297,6 +361,38 @@ class GraphHelper:
assert rdflib.compare.isomorphic(lhs, rhs), format_report(message)
@classmethod
+ def assert_cgraph_isomorphic(
+ cls,
+ lhs: ConjunctiveGraph,
+ rhs: ConjunctiveGraph,
+ exclude_bnodes: bool,
+ message: Optional[str] = None,
+ ) -> None:
+ def get_contexts(cgraph: ConjunctiveGraph) -> Dict[URIRef, Graph]:
+ result = {}
+ for context in cgraph.contexts():
+ if isinstance(context.identifier, BNode):
+ if exclude_bnodes:
+ continue
+ else:
+ raise AssertionError("BNode labelled graphs not supported")
+ elif isinstance(context.identifier, URIRef):
+ result[context.identifier] = context
+ else:
+ raise AssertionError(
+ f"unsupported context identifier {context.identifier}"
+ )
+ return result
+
+ lhs_contexts = get_contexts(lhs)
+ rhs_contexts = get_contexts(rhs)
+ assert (
+ lhs_contexts.keys() == rhs_contexts.keys()
+ ), f"must have same context ids in LHS and RHS (exclude_bnodes={exclude_bnodes})"
+ for id, lhs_context in lhs_contexts.items():
+ cls.assert_isomorphic(lhs_context, rhs_contexts[id], message)
+
+ @classmethod
def strip_literal_datatypes(cls, graph: Graph, datatypes: Set[URIRef]) -> None:
"""
Strips datatypes in the provided set from literals in the graph.
@@ -353,11 +449,24 @@ def file_uri_to_path(
ParamsT = TypeVar("ParamsT", bound=tuple)
-Marks = Collection[Union[Mark, MarkDecorator]]
+MarksType = Collection[Union[MarkDecorator, Mark]]
+MarkListType = List[Union[MarkDecorator, Mark]]
+MarkType = Union[MarkDecorator, MarksType]
+
+MarkerType = Callable[..., Optional[MarkType]]
+
+
+def marks_to_list(mark: MarkType) -> MarkListType:
+ if isinstance(mark, (MarkDecorator, Mark)):
+ return [mark]
+ elif isinstance(mark, list):
+ return mark
+ return list(*mark)
def pytest_mark_filter(
- param_sets: Iterable[Union[ParamsT, ParameterSet]], mark_dict: Dict[ParamsT, Marks]
+ param_sets: Iterable[Union[ParamsT, ParameterSet]],
+ mark_dict: Dict[ParamsT, MarksType],
) -> Generator[ParameterSet, None, None]:
"""
Adds marks to test parameters. Useful for adding xfails to test parameters.
@@ -370,12 +479,14 @@ def pytest_mark_filter(
id=param_set.id,
marks=[
*param_set.marks,
- *mark_dict.get(cast(ParamsT, param_set.values), cast(Marks, ())),
+ *mark_dict.get(
+ cast(ParamsT, param_set.values), cast(MarksType, ())
+ ),
],
)
else:
yield pytest.param(
- *param_set, marks=mark_dict.get(param_set, cast(Marks, ()))
+ *param_set, marks=mark_dict.get(param_set, cast(MarksType, ()))
)
diff --git a/test/utils/test/test_testutils.py b/test/utils/test/test_testutils.py
index 2485a404..a624c445 100644
--- a/test/utils/test/test_testutils.py
+++ b/test/utils/test/test_testutils.py
@@ -1,12 +1,19 @@
import os
+from contextlib import ExitStack
from dataclasses import dataclass
from pathlib import PurePosixPath, PureWindowsPath
-from test.utils import GraphHelper, affix_tuples, file_uri_to_path
-from typing import Any, List, Optional, Tuple, Union
+from test.utils import (
+ COLLAPSED_BNODE,
+ BNodeHandling,
+ GraphHelper,
+ affix_tuples,
+ file_uri_to_path,
+)
+from typing import Any, List, Optional, Tuple, Type, Union
import pytest
-from rdflib.graph import ConjunctiveGraph, Graph
+from rdflib.graph import ConjunctiveGraph, Dataset, Graph
from rdflib.term import URIRef
@@ -99,7 +106,7 @@ def test_paths(
class SetsEqualTestCase:
equal: bool
format: Union[str, Tuple[str, str]]
- ignore_blanks: bool
+ bnode_handling: BNodeHandling
lhs: str
rhs: str
@@ -122,7 +129,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=False,
format="turtle",
- ignore_blanks=False,
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
@prefix eg: <example:> .
_:a _:b _:c .
@@ -138,7 +145,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format="turtle",
- ignore_blanks=True,
+ bnode_handling=BNodeHandling.EXCLUDE,
lhs="""
@prefix eg: <example:> .
_:a _:b _:c .
@@ -154,7 +161,25 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format="turtle",
- ignore_blanks=False,
+ bnode_handling=BNodeHandling.COLLAPSE,
+ lhs="""
+ @prefix eg: <example:> .
+ _:a _:b _:c .
+ _:z _:b _:c .
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ rhs=f"""
+ @prefix eg: <example:> .
+ <{COLLAPSED_BNODE}> <{COLLAPSED_BNODE}> <{COLLAPSED_BNODE}>.
+ eg:o0 eg:p0 eg:s0 .
+ eg:o1 eg:p1 eg:s1 .
+ """,
+ ),
+ SetsEqualTestCase(
+ equal=True,
+ format="turtle",
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
<example:o0> <example:p0> <example:s0> .
<example:o1> <example:p1> <example:s1> .
@@ -168,7 +193,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=False,
format="turtle",
- ignore_blanks=False,
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
<example:o0> <example:p0> <example:s0> .
<example:o1> <example:p1> <example:s1> .
@@ -183,7 +208,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format=("nquads", "trig"),
- ignore_blanks=True,
+ bnode_handling=BNodeHandling.EXCLUDE,
lhs="""
<example:o0> <example:p0> <example:s0> .
<example:o1> <example:p1> <example:s1> .
@@ -199,7 +224,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format=("nquads", "trig"),
- ignore_blanks=True,
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
<example:o0> <example:p0> <example:s0> .
<example:o1> <example:p1> <example:s1> <example:g1>.
@@ -219,7 +244,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format="n3",
- ignore_blanks=False,
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
{ <example:ss0> <example:sp0> <example:so0> } <example:p0> {}.
""",
@@ -231,7 +256,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format="n3",
- ignore_blanks=False,
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
{ <example:ss0> <example:sp0> <example:so0> } <example:p0> {}.
""",
@@ -243,7 +268,7 @@ class SetsEqualTestCase:
SetsEqualTestCase(
equal=True,
format="n3",
- ignore_blanks=False,
+ bnode_handling=BNodeHandling.COMPARE,
lhs="""
{ { <example:sss0> <example:ssp0> <example:sso0> } <example:sp0> <example:so0> } <example:p0> {}.
""",
@@ -278,44 +303,44 @@ def test_assert_sets_equal(test_case: SetsEqualTestCase):
graph: Graph
cgraph: ConjunctiveGraph
for graph, cgraph in ((lhs_graph, lhs_cgraph), (rhs_graph, rhs_cgraph)):
- GraphHelper.assert_sets_equals(graph, graph, True)
- GraphHelper.assert_sets_equals(cgraph, cgraph, True)
- GraphHelper.assert_triple_sets_equals(graph, graph, True)
- GraphHelper.assert_triple_sets_equals(cgraph, cgraph, True)
- GraphHelper.assert_quad_sets_equals(cgraph, cgraph, True)
+ GraphHelper.assert_sets_equals(graph, graph, BNodeHandling.COLLAPSE)
+ GraphHelper.assert_sets_equals(cgraph, cgraph, BNodeHandling.COLLAPSE)
+ GraphHelper.assert_triple_sets_equals(graph, graph, BNodeHandling.COLLAPSE)
+ GraphHelper.assert_triple_sets_equals(cgraph, cgraph, BNodeHandling.COLLAPSE)
+ GraphHelper.assert_quad_sets_equals(cgraph, cgraph, BNodeHandling.COLLAPSE)
if not test_case.equal:
with pytest.raises(AssertionError):
GraphHelper.assert_sets_equals(
- lhs_graph, rhs_graph, test_case.ignore_blanks
+ lhs_graph, rhs_graph, test_case.bnode_handling
)
with pytest.raises(AssertionError):
GraphHelper.assert_sets_equals(
- lhs_cgraph, rhs_cgraph, test_case.ignore_blanks
+ lhs_cgraph, rhs_cgraph, test_case.bnode_handling
)
with pytest.raises(AssertionError):
GraphHelper.assert_triple_sets_equals(
- lhs_graph, rhs_graph, test_case.ignore_blanks
+ lhs_graph, rhs_graph, test_case.bnode_handling
)
with pytest.raises(AssertionError):
GraphHelper.assert_triple_sets_equals(
- lhs_cgraph, rhs_cgraph, test_case.ignore_blanks
+ lhs_cgraph, rhs_cgraph, test_case.bnode_handling
)
with pytest.raises(AssertionError):
GraphHelper.assert_quad_sets_equals(
- lhs_cgraph, rhs_cgraph, test_case.ignore_blanks
+ lhs_cgraph, rhs_cgraph, test_case.bnode_handling
)
else:
- GraphHelper.assert_sets_equals(lhs_graph, rhs_graph, test_case.ignore_blanks)
- GraphHelper.assert_sets_equals(lhs_cgraph, rhs_cgraph, test_case.ignore_blanks)
+ GraphHelper.assert_sets_equals(lhs_graph, rhs_graph, test_case.bnode_handling)
+ GraphHelper.assert_sets_equals(lhs_cgraph, rhs_cgraph, test_case.bnode_handling)
GraphHelper.assert_triple_sets_equals(
- lhs_graph, rhs_graph, test_case.ignore_blanks
+ lhs_graph, rhs_graph, test_case.bnode_handling
)
GraphHelper.assert_triple_sets_equals(
- lhs_cgraph, rhs_cgraph, test_case.ignore_blanks
+ lhs_cgraph, rhs_cgraph, test_case.bnode_handling
)
GraphHelper.assert_quad_sets_equals(
- lhs_cgraph, rhs_cgraph, test_case.ignore_blanks
+ lhs_cgraph, rhs_cgraph, test_case.bnode_handling
)
@@ -357,3 +382,104 @@ def test_prefix_tuples(
expected_result: List[Tuple[Any, ...]],
) -> None:
assert expected_result == list(affix_tuples(prefix, tuples, suffix))
+
+
+@pytest.mark.parametrize(
+ ["graph_type", "format", "lhs", "rhs", "expected_result"],
+ [
+ (
+ Dataset,
+ "trig",
+ """
+ @prefix eg: <example:> .
+
+ _:b0 eg:p0 eg:o0.
+ eg:s1 eg:p1 eg:o1.
+
+ eg:g0 {
+ _:g0b0 eg:g0p0 eg:g0o0.
+ eg:g0s1 eg:g0p1 eg:g0o1.
+ }
+ """,
+ """
+ @prefix eg: <example:> .
+
+ _:b1 eg:p0 eg:o0.
+ eg:s1 eg:p1 eg:o1.
+
+ eg:g0 {
+ _:g0b1 eg:g0p0 eg:g0o0.
+ eg:g0s1 eg:g0p1 eg:g0o1.
+ }
+ """,
+ None,
+ ),
+ (
+ Dataset,
+ "trig",
+ """
+ @prefix eg: <example:> .
+
+ eg:g0 {
+ _:b0 eg:g0p0 eg:g0o0.
+ eg:g0s1 eg:g0p1 eg:g0o1.
+ }
+ """,
+ """
+ @prefix eg: <example:> .
+
+ eg:g0 {
+ _:b1 eg:g0p0 eg:g0o1.
+ eg:g0s1 eg:g0p1 eg:g0o1.
+ }
+ """,
+ AssertionError,
+ ),
+ (
+ Dataset,
+ "trig",
+ """
+ @prefix eg: <example:> .
+
+ eg:g0 {
+ _:b0 eg:g0p0 eg:g0o0.
+ eg:g0s1 eg:g0p1 eg:g0o1.
+ }
+ """,
+ """
+ @prefix eg: <example:> .
+
+ eg:g0 {
+ _:b1 eg:g0p0 eg:g0o0.
+ eg:g0s1 eg:g0p1 eg:g0o1.
+ }
+
+ eg:g1 {
+ _:b0 eg:g1p0 eg:g1o0.
+ eg:g1s1 eg:g1p1 eg:g1o1.
+ }
+ """,
+ AssertionError,
+ ),
+ ],
+)
+def test_assert_cgraph_isomorphic(
+ graph_type: Type[ConjunctiveGraph],
+ format: str,
+ lhs: str,
+ rhs: str,
+ expected_result: Union[None, Type[Exception]],
+) -> None:
+ lhs_graph = graph_type()
+ lhs_graph.parse(data=lhs, format=format)
+ rhs_graph = graph_type()
+ rhs_graph.parse(data=rhs, format=format)
+ catcher: Optional[pytest.ExceptionInfo[Exception]] = None
+ with ExitStack() as xstack:
+ if expected_result is not None:
+ catcher = xstack.enter_context(pytest.raises(expected_result))
+ GraphHelper.assert_cgraph_isomorphic(lhs_graph, rhs_graph, exclude_bnodes=True)
+ if expected_result is None:
+ assert catcher is None
+ else:
+ assert catcher is not None