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/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, Response, HTTPError, RequestException, ConnectionError
29
- from bs4 import BeautifulSoup
26
+ from requests import ConnectTimeout, HTTPError, ConnectionError
30
27
  import logging
31
- from .namespace import MUST
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
- when = when.replace("${fromSources}", f"FROM <{triple_store['input_graph']}>\nFROM <{triple_store['output_graph']}>").replace(
60
- "${targetGraph}", f"<{triple_store['output_graph']}>")
61
- data = {'datasourceURI': triple_store['gqe_uri'], 'query': when,
62
- 'default-graph-uri': triple_store['input_graph'],
63
- 'named-graph-uri': triple_store['input_graph'],
64
- 'skipCache': 'true'}
65
- url = f"https://{triple_store['url']}:{triple_store['port']}/sparql?format=application/sparql-results+json"
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
- substituted_query = when.replace("${usingSources}", f"USING <{triple_store['input_graph']}> \nUSING <{triple_store['output_graph']}>").replace(
80
- "${targetGraph}", f"<{output_graph}>")
81
-
82
- data = {'datasourceURI': triple_store['gqe_uri'],
83
- 'update': substituted_query,
84
- 'using-graph-uri': [output_graph, input_graph],
85
- 'using-named-graph-uri': [output_graph, input_graph],
86
- 'skipCache': 'true'}
87
- url = f"https://{triple_store['url']}:{triple_store['port']}/sparql?format=ttl"
88
- response = manage_anzo_response(requests.post(url=url,
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
- check_data = {'datasourceURI': triple_store['gqe_uri'], 'query': "construct {?s ?p ?o} { ?s ?p ?o }",
95
- 'default-graph-uri': output_graph,
96
- 'named-graph-uri': output_graph,
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
- data = {'datasourceURI': triple_store['gqe_uri'], 'query': when,
114
- 'default-graph-uri': triple_store['input_graph'],
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
- anzo_client = AnzoClient(triple_store['url'], triple_store['port'],
136
- username=triple_store['username'],
137
- password=triple_store['password'])
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
- anzo_client = AnzoClient(triple_store['url'], triple_store['port'],
157
- username=triple_store['username'],
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(f"Query {query_name} not found in folder {folder_name}")
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 ?stepUri ?query WHERE {{
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
- <http://cambridgesemantics.com/ontologies/Graphmarts#parametersTemplate> ?param_query ;
188
- <http://cambridgesemantics.com/ontologies/Graphmarts#template> ?query_template .
131
+ <http://cambridgesemantics.com/ontologies/Graphmarts#parametersTemplate> ?param_query ;
132
+ <http://cambridgesemantics.com/ontologies/Graphmarts#template> ?query_template .
189
133
  }}
190
134
  """
191
- anzo_client = AnzoClient(triple_store['url'], triple_store['port'],
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
- graphmarts:parametersTemplate ?param_query ;
208
- graphmarts:template ?query_template ;
147
+ graphmarts:parametersTemplate ?param_query ;
148
+ graphmarts:template ?query_template ;
209
149
  . }}
210
150
  OPTIONAL {{ ?query_step
211
- graphmarts:transformQuery ?query ;
151
+ graphmarts:transformQuery ?query ;
212
152
  . }}
213
153
  }}
214
154
  ORDER BY ?index"""
215
- anzo_client = AnzoClient(triple_store['url'], triple_store['port'],
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
- input_graph = triple_store['input_graph']
226
- output_graph = triple_store['output_graph']
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
- data = {'datasourceURI': triple_store['gqe_uri'],
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
- data = {'datasourceURI': triple_store['gqe_uri'], 'update': clear_query}
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']}:{triple_store['port']}/repositories/{triple_store['repository']}" \
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']}:{triple_store['port']}/repositories/{triple_store['repository']}/statements",
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']}:{triple_store['port']}/repositories/{triple_store['repository']}",
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']),
@@ -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
- test_configs, config.getoption("secrets")))
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(Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl")))
95
- ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl")))
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
- data_graph=config_graph,
98
- shacl_graph=shacl_graph,
99
- ont_graph=ont_graph,
100
- advanced=True,
101
- inference='none'
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(config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str)
109
- data_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str)
110
- triplestore_spec_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str)
111
- pytest_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str)
112
- filter_on_tripleStore = list(config_graph.objects(subject=test_config_subject,
113
- predicate=MUSTRDTEST.filterOnTripleStore))
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 / Path(triplestore_spec_path) if triplestore_spec_path else None
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(subject=config_subject, predicate=config_param, any=True)
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
- test_configs: list
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, test_configs, secrets):
173
+ def __init__(self, md_path, test_config_file, secrets):
156
174
  self.md_path = md_path
157
- self.test_configs = test_configs
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
- if len(args) > 0:
166
- file_name = self.get_file_name_from_arg(args[0])
167
- # Filter test to collect only specified path
168
- config_to_collect = list(filter(lambda config:
169
- # Case we want to collect everything
170
- MUSTRD_PYTEST_PATH not in args[0]
171
- # Case we want to collect a test or sub test
172
- or (config.pytest_path or "") in args[0]
173
- # Case we want to collect a whole test folder
174
- or args[0].replace(f"./{MUSTRD_PYTEST_PATH}", "") in config.pytest_path,
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@" in arg:
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(hookwrapper=True)
204
- def pytest_pycollect_makeitem(self, collector, name, obj):
205
- report = yield
206
- if name == "test_unit":
207
- items = report.get_result()
208
- new_results = []
209
- for item in items:
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("https://mustrd.com/model/", "")
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 = [{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
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 = [{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
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(class_name, "").replace("[", "").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(test_name, class_name, module_name, result.outcome, is_mustrd))
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