mustrd 0.2.0__py3-none-any.whl → 0.2.1__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.
@@ -1,328 +1,327 @@
1
- """
2
- MIT License
3
-
4
- Copyright (c) 2023 Semantic Partners Ltd
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.
23
- """
24
-
25
- from dataclasses import dataclass
26
- import pytest
27
- import os
28
- from pathlib import Path
29
- from rdflib.namespace import Namespace
30
- from rdflib import Graph, RDF
31
- from pytest import Session
32
-
33
- from mustrd.TestResult import ResultList, TestResult, get_result_list
34
- from mustrd.utils import get_mustrd_root
35
- from mustrd.mustrd import get_triple_store_graph, get_triple_stores
36
- from mustrd.mustrd import Specification, SpecSkipped, validate_specs, get_specs, SpecPassed, run_spec
37
- from mustrd.namespace import MUST, TRIPLESTORE, MUSTRDTEST
38
- from typing import Union
39
- from pyshacl import validate
40
-
41
- spnamespace = Namespace("https://semanticpartners.com/data/test/")
42
-
43
- mustrd_root = get_mustrd_root()
44
-
45
- MUSTRD_PYTEST_PATH = "mustrd_tests/"
46
-
47
-
48
- def pytest_addoption(parser):
49
- group = parser.getgroup("mustrd option")
50
- group.addoption(
51
- "--mustrd",
52
- action="store_true",
53
- dest="mustrd",
54
- help="Activate/deactivate mustrd test generation.",
55
- )
56
- group.addoption(
57
- "--md",
58
- action="store",
59
- dest="mdpath",
60
- metavar="pathToMdSummary",
61
- default=None,
62
- help="create md summary file at that path.",
63
- )
64
- group.addoption(
65
- "--config",
66
- action="store",
67
- dest="configpath",
68
- metavar="pathToTestConfig",
69
- default=None,
70
- help="Ttl file containing the list of test to construct.",
71
- )
72
- group.addoption(
73
- "--secrets",
74
- action="store",
75
- dest="secrets",
76
- metavar="Secrets",
77
- default=None,
78
- help="Give the secrets by command line in order to be able to store secrets safely in CI tools",
79
- )
80
- return
81
-
82
-
83
- def pytest_configure(config) -> None:
84
- # Read configuration file
85
- if config.getoption("mustrd"):
86
- test_configs = parse_config(config.getoption("configpath"))
87
- config.pluginmanager.register(MustrdTestPlugin(config.getoption("mdpath"),
88
- test_configs, config.getoption("secrets")))
89
-
90
- def parse_config(config_path):
91
- test_configs = []
92
- config_graph = Graph().parse(config_path)
93
- shacl_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl")))
94
- ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl")))
95
- conforms, results_graph, results_text = validate(
96
- data_graph= config_graph,
97
- shacl_graph = shacl_graph,
98
- ont_graph = ont_graph,
99
- advanced= True,
100
- inference= 'none'
101
- )
102
- if not conforms:
103
- raise ValueError(f"Mustrd test configuration not conform to the shapes. SHACL report: {results_text}", results_graph)
104
-
105
- for test_config_subject in config_graph.subjects(predicate=RDF.type, object=MUSTRDTEST.MustrdTest):
106
- spec_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str)
107
- data_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str)
108
- triplestore_spec_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str)
109
- pytest_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str)
110
- filter_on_tripleStore = list(config_graph.objects(subject=test_config_subject,
111
- predicate=MUSTRDTEST.filterOnTripleStore))
112
-
113
- test_configs.append(TestConfig(spec_path=spec_path, data_path=data_path,
114
- triplestore_spec_path=triplestore_spec_path,
115
- pytest_path = pytest_path,
116
- filter_on_tripleStore=filter_on_tripleStore))
117
- return test_configs
118
-
119
-
120
-
121
- def get_config_param(config_graph, config_subject, config_param, convert_function):
122
- raw_value = config_graph.value(subject=config_subject, predicate=config_param, any=True)
123
- return convert_function(raw_value) if raw_value else None
124
-
125
-
126
- @dataclass
127
- class TestConfig:
128
- spec_path: str
129
- data_path: str
130
- triplestore_spec_path: str
131
- pytest_path: str
132
- filter_on_tripleStore: str = None
133
-
134
-
135
- @dataclass
136
- class TestParamWrapper:
137
- test_config: TestConfig
138
- unit_test: Union[Specification, SpecSkipped]
139
-
140
- class MustrdTestPlugin:
141
- md_path: str
142
- test_configs: list
143
- secrets: str
144
- unit_tests: Union[Specification, SpecSkipped]
145
- items: list
146
-
147
- def __init__(self, md_path, test_configs, secrets):
148
- self.md_path = md_path
149
- self.test_configs = test_configs
150
- self.secrets = secrets
151
- self.items = []
152
-
153
- @pytest.hookimpl(tryfirst=True)
154
- def pytest_collection(self, session):
155
- self.unit_tests = []
156
- args = session.config.args
157
- if len(args) > 0:
158
- file_name = self.get_file_name_from_arg(args[0])
159
- # Filter test to collect only specified path
160
- config_to_collect = list(filter(lambda config:
161
- # Case we want to collect everything
162
- MUSTRD_PYTEST_PATH not in args[0]
163
- # Case we want to collect a test or sub test
164
- or (config.pytest_path or "") in args[0]
165
- # Case we want to collect a whole test folder
166
- or args[0].replace(f"./{MUSTRD_PYTEST_PATH}", "") in config.pytest_path,
167
- self.test_configs))
168
-
169
- # Redirect everything to test_mustrd.py, no need to filter on specified test: Only specified test will be collected anyway
170
- session.config.args[0] = os.path.join(mustrd_root, "test/test_mustrd.py")
171
- # Collecting only relevant tests
172
-
173
- for one_test_config in config_to_collect:
174
- triple_stores = self.get_triple_stores_from_file(one_test_config)
175
-
176
- if one_test_config.filter_on_tripleStore and not triple_stores:
177
- self.unit_tests.extend(list(map(lambda triple_store:
178
- TestParamWrapper(test_config = one_test_config, unit_test=SpecSkipped(MUST.TestSpec, triple_store, "No triplestore found")),
179
- one_test_config.filter_on_tripleStore)))
180
- else:
181
- specs = self.generate_tests_for_config({"spec_path": Path(one_test_config.spec_path),
182
- "data_path": Path(one_test_config.data_path)},
183
- triple_stores, file_name)
184
- self.unit_tests.extend(list(map(lambda spec: TestParamWrapper(test_config = one_test_config, unit_test=spec),specs)))
185
-
186
- def get_file_name_from_arg(self, arg):
187
- if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl@" in arg:
188
- return arg[arg.index("[") + 1: arg.index(".mustrd.ttl@")]
189
- return None
190
-
191
-
192
- @pytest.hookimpl(hookwrapper=True)
193
- def pytest_pycollect_makeitem(self, collector, name, obj):
194
- report = yield
195
- if name == "test_unit":
196
- items = report.get_result()
197
- new_results = []
198
- for item in items:
199
- virtual_path = MUSTRD_PYTEST_PATH + (item.callspec.params["unit_tests"].test_config.pytest_path or "default")
200
- item.fspath = Path(virtual_path)
201
- item._nodeid = virtual_path + "::" + item.name
202
- self.items.append(item)
203
- new_results.append(item)
204
- return new_results
205
-
206
-
207
- # Hook called at collection time: reads the configuration of the tests, and generate pytests from it
208
- def pytest_generate_tests(self, metafunc):
209
- if len(metafunc.fixturenames) > 0:
210
- if metafunc.function.__name__ == "test_unit":
211
- # Create the test in itself
212
- if self.unit_tests:
213
- metafunc.parametrize(metafunc.fixturenames[0], self.unit_tests,
214
- ids=lambda test_param: (test_param.unit_test.spec_file_name or "") + "@" +
215
- (test_param.test_config.pytest_path or ""))
216
- else:
217
- metafunc.parametrize(metafunc.fixturenames[0],
218
- [SpecSkipped(MUST.TestSpec, None, "No triplestore found")],
219
- ids=lambda x: "No configuration found for this test")
220
-
221
-
222
-
223
-
224
- # Generate test for each triple store available
225
- def generate_tests_for_config(self, config, triple_stores, file_name):
226
-
227
- shacl_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdShapes.ttl")))
228
- ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/ontology.ttl")))
229
- valid_spec_uris, spec_graph, invalid_spec_results = validate_specs(config, triple_stores,
230
- shacl_graph, ont_graph, file_name or "*")
231
-
232
- specs, skipped_spec_results = \
233
- get_specs(valid_spec_uris, spec_graph, triple_stores, config)
234
-
235
- # Return normal specs + skipped results
236
- return specs + skipped_spec_results + invalid_spec_results
237
-
238
- # Function called to generate the name of the test
239
- def get_test_name(self, spec):
240
- # FIXME: SpecSkipped should have the same structure?
241
- if isinstance(spec, SpecSkipped):
242
- triple_store = spec.triple_store
243
- else:
244
- triple_store = spec.triple_store['type']
245
- triple_store_name = triple_store.replace("https://mustrd.com/model/", "")
246
- test_name = spec.spec_uri.replace(spnamespace, "").replace("_", " ")
247
- return triple_store_name + ": " + test_name
248
-
249
- # Get triple store configuration or default
250
- def get_triple_stores_from_file(self, test_config):
251
- if test_config.triplestore_spec_path:
252
- try:
253
- triple_stores = get_triple_stores(get_triple_store_graph(Path(test_config.triplestore_spec_path),
254
- self.secrets))
255
- except Exception as e:
256
- print(f"""Triplestore configuration parsing failed {test_config.triplestore_spec_path}.
257
- Only rdflib will be executed""", e)
258
- triple_stores = [{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
259
- else:
260
- print("No triple store configuration required: using embedded rdflib")
261
- triple_stores = [{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
262
-
263
- if test_config.filter_on_tripleStore:
264
- triple_stores = list(filter(lambda triple_store: (triple_store["uri"] in test_config.filter_on_tripleStore),
265
- triple_stores))
266
- return triple_stores
267
-
268
- # Hook function. Initialize the list of result in session
269
- def pytest_sessionstart(self, session):
270
- session.results = dict()
271
-
272
- # Hook function called each time a report is generated by a test
273
- # The report is added to a list in the session
274
- # so it can be used later in pytest_sessionfinish to generate the global report md file
275
- @pytest.hookimpl(tryfirst=True, hookwrapper=True)
276
- def pytest_runtest_makereport(self, item, call):
277
- outcome = yield
278
- result = outcome.get_result()
279
-
280
- if result.when == 'call':
281
- # Add the result of the test to the session
282
- item.session.results[item] = result
283
-
284
- # Take all the test results in session, parse them, split them in mustrd and standard pytest and generate md file
285
- def pytest_sessionfinish(self, session: Session, exitstatus):
286
- # if md path has not been defined in argument, then do not generate md file
287
- if not self.md_path:
288
- return
289
-
290
- test_results = []
291
- for test_conf, result in session.results.items():
292
- # Case auto generated tests
293
- if test_conf.originalname != test_conf.name:
294
- module_name = test_conf.parent.name
295
- class_name = test_conf.originalname
296
- test_name = test_conf.name.replace(class_name, "").replace("[", "").replace("]", "")
297
- is_mustrd = True
298
- # Case normal unit tests
299
- else:
300
- module_name = test_conf.parent.parent.name
301
- class_name = test_conf.parent.name
302
- test_name = test_conf.originalname
303
- is_mustrd = False
304
-
305
- test_results.append(TestResult(test_name, class_name, module_name, result.outcome, is_mustrd))
306
-
307
- result_list = ResultList(None, get_result_list(test_results,
308
- lambda result: result.type,
309
- lambda result: result.module_name,
310
- lambda result: result.class_name),
311
- False)
312
-
313
- md = result_list.render()
314
- with open(self.md_path, 'w') as file:
315
- file.write(md)
316
-
317
-
318
- # Function called in the test to actually run it
319
- def run_test_spec(test_spec):
320
- if isinstance(test_spec, SpecSkipped):
321
- pytest.skip(f"Invalid configuration, error : {test_spec.message}")
322
- result = run_spec(test_spec)
323
-
324
- result_type = type(result)
325
- if result_type == SpecSkipped:
326
- # FIXME: Better exception management
327
- pytest.skip("Unsupported configuration")
328
- return result_type == SpecPassed
1
+ """
2
+ MIT License
3
+
4
+ Copyright (c) 2023 Semantic Partners Ltd
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+
25
+ from dataclasses import dataclass
26
+ import pytest
27
+ import os
28
+ from pathlib import Path
29
+ from rdflib.namespace import Namespace
30
+ from rdflib import Graph, RDF
31
+ from pytest import Session
32
+
33
+ from mustrd.TestResult import ResultList, TestResult, get_result_list
34
+ from mustrd.utils import get_mustrd_root
35
+ from mustrd.mustrd import get_triple_store_graph, get_triple_stores
36
+ from mustrd.mustrd import Specification, SpecSkipped, validate_specs, get_specs, SpecPassed, run_spec
37
+ from mustrd.namespace import MUST, TRIPLESTORE, MUSTRDTEST
38
+ from typing import Union
39
+ from pyshacl import validate
40
+
41
+ spnamespace = Namespace("https://semanticpartners.com/data/test/")
42
+
43
+ mustrd_root = get_mustrd_root()
44
+
45
+ MUSTRD_PYTEST_PATH = "mustrd_tests/"
46
+
47
+
48
+ def pytest_addoption(parser):
49
+ group = parser.getgroup("mustrd option")
50
+ group.addoption(
51
+ "--mustrd",
52
+ action="store_true",
53
+ dest="mustrd",
54
+ help="Activate/deactivate mustrd test generation.",
55
+ )
56
+ group.addoption(
57
+ "--md",
58
+ action="store",
59
+ dest="mdpath",
60
+ metavar="pathToMdSummary",
61
+ default=None,
62
+ help="create md summary file at that path.",
63
+ )
64
+ group.addoption(
65
+ "--config",
66
+ action="store",
67
+ dest="configpath",
68
+ metavar="pathToTestConfig",
69
+ default=None,
70
+ help="Ttl file containing the list of test to construct.",
71
+ )
72
+ group.addoption(
73
+ "--secrets",
74
+ action="store",
75
+ dest="secrets",
76
+ metavar="Secrets",
77
+ default=None,
78
+ help="Give the secrets by command line in order to be able to store secrets safely in CI tools",
79
+ )
80
+ return
81
+
82
+
83
+ def pytest_configure(config) -> None:
84
+ # Read configuration file
85
+ if config.getoption("mustrd"):
86
+ test_configs = parse_config(config.getoption("configpath"))
87
+ config.pluginmanager.register(MustrdTestPlugin(config.getoption("mdpath"),
88
+ test_configs, config.getoption("secrets")))
89
+
90
+ def parse_config(config_path):
91
+ test_configs = []
92
+ config_graph = Graph().parse(config_path)
93
+ shacl_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl")))
94
+ ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl")))
95
+ conforms, results_graph, results_text = validate(
96
+ data_graph= config_graph,
97
+ shacl_graph = shacl_graph,
98
+ ont_graph = ont_graph,
99
+ advanced= True,
100
+ inference= 'none'
101
+ )
102
+ if not conforms:
103
+ raise ValueError(f"Mustrd test configuration not conform to the shapes. SHACL report: {results_text}", results_graph)
104
+
105
+ for test_config_subject in config_graph.subjects(predicate=RDF.type, object=MUSTRDTEST.MustrdTest):
106
+ spec_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str)
107
+ data_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str)
108
+ triplestore_spec_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str)
109
+ pytest_path = get_config_param(config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str)
110
+ filter_on_tripleStore = list(config_graph.objects(subject=test_config_subject,
111
+ predicate=MUSTRDTEST.filterOnTripleStore))
112
+
113
+ test_configs.append(TestConfig(spec_path=spec_path, data_path=data_path,
114
+ triplestore_spec_path=triplestore_spec_path,
115
+ pytest_path = pytest_path,
116
+ filter_on_tripleStore=filter_on_tripleStore))
117
+ return test_configs
118
+
119
+
120
+
121
+ def get_config_param(config_graph, config_subject, config_param, convert_function):
122
+ raw_value = config_graph.value(subject=config_subject, predicate=config_param, any=True)
123
+ return convert_function(raw_value) if raw_value else None
124
+
125
+
126
+ @dataclass
127
+ class TestConfig:
128
+ spec_path: str
129
+ data_path: str
130
+ triplestore_spec_path: str
131
+ pytest_path: str
132
+ filter_on_tripleStore: str = None
133
+
134
+
135
+ @dataclass
136
+ class TestParamWrapper:
137
+ test_config: TestConfig
138
+ unit_test: Union[Specification, SpecSkipped]
139
+
140
+ class MustrdTestPlugin:
141
+ md_path: str
142
+ test_configs: list
143
+ secrets: str
144
+ unit_tests: Union[Specification, SpecSkipped]
145
+ items: list
146
+
147
+ def __init__(self, md_path, test_configs, secrets):
148
+ self.md_path = md_path
149
+ self.test_configs = test_configs
150
+ self.secrets = secrets
151
+ self.items = []
152
+
153
+ @pytest.hookimpl(tryfirst=True)
154
+ def pytest_collection(self, session):
155
+ self.unit_tests = []
156
+ args = session.config.args
157
+ if len(args) > 0:
158
+ file_name = self.get_file_name_from_arg(args[0])
159
+ # Filter test to collect only specified path
160
+ config_to_collect = list(filter(lambda config:
161
+ # Case we want to collect everything
162
+ MUSTRD_PYTEST_PATH not in args[0]
163
+ # Case we want to collect a test or sub test
164
+ or (config.pytest_path or "") in args[0]
165
+ # Case we want to collect a whole test folder
166
+ or args[0].replace(f"./{MUSTRD_PYTEST_PATH}", "") in config.pytest_path,
167
+ self.test_configs))
168
+
169
+ # Redirect everything to test_mustrd.py, no need to filter on specified test: Only specified test will be collected anyway
170
+ session.config.args[0] = os.path.join(mustrd_root, "test/test_mustrd.py")
171
+ # Collecting only relevant tests
172
+
173
+ for one_test_config in config_to_collect:
174
+ triple_stores = self.get_triple_stores_from_file(one_test_config)
175
+
176
+ if one_test_config.filter_on_tripleStore and not triple_stores:
177
+ self.unit_tests.extend(list(map(lambda triple_store:
178
+ TestParamWrapper(test_config = one_test_config, unit_test=SpecSkipped(MUST.TestSpec, triple_store, "No triplestore found")),
179
+ one_test_config.filter_on_tripleStore)))
180
+ else:
181
+ specs = self.generate_tests_for_config({"spec_path": Path(one_test_config.spec_path),
182
+ "data_path": Path(one_test_config.data_path)},
183
+ triple_stores, file_name)
184
+ self.unit_tests.extend(list(map(lambda spec: TestParamWrapper(test_config = one_test_config, unit_test=spec),specs)))
185
+
186
+ def get_file_name_from_arg(self, arg):
187
+ if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl@" in arg:
188
+ return arg[arg.index("[") + 1: arg.index(".mustrd.ttl@")]
189
+ return None
190
+
191
+
192
+ @pytest.hookimpl(hookwrapper=True)
193
+ def pytest_pycollect_makeitem(self, collector, name, obj):
194
+ report = yield
195
+ if name == "test_unit":
196
+ items = report.get_result()
197
+ new_results = []
198
+ for item in items:
199
+ virtual_path = MUSTRD_PYTEST_PATH + (item.callspec.params["unit_tests"].test_config.pytest_path or "default")
200
+ item.fspath = Path(virtual_path)
201
+ item._nodeid = virtual_path + "::" + item.name
202
+ self.items.append(item)
203
+ new_results.append(item)
204
+ return new_results
205
+
206
+
207
+ # Hook called at collection time: reads the configuration of the tests, and generate pytests from it
208
+ def pytest_generate_tests(self, metafunc):
209
+ if len(metafunc.fixturenames) > 0:
210
+ if metafunc.function.__name__ == "test_unit":
211
+ # Create the test in itself
212
+ if self.unit_tests:
213
+ metafunc.parametrize(metafunc.fixturenames[0], self.unit_tests,
214
+ ids=lambda test_param: (test_param.unit_test.spec_file_name or "") + "@" +
215
+ (test_param.test_config.pytest_path or ""))
216
+ else:
217
+ metafunc.parametrize(metafunc.fixturenames[0],
218
+ [SpecSkipped(MUST.TestSpec, None, "No triplestore found")],
219
+ ids=lambda x: "No configuration found for this test")
220
+
221
+
222
+
223
+
224
+ # Generate test for each triple store available
225
+ def generate_tests_for_config(self, config, triple_stores, file_name):
226
+
227
+ shacl_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/mustrdShapes.ttl")))
228
+ ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/ontology.ttl")))
229
+ valid_spec_uris, spec_graph, invalid_spec_results = validate_specs(config, triple_stores,
230
+ shacl_graph, ont_graph, file_name or "*")
231
+
232
+ specs, skipped_spec_results = \
233
+ get_specs(valid_spec_uris, spec_graph, triple_stores, config)
234
+
235
+ # Return normal specs + skipped results
236
+ return specs + skipped_spec_results + invalid_spec_results
237
+
238
+ # Function called to generate the name of the test
239
+ def get_test_name(self, spec):
240
+ # FIXME: SpecSkipped should have the same structure?
241
+ if isinstance(spec, SpecSkipped):
242
+ triple_store = spec.triple_store
243
+ else:
244
+ triple_store = spec.triple_store['type']
245
+ triple_store_name = triple_store.replace("https://mustrd.com/model/", "")
246
+ test_name = spec.spec_uri.replace(spnamespace, "").replace("_", " ")
247
+ return triple_store_name + ": " + test_name
248
+
249
+ # Get triple store configuration or default
250
+ def get_triple_stores_from_file(self, test_config):
251
+ if test_config.triplestore_spec_path:
252
+ try:
253
+ triple_stores = get_triple_stores(get_triple_store_graph(Path(test_config.triplestore_spec_path),
254
+ self.secrets))
255
+ except Exception as e:
256
+ print(f"""Triplestore configuration parsing failed {test_config.triplestore_spec_path}.
257
+ Only rdflib will be executed""", e)
258
+ triple_stores = [{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
259
+ else:
260
+ print("No triple store configuration required: using embedded rdflib")
261
+ triple_stores = [{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
262
+
263
+ if test_config.filter_on_tripleStore:
264
+ triple_stores = list(filter(lambda triple_store: (triple_store["uri"] in test_config.filter_on_tripleStore),
265
+ triple_stores))
266
+ return triple_stores
267
+
268
+ # Hook function. Initialize the list of result in session
269
+ def pytest_sessionstart(self, session):
270
+ session.results = dict()
271
+
272
+ # Hook function called each time a report is generated by a test
273
+ # The report is added to a list in the session
274
+ # so it can be used later in pytest_sessionfinish to generate the global report md file
275
+ @pytest.hookimpl(tryfirst=True, hookwrapper=True)
276
+ def pytest_runtest_makereport(self, item, call):
277
+ outcome = yield
278
+ result = outcome.get_result()
279
+
280
+ if result.when == 'call':
281
+ # Add the result of the test to the session
282
+ item.session.results[item] = result
283
+
284
+ # Take all the test results in session, parse them, split them in mustrd and standard pytest and generate md file
285
+ def pytest_sessionfinish(self, session: Session, exitstatus):
286
+ # if md path has not been defined in argument, then do not generate md file
287
+ if not self.md_path:
288
+ return
289
+
290
+ test_results = []
291
+ for test_conf, result in session.results.items():
292
+ # Case auto generated tests
293
+ if test_conf.originalname != test_conf.name:
294
+ module_name = test_conf.parent.name
295
+ class_name = test_conf.originalname
296
+ test_name = test_conf.name.replace(class_name, "").replace("[", "").replace("]", "")
297
+ is_mustrd = True
298
+ # Case normal unit tests
299
+ else:
300
+ module_name = test_conf.parent.parent.name
301
+ class_name = test_conf.parent.name
302
+ test_name = test_conf.originalname
303
+ is_mustrd = False
304
+
305
+ test_results.append(TestResult(test_name, class_name, module_name, result.outcome, is_mustrd))
306
+
307
+ result_list = ResultList(None, get_result_list(test_results,
308
+ lambda result: result.type,
309
+ lambda result: is_mustrd and result.test_name.split("@")[1]),
310
+ False)
311
+
312
+ md = result_list.render()
313
+ with open(self.md_path, 'w') as file:
314
+ file.write(md)
315
+
316
+
317
+ # Function called in the test to actually run it
318
+ def run_test_spec(test_spec):
319
+ if isinstance(test_spec, SpecSkipped):
320
+ pytest.skip(f"Invalid configuration, error : {test_spec.message}")
321
+ result = run_spec(test_spec)
322
+
323
+ result_type = type(result)
324
+ if result_type == SpecSkipped:
325
+ # FIXME: Better exception management
326
+ pytest.skip("Unsupported configuration")
327
+ return result_type == SpecPassed