mustrd 0.2.6.1__py3-none-any.whl → 0.2.7a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mustrd/README.adoc +33 -0
- mustrd/anzo_utils.py +121 -0
- mustrd/logger_setup.py +4 -0
- mustrd/model/triplestoreOntology.ttl +0 -8
- mustrd/model/triplestoreshapes.ttl +0 -3
- mustrd/mustrd.py +340 -204
- mustrd/mustrdAnzo.py +55 -130
- mustrd/mustrdGraphDb.py +3 -3
- mustrd/mustrdTestPlugin.py +137 -93
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.7a0.dist-info}/METADATA +7 -8
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.7a0.dist-info}/RECORD +14 -13
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.7a0.dist-info}/WHEEL +1 -1
- mustrd/test/test_mustrd.py +0 -5
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.7a0.dist-info}/LICENSE +0 -0
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.7a0.dist-info}/entry_points.txt +0 -0
mustrd/mustrdAnzo.py
CHANGED
@@ -22,51 +22,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
SOFTWARE.
|
23
23
|
"""
|
24
24
|
|
25
|
-
import requests
|
26
|
-
from pyanzo import AnzoClient
|
27
25
|
from rdflib import Graph, ConjunctiveGraph, Literal, URIRef
|
28
|
-
from requests import ConnectTimeout,
|
29
|
-
from bs4 import BeautifulSoup
|
26
|
+
from requests import ConnectTimeout, HTTPError, ConnectionError
|
30
27
|
import logging
|
31
|
-
from .
|
32
|
-
|
33
|
-
|
34
|
-
# https://github.com/Semantic-partners/mustrd/issues/73
|
35
|
-
def manage_anzo_response(response: Response) -> str:
|
36
|
-
content_string = response.content.decode("utf-8")
|
37
|
-
if response.status_code == 200:
|
38
|
-
return content_string
|
39
|
-
elif response.status_code == 403:
|
40
|
-
html = BeautifulSoup(content_string, 'html.parser')
|
41
|
-
title_tag = html.title.string
|
42
|
-
raise HTTPError(f"Anzo authentication error, status code: {response.status_code}, content: {title_tag}")
|
43
|
-
else:
|
44
|
-
raise RequestException(f"Anzo error, status code: {response.status_code}, content: {content_string}")
|
45
|
-
|
46
|
-
|
47
|
-
def query_with_bindings(bindings: dict, when: str) -> str:
|
48
|
-
values = ""
|
49
|
-
for key, value in bindings.items():
|
50
|
-
values += f"VALUES ?{key} {{{value.n3()}}} "
|
51
|
-
split_query = when.lower().split("where {", 1)
|
52
|
-
return f"{split_query[0].strip()} WHERE {{ {values} {split_query[1].strip()}"
|
28
|
+
from mustrd.anzo_utils import query_azg, query_graphmart
|
29
|
+
from mustrd.anzo_utils import query_configuration, json_to_dictlist, ttl_to_graph
|
53
30
|
|
54
31
|
|
55
32
|
def execute_select(triple_store: dict, when: str, bindings: dict = None) -> str:
|
56
33
|
try:
|
57
34
|
if bindings:
|
58
35
|
when = query_with_bindings(bindings, when)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
return manage_anzo_response(requests.post(url=url,
|
67
|
-
auth=(triple_store['username'], triple_store['password']),
|
68
|
-
data=data,
|
69
|
-
verify=False))
|
36
|
+
# FIXME: why do we have those tokens in a select query? in particular ${targetGraph}?
|
37
|
+
# FIXME: why do we also query the output graph?
|
38
|
+
when = when.replace("${fromSources}",
|
39
|
+
f"FROM <{triple_store['input_graph']}>\nFROM <{triple_store['output_graph']}>").replace(
|
40
|
+
"${targetGraph}", f"<{triple_store['output_graph']}>")
|
41
|
+
# TODO: manage results here
|
42
|
+
return query_azg(anzo_config=triple_store, query=when)
|
70
43
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout):
|
71
44
|
raise
|
72
45
|
|
@@ -76,32 +49,20 @@ def execute_update(triple_store: dict, when: str, bindings: dict = None) -> Grap
|
|
76
49
|
input_graph = triple_store['input_graph']
|
77
50
|
output_graph = triple_store['output_graph']
|
78
51
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
auth=(triple_store['username'],
|
90
|
-
triple_store['password']),
|
91
|
-
data=data,
|
92
|
-
verify=False))
|
52
|
+
# FIXME: that will only work with steps.
|
53
|
+
# We could replace USING clauses with using-graph-uri parameter
|
54
|
+
# But there is no parameter for default insert graphs.
|
55
|
+
substituted_query = when.replace("${usingSources}",
|
56
|
+
f"""USING <{input_graph}>
|
57
|
+
USING <{triple_store['output_graph']}>""").replace(
|
58
|
+
"${targetGraph}", f"<{output_graph}>")
|
59
|
+
|
60
|
+
response = query_azg(anzo_config=triple_store, query=substituted_query, is_update=True,
|
61
|
+
data_layers=input_graph, format="ttl")
|
93
62
|
logging.debug(f'response {response}')
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
'skipCache': 'true'}
|
98
|
-
everything_response = manage_anzo_response(requests.post(url=url,
|
99
|
-
auth=(triple_store['username'],
|
100
|
-
triple_store['password']),
|
101
|
-
data=check_data,
|
102
|
-
verify=False))
|
103
|
-
# todo deal with error responses
|
104
|
-
new_graph = Graph().parse(data=everything_response)
|
63
|
+
# TODO: deal with error responses
|
64
|
+
new_graph = ttl_to_graph(query_azg(anzo_config=triple_store, query="construct {?s ?p ?o} { ?s ?p ?o }",
|
65
|
+
format="ttl", data_layers=output_graph))
|
105
66
|
logging.debug(f"new_graph={new_graph.serialize(format='ttl')}")
|
106
67
|
return new_graph
|
107
68
|
|
@@ -110,35 +71,27 @@ def execute_construct(triple_store: dict, when: str, bindings: dict = None) -> G
|
|
110
71
|
try:
|
111
72
|
if bindings:
|
112
73
|
when = query_with_bindings(bindings, when)
|
113
|
-
|
114
|
-
|
115
|
-
'named-graph-uri': triple_store['input_graph'],
|
116
|
-
'skipCache': 'true'}
|
117
|
-
url = f"https://{triple_store['url']}:{triple_store['port']}/sparql?format=ttl"
|
118
|
-
response = requests.post(url=url,
|
119
|
-
auth=(triple_store['username'],
|
120
|
-
triple_store['password']),
|
121
|
-
data=data,
|
122
|
-
verify=False)
|
123
|
-
logging.debug(f'response {response}')
|
124
|
-
g = Graph().parse(data=manage_anzo_response(response))
|
125
|
-
logging.debug(f"Actual Result = {g.serialize(format='ttl')}")
|
126
|
-
return g
|
74
|
+
return ttl_to_graph(query_azg(anzo_config=triple_store, query=when, format="ttl",
|
75
|
+
data_layers=triple_store['input_graph']))
|
127
76
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout) as e:
|
128
77
|
logging.error(f'response {e}')
|
129
78
|
raise
|
130
79
|
|
131
80
|
|
81
|
+
def query_with_bindings(bindings: dict, when: str) -> str:
|
82
|
+
values = ""
|
83
|
+
for key, value in bindings.items():
|
84
|
+
values += f"VALUES ?{key} {{{value.n3()}}} "
|
85
|
+
split_query = when.lower().split("where {", 1)
|
86
|
+
return f"{split_query[0].strip()} WHERE {{ {values} {split_query[1].strip()}"
|
87
|
+
|
88
|
+
|
132
89
|
# Get Given or then from the content of a graphmart
|
133
90
|
def get_spec_component_from_graphmart(triple_store: dict, graphmart: URIRef, layer: URIRef = None) -> ConjunctiveGraph:
|
134
91
|
try:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
return anzo_client.query_graphmart(graphmart=graphmart,
|
139
|
-
data_layers=layer,
|
140
|
-
query_string="CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}",
|
141
|
-
skip_cache=True).as_quad_store().as_rdflib_graph()
|
92
|
+
return ttl_to_graph(query_graphmart(anzo_config=triple_store, graphmart=graphmart,
|
93
|
+
query="CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}",
|
94
|
+
data_layers=layer, format="ttl"))
|
142
95
|
except RuntimeError as e:
|
143
96
|
raise ConnectionError(f"Anzo connection error, {e}")
|
144
97
|
|
@@ -153,46 +106,33 @@ def get_query_from_querybuilder(triple_store: dict, folder_name: Literal, query_
|
|
153
106
|
?queryFolder a <http://www.cambridgesemantics.com/ontologies/QueryPlayground#QueryFolder>;
|
154
107
|
<http://purl.org/dc/elements/1.1/title> "{folder_name}"
|
155
108
|
}}"""
|
156
|
-
|
157
|
-
|
158
|
-
password=triple_store['password'])
|
159
|
-
|
160
|
-
result = anzo_client.query_journal(query_string=query).as_table_results().as_record_dictionaries()
|
109
|
+
result = json_to_dictlist(query_configuration(
|
110
|
+
anzo_config=triple_store, query=query))
|
161
111
|
if len(result) == 0:
|
162
|
-
raise FileNotFoundError(
|
112
|
+
raise FileNotFoundError(
|
113
|
+
f"Query {query_name} not found in folder {folder_name}")
|
163
114
|
return result[0].get("query")
|
164
115
|
|
165
116
|
|
166
117
|
# https://github.com/Semantic-partners/mustrd/issues/102
|
167
118
|
def get_query_from_step(triple_store: dict, query_step_uri: URIRef) -> str:
|
168
|
-
query = f"""SELECT ?
|
119
|
+
query = f"""SELECT ?query WHERE {{
|
169
120
|
BIND(<{query_step_uri}> as ?stepUri)
|
170
121
|
?stepUri a <http://cambridgesemantics.com/ontologies/Graphmarts#Step>;
|
171
122
|
<http://cambridgesemantics.com/ontologies/Graphmarts#transformQuery> ?query
|
172
|
-
}}
|
173
|
-
|
174
|
-
anzo_client = AnzoClient(triple_store['url'], triple_store['port'],
|
175
|
-
username=triple_store['username'],
|
176
|
-
password=triple_store['password'])
|
177
|
-
record_dictionaries = anzo_client.query_journal(query_string=query).as_table_results().as_record_dictionaries()
|
123
|
+
}}"""
|
124
|
+
return json_to_dictlist(query_configuration(anzo_config=triple_store, query=query))[0]['query']
|
178
125
|
|
179
|
-
return record_dictionaries[0].get(
|
180
|
-
"query")
|
181
126
|
|
182
127
|
def get_queries_from_templated_step(triple_store: dict, query_step_uri: URIRef) -> dict:
|
183
|
-
|
184
|
-
query = f"""SELECT ?stepUri ?param_query ?query_template WHERE {{
|
128
|
+
query = f"""SELECT ?param_query ?query_template WHERE {{
|
185
129
|
BIND(<{query_step_uri}> as ?stepUri)
|
186
130
|
?stepUri a <http://cambridgesemantics.com/ontologies/Graphmarts#Step> ;
|
187
|
-
|
188
|
-
|
131
|
+
<http://cambridgesemantics.com/ontologies/Graphmarts#parametersTemplate> ?param_query ;
|
132
|
+
<http://cambridgesemantics.com/ontologies/Graphmarts#template> ?query_template .
|
189
133
|
}}
|
190
134
|
"""
|
191
|
-
|
192
|
-
username=triple_store['username'],
|
193
|
-
password=triple_store['password'])
|
194
|
-
record_dictionaries = anzo_client.query_journal(query_string=query).as_table_results().as_record_dictionaries()
|
195
|
-
return record_dictionaries[0]
|
135
|
+
return json_to_dictlist(query_configuration(anzo_config=triple_store, query=query))[0]
|
196
136
|
|
197
137
|
|
198
138
|
def get_queries_for_layer(triple_store: dict, graphmart_layer_uri: URIRef):
|
@@ -204,37 +144,26 @@ SELECT ?query ?param_query ?query_template
|
|
204
144
|
anzo:orderedValue ?query_step .
|
205
145
|
?query_step graphmarts:enabled true ;
|
206
146
|
OPTIONAL {{ ?query_step
|
207
|
-
|
208
|
-
|
147
|
+
graphmarts:parametersTemplate ?param_query ;
|
148
|
+
graphmarts:template ?query_template ;
|
209
149
|
. }}
|
210
150
|
OPTIONAL {{ ?query_step
|
211
|
-
|
151
|
+
graphmarts:transformQuery ?query ;
|
212
152
|
. }}
|
213
153
|
}}
|
214
154
|
ORDER BY ?index"""
|
215
|
-
|
216
|
-
username=triple_store['username'],
|
217
|
-
password=triple_store['password'])
|
218
|
-
return anzo_client.query_journal(query_string=query).as_table_results().as_record_dictionaries()
|
155
|
+
return json_to_dictlist(query_configuration(anzo_config=triple_store, query=query))
|
219
156
|
|
220
157
|
|
221
158
|
def upload_given(triple_store: dict, given: Graph):
|
222
159
|
logging.debug(f"upload_given {triple_store} {given}")
|
223
160
|
|
224
161
|
try:
|
225
|
-
|
226
|
-
|
227
|
-
clear_graph(triple_store, input_graph)
|
228
|
-
clear_graph(triple_store, output_graph)
|
162
|
+
clear_graph(triple_store, triple_store['input_graph'])
|
163
|
+
clear_graph(triple_store, triple_store['output_graph'])
|
229
164
|
serialized_given = given.serialize(format="nt")
|
230
165
|
insert_query = f"INSERT DATA {{graph <{triple_store['input_graph']}>{{{serialized_given}}}}}"
|
231
|
-
|
232
|
-
'update': insert_query,
|
233
|
-
'using-graph-uri': input_graph,
|
234
|
-
'using-named-graph-uri': input_graph}
|
235
|
-
response = requests.post(url=f"https://{triple_store['url']}:{triple_store['port']}/sparql",
|
236
|
-
auth=(triple_store['username'], triple_store['password']), data=data, verify=False)
|
237
|
-
manage_anzo_response(response)
|
166
|
+
query_azg(anzo_config=triple_store, query=insert_query, is_update=True)
|
238
167
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout):
|
239
168
|
raise
|
240
169
|
|
@@ -242,10 +171,6 @@ def upload_given(triple_store: dict, given: Graph):
|
|
242
171
|
def clear_graph(triple_store: dict, graph_uri: str):
|
243
172
|
try:
|
244
173
|
clear_query = f"CLEAR GRAPH <{graph_uri}>"
|
245
|
-
|
246
|
-
url = f"https://{triple_store['url']}:{triple_store['port']}/sparql"
|
247
|
-
response = requests.post(url=url,
|
248
|
-
auth=(triple_store['username'], triple_store['password']), data=data, verify=False)
|
249
|
-
manage_anzo_response(response)
|
174
|
+
query_azg(anzo_config=triple_store, query=clear_query, is_update=True)
|
250
175
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout):
|
251
176
|
raise
|
mustrd/mustrdGraphDb.py
CHANGED
@@ -49,7 +49,7 @@ def upload_given(triple_store: dict, given: Graph):
|
|
49
49
|
graph = "default"
|
50
50
|
if triple_store['input_graph']:
|
51
51
|
graph = urllib.parse.urlencode({'graph': triple_store['input_graph']})
|
52
|
-
url = f"{triple_store['url']}
|
52
|
+
url = f"{triple_store['url']}/repositories/{triple_store['repository']}" \
|
53
53
|
f"/rdf-graphs/service?{graph}"
|
54
54
|
# graph store PUT drop silently the graph or default and upload the payload
|
55
55
|
# https://www.w3.org/TR/sparql11-http-rdf-update/#http-put
|
@@ -82,7 +82,7 @@ def post_update_query(triple_store: dict, query: str, params: dict = None) -> st
|
|
82
82
|
params = add_graph_to_params(params, triple_store["input_graph"])
|
83
83
|
try:
|
84
84
|
return manage_graphdb_response(requests.post(
|
85
|
-
url=f"{triple_store['url']}
|
85
|
+
url=f"{triple_store['url']}/repositories/{triple_store['repository']}/statements",
|
86
86
|
data=query,
|
87
87
|
params=params,
|
88
88
|
auth=(triple_store['username'], triple_store['password']),
|
@@ -99,7 +99,7 @@ def post_query(triple_store: dict, query: str, accept: str, params: dict = None)
|
|
99
99
|
params = add_graph_to_params(params, triple_store["input_graph"])
|
100
100
|
try:
|
101
101
|
return manage_graphdb_response(
|
102
|
-
requests.post(url=f"{triple_store['url']}
|
102
|
+
requests.post(url=f"{triple_store['url']}/repositories/{triple_store['repository']}",
|
103
103
|
data=query,
|
104
104
|
params=params,
|
105
105
|
auth=(triple_store['username'], triple_store['password']),
|
mustrd/mustrdTestPlugin.py
CHANGED
@@ -22,6 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
SOFTWARE.
|
23
23
|
"""
|
24
24
|
|
25
|
+
import logging
|
25
26
|
from dataclasses import dataclass
|
26
27
|
import pytest
|
27
28
|
import os
|
@@ -30,14 +31,15 @@ from rdflib.namespace import Namespace
|
|
30
31
|
from rdflib import Graph, RDF
|
31
32
|
from pytest import Session
|
32
33
|
|
34
|
+
from mustrd import logger_setup
|
33
35
|
from mustrd.TestResult import ResultList, TestResult, get_result_list
|
34
36
|
from mustrd.utils import get_mustrd_root
|
35
|
-
from mustrd.mustrd import get_triple_store_graph, get_triple_stores
|
37
|
+
from mustrd.mustrd import write_result_diff_to_log, get_triple_store_graph, get_triple_stores
|
36
38
|
from mustrd.mustrd import Specification, SpecSkipped, validate_specs, get_specs, SpecPassed, run_spec
|
37
39
|
from mustrd.namespace import MUST, TRIPLESTORE, MUSTRDTEST
|
38
40
|
from typing import Union
|
39
41
|
from pyshacl import validate
|
40
|
-
|
42
|
+
import logging
|
41
43
|
spnamespace = Namespace("https://semanticpartners.com/data/test/")
|
42
44
|
|
43
45
|
mustrd_root = get_mustrd_root()
|
@@ -82,41 +84,48 @@ def pytest_addoption(parser):
|
|
82
84
|
|
83
85
|
def pytest_configure(config) -> None:
|
84
86
|
# Read configuration file
|
85
|
-
if config.getoption("mustrd"):
|
86
|
-
test_configs = parse_config(config.getoption("configpath"))
|
87
|
+
if config.getoption("mustrd") and config.getoption("configpath"):
|
87
88
|
config.pluginmanager.register(MustrdTestPlugin(config.getoption("mdpath"),
|
88
|
-
|
89
|
+
Path(config.getoption("configpath")), config.getoption("secrets")))
|
89
90
|
|
90
91
|
|
91
92
|
def parse_config(config_path):
|
92
93
|
test_configs = []
|
94
|
+
print(f"{config_path=}")
|
93
95
|
config_graph = Graph().parse(config_path)
|
94
|
-
shacl_graph = Graph().parse(
|
95
|
-
|
96
|
+
shacl_graph = Graph().parse(
|
97
|
+
Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl")))
|
98
|
+
ont_graph = Graph().parse(
|
99
|
+
Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl")))
|
96
100
|
conforms, results_graph, results_text = validate(
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
data_graph=config_graph,
|
102
|
+
shacl_graph=shacl_graph,
|
103
|
+
ont_graph=ont_graph,
|
104
|
+
advanced=True,
|
105
|
+
inference='none'
|
106
|
+
)
|
103
107
|
if not conforms:
|
104
108
|
raise ValueError(f"Mustrd test configuration not conform to the shapes. SHACL report: {results_text}",
|
105
109
|
results_graph)
|
106
110
|
|
107
111
|
for test_config_subject in config_graph.subjects(predicate=RDF.type, object=MUSTRDTEST.MustrdTest):
|
108
|
-
spec_path = get_config_param(
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
112
|
+
spec_path = get_config_param(
|
113
|
+
config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str)
|
114
|
+
data_path = get_config_param(
|
115
|
+
config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str)
|
116
|
+
triplestore_spec_path = get_config_param(
|
117
|
+
config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str)
|
118
|
+
pytest_path = get_config_param(
|
119
|
+
config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str)
|
120
|
+
filter_on_tripleStore = tuple(config_graph.objects(subject=test_config_subject,
|
121
|
+
predicate=MUSTRDTEST.filterOnTripleStore))
|
114
122
|
|
115
123
|
# Root path is the mustrd test config path
|
116
124
|
root_path = Path(config_path).parent
|
117
125
|
spec_path = root_path / Path(spec_path) if spec_path else None
|
118
126
|
data_path = root_path / Path(data_path) if data_path else None
|
119
|
-
triplestore_spec_path = root_path /
|
127
|
+
triplestore_spec_path = root_path / \
|
128
|
+
Path(triplestore_spec_path) if triplestore_spec_path else None
|
120
129
|
|
121
130
|
test_configs.append(TestConfig(spec_path=spec_path, data_path=data_path,
|
122
131
|
triplestore_spec_path=triplestore_spec_path,
|
@@ -126,11 +135,12 @@ def parse_config(config_path):
|
|
126
135
|
|
127
136
|
|
128
137
|
def get_config_param(config_graph, config_subject, config_param, convert_function):
|
129
|
-
raw_value = config_graph.value(
|
138
|
+
raw_value = config_graph.value(
|
139
|
+
subject=config_subject, predicate=config_param, any=True)
|
130
140
|
return convert_function(raw_value) if raw_value else None
|
131
141
|
|
132
142
|
|
133
|
-
@dataclass
|
143
|
+
@dataclass(frozen=True)
|
134
144
|
class TestConfig:
|
135
145
|
spec_path: Path
|
136
146
|
data_path: Path
|
@@ -139,94 +149,61 @@ class TestConfig:
|
|
139
149
|
filter_on_tripleStore: str = None
|
140
150
|
|
141
151
|
|
142
|
-
@dataclass
|
152
|
+
@dataclass(frozen=True)
|
143
153
|
class TestParamWrapper:
|
154
|
+
id: str
|
144
155
|
test_config: TestConfig
|
145
156
|
unit_test: Union[Specification, SpecSkipped]
|
146
157
|
|
147
158
|
|
159
|
+
# Configure logging
|
160
|
+
logger = logger_setup.setup_logger(__name__)
|
161
|
+
|
162
|
+
|
148
163
|
class MustrdTestPlugin:
|
149
164
|
md_path: str
|
150
|
-
|
165
|
+
test_config_file: Path
|
166
|
+
selected_tests: list
|
151
167
|
secrets: str
|
152
168
|
unit_tests: Union[Specification, SpecSkipped]
|
153
169
|
items: list
|
170
|
+
path_filter: str
|
171
|
+
collect_error: BaseException
|
154
172
|
|
155
|
-
def __init__(self, md_path,
|
173
|
+
def __init__(self, md_path, test_config_file, secrets):
|
156
174
|
self.md_path = md_path
|
157
|
-
self.
|
175
|
+
self.test_config_file = test_config_file
|
158
176
|
self.secrets = secrets
|
159
177
|
self.items = []
|
160
178
|
|
161
179
|
@pytest.hookimpl(tryfirst=True)
|
162
180
|
def pytest_collection(self, session):
|
181
|
+
logger.info("Starting test collection")
|
163
182
|
self.unit_tests = []
|
164
183
|
args = session.config.args
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
self.test_configs))
|
176
|
-
|
177
|
-
# Redirect everything to test_mustrd.py,
|
178
|
-
# no need to filter on specified test: Only specified test will be collected anyway
|
179
|
-
session.config.args[0] = os.path.join(mustrd_root, "test/test_mustrd.py")
|
180
|
-
# Collecting only relevant tests
|
181
|
-
|
182
|
-
for one_test_config in config_to_collect:
|
183
|
-
triple_stores = self.get_triple_stores_from_file(one_test_config)
|
184
|
-
|
185
|
-
if one_test_config.filter_on_tripleStore and not triple_stores:
|
186
|
-
self.unit_tests.extend(list(map(
|
187
|
-
lambda triple_store:
|
188
|
-
TestParamWrapper(test_config=one_test_config,
|
189
|
-
unit_test=SpecSkipped(MUST.TestSpec, triple_store, "No triplestore found")),
|
190
|
-
one_test_config.filter_on_tripleStore)))
|
191
|
-
else:
|
192
|
-
specs = self.generate_tests_for_config({"spec_path": one_test_config.spec_path,
|
193
|
-
"data_path": one_test_config.data_path},
|
194
|
-
triple_stores, file_name)
|
195
|
-
self.unit_tests.extend(list(map(
|
196
|
-
lambda spec: TestParamWrapper(test_config=one_test_config, unit_test=spec), specs)))
|
184
|
+
logger.info("Used arguments: " + str(args))
|
185
|
+
self.selected_tests = list(map(lambda arg: Path(arg.split("::")[0]).resolve(),
|
186
|
+
# By default the current directory is given as argument
|
187
|
+
# Remove it as it is not a test
|
188
|
+
filter(lambda arg: arg != os.getcwd() and "::" in arg, args)))
|
189
|
+
|
190
|
+
self.path_filter = args[0] if len(
|
191
|
+
args) == 1 and args[0] != os.getcwd() and not "::" in args[0] else None
|
192
|
+
|
193
|
+
session.config.args = [str(self.test_config_file.resolve())]
|
197
194
|
|
198
195
|
def get_file_name_from_arg(self, arg):
|
199
|
-
if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl
|
200
|
-
return arg[arg.index("[") + 1: arg.index(".mustrd.ttl
|
196
|
+
if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl " in arg:
|
197
|
+
return arg[arg.index("[") + 1: arg.index(".mustrd.ttl ")]
|
201
198
|
return None
|
202
199
|
|
203
|
-
@pytest.hookimpl
|
204
|
-
def
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
virtual_path = MUSTRD_PYTEST_PATH + (item.callspec.params["unit_tests"].test_config.pytest_path or "default")
|
211
|
-
item.fspath = Path(virtual_path)
|
212
|
-
item._nodeid = virtual_path + "::" + item.name
|
213
|
-
self.items.append(item)
|
214
|
-
new_results.append(item)
|
215
|
-
return new_results
|
216
|
-
|
217
|
-
# Hook called at collection time: reads the configuration of the tests, and generate pytests from it
|
218
|
-
def pytest_generate_tests(self, metafunc):
|
219
|
-
if len(metafunc.fixturenames) > 0:
|
220
|
-
if metafunc.function.__name__ == "test_unit":
|
221
|
-
# Create the test in itself
|
222
|
-
if self.unit_tests:
|
223
|
-
metafunc.parametrize(metafunc.fixturenames[0], self.unit_tests,
|
224
|
-
ids=lambda test_param: (test_param.unit_test.spec_file_name or "") + "@" +
|
225
|
-
(test_param.test_config.pytest_path or ""))
|
226
|
-
else:
|
227
|
-
metafunc.parametrize(metafunc.fixturenames[0],
|
228
|
-
[SpecSkipped(MUST.TestSpec, None, "No triplestore found")],
|
229
|
-
ids=lambda x: "No configuration found for this test")
|
200
|
+
@pytest.hookimpl
|
201
|
+
def pytest_collect_file(self, parent, path):
|
202
|
+
logger.debug(f"Collecting file: {path}")
|
203
|
+
mustrd_file = MustrdFile.from_parent(
|
204
|
+
parent, fspath=path, mustrd_plugin=self)
|
205
|
+
mustrd_file.mustrd_plugin = self
|
206
|
+
return mustrd_file
|
230
207
|
|
231
208
|
# Generate test for each triple store available
|
232
209
|
def generate_tests_for_config(self, config, triple_stores, file_name):
|
@@ -249,9 +226,10 @@ class MustrdTestPlugin:
|
|
249
226
|
triple_store = spec.triple_store
|
250
227
|
else:
|
251
228
|
triple_store = spec.triple_store['type']
|
252
|
-
triple_store_name = triple_store.replace(
|
229
|
+
triple_store_name = triple_store.replace(
|
230
|
+
"https://mustrd.com/model/", "")
|
253
231
|
test_name = spec.spec_uri.replace(spnamespace, "").replace("_", " ")
|
254
|
-
return triple_store_name + ": " + test_name
|
232
|
+
return spec.spec_file_name + " : " + triple_store_name + ": " + test_name
|
255
233
|
|
256
234
|
# Get triple store configuration or default
|
257
235
|
def get_triple_stores_from_file(self, test_config):
|
@@ -262,10 +240,12 @@ class MustrdTestPlugin:
|
|
262
240
|
except Exception as e:
|
263
241
|
print(f"""Triplestore configuration parsing failed {test_config.triplestore_spec_path}.
|
264
242
|
Only rdflib will be executed""", e)
|
265
|
-
triple_stores = [
|
243
|
+
triple_stores = [
|
244
|
+
{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
|
266
245
|
else:
|
267
246
|
print("No triple store configuration required: using embedded rdflib")
|
268
|
-
triple_stores = [
|
247
|
+
triple_stores = [
|
248
|
+
{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
|
269
249
|
|
270
250
|
if test_config.filter_on_tripleStore:
|
271
251
|
triple_stores = list(filter(lambda triple_store: (triple_store["uri"] in test_config.filter_on_tripleStore),
|
@@ -300,7 +280,8 @@ class MustrdTestPlugin:
|
|
300
280
|
if test_conf.originalname != test_conf.name:
|
301
281
|
module_name = test_conf.parent.name
|
302
282
|
class_name = test_conf.originalname
|
303
|
-
test_name = test_conf.name.replace(
|
283
|
+
test_name = test_conf.name.replace(
|
284
|
+
class_name, "").replace("[", "").replace("]", "")
|
304
285
|
is_mustrd = True
|
305
286
|
# Case normal unit tests
|
306
287
|
else:
|
@@ -309,7 +290,8 @@ class MustrdTestPlugin:
|
|
309
290
|
test_name = test_conf.originalname
|
310
291
|
is_mustrd = False
|
311
292
|
|
312
|
-
test_results.append(TestResult(
|
293
|
+
test_results.append(TestResult(
|
294
|
+
test_name, class_name, module_name, result.outcome, is_mustrd))
|
313
295
|
|
314
296
|
result_list = ResultList(None, get_result_list(test_results,
|
315
297
|
lambda result: result.type,
|
@@ -321,6 +303,66 @@ class MustrdTestPlugin:
|
|
321
303
|
file.write(md)
|
322
304
|
|
323
305
|
|
306
|
+
class MustrdFile(pytest.File):
|
307
|
+
mustrd_plugin: MustrdTestPlugin
|
308
|
+
|
309
|
+
def collect(self):
|
310
|
+
try:
|
311
|
+
logger.debug(f"Collecting tests from file: {self.fspath}")
|
312
|
+
test_configs = parse_config(self.fspath)
|
313
|
+
for test_config in test_configs:
|
314
|
+
# Skip if there is a path filter and it is not in the pytest path
|
315
|
+
if self.mustrd_plugin.path_filter is not None and self.mustrd_plugin.path_filter not in test_config.pytest_path:
|
316
|
+
continue
|
317
|
+
triple_stores = self.mustrd_plugin.get_triple_stores_from_file(
|
318
|
+
test_config)
|
319
|
+
|
320
|
+
if test_config.filter_on_tripleStore and not triple_stores:
|
321
|
+
specs = list(map(
|
322
|
+
lambda triple_store:
|
323
|
+
SpecSkipped(MUST.TestSpec, triple_store,
|
324
|
+
"No triplestore found"),
|
325
|
+
test_config.filter_on_tripleStore))
|
326
|
+
else:
|
327
|
+
specs = self.mustrd_plugin.generate_tests_for_config({"spec_path": test_config.spec_path,
|
328
|
+
"data_path": test_config.data_path},
|
329
|
+
triple_stores, None)
|
330
|
+
for spec in specs:
|
331
|
+
# Check if the current test is in the selected tests in arguments
|
332
|
+
if spec.spec_source_file.resolve() in self.mustrd_plugin.selected_tests \
|
333
|
+
or self.mustrd_plugin.selected_tests == []:
|
334
|
+
item = MustrdItem.from_parent(
|
335
|
+
self, name=test_config.pytest_path + "/" + spec.spec_file_name, spec=spec)
|
336
|
+
self.mustrd_plugin.items.append(item)
|
337
|
+
yield item
|
338
|
+
except BaseException as e:
|
339
|
+
# Catch error here otherwise it will be lost
|
340
|
+
self.mustrd_plugin.collect_error = e
|
341
|
+
logger.error(f"Error during collection: {e}")
|
342
|
+
raise e
|
343
|
+
|
344
|
+
|
345
|
+
class MustrdItem(pytest.Item):
|
346
|
+
def __init__(self, name, parent, spec):
|
347
|
+
logging.info(f"Creating item: {name}")
|
348
|
+
super().__init__(name, parent)
|
349
|
+
self.spec = spec
|
350
|
+
self.fspath = spec.spec_source_file
|
351
|
+
|
352
|
+
def runtest(self):
|
353
|
+
result = run_test_spec(self.spec)
|
354
|
+
if not result:
|
355
|
+
raise AssertionError(f"Test {self.name} failed")
|
356
|
+
|
357
|
+
def repr_failure(self, excinfo):
|
358
|
+
return f"{self.name} failed: {excinfo.value}"
|
359
|
+
|
360
|
+
def reportinfo(self):
|
361
|
+
r = "", 0, f"mustrd test: {self.name}"
|
362
|
+
logger.debug(f"Reporting info for {self.name} {r=}")
|
363
|
+
return r
|
364
|
+
|
365
|
+
|
324
366
|
# Function called in the test to actually run it
|
325
367
|
def run_test_spec(test_spec):
|
326
368
|
if isinstance(test_spec, SpecSkipped):
|
@@ -331,4 +373,6 @@ def run_test_spec(test_spec):
|
|
331
373
|
if result_type == SpecSkipped:
|
332
374
|
# FIXME: Better exception management
|
333
375
|
pytest.skip("Unsupported configuration")
|
376
|
+
if result_type != SpecPassed:
|
377
|
+
write_result_diff_to_log(result)
|
334
378
|
return result_type == SpecPassed
|