robotframework-robotlog2rqm 1.4.2__tar.gz → 1.6.0__tar.gz

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.
Files changed (27) hide show
  1. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/PKG-INFO +79 -17
  2. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/README.rst +65 -11
  3. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/CRQM.py +169 -10
  4. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RobotLog2RQM.pdf +0 -0
  5. robotframework_robotlog2rqm-1.6.0/RobotLog2RQM/logger.py +180 -0
  6. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/robotlog2rqm.py +3 -158
  7. robotframework_robotlog2rqm-1.6.0/RobotLog2RQM/rqmtool.py +392 -0
  8. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/version.py +2 -2
  9. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/robotframework_robotlog2rqm.egg-info/PKG-INFO +79 -17
  10. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/robotframework_robotlog2rqm.egg-info/SOURCES.txt +2 -0
  11. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/robotframework_robotlog2rqm.egg-info/entry_points.txt +1 -1
  12. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/LICENSE +0 -0
  13. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/buildrecord.xml +0 -0
  14. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/configuration.xml +0 -0
  15. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/executionresult.xml +0 -0
  16. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/executionworkitem.xml +0 -0
  17. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml +0 -0
  18. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/testcase.xml +0 -0
  19. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/testsuite.xml +0 -0
  20. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/RQM_templates/testsuitelog.xml +0 -0
  21. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/__init__.py +0 -0
  22. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/RobotLog2RQM/__main__.py +0 -0
  23. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/robotframework_robotlog2rqm.egg-info/dependency_links.txt +0 -0
  24. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/robotframework_robotlog2rqm.egg-info/requires.txt +0 -0
  25. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/robotframework_robotlog2rqm.egg-info/top_level.txt +0 -0
  26. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/setup.cfg +0 -0
  27. {robotframework-robotlog2rqm-1.4.2 → robotframework_robotlog2rqm-1.6.0}/setup.py +0 -0
@@ -1,12 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.4.2
3
+ Version: 1.6.0
4
4
  Summary: Imports robot result(s) to IBM Rational Quality Manager (RQM)
5
5
  Home-page: https://github.com/test-fullautomation/robotframework-robotlog2rqm
6
6
  Author: Tran Duy Ngoan
7
7
  Author-email: Ngoan.TranDuy@vn.bosch.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Classifier: Programming Language :: Python :: 3
11
9
  Classifier: License :: OSI Approved :: Apache Software License
12
10
  Classifier: Operating System :: OS Independent
@@ -16,6 +14,20 @@ Classifier: Topic :: Software Development
16
14
  Requires-Python: >=3.0
17
15
  Description-Content-Type: text/markdown
18
16
  License-File: LICENSE
17
+ Requires-Dist: lxml
18
+ Requires-Dist: requests
19
+ Requires-Dist: colorama
20
+ Requires-Dist: robotframework
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license-file
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
19
31
 
20
32
  # RobotLog2RQM Description
21
33
 
@@ -125,15 +137,15 @@ Use below command to get tools\'s usage:
125
137
 
126
138
  The usage should be showed as below:
127
139
 
