Skip to content

Commit f530add

Browse files
Merge pull request #4 from casework/AC-139
Add tests for hexBinary
2 parents 01e1a97 + 4436f49 commit f530add

File tree

1 file changed

+367
-0
lines changed

1 file changed

+367
-0
lines changed

tests/hexbinary/test_hexbinary.py

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
#!/usr/bin/env python3
2+
3+
# This software was developed at the National Institute of Standards
4+
# and Technology by employees of the Federal Government in the course
5+
# of their official duties. Pursuant to title 17 Section 105 of the
6+
# United States Code this software is not subject to copyright
7+
# protection and is in the public domain. NIST assumes no
8+
# responsibility whatsoever for its use by other parties, and makes
9+
# no guarantees, expressed or implied, about its quality,
10+
# reliability, or any other characteristic.
11+
#
12+
# We would appreciate acknowledgement if the software is used.
13+
14+
"""
15+
This test suite tests some assumptions that might be made about hexBinary value comparison in Python's rdflib and its SPARQL engine.
16+
17+
This script is expected to have pytest exit in a success state, reporting some tests passing, and some tests XFailing (i.e. being expected to fail).
18+
19+
The overall finding is: in rdflib and rdflib's SPARQL engine, xsd:hexBinaryCanonical is not given any support not given to arbitrary string datatypes. This, and more specific, findings are affirmed by the tests:
20+
21+
* Some of the tests serve as syntax reminders for SPARQL and pytest.
22+
- test_sparql_syntax_bind_boolean
23+
- test_pytest_syntax_xfail
24+
- test_sparql_syntax_integer_coercion
25+
- test_sparql_syntax_integer_cast
26+
* SPARQL Literal datatype-casting can coerce known types, but will not cast strings of unknown datatypes.
27+
- test_sparql_syntax_integer_cast
28+
- test_sparql_cast_custom_type
29+
* rdflib WILL match xsd:hexBinary data as casing-insensitive. So, Literals with values "ab" and "AB" match if both have the datatype xsd:hexBinary.
30+
- test_rdflib_literal_hexbinary
31+
* rdflib WILL NOT match xsd:hexBinaryCanonical data with xsd:hexBinary data, either as Literal objects or with a call to .toPython().
32+
- test_rdflib_literal_hexbinarycanonical
33+
- test_rdflib_literal_topython_hexbinarycanonical
34+
* The rdflib SPARQL engine WILL match xsd:hexBinary data as casing-insensitive. So, "ab" and "AB" match if both have the datatype xsd:hexBinary.
35+
- test_sparql_compare_hexbinary_matchcase
36+
- test_sparql_compare_hexbinary_mixcase
37+
- test_graph_repeat
38+
- test_graph_all_hexbinary_literals
39+
* The rdflib SPARQL engine WILL match xsd:hexBinaryCanonical data with xsd:hexBinaryCanonical data, when casing matches.
40+
- test_sparql_compare_hexbinarycanonical_matchcase
41+
* The rdflib SPARQL engine WILL NOT match xsd:hexBinaryCanonical data with xsd:hexBinaryCanonical data, when casing does not match.
42+
- test_sparql_compare_hexbinarycanonical_mixcase
43+
* The rdflib SPARQL engine WILL NOT compare xsd:hexBinaryCanonical data with xsd:hexBinary data.
44+
- test_sparql_compare_hb_hbc_mixcase
45+
- test_sparql_compare_hb_hbc_mixcase_cast
46+
- test_graph_hexbinarycanonical
47+
"""
48+
49+
import logging
50+
import os
51+
52+
import pytest
53+
import rdflib.plugins.sparql
54+
55+
_logger = logging.getLogger(os.path.basename(__file__))
56+
57+
# Variables used in several tests.
58+
l_hb_lowercase = rdflib.Literal("ab", datatype=rdflib.XSD.hexBinary)
59+
l_hb_uppercase = rdflib.Literal("AB", datatype=rdflib.XSD.hexBinary)
60+
l_hbc_uppercase = rdflib.Literal("AB", datatype=rdflib.XSD.hexBinaryCanonical)
61+
n_canonical1 = rdflib.URIRef("urn:example:canonical1")
62+
n_lowercase1 = rdflib.URIRef("urn:example:lowercase1")
63+
n_lowercase2 = rdflib.URIRef("urn:example:lowercase2")
64+
n_uppercase1 = rdflib.URIRef("urn:example:uppercase1")
65+
p_predicate = rdflib.URIRef("urn:example:predicate1")
66+
67+
def test_sparql_syntax_bind_boolean():
68+
"""
69+
This test serves as a syntax reminder for binding boolean values.
70+
"""
71+
confirmed = None
72+
graph = rdflib.Graph()
73+
for result in graph.query("""\
74+
SELECT ?lValue
75+
WHERE {
76+
BIND( 1 = 1 AS ?lValue )
77+
}
78+
"""):
79+
(l_value,) = result
80+
confirmed = l_value.toPython()
81+
assert confirmed
82+
83+
@pytest.mark.xfail(reason="hard-coded failure")
84+
def test_pytest_syntax_xfail():
85+
"""
86+
This test serves as a syntax reminder for the XFail decorator.
87+
"""
88+
confirmed = None
89+
graph = rdflib.Graph()
90+
for result in graph.query("""\
91+
SELECT ?lValue
92+
WHERE {
93+
BIND( 1 = 2 AS ?lValue )
94+
}
95+
"""):
96+
(l_value,) = result
97+
confirmed = l_value.toPython()
98+
assert confirmed
99+
100+
def test_sparql_syntax_integer_coercion():
101+
"""
102+
This test serves as a syntax reminder for type coercions.
103+
"""
104+
confirmed = None
105+
graph = rdflib.Graph()
106+
for result in graph.query("""\
107+
SELECT ?lValue
108+
WHERE {
109+
BIND( 1 = "1"^^xsd:integer AS ?lValue )
110+
}
111+
"""):
112+
(l_value,) = result
113+
confirmed = l_value.toPython()
114+
assert confirmed
115+
116+
def test_sparql_syntax_integer_cast():
117+
"""
118+
This test serves as a syntax reminder for the casting form of type coercions.
119+
"""
120+
confirmed = None
121+
graph = rdflib.Graph()
122+
for result in graph.query("""\
123+
SELECT ?lValue
124+
WHERE {
125+
BIND( 1 = xsd:integer("1") AS ?lValue )
126+
}
127+
"""):
128+
(l_value,) = result
129+
confirmed = l_value.toPython()
130+
assert confirmed
131+
132+
@pytest.mark.xfail
133+
def test_sparql_cast_custom_type():
134+
"""
135+
This test checks for nonexistent literal-datatype assignments.
136+
"""
137+
confirmed = None
138+
graph = rdflib.Graph()
139+
for result in graph.query("""\
140+
SELECT ?lValue
141+
WHERE {
142+
BIND( 1 = xsd:integer("1"^^xsd:hexBinaryTypoXXXX) AS ?lValue )
143+
}
144+
"""):
145+
(l_value,) = result
146+
confirmed = l_value.toPython()
147+
assert confirmed
148+
149+
def test_sparql_compare_hexbinary_mixcase():
150+
confirmed = None
151+
graph = rdflib.Graph()
152+
for result in graph.query("""\
153+
SELECT ?lValue
154+
WHERE {
155+
BIND( "ab"^^xsd:hexBinary = "AB"^^xsd:hexBinary AS ?lValue )
156+
}
157+
"""):
158+
(l_value,) = result
159+
confirmed = l_value.toPython()
160+
assert confirmed
161+
162+
def test_sparql_compare_hexbinary_matchcase():
163+
confirmed = None
164+
graph = rdflib.Graph()
165+
for result in graph.query("""\
166+
SELECT ?lValue
167+
WHERE {
168+
BIND( "AB"^^xsd:hexBinary = "AB"^^xsd:hexBinary AS ?lValue )
169+
}
170+
"""):
171+
(l_value,) = result
172+
confirmed = l_value.toPython()
173+
assert confirmed
174+
175+
def test_sparql_compare_hexbinarycanonical_matchcase():
176+
confirmed = None
177+
graph = rdflib.Graph()
178+
for result in graph.query("""\
179+
SELECT ?lValue
180+
WHERE {
181+
BIND( "AB"^^xsd:hexBinaryCanonical = "AB"^^xsd:hexBinaryCanonical AS ?lValue )
182+
}
183+
"""):
184+
(l_value,) = result
185+
confirmed = l_value.toPython()
186+
assert confirmed
187+
188+
@pytest.mark.xfail
189+
def test_sparql_compare_hexbinarycanonical_mixcase():
190+
"""
191+
This test shows hexBinaryCanonical does not induce a casing-insensitive comparison.
192+
"""
193+
confirmed = None
194+
graph = rdflib.Graph()
195+
for result in graph.query("""\
196+
SELECT ?lValue
197+
WHERE {
198+
BIND( "ab"^^xsd:hexBinaryCanonical = "AB"^^xsd:hexBinaryCanonical AS ?lValue )
199+
}
200+
"""):
201+
(l_value,) = result
202+
confirmed = l_value.toPython()
203+
assert confirmed
204+
205+
@pytest.mark.xfail
206+
def test_sparql_compare_hb_hbc_mixcase():
207+
"""
208+
This test confirms that literal-comparison takes into account datatype when one type is unknown.
209+
"""
210+
confirmed = None
211+
graph = rdflib.Graph()
212+
for result in graph.query("""\
213+
SELECT ?lValue
214+
WHERE {
215+
BIND( "AB"^^xsd:hexBinary = "AB"^^xsd:hexBinaryCanonical AS ?lValue )
216+
}
217+
"""):
218+
(l_value,) = result
219+
confirmed = l_value.toPython()
220+
assert confirmed
221+
222+
@pytest.mark.xfail
223+
def test_sparql_compare_hb_hbc_mixcase_cast():
224+
"""
225+
This test is a bit redundant with test_sparql_cast_custom_type, but is here as an explicit demonstration of failure to cast a hexBinary value.
226+
"""
227+
confirmed = None
228+
graph = rdflib.Graph()
229+
for result in graph.query("""\
230+
SELECT ?lValue
231+
WHERE {
232+
BIND( "ab"^^xsd:hexBinary = xsd:hexBinary("AB"^^xsd:hexBinaryCanonical) AS ?lValue )
233+
}
234+
"""):
235+
(l_value,) = result
236+
confirmed = l_value.toPython()
237+
assert confirmed
238+
239+
def test_rdflib_literal_hexbinary():
240+
_logger.debug("l_hb_lowercase = %r." % l_hb_lowercase)
241+
_logger.debug("l_hb_uppercase = %r." % l_hb_uppercase)
242+
_logger.debug("l_hb_lowercase.toPython() = %r." % l_hb_lowercase.toPython())
243+
_logger.debug("l_hb_uppercase.toPython() = %r." % l_hb_uppercase.toPython())
244+
245+
assert l_hb_lowercase == l_hb_lowercase
246+
assert l_hb_lowercase.toPython() == l_hb_lowercase.toPython()
247+
248+
assert l_hb_lowercase == l_hb_uppercase
249+
assert l_hb_lowercase.toPython() == l_hb_uppercase.toPython()
250+
251+
@pytest.mark.xfail
252+
def test_rdflib_literal_hexbinarycanonical():
253+
_logger.debug("l_hb_uppercase = %r." % l_hb_uppercase)
254+
_logger.debug("l_hbc_uppercase = %r." % l_hbc_uppercase)
255+
256+
assert l_hb_uppercase == l_hbc_uppercase
257+
258+
@pytest.mark.xfail
259+
def test_rdflib_literal_topython_hexbinarycanonical():
260+
_logger.debug("l_hb_lowercase.toPython() = %r." % l_hb_lowercase.toPython())
261+
_logger.debug("l_hb_uppercase.toPython() = %r." % l_hb_uppercase.toPython())
262+
263+
assert l_hb_uppercase.toPython() == l_hbc_uppercase.toPython()
264+
265+
def _query_all_value_matches(graph):
266+
"""
267+
Return set of all node names (as strings) that have a matching value, where
268+
"matching" is determined by the SPARQL engine's type and data coercions.
269+
"""
270+
computed = set()
271+
for result in graph.query("""\
272+
SELECT ?nNode1 ?nNode2
273+
WHERE {
274+
?nNode1 ?p ?lValue .
275+
?nNode2 ?p ?lValue .
276+
FILTER ( ?nNode1 != ?nNode2 )
277+
}"""):
278+
(n_node1, n_node2) = result
279+
computed.add(n_node1.toPython())
280+
computed.add(n_node2.toPython())
281+
return computed
282+
283+
def test_graph_repeat():
284+
"""
285+
Two nodes are given the same literal value, and are found to match on literal values.
286+
"""
287+
graph = rdflib.Graph()
288+
graph.add((
289+
n_lowercase1,
290+
p_predicate,
291+
l_hb_lowercase
292+
))
293+
graph.add((
294+
n_lowercase2,
295+
p_predicate,
296+
l_hb_lowercase
297+
))
298+
expected = {
299+
"urn:example:lowercase1",
300+
"urn:example:lowercase2"
301+
}
302+
computed = _query_all_value_matches(graph)
303+
assert computed == expected
304+
305+
def test_graph_all_hexbinary_literals():
306+
"""
307+
Two nodes with the same literal value, and another node with the uppercase of the literal hexBinary value, are found to match on literal values.
308+
"""
309+
graph = rdflib.Graph()
310+
graph.add((
311+
n_lowercase1,
312+
p_predicate,
313+
l_hb_lowercase
314+
))
315+
graph.add((
316+
n_lowercase2,
317+
p_predicate,
318+
l_hb_lowercase
319+
))
320+
graph.add((
321+
n_uppercase1,
322+
p_predicate,
323+
l_hb_uppercase
324+
))
325+
326+
expected = {
327+
"urn:example:lowercase1",
328+
"urn:example:lowercase2",
329+
"urn:example:uppercase1"
330+
}
331+
332+
computed = _query_all_value_matches(graph)
333+
assert computed == expected
334+
335+
@pytest.mark.xfail
336+
def test_graph_hexbinarycanonical():
337+
graph = rdflib.Graph()
338+
graph.add((
339+
n_lowercase1,
340+
p_predicate,
341+
l_hb_lowercase
342+
))
343+
graph.add((
344+
n_lowercase2,
345+
p_predicate,
346+
l_hb_lowercase
347+
))
348+
graph.add((
349+
n_uppercase1,
350+
p_predicate,
351+
l_hb_uppercase
352+
))
353+
graph.add((
354+
n_canonical1,
355+
p_predicate,
356+
l_hbc_uppercase
357+
))
358+
359+
expected = {
360+
"urn:example:canonical1",
361+
"urn:example:lowercase1",
362+
"urn:example:lowercase2",
363+
"urn:example:uppercase1"
364+
}
365+
366+
computed = _query_all_value_matches(graph)
367+
assert computed == expected

0 commit comments

Comments
 (0)