diff --git a/CHANGELOG.md b/CHANGELOG.md index ad463819..bf7030f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ ### 0.9.18 +* Add support to `lobster-codebeamer` to generate output using the following schemas: + - requirement + - implementation + - activity + + The user can select the schema with a command line flag, + or through the configuration file. + * Added a new tool `lobster-cpptest` which can extract references from C++ unit tests using various regex patterns. The references must be provided in a format similar to Doxygen comments. diff --git a/lobster/tools/codebeamer/codebeamer.py b/lobster/tools/codebeamer/codebeamer.py index 2b9d7746..84bdac5b 100755 --- a/lobster/tools/codebeamer/codebeamer.py +++ b/lobster/tools/codebeamer/codebeamer.py @@ -45,7 +45,7 @@ import json import requests -from lobster.items import Tracing_Tag, Requirement +from lobster.items import Tracing_Tag, Requirement, Implementation, Activity from lobster.location import Codebeamer_Reference from lobster.errors import Message_Handler, LOBSTER_Error from lobster.io import lobster_read, lobster_write @@ -54,11 +54,13 @@ REFERENCES = 'references' -class References(Enum): +class SupportedConfigKeys(Enum): REFS = "refs" + SCHEMA = "schema" - -SUPPORTED_REFERENCES = [References.REFS.value] + @classmethod + def as_set(cls) -> set: + return {parameter.value for parameter in cls} def add_refs_refrences(req, flat_values_list): @@ -70,7 +72,7 @@ def add_refs_refrences(req, flat_values_list): map_reference_name_to_function = { - References.REFS.value: add_refs_refrences + SupportedConfigKeys.REFS.value: add_refs_refrences } @@ -190,6 +192,35 @@ def get_query(mh, cb_config, query_id): return rv +def get_schema_config(cb_config): + """ + The function returns a schema map based on the schema mentioned + in the cb_config dictionary. + + If there is no match, it raises a KeyError. + + Positional arguments: + cb_config -- configuration dictionary containing the schema. + + Returns: + A dictionary containing the namespace and class associated with the schema. + + Raises: + KeyError -- if the provided schema is not supported. + """ + schema_map = { + 'requirement': {"namespace": "req", "class": Requirement}, + 'implementation': {"namespace": "imp", "class": Implementation}, + 'activity': {"namespace": "act", "class": Activity}, + } + schema = cb_config.get("schema", "requirement").lower() + + if schema not in schema_map: + raise KeyError(f"Unsupported SCHEMA '{schema}' provided in configuration.") + + return schema_map[schema] + + def to_lobster(cb_config, cb_item): assert isinstance(cb_config, dict) assert isinstance(cb_item, dict) and "id" in cb_item @@ -217,20 +248,15 @@ def to_lobster(cb_config, cb_item): else: item_name = "Unnamed item %u" % cb_item["id"] - req = Requirement( - tag = Tracing_Tag(namespace = "req", - tag = str(cb_item["id"]), - version = cb_item["version"]), - location = Codebeamer_Reference(cb_root = cb_config["root"], - tracker = cb_item["tracker"]["id"], - item = cb_item["id"], - version = cb_item["version"], - name = item_name), - framework = "codebeamer", - kind = kind, - name = item_name, - text = None, - status = status) + schema_config = get_schema_config(cb_config) + + # Construct the appropriate object based on 'kind' + common_params = _create_common_params( + schema_config["namespace"], cb_item, + cb_config["root"], item_name, kind) + item = _create_lobster_item( + schema_config["class"], + common_params, item_name, status) if cb_config.get(REFERENCES): for reference_name, displayed_chosen_names in ( @@ -254,9 +280,74 @@ def to_lobster(cb_config, cb_item): continue (map_reference_name_to_function[reference_name] - (req, flat_values_list)) + (item, flat_values_list)) + + return item + + +def _create_common_params(namespace: str, cb_item: dict, cb_root: str, + item_name: str, kind: str): + """ + Creates and returns common parameters for a Codebeamer item. + Args: + namespace (str): Namespace for the tag. + cb_item (dict): Codebeamer item dictionary. + cb_root (str): Root URL or path of Codebeamer. + item_name (str): Name of the item. + kind (str): Type of the item. + Returns: + dict: Common parameters including tag, location, and kind. + """ + return { + 'tag': Tracing_Tag( + namespace=namespace, + tag=str(cb_item["id"]), + version=cb_item["version"] + ), + 'location': Codebeamer_Reference( + cb_root=cb_root, + tracker=cb_item["tracker"]["id"], + item=cb_item["id"], + version=cb_item["version"], + name=item_name + ), + 'kind': kind + } + - return req +def _create_lobster_item(schema_class, common_params, item_name, status): + """ + Creates and returns a Lobster item based on the schema class. + Args: + schema_class: Class of the schema (Requirement, Implementation, Activity). + common_params (dict): Common parameters for the item. + item_name (str): Name of the item. + status (str): Status of the item. + Returns: + Object: An instance of the schema class with the appropriate parameters. + """ + if schema_class is Requirement: + return Requirement( + **common_params, + framework="codebeamer", + text=None, + status=status, + name= item_name + ) + + elif schema_class is Implementation: + return Implementation( + **common_params, + language="python", + name= item_name, + ) + + else: + return Activity( + **common_params, + framework="codebeamer", + status=status + ) def import_tagged(mh, cb_config, items_to_import): @@ -295,16 +386,20 @@ def parse_cb_config(file_name): json_config["token"] = data.pop(TOKEN) provided_config_keys = set(data.keys()) - supported_references = set(SUPPORTED_REFERENCES) + schema = data.get("schema", "Requirement").lower() - if not provided_config_keys.issubset(supported_references): - raise KeyError("The provided references are not supported! " - "supported referenes: '%s'" % - ', '.join(SUPPORTED_REFERENCES)) + if not provided_config_keys.issubset(SupportedConfigKeys.as_set()): + raise KeyError("The provided config keys are not supported! " + "supported keys: '%s'" % + ', '.join(SupportedConfigKeys.as_set())) for key, value in data.items(): json_config[REFERENCES][key] = ensure_array_of_strings(value) + json_config[key] = ensure_array_of_strings(value) + + json_config["schema"] = schema + return json_config @@ -323,7 +418,7 @@ def main(): ap.add_argument("--config", help=("name of codebeamer " "config file, supported references: '%s'" % - ', '.join(SUPPORTED_REFERENCES)), + ', '.join(SupportedConfigKeys.as_set())), default=None) ap.add_argument("--ignore-ssl-errors", @@ -336,11 +431,17 @@ def main(): default=100, help=("Fetch this many cb items at once (by default 100)," " reduce if you get too many timeouts.")) + ap.add_argument("--timeout", type=int, default=30, help="Timeout in s (by default 30) for each REST call.") + ap.add_argument("--schema", + default='requirement', + help="Specify the output schema" + "(Requirement, Implementation, Activity).") + ap.add_argument("--cb-root", default=os.environ.get("CB_ROOT", None)) ap.add_argument("--cb-user", default=os.environ.get("CB_USERNAME", None)) ap.add_argument("--cb-pass", default=os.environ.get("CB_PASSWORD", None)) @@ -351,6 +452,7 @@ def main(): mh = Message_Handler() cb_config = { + 'schema' : options.schema, "root" : options.cb_root, "base" : "%s/cb/api/v3" % options.cb_root, "user" : options.cb_user, @@ -433,14 +535,15 @@ def main(): except LOBSTER_Error: return 1 + schema_config = get_schema_config(cb_config) + if options.out is None: - lobster_write(sys.stdout, Requirement, "lobster_codebeamer", items) - print() + with sys.stdout as fd: + lobster_write(fd, schema_config["class"], "lobster_codebeamer", items) else: with open(options.out, "w", encoding="UTF-8") as fd: - lobster_write(fd, Requirement, "lobster_codebeamer", items) - print("Written %u requirements to %s" % (len(items), - options.out)) + lobster_write(fd, schema_config["class"], "lobster_codebeamer", items) + print(f"Written {len(items)} requirements to {options.out}") return 0 diff --git a/packages/lobster-tool-codebeamer/README.md b/packages/lobster-tool-codebeamer/README.md index 06cf3545..216a426d 100644 --- a/packages/lobster-tool-codebeamer/README.md +++ b/packages/lobster-tool-codebeamer/README.md @@ -11,7 +11,7 @@ requirements management tool ## Tools -* `lobster-codebeamer`: Extrat requirements from codebeamer. +* `lobster-codebeamer`: Extract requirements from codebeamer. ## Configuration This tool works with an optional config file. @@ -87,6 +87,32 @@ Is it also possible to define the codebeamer token in this file: } ``` +### Schema +You can also specify the type of schema for the resulting output file. The supported values for the schema field are: +- Activity: Sets the schema to lobster-act-trace. +- Implementation: Sets the schema to lobster-imp-trace. +- Requirement: Sets the schema to lobster-req-trace. + +If the schema is not specified, the tool will default to Requirement, and the schema lobster-req-trace will be used. + +Here is an example configuration file: +```json +{ + "schema": "Activity", // Specifies schema + "refs": ["cb-fieldname1", "cb-fieldname2"] // Specifies references +} +``` + +If an invalid schema is provided, the tool will raise an exception. Supported schema values are Activity, Implementation, and Requirement. + +## Command-Line Arguments and Configuration + +When running the tool, you can specify the `--schema` flag via the command line or set the `schema` value in +the configuration file. **The configuration file will always take precedence over the command-line argument**. + +- **`--schema`**: The schema to be used (optional, overrides the config file if provided). +- **Configuration file**: If `schema` is defined in the configuration file, it will be used, and the command-line `--schema` argument will be ignored. + ## Usage There are two ways you can use this tool: @@ -97,6 +123,21 @@ There are two ways you can use this tool: 2. Download all requirements generated by a saved codebeamer query (using `--import-query`) +* Configure the 'refs' upstream reference (this argument is optional) +(using `--config`) + +* Additionally, you can specify the schema and references: + + 1. Specify the schema of trace to be generated (optional, defaults to Requirement) + using the --schema argument, or configure it in the config file. + 2. Configure the 'refs' upstream reference (optional) using --config or + specify directly via command line. + + * For example: + ``` codepython lobster_codebeamer.py --config lobster-codebeamer-config.json --schema "Activity" --references "custom_ref_1``` + + This command will extract activity traces (lobster-act-trace) with specified references. + ## Limitations The key limitation is item text, which is currently not diff --git a/test-unit/lobster-codebeamer/__init__.py b/test-unit/lobster-codebeamer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test-unit/lobster-codebeamer/test_codebeamer_schema.py b/test-unit/lobster-codebeamer/test_codebeamer_schema.py new file mode 100644 index 00000000..8aa718aa --- /dev/null +++ b/test-unit/lobster-codebeamer/test_codebeamer_schema.py @@ -0,0 +1,88 @@ +import unittest +from lobster.items import Tracing_Tag, Requirement, Implementation, Activity +from lobster.location import Codebeamer_Reference +from lobster.errors import LOBSTER_Error +from lobster.tools.codebeamer.codebeamer import _create_common_params, _create_lobster_item + +class TestCreateFunctions(unittest.TestCase): + + + def setUp(self): + self.root_url = 'http://root_url' + self.cb_item_template = { + 'version': 1, + 'tracker': {'id': 123} + } + + + def generate_cb_item(self, item_id, name): + """Generate a codebeamer item dictionary.""" + return { + 'id': item_id, + **self.cb_item_template, + 'name': name + } + + + def generate_common_params(self, namespace, item_name, kind, expected_class): + """Generate a test case for common params and lobster item creation.""" + cb_item = self.generate_cb_item(1, item_name) + common_params = _create_common_params(namespace, cb_item, self.root_url, item_name, kind) + + return { + 'common_params': common_params, + 'item_name': item_name, + 'expected_class': expected_class, + 'tag': Tracing_Tag(namespace, str(cb_item["id"]), cb_item["version"]), + 'location': Codebeamer_Reference(self.root_url, cb_item["tracker"]["id"], cb_item["id"], cb_item["version"], item_name), + 'kind' : kind + } + + + def generate_test_case(self): + + return [ + self.generate_common_params('req', 'Requirement Item', 'requirement', Requirement), + self.generate_common_params('imp', 'Implementation Item', 'implementation', Implementation), + self.generate_common_params('act', 'Activity Item', 'activity', Activity), + ] + + + def test_create_common_params(self): + + test_cases = self.generate_test_case() + + for case in test_cases: + with self.subTest(case=case): + + self.assertEqual(case['common_params']['tag'].namespace, case['tag'].namespace) + self.assertEqual(case['common_params']['tag'].tag, case['tag'].tag) + self.assertEqual(case['common_params']['tag'].version, case['tag'].version) + self.assertEqual(case['common_params']['location'].cb_root, case['location'].cb_root) + self.assertEqual(case['common_params']['location'].tracker, case['location'].tracker) + self.assertEqual(case['common_params']['location'].item, case['location'].item) + self.assertEqual(case['common_params']['location'].version, case['location'].version) + self.assertEqual(case['common_params']['location'].name, case['location'].name) + self.assertEqual(case['common_params']['kind'], case['kind']) + + + def test_create_lobster_item(self): + + test_cases = self.generate_test_case() + for case in test_cases: + with self.subTest(case=case): + lobster_item = _create_lobster_item(case['expected_class'], case['common_params'], case['item_name'], None) + self.assertIsInstance(lobster_item, case['expected_class']) + self.assertEqual(lobster_item.tag, case['common_params']['tag']) + self.assertEqual(lobster_item.location, case['common_params']['location']) + self.assertEqual(lobster_item.kind, case['kind']) + + if case['kind'] == 'requirement': + self.assertEqual(lobster_item.framework, 'codebeamer') + elif case['kind'] == 'implementation': + self.assertEqual(lobster_item.language, 'python') + elif case['kind'] == 'activity': + self.assertEqual(lobster_item.framework, 'codebeamer') + +if __name__ == '__main__': + unittest.main() \ No newline at end of file