128
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
129
- [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
140
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
141
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
130
142
  resultxmlfile host project user password testplan
131
143
 
132
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
144
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
133
145
  Robot Framework into an IBM Rational Quality Manager.
134
146
 
135
147
  positional arguments:
136
- resultxmlfile absolute or relative path to the xml result file
148
+ resultxmlfile absolute or relative path to the xml result file
137
149
  or directory of result files to be imported.
138
150
  host RQM host url.
139
151
  project project on RQM.
@@ -145,19 +157,19 @@ The usage should be showed as below:
145
157
  -h, --help show this help message and exit
146
158
  -v, --version Version of the RobotLog2RQM importer.
147
159
  --testsuite TESTSUITE
148
- testsuite ID for this execution. If 'new', then create a new
160
+ testsuite ID for this execution. If 'new', then create a new
149
161
  testsuite for this execution.
150
- --recursive if set, then the path is searched recursively for
162
+ --recursive if set, then the path is searched recursively for
151
163
  log files to be imported.
152
- --createmissing if set, then all testcases without tcid are created
164
+ --createmissing if set, then all testcases without tcid are created
153
165
  when importing.
154
- --updatetestcase if set, then testcase information on RQM will be updated
166
+ --updatetestcase if set, then testcase information on RQM will be updated
155
167
  bases on robot testfile.
156
- --dryrun if set, then verify all input arguments
168
+ --dryrun if set, then verify all input arguments
157
169
  (includes RQM authentication) and show what would be done.
158
- --stream STREAM project stream. Note, requires Configuration Management (CM)
170
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
159
171
  to be enabled for the project area.
160
- --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
172
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
161
173
  or Baselines Only to be enabled for the project area.
162
174
 
163
175
  The below command is simple usage witth all required arguments to import
@@ -209,6 +221,58 @@ Then, open RQM with your favourite browser and you will see that the
209
221
  test case execution records and their results are imported in the given
210
222
  testplan ID.
211
223
 
224
+ # RQMTool Submodule
225
+
226
+ The package now includes a submodule **RQMTool** which provides a
227
+ standalone CLI to fetch test cases and test suites from IBM RQM test
228
+ plans.
229
+
230
+ RQMTool is accessible as a Python module:
231
+
232
+ python -m RobotLog2RQM.rqmtool --host <RQM_SERVER_URL> --project <PROJECT_AREA> \
233
+ --user <USERNAME> --password <PASSWORD> \
234
+ --testplan <TESTPLAN_ID> [--types <artifact_types>] \
235
+ [--format <csv|json>] [--output-dir <DIR>] \
236
+ [--basename <BASENAME>] [--dryrun]
237
+
238
+ Main features:
239
+
240
+ - Fetches selected artifact types ([testcase]{.title-ref} or
241
+ [testsuite]{.title-ref}) from a given test plan.
242
+ - Supports CSV or JSON export, with automatic filenames including the
243
+ test plan ID.
244
+
245
+ ## How to use RQMTool
246
+
247
+ RQMTool requires RQM information (host URL, project, credentials) and a
248
+ test plan ID. Use the [-h]{.title-ref} argument to see full usage:
249
+
250
+ python -m RobotLog2RQM.rqmtool -h
251
+
252
+ This will display all available command line options for RQMTool, such
253
+ as:
254
+
255
+ - [\--types]{.title-ref} : artifact types to fetch
256
+ ([testcase]{.title-ref}, [testsuite]{.title-ref}). Default: both.
257
+ - \`\--format\`: output format, either [csv]{.title-ref} or
258
+ [json]{.title-ref}.
259
+ - \`\--output-dir\`: directory to save exported files.
260
+ - \`\--basename\`: base name for output files.
261
+ - \`\--dryrun\`: validate inputs and RQM login without fetching data.
262
+
263
+ ## Example
264
+
265
+ Fetch all test cases and test suites from test plan ID 720 and export to
266
+ CSV:
267
+
268
+ python -m RobotLog2RQM.rqmtool --host https://sample-rqm-host.com \
269
+ --project ROBFW-AIO \
270
+ --user test_user \
271
+ --password test_pw \
272
+ --testplan 720 \
273
+ --types testcase,testsuite \
274
+ --format csv
275
+
212
276
  ### Sourcecode Documentation
213
277
 
214
278
  To understand more detail about the tool\'s features and how Robot test
@@ -256,5 +320,3 @@ distributed under the License is distributed on an \"AS IS\" BASIS,
256
320
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
257
321
  See the License for the specific language governing permissions and
258
322
  limitations under the License.
259
-
260
-
@@ -112,15 +112,15 @@ The usage should be showed as below:
112
112
 
113
113
  ::
114
114
 
115
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
116
- [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
115
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
116
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
117
117
  resultxmlfile host project user password testplan
118
118
 
119
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
119
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
120
120
  Robot Framework into an IBM Rational Quality Manager.
121
121
 
122
122
  positional arguments:
123
- resultxmlfile absolute or relative path to the xml result file
123
+ resultxmlfile absolute or relative path to the xml result file
124
124
  or directory of result files to be imported.
125
125
  host RQM host url.
126
126
  project project on RQM.
@@ -132,19 +132,19 @@ The usage should be showed as below:
132
132
  -h, --help show this help message and exit
133
133
  -v, --version Version of the RobotLog2RQM importer.
134
134
  --testsuite TESTSUITE
135
- testsuite ID for this execution. If 'new', then create a new
135
+ testsuite ID for this execution. If 'new', then create a new
136
136
  testsuite for this execution.
137
- --recursive if set, then the path is searched recursively for
137
+ --recursive if set, then the path is searched recursively for
138
138
  log files to be imported.
139
- --createmissing if set, then all testcases without tcid are created
139
+ --createmissing if set, then all testcases without tcid are created
140
140
  when importing.
141
- --updatetestcase if set, then testcase information on RQM will be updated
141
+ --updatetestcase if set, then testcase information on RQM will be updated
142
142
  bases on robot testfile.
143
- --dryrun if set, then verify all input arguments
143
+ --dryrun if set, then verify all input arguments
144
144
  (includes RQM authentication) and show what would be done.
145
- --stream STREAM project stream. Note, requires Configuration Management (CM)
145
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
146
146
  to be enabled for the project area.
147
- --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
147
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
148
148
  or Baselines Only to be enabled for the project area.
149
149
 
150
150
 
@@ -203,6 +203,60 @@ at ``https://sample-rqm-host.com``
203
203
  Then, open RQM with your favourite browser and you will see that the test case
204
204
  execution records and their results are imported in the given testplan ID.
205
205
 
206
+ RQMTool Submodule
207
+ =================
208
+
209
+ The package now includes a submodule **RQMTool** which provides a standalone CLI to fetch test cases
210
+ and test suites from IBM RQM test plans.
211
+
212
+ RQMTool is accessible as a Python module:
213
+
214
+ ::
215
+
216
+ python -m RobotLog2RQM.rqmtool --host <RQM_SERVER_URL> --project <PROJECT_AREA> \
217
+ --user <USERNAME> --password <PASSWORD> \
218
+ --testplan <TESTPLAN_ID> [--types <artifact_types>] \
219
+ [--format <csv|json>] [--output-dir <DIR>] \
220
+ [--basename <BASENAME>] [--dryrun]
221
+
222
+ Main features:
223
+
224
+ * Fetches selected artifact types (`testcase` or `testsuite`) from a given test plan.
225
+ * Supports CSV or JSON export, with automatic filenames including the test plan ID.
226
+
227
+ How to use RQMTool
228
+ ------------------
229
+
230
+ RQMTool requires RQM information (host URL, project, credentials) and a test plan ID.
231
+ Use the `-h` argument to see full usage:
232
+
233
+ ::
234
+
235
+ python -m RobotLog2RQM.rqmtool -h
236
+
237
+ This will display all available command line options for RQMTool, such as:
238
+
239
+ * `--types` : artifact types to fetch (`testcase`, `testsuite`). Default: both.
240
+ * `--format`: output format, either `csv` or `json`.
241
+ * `--output-dir`: directory to save exported files.
242
+ * `--basename`: base name for output files.
243
+ * `--dryrun`: validate inputs and RQM login without fetching data.
244
+
245
+ Example
246
+ -------
247
+
248
+ Fetch all test cases and test suites from test plan ID 720 and export to CSV:
249
+
250
+ ::
251
+
252
+ python -m RobotLog2RQM.rqmtool --host https://sample-rqm-host.com \
253
+ --project ROBFW-AIO \
254
+ --user test_user \
255
+ --password test_pw \
256
+ --testplan 720 \
257
+ --types testcase,testsuite \
258
+ --format csv
259
+
206
260
  Sourcecode Documentation
207
261
  ~~~~~~~~~~~~~~~~~~~~~~~~
208
262
 
@@ -308,9 +308,9 @@ Disconnect from RQM.
308
308
  """
309
309
  self.session.close()
310
310
 
311
- def config(self, plan_id, build_name=None, config_name=None,
311
+ def config(self, plan_id=None, build_name=None, config_name=None,
312
312
  createmissing=False, updatetestcase=False, suite_id=None,
313
- stream=None, baseline=None, naming_convention=None):
313
+ stream=None, baseline=None, naming_convention=None, fetch_team_areas=True):
314
314
  """
315
315
  Configure RQMClient with testplan ID, build, configuration, createmissing, ...
316
316
 
@@ -322,7 +322,7 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
322
322
 
323
323
  * ``plan_id``
324
324
 
325
- / *Condition*: required / *Type*: str /
325
+ / *Condition*: optional / *Type*: str /
326
326
 
327
327
  Testplan ID of RQM project for importing result(s).
328
328
 
@@ -352,12 +352,36 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
352
352
 
353
353
  If `True`, the information of testcase on RQM will be updated bases on robot testfile.
354
354
 
355
- * ``suite_id (optional)``
355
+ * ``suite_id``
356
356
 
357
357
  / *Condition*: optional / *Type*: str / *Default*: None /
358
358
 
359
359
  Testsuite ID of RQM project for importing result(s).
360
360
 
361
+ * ``stream``
362
+
363
+ / *Condition*: optional / *Type*: str / *Default*: None /
364
+
365
+ Project stream which is enabled in Configuration Management (CM).
366
+
367
+ * ``baseline``
368
+
369
+ / *Condition*: optional / *Type*: str / *Default*: None /
370
+
371
+ Project baseline which is enabled in Configuration Management (CM).
372
+
373
+ * ``naming_convention``
374
+
375
+ / *Condition*: optional / *Type*: dict / *Default*: None /
376
+
377
+ Naming convention for the creating new resources (testcase, TCER, ...).
378
+
379
+ * ``fetch_team_areas``
380
+
381
+ / *Condition*: optional / *Type*: bool / *Default*: True /
382
+
383
+ If `True`, fetch team-areas information for further process.
384
+
361
385
  **Returns:**
362
386
 
363
387
  (*no returns*)
@@ -411,12 +435,13 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
411
435
  raise Exception("Get all baselines failed. Reason: %s"%res['message'])
412
436
 
413
437
  # Verify testplan ID
414
- res_plan = self.getResourceByID('testplan', plan_id)
415
- if res_plan.status_code != 200:
416
- raise Exception('Testplan with ID %s is not existing!'%str(plan_id))
438
+ if plan_id != None:
439
+ res_plan = self.getResourceByID('testplan', plan_id)
440
+ if res_plan.status_code != 200:
441
+ raise Exception('Testplan with ID %s is not existing!'%str(plan_id))
417
442
 
418
- oTestplan = get_xml_tree(BytesIO(str(res_plan.text).encode()), bdtd_validation=False)
419
- self.testplan.name = oTestplan.find('ns4:title', oTestplan.getroot().nsmap).text
443
+ oTestplan = get_xml_tree(BytesIO(str(res_plan.text).encode()), bdtd_validation=False)
444
+ self.testplan.name = oTestplan.find('ns4:title', oTestplan.getroot().nsmap).text
420
445
 
421
446
  # Verify and create build version if required
422
447
  if build_name != None:
@@ -453,7 +478,8 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
453
478
  self.testsuite.name = oTestsuite.find('ns4:title', oTestsuite.getroot().nsmap).text
454
479
 
455
480
  # get all team-areas for testcase template
456
- self.getAllTeamAreas()
481
+ if fetch_team_areas:
482
+ self.getAllTeamAreas()
457
483
 
458
484
 
459
485
  except Exception as error:
@@ -680,6 +706,54 @@ Return the name for given resource bases on the naming convention
680
706
  except:
681
707
  raise Exception(f"Failed to generate name for {resource} '{name}'")
682
708
 
709
+ def __parse_test_artifact(self, xml_data):
710
+ """
711
+ Parse XML test case response and extract its data.
712
+
713
+ **Arguments:**
714
+
715
+ * ``xml_data``
716
+
717
+ / *Condition*: required / *Type*: str /
718
+
719
+ The response text from request to get test case as xml string.
720
+
721
+ **Returns:**
722
+
723
+ * / *Type*: dict /
724
+
725
+ The dictionary which contains the parsed data from given xml.
726
+
727
+ .. code:: python
728
+
729
+ {
730
+ 'id': ...,
731
+ 'name': ...,
732
+ 'url': ...,
733
+ ...
734
+ }
735
+
736
+ """
737
+ test_tree = get_xml_tree(BytesIO(str(xml_data).encode()), bdtd_validation=False)
738
+ nsmap = test_tree.getroot().nsmap
739
+
740
+ test_name = test_tree.find('ns4:title', test_tree.getroot().nsmap)
741
+ test_web_id = test_tree.find('ns2:webId', test_tree.getroot().nsmap)
742
+ test_url = test_tree.find('ns4:identifier', test_tree.getroot().nsmap)
743
+ test_category = test_tree.findall('ns2:category', test_tree.getroot().nsmap)
744
+ test_custom_attr = test_tree.findall('.//ns2:customAttribute', test_tree.getroot().nsmap)
745
+ test_data = {'id': test_web_id.text,
746
+ 'name': test_name.text if test_name is not None else '',
747
+ 'url': test_url.text}
748
+ for item in test_category:
749
+ test_data[item.attrib.get('term')] = item.attrib.get('value')
750
+ for attr in test_custom_attr:
751
+ try:
752
+ test_data[attr.find("ns2:name", nsmap).text] = attr.find("ns2:value", nsmap).text
753
+ except:
754
+ pass
755
+
756
+ return test_data
683
757
  #
684
758
  # Methods to get resources
685
759
  #
@@ -849,6 +923,91 @@ Example:
849
923
  else:
850
924
  raise Exception(f"Could not get 'team-areas' of project '{self.projectname}'.")
851
925
 
926
+ def getTestArtifactsFromResource(self, resource_type, resource_id, artifact_types):
927
+ """
928
+ Get all test cases and test suites associated with a given test plan.
929
+
930
+ **Arguments:**
931
+
932
+ * ``resource_type``
933
+
934
+ / *Condition*: required / *Type*: str /
935
+
936
+ The RQM test resource (`testplan` or `testsuite`) to get test artifact(s).
937
+
938
+ * ``resource_id``
939
+
940
+ / *Condition*: required / *Type*: str /
941
+
942
+ The RQM test plan/suite to get test artifact(s).
943
+
944
+ * ``artifact_types``
945
+
946
+ / *Condition*: required / *Type*: list /
947
+
948
+ List of artifact types (`testcase`, `testsuite`) for fetching.
949
+
950
+ **Returns:**
951
+
952
+ * / *Type*: dict /
953
+
954
+ A dictionary containing fetched artifacts:
955
+
956
+ .. code:: python
957
+
958
+ {
959
+ 'testcase': [{'id': ..., 'name': ..., 'url': ...}, ...],
960
+ 'testsuite': [{'id': ..., 'name': ..., 'url': ...}, ...]
961
+ }
962
+ """
963
+ ALLOW_RESOURCE_TYPES = ['testplan', 'testsuite']
964
+ ALLOW_ARTIFACT_TYPES = ['testcase', 'testsuite']
965
+ result = {}
966
+
967
+ if resource_type not in ALLOW_RESOURCE_TYPES:
968
+ raise ValueError(
969
+ f"Unsupported resource type '{resource_type}'. "
970
+ f"Please use one of the following: {', '.join(ALLOW_RESOURCE_TYPES)}."
971
+ )
972
+
973
+ if isinstance(artifact_types, str):
974
+ artifact_types = [artifact_types]
975
+ elif not isinstance(artifact_types, (list, tuple)):
976
+ raise TypeError(
977
+ f"Invalid type for 'artifact_types': expected str or list, got {type(artifact_types).__name__}."
978
+ )
979
+
980
+ for item in artifact_types:
981
+ artifact_type = item.lower()
982
+ if artifact_type not in ALLOW_ARTIFACT_TYPES:
983
+ raise ValueError(
984
+ f"Unsupported artifact type '{artifact_type}'. "
985
+ f"Please use one of the following: {', '.join(ALLOW_ARTIFACT_TYPES)}."
986
+ )
987
+ result[artifact_type] = []
988
+
989
+ res = self.getResourceByID(resource_type, resource_id)
990
+ if res.status_code != 200:
991
+ raise Exception(f"Failed to get {resource_type} {resource_id}: {res.reason}")
992
+
993
+ oTree = get_xml_tree(BytesIO(str(res.text).encode()), bdtd_validation=False)
994
+ root = oTree.getroot()
995
+ nsmap = root.nsmap
996
+
997
+ for item in artifact_types:
998
+ artifact_type = item.lower()
999
+ # Find all linked test artifact
1000
+ for oTest in root.findall(f'.//ns2:{artifact_type}', nsmap):
1001
+ href = oTest.attrib.get('href')
1002
+ if href:
1003
+ test_id = href.split('/')[-1]
1004
+ # Get testcase/testsuite by ID
1005
+ test_res = self.getResourceByID(artifact_type, test_id)
1006
+ if test_res.status_code == 200:
1007
+ test_data = self.__parse_test_artifact(test_res.text)
1008
+ result[artifact_type].append(test_data)
1009
+
1010
+ return result
852
1011
 
853
1012
  #
854
1013
  # Methods to create XML template for resources
@@ -0,0 +1,180 @@
1
+ # Copyright 2020-2023 Robert Bosch GmbH
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ******************************************************************************
15
+ #
16
+ # File: logging.py
17
+ #
18
+ # Initially created by Tran Duy Ngoan(RBVH/EMC51) / October 2025
19
+ #
20
+ # Logging class
21
+ #
22
+ # History:
23
+ #
24
+ # 2025-10-20:
25
+ # - initial version
26
+ #
27
+ # ******************************************************************************
28
+ import sys
29
+ import colorama as col
30
+ import os
31
+
32
+ class Logger():
33
+ """
34
+ Logger class for logging message.
35
+ """
36
+ output_logfile = None
37
+ output_console = True
38
+ color_normal = col.Fore.WHITE + col.Style.NORMAL
39
+ color_error = col.Fore.RED + col.Style.BRIGHT
40
+ color_warn = col.Fore.YELLOW + col.Style.BRIGHT
41
+ color_reset = col.Style.RESET_ALL + col.Fore.RESET + col.Back.RESET
42
+ prefix_warn = "WARN: "
43
+ prefix_error = "ERROR: "
44
+ prefix_fatalerror = "FATAL ERROR: "
45
+ prefix_all = ""
46
+ dryrun = False
47
+
48
+ @classmethod
49
+ def config(cls, output_console=True, output_logfile=None, dryrun=False):
50
+ """
51
+ Configure Logger class.
52
+
53
+ **Arguments:**
54
+
55
+ * ``output_console``
56
+
57
+ / *Condition*: optional / *Type*: bool / *Default*: True /
58
+
59
+ Write message to console output.
60
+
61
+ * ``output_logfile``
62
+
63
+ / *Condition*: optional / *Type*: str / *Default*: None /
64
+
65
+ Path to log file output.
66
+
67
+ * ``dryrun``
68
+
69
+ / *Condition*: optional / *Type*: bool / *Default*: True /
70
+
71
+ If set, a prefix as 'dryrun' is added for all messages.
72
+
73
+ **Returns:**
74
+
75
+ (*no returns*)
76
+ """
77
+ cls.output_console = output_console
78
+ cls.output_logfile = output_logfile
79
+ cls.dryrun = dryrun
80
+ if cls.dryrun:
81
+ cls.prefix_all = cls.color_warn + "DRYRUN " + cls.color_reset
82
+
83
+ @classmethod
84
+ def log(cls, msg='', color=None, indent=0):
85
+ """
86
+ Write log message to console/file output.
87
+
88
+ **Arguments:**
89
+
90
+ * ``msg``
91
+
92
+ / *Condition*: optional / *Type*: str / *Default*: '' /
93
+
94
+ Message which is written to output.
95
+
96
+ * ``color``
97
+
98
+ / *Condition*: optional / *Type*: str / *Default*: None /
99
+
100
+ Color style for the message.
101
+
102
+ * ``indent``
103
+
104
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
105
+
106
+ Offset indent.
107
+
108
+ **Returns:**
109
+
110
+ (*no returns*)
111
+ """
112
+ if color is None:
113
+ color = cls.color_normal
114
+ if cls.output_console:
115
+ print(cls.prefix_all + cls.color_reset + color + " "*indent + msg + cls.color_reset)
116
+ if cls.output_logfile is not None and os.path.isfile(cls.output_logfile):
117
+ with open(cls.output_logfile, 'a') as f:
118
+ f.write(" "*indent + msg)
119
+ return
120
+
121
+ @classmethod
122
+ def log_warning(cls, msg, indent=0):
123
+ """
124
+ Write warning message to console/file output.
125
+
126
+ **Arguments:**
127
+
128
+ * ``msg``
129
+
130
+ / *Condition*: required / *Type*: str /
131
+
132
+ Warning message which is written to output.
133
+
134
+ * ``indent``
135
+
136
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
137
+
138
+ Offset indent.
139
+
140
+ **Returns:**
141
+
142
+ (*no returns*)
143
+ """
144
+ cls.log(cls.prefix_warn+str(msg), cls.color_warn, indent)
145
+
146
+ @classmethod
147
+ def log_error(cls, msg, fatal_error=False, indent=0):
148
+ """
149
+ Write error message to console/file output.
150
+
151
+ * ``msg``
152
+
153
+ / *Condition*: required / *Type*: str /
154
+
155
+ Error message which is written to output.
156
+
157
+ * ``fatal_error``
158
+
159
+ / *Condition*: optional / *Type*: bool / *Default*: False /
160
+
161
+ If set, tool will terminate after logging error message.
162
+
163
+ * ``indent``
164
+
165
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
166
+
167
+ Offset indent.
168
+
169
+ **Returns:**
170
+
171
+ (*no returns*)
172
+ """
173
+ prefix = cls.prefix_error
174
+ if fatal_error:
175
+ prefix = cls.prefix_fatalerror
176
+
177
+ cls.log(prefix+str(msg), cls.color_error, indent)
178
+ if fatal_error:
179
+ cls.log(f"{sys.argv[0]} has been stopped!", cls.color_error)
180
+ exit(1)