diff --git a/test/data/variants/forward_slash-asserts.json b/test/data/variants/forward_slash-asserts.json new file mode 100644 index 000000000..3df873922 --- /dev/null +++ b/test/data/variants/forward_slash-asserts.json @@ -0,0 +1,10 @@ +{ + "quad_count": 4, + "exact_match": true, + "has_subject_iris": [ + "http://example.org/kb/individual-a", + "http://example.org/kb/individual-b", + "http://example.org/ontology/core/MyClassA", + "http://example.org/ontology/core/MyClassB" + ] +} diff --git a/test/data/variants/forward_slash-variant-prefixed.jsonld b/test/data/variants/forward_slash-variant-prefixed.jsonld new file mode 100644 index 000000000..1329eac65 --- /dev/null +++ b/test/data/variants/forward_slash-variant-prefixed.jsonld @@ -0,0 +1,33 @@ +{ + "@context": { + "ex": "http://example.org/ontology/", + "kb": "http://example.org/kb/", + "owl": "http://www.w3.org/2002/07/owl#" + }, + "@x-comment": [ + "The JSON-LD spec does not provide a grammar production rule set in,", + "EBNF. However, the section on compact IRIs indicates that an IRI can", + "be prefixed at any point that would not result in a suffix starting", + "with \"//\". Hence, an unpaired forward slash, as a legal character of", + "an IRI, can appear in the suffix component of a compact IRI.", + "https://json-ld.org/spec/latest/json-ld/#compact-iris" + ], + "@graph": [ + { + "@id": "kb:individual-a", + "@type": "ex:core/MyClassA" + }, + { + "@id": "ex:core/MyClassA", + "@type": "owl:Class" + }, + { + "@id": "kb:individual-b", + "@type": "ex:core/MyClassB" + }, + { + "@id": "ex:core/MyClassB", + "@type": "owl:Class" + } + ] +} diff --git a/test/data/variants/forward_slash-variant-prefixed.ttl b/test/data/variants/forward_slash-variant-prefixed.ttl new file mode 100644 index 000000000..633a4e9f6 --- /dev/null +++ b/test/data/variants/forward_slash-variant-prefixed.ttl @@ -0,0 +1,18 @@ +@prefix ex: . +@prefix kb: . +@prefix owl: . + +# Spell a class name with prefixing, but have the prefixing NOT include +# one of the forward-slashed path components. +# The forward slash must be escaped, according to Turtle grammar +# production rules grammar rules including and between PN_LOCAL and +# PN_LOCAL_ESC. +# https://www.w3.org/TR/turtle/#sec-grammar-grammar + +ex:core\/MyClassA a owl:Class . + +kb:individual-a a ex:core\/MyClassA . + +ex:core\/MyClassB a owl:Class . + +kb:individual-b a ex:core\/MyClassB . diff --git a/test/data/variants/forward_slash.jsonld b/test/data/variants/forward_slash.jsonld new file mode 100644 index 000000000..d55faa28f --- /dev/null +++ b/test/data/variants/forward_slash.jsonld @@ -0,0 +1,24 @@ +{ + "@context": { + "kb": "http://example.org/kb/", + "owl": "http://www.w3.org/2002/07/owl#" + }, + "@graph": [ + { + "@id": "kb:individual-a", + "@type": "http://example.org/ontology/core/MyClassA" + }, + { + "@id": "http://example.org/ontology/core/MyClassA", + "@type": "owl:Class" + }, + { + "@id": "kb:individual-b", + "@type": "http://example.org/ontology/core/MyClassB" + }, + { + "@id": "http://example.org/ontology/core/MyClassB", + "@type": "owl:Class" + } + ] +} diff --git a/test/data/variants/forward_slash.nt b/test/data/variants/forward_slash.nt new file mode 100644 index 000000000..e46471107 --- /dev/null +++ b/test/data/variants/forward_slash.nt @@ -0,0 +1,4 @@ + . + . + . + . diff --git a/test/data/variants/forward_slash.ttl b/test/data/variants/forward_slash.ttl new file mode 100644 index 000000000..67afd7cc2 --- /dev/null +++ b/test/data/variants/forward_slash.ttl @@ -0,0 +1,10 @@ +@prefix kb: . +@prefix owl: . + + a owl:Class . + +kb:individual-a a . + + a owl:Class . + +kb:individual-b a . diff --git a/test/test_graph/test_variants.py b/test/test_graph/test_variants.py index dbb20396b..9025f2e79 100644 --- a/test/test_graph/test_variants.py +++ b/test/test_graph/test_variants.py @@ -49,14 +49,26 @@ class GraphAsserts: quad_count: Optional[int] = None exact_match: bool = False + has_subject_iris: Optional[List[str]] = None def check( self, first_graph: Optional[ConjunctiveGraph], graph: ConjunctiveGraph ) -> None: + """ + if `first_graph` is `None` then this is the first check before any + other graphs have been processed. + """ if self.quad_count is not None: assert self.quad_count == len(list(graph.quads())) if first_graph is not None and self.exact_match: GraphHelper.assert_quad_sets_equals(first_graph, graph) + if first_graph is None and self.has_subject_iris is not None: + subjects_iris = { + f"{subject}" + for subject in graph.subjects() + if isinstance(subject, URIRef) + } + assert set(self.has_subject_iris) == subjects_iris @dataclass(order=True) @@ -219,6 +231,8 @@ def test_variants(graph_variant: GraphVariants) -> None: assert len(graph_variant.variants) > 0 first_graph: Optional[ConjunctiveGraph] = None first_path: Optional[Path] = None + logging.debug("graph_variant.asserts = %s", graph_variant.asserts) + for variant_key, variant_path in graph_variant.variants.items(): logging.debug("variant_path = %s", variant_path) format = guess_format(variant_path.name, fmap=SUFFIX_FORMAT_MAP) diff --git a/test/test_sparql/test_forward_slash_escapes.py b/test/test_sparql/test_forward_slash_escapes.py index 4f60c9ac6..43e22336c 100644 --- a/test/test_sparql/test_forward_slash_escapes.py +++ b/test/test_sparql/test_forward_slash_escapes.py @@ -19,99 +19,15 @@ "mime:application/json". """ -from typing import List, Set, Tuple +from test.data import TEST_DATA_DIR +from test.utils.graph import cached_graph +from typing import Set import pytest -import rdflib from rdflib import Graph from rdflib.plugins.sparql.processor import prepareQuery from rdflib.plugins.sparql.sparql import Query -from rdflib.term import Node - -# Determine version to delay a test until a version > 6 is being -# prepared for release. A 7-point-0-anything, alpha or beta designation -# should enable some further tests backwards-incompatible with version 6. -# Treat rdflib.__version__ like it can be compiled into a version_info -# tuple similar to sys.version_info. -# TODO: During the release of version 7, this set of version-detection -# code and its corresponding skipifs can be deleted. -_rdflib_version_info_strs: List[str] = rdflib.__version__.split(".") -_is_version_7_started = False -if _rdflib_version_info_strs[0].isdigit(): - if int(_rdflib_version_info_strs[0]) >= 7: - _is_version_7_started = True - -# Note that the data and query strings are Python raw strings, so -# backslashes won't be escape characters to Python. -# The "*_expanded" variables eschew prefixing. - -# Spell a class name without prefixing. -turtle_data_expanded = r""" -@prefix kb: . -@prefix owl: . - - a owl:Class . - -kb:individual-a a . -""" - -# Spell a class name with prefixing, but have the prefixing NOT include -# one of the forward-slashed path components. -# The forward slash must be escaped, according to Turtle grammar -# production rules grammar rules including and between PN_LOCAL and -# PN_LOCAL_ESC. -# https://www.w3.org/TR/turtle/#sec-grammar-grammar -turtle_data_prefixed = r""" -@prefix ex: . -@prefix kb: . -@prefix owl: . - -ex:core\/MyClassB a owl:Class . - -kb:individual-b a ex:core\/MyClassB . -""" - -# This data is an equivalant graph with turtle_data_expanded. -jsonld_data_expanded = r""" -{ - "@context": { - "kb": "http://example.org/kb/", - "owl": "http://www.w3.org/2002/07/owl#" - }, - "@graph": { - "@id": "kb:individual-a", - "@type": { - "@id": "http://example.org/ontology/core/MyClassA", - "@type": "owl:Class" - } - } -} -""" - -# This data is an equivalant graph with turtle_data_prefixed. -# The JSON-LD spec does not provide a grammar production rule set in -# EBNF. However, the section on compact IRIs indicates that an IRI can -# be prefixed at any point that would not result in a suffix starting -# with "//". Hence, an unpaired forward slash, as a legal character of -# an IRI, can appear in the suffix component of a compact IRI. -# https://json-ld.org/spec/latest/json-ld/#compact-iris -jsonld_data_prefixed = r""" -{ - "@context": { - "ex": "http://example.org/ontology/", - "kb": "http://example.org/kb/", - "owl": "http://www.w3.org/2002/07/owl#" - }, - "@graph": { - "@id": "kb:individual-b", - "@type": { - "@id": "ex:core/MyClassB", - "@type": "owl:Class" - } - } -} -""" query_string_expanded = r""" SELECT ?nIndividual @@ -132,66 +48,16 @@ ?nIndividual a ex:core\/MyClassB . }""" +PN_LOCAL_BACKSLASH_XFAIL_REASON = """\ + Contrary to the ratiried SPARQL 1.1. + grammar, the RDFlib SPARQL propcessor accepts backslashes as part of + PN_LOCAL which it treats as escape charachters. -def _test_parse_matches(turtle_data: str, jsonld_data: str) -> None: - """ - Confirm that the two concrete syntaxes parse to the same set of - triples. - """ - g_turtle = Graph() - g_jsonld = Graph() + There should be a way to instruct the SPARQL parser to operate in strict + mode, and in strict mode backslashes should not be permitted in PN_LOCAL. - g_turtle.parse(data=turtle_data_expanded, format="turtle") - g_jsonld.parse(data=jsonld_data_expanded, format="json-ld") - - triples_turtle: Set[Tuple[Node, Node, Node]] = { - x for x in g_turtle.triples((None, None, None)) - } - triples_jsonld: Set[Tuple[Node, Node, Node]] = { - x for x in g_jsonld.triples((None, None, None)) - } - - assert triples_turtle == triples_jsonld - - -def test_parse_matches_expanded() -> None: - _test_parse_matches(turtle_data_expanded, jsonld_data_expanded) - - -def test_parse_matches_prefixed() -> None: - _test_parse_matches(turtle_data_prefixed, jsonld_data_prefixed) - - -def _test_escapes_to_iri(graph: Graph) -> None: - """ - Confirm all triple-subjects within the two data graphs are found. - """ - expected: Set[str] = { - "http://example.org/kb/individual-a", - "http://example.org/kb/individual-b", - "http://example.org/ontology/core/MyClassA", - "http://example.org/ontology/core/MyClassB", - } - computed: Set[str] = set() - - for triple in graph.triples((None, None, None)): - computed.add(str(triple[0])) - - assert expected == computed - - -def test_escapes_to_iri_turtle() -> None: - graph = Graph() - graph.parse(data=turtle_data_expanded, format="turtle") - graph.parse(data=turtle_data_prefixed, format="turtle") - _test_escapes_to_iri(graph) - - -def test_escapes_to_iri_jsonld() -> None: - graph = Graph() - graph.parse(data=jsonld_data_expanded, format="json-ld") - graph.parse(data=jsonld_data_prefixed, format="json-ld") - _test_escapes_to_iri(graph) + See https://github.com/RDFLib/rdflib/issues/1871\ +""" def _test_query_prepares(query_string: str) -> None: @@ -215,10 +81,7 @@ def test_query_prepares_expanded() -> None: _test_query_prepares(query_string_expanded) -@pytest.mark.skipif( - not _is_version_7_started, - reason="query failure detection delayed until rdflib version 7", -) +@pytest.mark.xfail(reason=PN_LOCAL_BACKSLASH_XFAIL_REASON) def test_query_prepares_prefixed() -> None: with pytest.raises(ValueError): _test_query_prepares(query_string_prefixed) @@ -256,36 +119,22 @@ def _test_escapes_and_query( def test_escapes_and_query_turtle_expanded() -> None: - graph = Graph() - graph.parse(data=turtle_data_expanded, format="turtle") - graph.parse(data=turtle_data_prefixed, format="turtle") + graph = cached_graph((TEST_DATA_DIR / "variants/forward_slash.ttl",)) _test_escapes_and_query(graph, query_string_expanded, True) -@pytest.mark.skipif( - not _is_version_7_started, - reason="query failure detection delayed until rdflib version 7", -) +@pytest.mark.xfail(reason=PN_LOCAL_BACKSLASH_XFAIL_REASON, raises=AssertionError) def test_escapes_and_query_turtle_prefixed() -> None: - graph = Graph() - graph.parse(data=turtle_data_expanded, format="turtle") - graph.parse(data=turtle_data_prefixed, format="turtle") + graph = cached_graph((TEST_DATA_DIR / "variants/forward_slash.ttl",)) _test_escapes_and_query(graph, query_string_prefixed, False) def test_escapes_and_query_jsonld_expanded() -> None: - graph = Graph() - graph.parse(data=jsonld_data_expanded, format="json-ld") - graph.parse(data=jsonld_data_prefixed, format="json-ld") + graph = cached_graph((TEST_DATA_DIR / "variants/forward_slash.jsonld",)) _test_escapes_and_query(graph, query_string_expanded, True) -@pytest.mark.skipif( - not _is_version_7_started, - reason="query failure detection delayed until rdflib version 7", -) +@pytest.mark.xfail(reason=PN_LOCAL_BACKSLASH_XFAIL_REASON, raises=AssertionError) def test_escapes_and_query_jsonld_prefixed() -> None: - graph = Graph() - graph.parse(data=jsonld_data_expanded, format="json-ld") - graph.parse(data=jsonld_data_prefixed, format="json-ld") + graph = cached_graph((TEST_DATA_DIR / "variants/forward_slash.jsonld",)) _test_escapes_and_query(graph, query_string_prefixed, False)