Skip to content

Commit 44e82b9

Browse files
committed
WIP
1 parent 4f96f46 commit 44e82b9

File tree

5 files changed

+300
-58
lines changed

5 files changed

+300
-58
lines changed

test/README.rst

Lines changed: 5 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -46,60 +46,8 @@ test_conneg - test content negotiation when reading remote graphs
4646
EARL Test Reports
4747
=================
4848

49-
EARL test reports can be generated using the EARL reporter plugin from ``earl.py``.
50-
51-
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``.
52-
53-
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.
54-
55-
Some examples of generating test reports:
56-
57-
.. code-block:: bash
58-
59-
pytest \
60-
--earl-assertor-homepage=http://example.com \
61-
--earl-assertor-name 'Example Name' \
62-
--earl-output-file=/var/tmp/earl/earl-jsonld-local.ttl \
63-
test/jsonld/test_localsuite.py
64-
65-
pytest \
66-
--earl-assertor-homepage=http://example.com \
67-
--earl-assertor-name 'Example Name' \
68-
--earl-output-file=/var/tmp/earl/earl-jsonld-v1.1.ttl \
69-
test/jsonld/test_onedotone.py
70-
71-
pytest \
72-
--earl-assertor-homepage=http://example.com \
73-
--earl-assertor-name 'Example Name' \
74-
--earl-output-file=/var/tmp/earl/earl-jsonld-v1.0.ttl \
75-
test/jsonld/test_testsuite.py
76-
77-
pytest \
78-
--earl-assertor-homepage=http://example.com \
79-
--earl-assertor-name 'Example Name' \
80-
--earl-output-file=/var/tmp/earl/earl-sparql.ttl \
81-
test/test_w3c_spec/test_sparql_w3c.py
82-
83-
pytest \
84-
--earl-assertor-homepage=http://example.com \
85-
--earl-assertor-name 'Example Name' \
86-
--earl-output-file=/var/tmp/earl/earl-nquads.ttl \
87-
test/test_w3c_spec/test_nquads_w3c.py
88-
89-
pytest \
90-
--earl-assertor-homepage=http://example.com \
91-
--earl-assertor-name 'Example Name' \
92-
--earl-output-file=/var/tmp/earl/earl-nt.ttl \
93-
test/test_w3c_spec/test_nt_w3c.py
94-
95-
pytest \
96-
--earl-assertor-homepage=http://example.com \
97-
--earl-assertor-name 'Example Name' \
98-
--earl-output-file=/var/tmp/earl/earl-trig.ttl \
99-
test/test_w3c_spec/test_trig_w3c.py
100-
101-
pytest \
102-
--earl-assertor-homepage=http://example.com \
103-
--earl-assertor-name 'Example Name' \
104-
--earl-output-file=/var/tmp/earl/earl-turtle.ttl \
105-
test/test_w3c_spec/test_turtle_w3c.py
49+
EARL test reports are generated using the EARL reporter plugin from ``test/utils/earl.py``.
50+
51+
This plugin is enabled by default and writes test reports to ``test_reports/*-latest.ttl`` by default.
52+
53+
For EARL reporter plugin options see the output of ``pytest --help``.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
2+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
3+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
4+
@prefix dc: <http://purl.org/dc/elements/1.1/> .
5+
6+
<http://www.w3.org/1999/02/22-rdf-syntax-ns#> a owl:Ontology ;
7+
dc:title "The RDF Concepts Vocabulary (RDF)" ;
8+
dc:date "2019-12-16" ;
9+
dc:description "This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts." .
10+
11+
rdf:HTML a rdfs:Datatype ;
12+
rdfs:subClassOf rdfs:Literal ;
13+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
14+
rdfs:seeAlso <http://www.w3.org/TR/rdf11-concepts/#section-html> ;
15+
rdfs:label "HTML" ;
16+
rdfs:comment "The datatype of RDF literals storing fragments of HTML content" .
17+
18+
rdf:langString a rdfs:Datatype ;
19+
rdfs:subClassOf rdfs:Literal ;
20+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
21+
rdfs:seeAlso <http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal> ;
22+
rdfs:label "langString" ;
23+
rdfs:comment "The datatype of language-tagged string values" .
24+
25+
rdf:PlainLiteral a rdfs:Datatype ;
26+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
27+
rdfs:subClassOf rdfs:Literal ;
28+
rdfs:seeAlso <http://www.w3.org/TR/rdf-plain-literal/> ;
29+
rdfs:label "PlainLiteral" ;
30+
rdfs:comment "The class of plain (i.e. untyped) literal values, as used in RIF and OWL 2" .
31+
32+
rdf:type a rdf:Property ;
33+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
34+
rdfs:label "type" ;
35+
rdfs:comment "The subject is an instance of a class." ;
36+
rdfs:range rdfs:Class ;
37+
rdfs:domain rdfs:Resource .
38+
39+
rdf:Property a rdfs:Class ;
40+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
41+
rdfs:label "Property" ;
42+
rdfs:comment "The class of RDF properties." ;
43+
rdfs:subClassOf rdfs:Resource .
44+
45+
rdf:Statement a rdfs:Class ;
46+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
47+
rdfs:label "Statement" ;
48+
rdfs:subClassOf rdfs:Resource ;
49+
rdfs:comment "The class of RDF statements." .
50+
51+
rdf:subject a rdf:Property ;
52+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
53+
rdfs:label "subject" ;
54+
rdfs:comment "The subject of the subject RDF statement." ;
55+
rdfs:domain rdf:Statement ;
56+
rdfs:range rdfs:Resource .
57+
58+
rdf:predicate a rdf:Property ;
59+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
60+
rdfs:label "predicate" ;
61+
rdfs:comment "The predicate of the subject RDF statement." ;
62+
rdfs:domain rdf:Statement ;
63+
rdfs:range rdfs:Resource .
64+
65+
rdf:object a rdf:Property ;
66+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
67+
rdfs:label "object" ;
68+
rdfs:comment "The object of the subject RDF statement." ;
69+
rdfs:domain rdf:Statement ;
70+
rdfs:range rdfs:Resource .
71+
72+
rdf:Bag a rdfs:Class ;
73+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
74+
rdfs:label "Bag" ;
75+
rdfs:comment "The class of unordered containers." ;
76+
rdfs:subClassOf rdfs:Container .
77+
78+
rdf:Seq a rdfs:Class ;
79+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
80+
rdfs:label "Seq" ;
81+
rdfs:comment "The class of ordered containers." ;
82+
rdfs:subClassOf rdfs:Container .
83+
84+
rdf:Alt a rdfs:Class ;
85+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
86+
rdfs:label "Alt" ;
87+
rdfs:comment "The class of containers of alternatives." ;
88+
rdfs:subClassOf rdfs:Container .
89+
90+
rdf:value a rdf:Property ;
91+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
92+
rdfs:label "value" ;
93+
rdfs:comment "Idiomatic property used for structured values." ;
94+
rdfs:domain rdfs:Resource ;
95+
rdfs:range rdfs:Resource .
96+
97+
rdf:List a rdfs:Class ;
98+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
99+
rdfs:label "List" ;
100+
rdfs:comment "The class of RDF Lists." ;
101+
rdfs:subClassOf rdfs:Resource .
102+
103+
rdf:nil a rdf:List ;
104+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
105+
rdfs:label "nil" ;
106+
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." .
107+
108+
rdf:first a rdf:Property ;
109+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
110+
rdfs:label "first" ;
111+
rdfs:comment "The first item in the subject RDF list." ;
112+
rdfs:domain rdf:List ;
113+
rdfs:range rdfs:Resource .
114+
115+
rdf:rest a rdf:Property ;
116+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
117+
rdfs:label "rest" ;
118+
rdfs:comment "The rest of the subject RDF list after the first item." ;
119+
rdfs:domain rdf:List ;
120+
rdfs:range rdf:List .
121+
122+
rdf:XMLLiteral a rdfs:Datatype ;
123+
rdfs:subClassOf rdfs:Literal ;
124+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
125+
rdfs:label "XMLLiteral" ;
126+
rdfs:comment "The datatype of XML literal values." .
127+
128+
rdf:JSON a rdfs:Datatype ;
129+
rdfs:label "JSON" ;
130+
rdfs:comment "The datatype of RDF literals storing JSON content." ;
131+
rdfs:subClassOf rdfs:Literal ;
132+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
133+
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-json-datatype> .
134+
135+
rdf:CompoundLiteral a rdfs:Class ;
136+
rdfs:label "CompoundLiteral" ;
137+
rdfs:comment "A class representing a compound literal." ;
138+
rdfs:subClassOf rdfs:Resource ;
139+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
140+
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-compoundliteral-class-and-the-rdf-language-and-rdf-direction-properties> .
141+
142+
rdf:language a rdf:Property ;
143+
rdfs:label "language" ;
144+
rdfs:comment "The language component of a CompoundLiteral." ;
145+
rdfs:domain rdf:CompoundLiteral ;
146+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
147+
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-compoundliteral-class-and-the-rdf-language-and-rdf-direction-properties> .
148+
149+
rdf:direction a rdf:Property ;
150+
rdfs:label "direction" ;
151+
rdfs:comment "The base direction component of a CompoundLiteral." ;
152+
rdfs:domain rdf:CompoundLiteral ;
153+
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
154+
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-compoundliteral-class-and-the-rdf-language-and-rdf-direction-properties> .

test/data/fetcher.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ def _member_io(
260260
remote=Request("https://www.w3.org/2001/sw/DataAccess/tests/test-dawg#"),
261261
local_path=(DATA_PATH / "defined_namespaces/dawgt.ttl"),
262262
),
263+
FileResource(
264+
remote=Request("https://www.w3.org/1999/02/22-rdf-syntax-ns#"),
265+
local_path=(DATA_PATH / "defined_namespaces/rdf.ttl"),
266+
),
263267
FileResource(
264268
remote=Request("https://www.w3.org/2001/sw/DataAccess/tests/test-query#"),
265269
local_path=(DATA_PATH / "defined_namespaces/qt.ttl"),

test/utils/graph.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
from dataclasses import dataclass
33
from functools import lru_cache
44
from pathlib import Path
5-
from typing import Optional, Tuple, Union
5+
from typing import Optional, Set, Tuple, Union
66

77
from rdflib.graph import Graph
8+
from rdflib.namespace import RDFS
9+
from rdflib.term import IdentifiedNode, URIRef
810
from rdflib.util import guess_format
911

1012
GraphSourceType = Union["GraphSource", Path]
@@ -70,3 +72,19 @@ def cached_graph(
7072
sources: Tuple[Union[GraphSource, Path], ...], public_id: Optional[str] = None
7173
) -> Graph:
7274
return load_sources(*sources, public_id=public_id)
75+
76+
77+
def subclasses_of(graph: Graph, node: IdentifiedNode) -> Set[IdentifiedNode]:
78+
return set(graph.transitive_subjects(RDFS.subClassOf, node))
79+
80+
81+
def superclasses_of(graph: Graph, node: IdentifiedNode) -> Set[IdentifiedNode]:
82+
return set(graph.transitive_objects(node, RDFS.subClassOf))
83+
84+
85+
def is_subclass_of(graph: Graph, node: IdentifiedNode, cls: URIRef) -> bool:
86+
return cls in subclasses_of(graph, node)
87+
88+
89+
def is_superclass_of(graph: Graph, node: IdentifiedNode, cls: URIRef) -> bool:
90+
return cls in superclasses_of(graph, node)

test/utils/test/test_graph.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import logging
2+
from contextlib import ExitStack
3+
from pathlib import Path
4+
from test.data import TEST_DATA_DIR
5+
from test.utils.graph import cached_graph, subclasses_of, superclasses_of
6+
from test.utils.namespace import RDFT
7+
from typing import Set, Tuple, Type, Union
8+
9+
import pytest
10+
from pyparsing import Optional
11+
12+
from rdflib.namespace import RDFS
13+
from rdflib.term import IdentifiedNode
14+
15+
RDFT_GRAPHS = (
16+
TEST_DATA_DIR / "defined_namespaces/rdftest.ttl",
17+
TEST_DATA_DIR / "defined_namespaces/rdfs.ttl",
18+
)
19+
20+
21+
@pytest.mark.parametrize(
22+
[
23+
"graph_sources",
24+
"node",
25+
"expected_result",
26+
],
27+
[
28+
(
29+
RDFT_GRAPHS,
30+
RDFS.Resource,
31+
{RDFS.Resource, RDFS.Class, RDFS.Datatype, RDFS.Container, RDFS.Literal},
32+
),
33+
(RDFT_GRAPHS, RDFS.Class, {RDFS.Class, RDFS.Datatype}),
34+
(
35+
RDFT_GRAPHS,
36+
RDFT.Test,
37+
{
38+
RDFT.Test,
39+
RDFT.TestEval,
40+
RDFT.TestNQuadsNegativeSyntax,
41+
RDFT.TestNQuadsPositiveSyntax,
42+
RDFT.TestNTriplesNegativeSyntax,
43+
RDFT.TestNTriplesPositiveSyntax,
44+
RDFT.TestSyntax,
45+
RDFT.TestTriGNegativeSyntax,
46+
RDFT.TestTriGPositiveSyntax,
47+
RDFT.TestTrigNegativeEval,
48+
RDFT.TestTurtleEval,
49+
RDFT.TestTurtleNegativeEval,
50+
RDFT.TestTurtleNegativeSyntax,
51+
RDFT.TestTurtlePositiveSyntax,
52+
RDFT.XMLEval,
53+
},
54+
),
55+
],
56+
)
57+
def test_graph_subclasses_of(
58+
graph_sources: Tuple[Path, ...],
59+
node: IdentifiedNode,
60+
expected_result: Union[Set[IdentifiedNode], Type[Exception]],
61+
) -> None:
62+
63+
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
64+
65+
graph = cached_graph(graph_sources)
66+
67+
with ExitStack() as xstack:
68+
if isinstance(expected_result, type) and issubclass(expected_result, Exception):
69+
catcher = xstack.enter_context(pytest.raises(expected_result))
70+
result = subclasses_of(graph, node)
71+
logging.debug("result = %s", result)
72+
if catcher is not None:
73+
assert catcher is not None
74+
assert catcher.value is not None
75+
else:
76+
assert expected_result == result
77+
78+
79+
@pytest.mark.parametrize(
80+
[
81+
"graph_sources",
82+
"node",
83+
"expected_result",
84+
],
85+
[
86+
(RDFT_GRAPHS, RDFS.Class, {RDFS.Class, RDFS.Resource}),
87+
(RDFT_GRAPHS, RDFS.Literal, {RDFS.Literal, RDFS.Resource}),
88+
(
89+
RDFT_GRAPHS,
90+
RDFT.TestTurtleNegativeSyntax,
91+
{
92+
RDFT.Test,
93+
RDFT.TestSyntax,
94+
RDFT.TestTurtleNegativeSyntax,
95+
},
96+
),
97+
],
98+
)
99+
def test_graph_superclasses_of(
100+
graph_sources: Tuple[Path, ...],
101+
node: IdentifiedNode,
102+
expected_result: Union[Set[IdentifiedNode], Type[Exception]],
103+
) -> None:
104+
105+
catcher: Optional[pytest.ExceptionInfo[Exception]] = None
106+
107+
graph = cached_graph(graph_sources)
108+
109+
with ExitStack() as xstack:
110+
if isinstance(expected_result, type) and issubclass(expected_result, Exception):
111+
catcher = xstack.enter_context(pytest.raises(expected_result))
112+
result = superclasses_of(graph, node)
113+
logging.debug("result = %s", result)
114+
if catcher is not None:
115+
assert catcher is not None
116+
assert catcher.value is not None
117+
else:
118+
assert expected_result == result

0 commit comments

Comments
 (0)