From 44e82b919bbd2eb882248a342cfdcf1dbf85b8f5 Mon Sep 17 00:00:00 2001 From: Iwan Aucamp Date: Sat, 14 May 2022 22:53:58 +0200 Subject: [PATCH] WIP --- test/README.rst | 62 +---------- test/data/defined_namespaces/rdf.ttl | 154 +++++++++++++++++++++++++++ test/data/fetcher.py | 4 + test/utils/graph.py | 20 +++- test/utils/test/test_graph.py | 118 ++++++++++++++++++++ 5 files changed, 300 insertions(+), 58 deletions(-) create mode 100644 test/data/defined_namespaces/rdf.ttl create mode 100644 test/utils/test/test_graph.py diff --git a/test/README.rst b/test/README.rst index 74b1f0b76..d0b594965 100644 --- a/test/README.rst +++ b/test/README.rst @@ -46,60 +46,8 @@ test_conneg - test content negotiation when reading remote graphs EARL Test Reports ================= -EARL test reports can be generated using the EARL reporter plugin from ``earl.py``. - -When this plugin is enabled it will create an ``earl:Assertion`` for every test that has a ``rdf_test_uri`` parameter which can be either a string or an ``URIRef``. - -To enable the EARL reporter plugin an output file path must be supplied to pytest with ``--earl-output-file``. The report will be written to this location in turtle format. - -Some examples of generating test reports: - -.. code-block:: bash - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-jsonld-local.ttl \ - test/jsonld/test_localsuite.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-jsonld-v1.1.ttl \ - test/jsonld/test_onedotone.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-jsonld-v1.0.ttl \ - test/jsonld/test_testsuite.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-sparql.ttl \ - test/test_w3c_spec/test_sparql_w3c.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-nquads.ttl \ - test/test_w3c_spec/test_nquads_w3c.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-nt.ttl \ - test/test_w3c_spec/test_nt_w3c.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-trig.ttl \ - test/test_w3c_spec/test_trig_w3c.py - - pytest \ - --earl-assertor-homepage=http://example.com \ - --earl-assertor-name 'Example Name' \ - --earl-output-file=/var/tmp/earl/earl-turtle.ttl \ - test/test_w3c_spec/test_turtle_w3c.py +EARL test reports are generated using the EARL reporter plugin from ``test/utils/earl.py``. + +This plugin is enabled by default and writes test reports to ``test_reports/*-latest.ttl`` by default. + +For EARL reporter plugin options see the output of ``pytest --help``. diff --git a/test/data/defined_namespaces/rdf.ttl b/test/data/defined_namespaces/rdf.ttl new file mode 100644 index 000000000..150dfe475 --- /dev/null +++ b/test/data/defined_namespaces/rdf.ttl @@ -0,0 +1,154 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix dc: . + + a owl:Ontology ; + dc:title "The RDF Concepts Vocabulary (RDF)" ; + dc:date "2019-12-16" ; + dc:description "This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts." . + +rdf:HTML a rdfs:Datatype ; + rdfs:subClassOf rdfs:Literal ; + rdfs:isDefinedBy ; + rdfs:seeAlso ; + rdfs:label "HTML" ; + rdfs:comment "The datatype of RDF literals storing fragments of HTML content" . + +rdf:langString a rdfs:Datatype ; + rdfs:subClassOf rdfs:Literal ; + rdfs:isDefinedBy ; + rdfs:seeAlso ; + rdfs:label "langString" ; + rdfs:comment "The datatype of language-tagged string values" . + +rdf:PlainLiteral a rdfs:Datatype ; + rdfs:isDefinedBy ; + rdfs:subClassOf rdfs:Literal ; + rdfs:seeAlso ; + rdfs:label "PlainLiteral" ; + rdfs:comment "The class of plain (i.e. untyped) literal values, as used in RIF and OWL 2" . + +rdf:type a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "type" ; + rdfs:comment "The subject is an instance of a class." ; + rdfs:range rdfs:Class ; + rdfs:domain rdfs:Resource . + +rdf:Property a rdfs:Class ; + rdfs:isDefinedBy ; + rdfs:label "Property" ; + rdfs:comment "The class of RDF properties." ; + rdfs:subClassOf rdfs:Resource . + +rdf:Statement a rdfs:Class ; + rdfs:isDefinedBy ; + rdfs:label "Statement" ; + rdfs:subClassOf rdfs:Resource ; + rdfs:comment "The class of RDF statements." . + +rdf:subject a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "subject" ; + rdfs:comment "The subject of the subject RDF statement." ; + rdfs:domain rdf:Statement ; + rdfs:range rdfs:Resource . + +rdf:predicate a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "predicate" ; + rdfs:comment "The predicate of the subject RDF statement." ; + rdfs:domain rdf:Statement ; + rdfs:range rdfs:Resource . + +rdf:object a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "object" ; + rdfs:comment "The object of the subject RDF statement." ; + rdfs:domain rdf:Statement ; + rdfs:range rdfs:Resource . + +rdf:Bag a rdfs:Class ; + rdfs:isDefinedBy ; + rdfs:label "Bag" ; + rdfs:comment "The class of unordered containers." ; + rdfs:subClassOf rdfs:Container . + +rdf:Seq a rdfs:Class ; + rdfs:isDefinedBy ; + rdfs:label "Seq" ; + rdfs:comment "The class of ordered containers." ; + rdfs:subClassOf rdfs:Container . + +rdf:Alt a rdfs:Class ; + rdfs:isDefinedBy ; + rdfs:label "Alt" ; + rdfs:comment "The class of containers of alternatives." ; + rdfs:subClassOf rdfs:Container . + +rdf:value a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "value" ; + rdfs:comment "Idiomatic property used for structured values." ; + rdfs:domain rdfs:Resource ; + rdfs:range rdfs:Resource . + +rdf:List a rdfs:Class ; + rdfs:isDefinedBy ; + rdfs:label "List" ; + rdfs:comment "The class of RDF Lists." ; + rdfs:subClassOf rdfs:Resource . + +rdf:nil a rdf:List ; + rdfs:isDefinedBy ; + rdfs:label "nil" ; + rdfs:comment "The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it." . + +rdf:first a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "first" ; + rdfs:comment "The first item in the subject RDF list." ; + rdfs:domain rdf:List ; + rdfs:range rdfs:Resource . + +rdf:rest a rdf:Property ; + rdfs:isDefinedBy ; + rdfs:label "rest" ; + rdfs:comment "The rest of the subject RDF list after the first item." ; + rdfs:domain rdf:List ; + rdfs:range rdf:List . + +rdf:XMLLiteral a rdfs:Datatype ; + rdfs:subClassOf rdfs:Literal ; + rdfs:isDefinedBy ; + rdfs:label "XMLLiteral" ; + rdfs:comment "The datatype of XML literal values." . + +rdf:JSON a rdfs:Datatype ; + rdfs:label "JSON" ; + rdfs:comment "The datatype of RDF literals storing JSON content." ; + rdfs:subClassOf rdfs:Literal ; + rdfs:isDefinedBy ; + rdfs:seeAlso . + +rdf:CompoundLiteral a rdfs:Class ; + rdfs:label "CompoundLiteral" ; + rdfs:comment "A class representing a compound literal." ; + rdfs:subClassOf rdfs:Resource ; + rdfs:isDefinedBy ; + rdfs:seeAlso . + +rdf:language a rdf:Property ; + rdfs:label "language" ; + rdfs:comment "The language component of a CompoundLiteral." ; + rdfs:domain rdf:CompoundLiteral ; + rdfs:isDefinedBy ; + rdfs:seeAlso . + +rdf:direction a rdf:Property ; + rdfs:label "direction" ; + rdfs:comment "The base direction component of a CompoundLiteral." ; + rdfs:domain rdf:CompoundLiteral ; + rdfs:isDefinedBy ; + rdfs:seeAlso . diff --git a/test/data/fetcher.py b/test/data/fetcher.py index bd7a7171a..f6de0e807 100755 --- a/test/data/fetcher.py +++ b/test/data/fetcher.py @@ -260,6 +260,10 @@ def _member_io( remote=Request("https://www.w3.org/2001/sw/DataAccess/tests/test-dawg#"), local_path=(DATA_PATH / "defined_namespaces/dawgt.ttl"), ), + FileResource( + remote=Request("https://www.w3.org/1999/02/22-rdf-syntax-ns#"), + local_path=(DATA_PATH / "defined_namespaces/rdf.ttl"), + ), FileResource( remote=Request("https://www.w3.org/2001/sw/DataAccess/tests/test-query#"), local_path=(DATA_PATH / "defined_namespaces/qt.ttl"), diff --git a/test/utils/graph.py b/test/utils/graph.py index 42f62a189..f31729424 100644 --- a/test/utils/graph.py +++ b/test/utils/graph.py @@ -2,9 +2,11 @@ from dataclasses import dataclass from functools import lru_cache from pathlib import Path -from typing import Optional, Tuple, Union +from typing import Optional, Set, Tuple, Union from rdflib.graph import Graph +from rdflib.namespace import RDFS +from rdflib.term import IdentifiedNode, URIRef from rdflib.util import guess_format GraphSourceType = Union["GraphSource", Path] @@ -70,3 +72,19 @@ def cached_graph( sources: Tuple[Union[GraphSource, Path], ...], public_id: Optional[str] = None ) -> Graph: return load_sources(*sources, public_id=public_id) + + +def subclasses_of(graph: Graph, node: IdentifiedNode) -> Set[IdentifiedNode]: + return set(graph.transitive_subjects(RDFS.subClassOf, node)) + + +def superclasses_of(graph: Graph, node: IdentifiedNode) -> Set[IdentifiedNode]: + return set(graph.transitive_objects(node, RDFS.subClassOf)) + + +def is_subclass_of(graph: Graph, node: IdentifiedNode, cls: URIRef) -> bool: + return cls in subclasses_of(graph, node) + + +def is_superclass_of(graph: Graph, node: IdentifiedNode, cls: URIRef) -> bool: + return cls in superclasses_of(graph, node) diff --git a/test/utils/test/test_graph.py b/test/utils/test/test_graph.py new file mode 100644 index 000000000..90071d88d --- /dev/null +++ b/test/utils/test/test_graph.py @@ -0,0 +1,118 @@ +import logging +from contextlib import ExitStack +from pathlib import Path +from test.data import TEST_DATA_DIR +from test.utils.graph import cached_graph, subclasses_of, superclasses_of +from test.utils.namespace import RDFT +from typing import Set, Tuple, Type, Union + +import pytest +from pyparsing import Optional + +from rdflib.namespace import RDFS +from rdflib.term import IdentifiedNode + +RDFT_GRAPHS = ( + TEST_DATA_DIR / "defined_namespaces/rdftest.ttl", + TEST_DATA_DIR / "defined_namespaces/rdfs.ttl", +) + + +@pytest.mark.parametrize( + [ + "graph_sources", + "node", + "expected_result", + ], + [ + ( + RDFT_GRAPHS, + RDFS.Resource, + {RDFS.Resource, RDFS.Class, RDFS.Datatype, RDFS.Container, RDFS.Literal}, + ), + (RDFT_GRAPHS, RDFS.Class, {RDFS.Class, RDFS.Datatype}), + ( + RDFT_GRAPHS, + RDFT.Test, + { + RDFT.Test, + RDFT.TestEval, + RDFT.TestNQuadsNegativeSyntax, + RDFT.TestNQuadsPositiveSyntax, + RDFT.TestNTriplesNegativeSyntax, + RDFT.TestNTriplesPositiveSyntax, + RDFT.TestSyntax, + RDFT.TestTriGNegativeSyntax, + RDFT.TestTriGPositiveSyntax, + RDFT.TestTrigNegativeEval, + RDFT.TestTurtleEval, + RDFT.TestTurtleNegativeEval, + RDFT.TestTurtleNegativeSyntax, + RDFT.TestTurtlePositiveSyntax, + RDFT.XMLEval, + }, + ), + ], +) +def test_graph_subclasses_of( + graph_sources: Tuple[Path, ...], + node: IdentifiedNode, + expected_result: Union[Set[IdentifiedNode], Type[Exception]], +) -> None: + + catcher: Optional[pytest.ExceptionInfo[Exception]] = None + + graph = cached_graph(graph_sources) + + with ExitStack() as xstack: + if isinstance(expected_result, type) and issubclass(expected_result, Exception): + catcher = xstack.enter_context(pytest.raises(expected_result)) + result = subclasses_of(graph, node) + logging.debug("result = %s", result) + if catcher is not None: + assert catcher is not None + assert catcher.value is not None + else: + assert expected_result == result + + +@pytest.mark.parametrize( + [ + "graph_sources", + "node", + "expected_result", + ], + [ + (RDFT_GRAPHS, RDFS.Class, {RDFS.Class, RDFS.Resource}), + (RDFT_GRAPHS, RDFS.Literal, {RDFS.Literal, RDFS.Resource}), + ( + RDFT_GRAPHS, + RDFT.TestTurtleNegativeSyntax, + { + RDFT.Test, + RDFT.TestSyntax, + RDFT.TestTurtleNegativeSyntax, + }, + ), + ], +) +def test_graph_superclasses_of( + graph_sources: Tuple[Path, ...], + node: IdentifiedNode, + expected_result: Union[Set[IdentifiedNode], Type[Exception]], +) -> None: + + catcher: Optional[pytest.ExceptionInfo[Exception]] = None + + graph = cached_graph(graph_sources) + + with ExitStack() as xstack: + if isinstance(expected_result, type) and issubclass(expected_result, Exception): + catcher = xstack.enter_context(pytest.raises(expected_result)) + result = superclasses_of(graph, node) + logging.debug("result = %s", result) + if catcher is not None: + assert catcher is not None + assert catcher.value is not None + else: + assert expected_result == result