diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0041f641..7c5e4b53 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -41,11 +41,7 @@ RUN --mount=type=bind,target=/tmp_setup export DEBIAN_FRONTEND=noninteractive && xargs -a /tmp_setup/deb_requirements.txt apt-get install -y --no-install-recommends && \ xargs -a /tmp_setup/libs/scenario_execution_kubernetes/deb_requirements.txt apt-get install -y --no-install-recommends && \ rosdep update --rosdistro="${ROS_DISTRO}" && \ - for d in /tmp_setup/*; do \ - [[ ! -d "$d" ]] && continue; \ - [[ "$(basename $d)" =~ ^(install|build|log)$ ]] && continue; \ - rosdep install --rosdistro="${ROS_DISTRO}" --from-paths "$d" --ignore-src -r -y; \ - done && \ + rosdep install --rosdistro="${ROS_DISTRO}" --from-paths /tmp_setup --ignore-src -r -y && \ rm -rf /var/lib/apt/lists/* ############################################################################## diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index 4c678152..7875e579 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -58,7 +58,7 @@ jobs: colcon test --packages-select \ scenario_execution \ scenario_execution_os \ - scenario_coverage \ + scenario_execution_coverage \ scenario_execution_test \ --event-handlers console_direct+ \ --return-code-on-test-failure \ @@ -125,7 +125,7 @@ jobs: run: | source /opt/ros/${{ github.event.pull_request.base.ref == 'main' && 'humble' || github.event.pull_request.base.ref }}/setup.bash source install/setup.bash - find . -name "*.osc" | grep -Ev "lib_osc/*|examples/example_scenario_variation|scenario_coverage|fail*|install|build" | while read -r file; do + find . -name "*.osc" | grep -Ev "lib_osc/*|examples/example_scenario_variation|scenario_execution_coverage|fail*|install|build" | while read -r file; do echo "$file"; ros2 run scenario_execution scenario_execution "$file" -n; done @@ -543,7 +543,7 @@ jobs: comment_mode: always files: | downloaded-artifacts/test-scenario-execution/scenario_execution/TEST.xml - downloaded-artifacts/test-scenario-execution/scenario_coverage/TEST.xml + downloaded-artifacts/test-scenario-execution/scenario_execution_coverage/TEST.xml downloaded-artifacts/test-scenario-execution/libs/scenario_execution_os/TEST.xml downloaded-artifacts/test-scenario-execution/test/scenario_execution_test/TEST.xml downloaded-artifacts/test-scenario-execution-ros/scenario_execution_ros/TEST.xml diff --git a/README.md b/README.md index 6912f85c..d2761879 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,6 @@ source install/setup.bash To launch a scenario with ROS2: ```bash -ros2 launch scenario_execution_ros scenario_launch.py scenario:=examples/example_scenario/hello_world.osc live_tree:=True +ros2 run scenario_execution_ros scenario_execution_ros examples/example_scenario/hello_world.osc -t ``` diff --git a/docs/architecture.rst b/docs/architecture.rst index 44b31a4f..c80a9cce 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -66,7 +66,7 @@ Modules - ``scenario_execution``: The base package for scenario execution. It provides the parsing of OpenSCENARIO 2 files and the conversion to py-trees. It's middleware agnostic and can therefore be used as a basis for more specific implementations (e.g. ROS). It also provides basic OpenSCENARIO 2 libraries and actions. - ``scenario_execution_ros``: This package uses ``scenario_execution`` as a basis and implements a ROS2 version of scenario execution. It provides a OpenSCENARIO 2 library with basic ROS2-related actions like publishing on a topic or calling a service. - ``scenario_execution_control``: Provides code to control scenario execution (in ROS2) from another application such as RViz. -- ``scenario_coverage``: Provides tools to generate concrete scenarios from abstract OpenSCENARIO 2 scenario definition and execute them. +- ``scenario_execution_coverage``: Provides tools to generate concrete scenarios from abstract OpenSCENARIO 2 scenario definition and execute them. - ``scenario_execution_gazebo``: Provides a `Gazebo `_-specific OpenSCENARIO 2 library with actions. - ``scenario_execution_interfaces``: Provides ROS2 `interfaces `__, more specifically, messages and services, which are used to interface ROS2 with the ``scenario_execution_control`` package. - ``scenario_execution_rviz``: Contains several `rviz `__ plugins for visualizing and controlling scenarios when working with ROS2. diff --git a/docs/how_to_run.rst b/docs/how_to_run.rst index 1504b6ce..9ec2a054 100644 --- a/docs/how_to_run.rst +++ b/docs/how_to_run.rst @@ -187,15 +187,15 @@ PyQtEngine works on your machine and render web pages correctly. Scenario Coverage ----------------- -The ``scenario_coverage`` package provides the ability to run variations of a scenario from a single scenario definition. It offers a fast and efficient method to test scenario with different attribute values, streamlining the development and testing process. +The ``scenario_execution_coverage`` package provides the ability to run variations of a scenario from a single scenario definition. It offers a fast and efficient method to test scenario with different attribute values, streamlining the development and testing process. -Below are the steps to run a scenario using ``scenario_coverage``.. +Below are the steps to run a scenario using ``scenario_execution_coverage``.. First, build the packages: .. code-block:: bash - colcon build --packages-up-to scenario_coverage + colcon build --packages-up-to scenario_execution_coverage source install/setup.bash Then, generate the scenario files for each variation of scenario using the ``scenario_variation`` executable, you can pass your own custom scenario as an input. For this exercise, we will use a scenario present in :repo_link:`examples/example_scenario_variation/`. diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 2a3a4408..958d21a4 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -315,7 +315,7 @@ Create Scenarios with Variations -------------------------------- In this example, we'll demonstrate how to generate and run multiple scenarios using only one scenario definition. -For this we'll use the :repo_link:`scenario_coverage/scenario_coverage/scenario_variation`. to save the intermediate scenario models in ``.sce`` extension file and then use :repo_link:`scenario_coverage/scenario_coverage/scenario_batch_execution` to execute each generated scenario. +For this we'll use the :repo_link:`scenario_execution_coverage/scenario_execution_coverage/scenario_variation`. to save the intermediate scenario models in ``.sce`` extension file and then use :repo_link:`scenario_execution_coverage/scenario_execution_coverage/scenario_batch_execution` to execute each generated scenario. The scenario file looks as follows: @@ -332,14 +332,14 @@ The scenario file looks as follows: Here, a simple scenario variation example using log action plugin is created and two messages ``foo`` and ``bar`` using the array syntax are passed. -As this is not a concrete scenario, ``scenario_execution`` won't be able to execute it. Instead we'll use ``scenario_variation`` from the ``scenario_coverage`` package to generate all variations and save them to intermediate scenario model files with ``.sce`` extension. +As this is not a concrete scenario, ``scenario_execution`` won't be able to execute it. Instead we'll use ``scenario_variation`` from the ``scenario_execution_coverage`` package to generate all variations and save them to intermediate scenario model files with ``.sce`` extension. Afterwards we could either use ``scenario_execution`` to run each created scenario manually or make use of ``scenario_batch_execution`` which reads all scenarios within a directory and executes them one after the other. -Now, lets try to run this scenario. To do this, first build Packages ``scenario_execution`` and ``scenario_coverage``: +Now, lets try to run this scenario. To do this, first build Packages ``scenario_execution`` and ``scenario_execution_coverage``: .. code-block:: - colcon build --packages-up-to scenario_execution_ros && colcon build --packages-up-to scenario_coverage + colcon build --packages-up-to scenario_execution_ros && colcon build --packages-up-to scenario_execution_coverage * Now, create intermediate scenarios with ``.sce`` extension using the command: diff --git a/examples/example_scenario_variation/README.md b/examples/example_scenario_variation/README.md index 85adfe89..4ff27ac0 100644 --- a/examples/example_scenario_variation/README.md +++ b/examples/example_scenario_variation/README.md @@ -1,9 +1,9 @@ # Example Scenario Variation -To run the Example Scenario Variation with scenario, first build Packages `scenario_execution` and `scenario_coverage`: +To run the Example Scenario Variation with scenario, first build Packages `scenario_execution` and `scenario_execution_coverage`: ```bash -colcon build --packages-up-to scenario_execution && colcon build --packages-up-to scenario_coverage +colcon build --packages-up-to scenario_execution && colcon build --packages-up-to scenario_execution_coverage ``` Source the workspace: diff --git a/scenario_execution_coverage/CHANGELOG.rst b/scenario_execution_coverage/CHANGELOG.rst new file mode 100644 index 00000000..7bdd3f91 --- /dev/null +++ b/scenario_execution_coverage/CHANGELOG.rst @@ -0,0 +1,8 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package scenario_execution_coverage +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1.2.0 (2024-10-02) +------------------ +* Initial creation of coverage package for scenario execution + diff --git a/scenario_coverage/README.md b/scenario_execution_coverage/README.md similarity index 64% rename from scenario_coverage/README.md rename to scenario_execution_coverage/README.md index 1bb44f5e..68300f64 100644 --- a/scenario_coverage/README.md +++ b/scenario_execution_coverage/README.md @@ -1,6 +1,6 @@ -# Scenario Coverage +# Scenario Execution Coverage -The `scenario_coverage` packages provides two tools: +The `scenario_execution_coverage` packages provides two tools: - `scenario_variation`: Create concrete scenarios out of scenario with variation definition - `scenario_batch_execution`: Execute multiple scenarios, one after the other. \ No newline at end of file diff --git a/scenario_coverage/package.xml b/scenario_execution_coverage/package.xml similarity index 95% rename from scenario_coverage/package.xml rename to scenario_execution_coverage/package.xml index 41aef23d..07c01fdf 100644 --- a/scenario_coverage/package.xml +++ b/scenario_execution_coverage/package.xml @@ -1,7 +1,7 @@ - scenario_coverage + scenario_execution_coverage 1.2.0 Robotics Scenario Execution Coverage Tools Intel Labs diff --git a/scenario_coverage/resource/scenario_coverage b/scenario_execution_coverage/resource/scenario_execution_coverage similarity index 100% rename from scenario_coverage/resource/scenario_coverage rename to scenario_execution_coverage/resource/scenario_execution_coverage diff --git a/scenario_coverage/scenario_coverage/__init__.py b/scenario_execution_coverage/scenario_coverage/__init__.py similarity index 100% rename from scenario_coverage/scenario_coverage/__init__.py rename to scenario_execution_coverage/scenario_coverage/__init__.py diff --git a/scenario_coverage/scenario_coverage/scenario_batch_execution.py b/scenario_execution_coverage/scenario_coverage/scenario_batch_execution.py similarity index 100% rename from scenario_coverage/scenario_coverage/scenario_batch_execution.py rename to scenario_execution_coverage/scenario_coverage/scenario_batch_execution.py diff --git a/scenario_coverage/scenario_coverage/scenario_variation.py b/scenario_execution_coverage/scenario_coverage/scenario_variation.py similarity index 100% rename from scenario_coverage/scenario_coverage/scenario_variation.py rename to scenario_execution_coverage/scenario_coverage/scenario_variation.py diff --git a/scenario_execution_coverage/scenario_execution_coverage/__init__.py b/scenario_execution_coverage/scenario_execution_coverage/__init__.py new file mode 100644 index 00000000..3ba13780 --- /dev/null +++ b/scenario_execution_coverage/scenario_execution_coverage/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2024 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/scenario_execution_coverage/scenario_execution_coverage/scenario_batch_execution.py b/scenario_execution_coverage/scenario_execution_coverage/scenario_batch_execution.py new file mode 100644 index 00000000..0a32cf49 --- /dev/null +++ b/scenario_execution_coverage/scenario_execution_coverage/scenario_batch_execution.py @@ -0,0 +1,223 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2024 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys +import argparse +import subprocess # nosec B404 +from threading import Thread +from copy import deepcopy +import signal +from defusedxml import ElementTree as ETparse +import xml.etree.ElementTree as ET # nosec B405 +import logging + + +class ScenarioBatchExecution(object): + + def __init__(self, args) -> None: + self.ignore_process_return_value = args.ignore_process_return_value + if not os.path.isdir(args.output_dir): + try: + os.mkdir(args.output_dir) + except OSError as e: + raise ValueError(f"Could not create output directory: {e}") from e + if not os.access(args.output_dir, os.W_OK): + raise ValueError(f"Output directory '{args.output_dir}' not writable.") + if os.path.exists(os.path.join(args.output_dir, 'test.xml')): + os.remove(os.path.join(args.output_dir, 'test.xml')) + self.output_dir = args.output_dir + + dir_content = os.listdir(args.scenario_dir) + self.scenarios = [] + for entry in dir_content: + if entry.endswith(".sce") or entry.endswith(".osc"): + self.scenarios.append(os.path.join(args.scenario_dir, entry)) + if not self.scenarios: + raise ValueError(f"Directory {args.scenario_dir} does not contain any scenarios.") + self.scenarios.sort() + print(f"Detected {len(self.scenarios)} scenarios.") + self.launch_command = args.launch_command + if self.get_launch_command("", "") is None: + raise ValueError("Launch command does not contain {SCENARIO} and {OUTPUT_DIR}: " + " ".join(args.launch_command)) + print(f"Launch command: {self.launch_command}") + + def get_launch_command(self, scenario_name, output_dir): + launch_command = deepcopy(self.launch_command) + scenario_replaced = False + output_dir_replaced = False + for i in range(0, len(launch_command)): # pylint: disable=consider-using-enumerate + if "{SCENARIO}" in launch_command[i]: + launch_command[i] = launch_command[i].replace('{SCENARIO}', scenario_name) + scenario_replaced = True + if "{OUTPUT_DIR}" in launch_command[i]: + launch_command[i] = launch_command[i].replace('{OUTPUT_DIR}', output_dir) + output_dir_replaced = True + if scenario_replaced and output_dir_replaced: + return launch_command + else: + return None + + def run(self) -> bool: + def log_output(out, logger): + try: + for line in iter(out.readline, b''): + msg = line.decode().strip() + print(msg) + logger.info(msg) + out.close() + except ValueError: + pass + + def configure_logger(log_file_path): + logger = logging.getLogger(log_file_path) + if logger.hasHandlers(): + logger.handlers.clear() + file_handler = logging.FileHandler(filename=log_file_path, mode='a') + file_handler.setFormatter(logging.Formatter('%(message)s')) + file_handler.setLevel(logging.INFO) + logger.addHandler(file_handler) + logger.setLevel(logging.INFO) + return logger + + ret = True + for scenario in self.scenarios: + scenario_name = os.path.splitext(os.path.basename(scenario))[0] + output_file_path = os.path.join(self.output_dir, scenario_name) + if not os.path.isdir(output_file_path): + os.mkdir(output_file_path) + launch_command = self.get_launch_command(scenario, output_file_path) + log_cmd = " ".join(launch_command) + print(f"### For scenario {scenario}, executing process: '{log_cmd}'") + process = subprocess.Popen(launch_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + file_handler = logging.FileHandler(filename=os.path.join(output_file_path, scenario_name + '.log'), mode='w') + logger = configure_logger(os.path.join(output_file_path, scenario_name + '.log')) + log_stdout_thread = Thread(target=log_output, args=(process.stdout, logger, )) + log_stdout_thread.daemon = True # die with the program + log_stdout_thread.start() + log_stderr_thread = Thread(target=log_output, args=(process.stderr, logger, )) + log_stderr_thread.daemon = True # die with the program + log_stderr_thread.start() + + print(f"### Waiting for process to finish...") + try: + process.wait() + if process.returncode: + print("### Process failed.") + ret = False + else: + print("### Process finished successfully.") + except KeyboardInterrupt: + print("### Interrupted by user. Sending SIGINT...") + process.send_signal(signal.SIGINT) + try: + process.wait(timeout=20) + return False + except subprocess.TimeoutExpired: + print("### Process not stopped after 20s. Sending SIGKILL...") + process.send_signal(signal.SIGKILL) + try: + process.wait(timeout=10) + return False + except subprocess.TimeoutExpired: + print("### Process not stopped after 10s.") + return False + file_handler.flush() + file_handler.close() + xml_ret = self.combine_test_xml() + if self.ignore_process_return_value: + return xml_ret + else: + return xml_ret and ret + + def combine_test_xml(self): + print(f"### Writing combined tests to '{self.output_dir}/test.xml'.....") + tree = ET.Element('testsuite') + total_time = 0 + total_errors = 0 + total_failures = 0 + total_tests = 0 + for scenario in self.scenarios: + scenario_name = os.path.splitext(os.path.basename(scenario))[0] + test_file = os.path.join(self.output_dir, scenario_name, 'test.xml') + parsed_successfully = False + if os.path.exists(test_file): + root = None + try: + test_tree = ETparse.parse(test_file) + root = test_tree.getroot() + except ETparse.ParseError: + print(f"### Error XML file {test_file} could not be parsed") + if root is not None: + parsed_successfully = True + total_errors += int(root.attrib.get('errors', 0)) + total_failures += int(root.attrib.get('failures', 0)) + total_time += float(root.attrib.get('time', 0)) + total_tests += int(root.attrib.get('tests', 0)) + for testcase in root.findall('testcase'): + testcase.set('name', str(scenario_name)) + tree.append(testcase) + else: + print(f"### XML file has no 'testsuite' element. {test_file}") + + if not parsed_successfully: + total_errors += 1 + missing_test_elem = ET.Element('testcase') + missing_test_elem.set("classname", "tests.scenario") + missing_test_elem.set("name", "no_test_result") + missing_test_elem.set("time", "0.0") + failure_elem = ET.Element('failure') + failure_elem.set("message", f"expected file {test_file} not found") + missing_test_elem.append(failure_elem) + tree.append(missing_test_elem) + tree.set('errors', str(total_errors)) + tree.set('failures', str(total_failures)) + tree.set('time', str(total_time)) + tree.set('tests', str(total_tests)) + combined_tests = ET.ElementTree(tree) + ET.indent(combined_tests, space="\t", level=0) + combined_tests.write(os.path.join(self.output_dir, "test.xml"), encoding='utf-8', xml_declaration=True) + return total_errors == 0 and total_failures == 0 + + +def main(): + """ + main function + """ + parser = argparse.ArgumentParser() + parser.add_argument('-i', '--scenario-dir', type=str, help='Directory containing the scenarios') + parser.add_argument('-o', '--output-dir', type=str, help='Directory containing the output', default='out') + parser.add_argument('-r', '--ignore-process-return-value', action='store_true', + help='Should a non-zero return value of the executed process result in a failure?') + parser.add_argument('launch_command', nargs='+') + args = parser.parse_args(sys.argv[1:]) + + try: + scenario_batch_execution = ScenarioBatchExecution(args) + except Exception as e: # pylint: disable=broad-except + print(f"Error while initializing batch execution: {e}") + sys.exit(1) + if scenario_batch_execution.run(): + sys.exit(0) + else: + print("Error during batch executing!") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/scenario_execution_coverage/scenario_execution_coverage/scenario_variation.py b/scenario_execution_coverage/scenario_execution_coverage/scenario_variation.py new file mode 100644 index 00000000..83c205a3 --- /dev/null +++ b/scenario_execution_coverage/scenario_execution_coverage/scenario_variation.py @@ -0,0 +1,191 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2024 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys +import argparse +from copy import deepcopy + +import yaml +import py_trees +from scenario_execution.model.osc2_parser import OpenScenario2Parser +from scenario_execution.model.model_resolver import resolve_internal_model +from scenario_execution.model.types import RelationExpression, ListExpression, FieldAccessExpression, ModelExpression, print_tree, serialize, to_string +from scenario_execution.utils.logging import Logger + + +class ScenarioVariation(object): + + def __init__(self, output_dir, scenario, log_model, debug) -> None: + self.logger = Logger('scenario_variation', debug) + self.output_dir = output_dir + self.scenario = scenario + self.log_model = log_model + self.debug = debug + + def get_variations(self, variant_element: RelationExpression): + base_element = deepcopy(variant_element) + base_element.operator = '==' + base_element.delete_child(base_element.get_child_with_expected_type(1, ListExpression)) + variations = [] + list_expression = variant_element.get_child_with_expected_type(1, ListExpression) + for child in list_expression.get_children(): + variation = deepcopy(base_element) + variation.set_children(child) + variations.append((variation, child)) + return variations + + def run(self) -> bool: + model = self.load_model() + models = self.generate_concrete_models(model) + return self.save_resulting_scenarios(models) + + def load_model(self): + parser = OpenScenario2Parser(self.logger) + parsed_tree = parser.parse_file(self.scenario, self.log_model) + return parser.load_internal_model(parsed_tree, self.scenario, self.log_model, self.debug) + + def get_next_variation_element(self, elem): + if isinstance(elem, RelationExpression): + if elem.operator == 'in': + return elem + else: + return None + else: + for child in elem.get_children(): + elem = self.get_next_variation_element(child) + if elem: + return elem + return None + + def get_element_fully_qualified_name(self, element): + def get_name(elem): + if elem.name: + return elem.name + else: + name = elem.__class__.__name__ + if elem.has_siblings(): + idx = list(elem.get_parent().get_children()).index(elem) + name += f"[{idx}]" + return name + + fqn = get_name(element) + tmp = element.get_parent() + while tmp: + fqn = get_name(tmp) + "." + fqn + tmp = tmp.get_parent() + return fqn + + def generate_concrete_models(self, model): + models = [(model, [])] # model and variation description as tuple + while True: + # The following loop always looks at the first element in models. + # If it contains a variation_element the element is removed and the + # resulting models are appended at the back. + variation_element = self.get_next_variation_element(models[0][0]) + if variation_element is None: + self.logger.debug("No further variation") + return models + self.logger.info(f"Creating models for variation model {variation_element}") + # remove model with variation from list + model = models[0] + models.remove(model) + + # remove original variation element + parent = variation_element.get_parent() + parent.delete_child(variation_element) + + # set resolved variations in copies of original model + variations = self.get_variations(variation_element) + for variation in variations: + parent.set_children(variation[0]) + variation_model = deepcopy(model) + + fqn = self.get_element_fully_qualified_name(variation[0]) + variation_model[1].append((fqn, variation[0])) + models.append(variation_model) + parent.delete_child(variation[0]) + + def save_resulting_scenarios(self, models): + idx = 0 + file_path = os.path.join(self.output_dir, os.path.splitext(os.path.basename(self.scenario))[0]) + for model in models: + self.logger.debug("-----------------") + serialize_data = serialize(model[0])['CompilationUnit']['_children'] + if self.debug: + print_tree(model[0], self.logger) + try: + tree = py_trees.composites.Sequence(name="", memory=True) + resolve_internal_model(model[0], tree, self.logger, False) + except ValueError as e: + raise ValueError(f"Resulting model is not resolvable: {e}") from e + + # create description + variation_descriptions = [] + for descr, entry in model[1]: + if isinstance(entry, ModelExpression): + val = None + for child in entry.get_children(): + if not isinstance(child, FieldAccessExpression): + val = child + if val is None: + raise ValueError("Could not find value.") + value_string = to_string(val) + else: + raise ValueError("Can not write variation description") + variation_descriptions.append(f"{descr}=={value_string}") + filename = file_path + str(idx) + '.sce' + self.logger.info(f"Storing model in {filename}") + with open(filename, 'w') as output: + for descr in variation_descriptions: + output.write(f"#{descr}\n") + yaml.safe_dump(serialize_data, output, sort_keys=False) + idx += 1 + return True + + +def main(): + """ + main function + """ + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--debug', action='store_true', help='debugging output') + parser.add_argument('-l', '--log-model', action='store_true', help='Produce tree output of parsed model content') + parser.add_argument('-o', '--output-dir', type=str, help='Output directory for concrete scenarios', default='out') + parser.add_argument('scenario', type=str, help='abstract scenario file') + args = parser.parse_args(sys.argv[1:]) + + if not os.path.isdir(args.output_dir): + os.mkdir(args.output_dir) + + scenario_variation = ScenarioVariation(args.output_dir, args.scenario, args.log_model, args.debug) + try: + ret = scenario_variation.run() + except Exception as e: # pylint: disable=broad-except + scenario_variation.logger.error(e) + ret = False + + if ret: + sys.exit(0) + else: + print("Error!") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/scenario_coverage/scenarios/test_fault_injection.osc b/scenario_execution_coverage/scenarios/test_fault_injection.osc similarity index 100% rename from scenario_coverage/scenarios/test_fault_injection.osc rename to scenario_execution_coverage/scenarios/test_fault_injection.osc diff --git a/scenario_coverage/scenarios/test_fault_injection_drop.osc b/scenario_execution_coverage/scenarios/test_fault_injection_drop.osc similarity index 100% rename from scenario_coverage/scenarios/test_fault_injection_drop.osc rename to scenario_execution_coverage/scenarios/test_fault_injection_drop.osc diff --git a/scenario_coverage/scenarios/test_fault_injection_noise.osc b/scenario_execution_coverage/scenarios/test_fault_injection_noise.osc similarity index 100% rename from scenario_coverage/scenarios/test_fault_injection_noise.osc rename to scenario_execution_coverage/scenarios/test_fault_injection_noise.osc diff --git a/scenario_coverage/scenarios/test_log.osc b/scenario_execution_coverage/scenarios/test_log.osc similarity index 100% rename from scenario_coverage/scenarios/test_log.osc rename to scenario_execution_coverage/scenarios/test_log.osc diff --git a/scenario_coverage/scenarios/test_nav_to_pose.osc b/scenario_execution_coverage/scenarios/test_nav_to_pose.osc similarity index 100% rename from scenario_coverage/scenarios/test_nav_to_pose.osc rename to scenario_execution_coverage/scenarios/test_nav_to_pose.osc diff --git a/scenario_coverage/setup.cfg b/scenario_execution_coverage/setup.cfg similarity index 100% rename from scenario_coverage/setup.cfg rename to scenario_execution_coverage/setup.cfg diff --git a/scenario_coverage/setup.py b/scenario_execution_coverage/setup.py similarity index 82% rename from scenario_coverage/setup.py rename to scenario_execution_coverage/setup.py index abeffca6..9eba563b 100644 --- a/scenario_coverage/setup.py +++ b/scenario_execution_coverage/setup.py @@ -18,7 +18,7 @@ import os from setuptools import find_packages, setup -PACKAGE_NAME = 'scenario_coverage' +PACKAGE_NAME = 'scenario_execution_coverage' setup( name=PACKAGE_NAME, @@ -38,13 +38,13 @@ zip_safe=True, maintainer='Intel Labs', maintainer_email='scenario-execution@intel.com', - description='Robotics Scenario Execution', + description='Robotics Scenario Execution Coverage', license='Apache License 2.0', tests_require=['pytest'], entry_points={ 'console_scripts': [ - 'scenario_variation = scenario_coverage.scenario_variation:main', - 'scenario_batch_execution = scenario_coverage.scenario_batch_execution:main', + 'scenario_variation = scenario_execution_coverage.scenario_variation:main', + 'scenario_batch_execution = scenario_execution_coverage.scenario_batch_execution:main', ], }, ) diff --git a/scenario_coverage/test/test_variations.py b/scenario_execution_coverage/test/test_variations.py similarity index 98% rename from scenario_coverage/test/test_variations.py rename to scenario_execution_coverage/test/test_variations.py index cfa378db..7fea8ae2 100644 --- a/scenario_coverage/test/test_variations.py +++ b/scenario_execution_coverage/test/test_variations.py @@ -16,7 +16,7 @@ import unittest -from scenario_coverage.scenario_variation import ScenarioVariation +from scenario_execution_coverage.scenario_variation import ScenarioVariation from scenario_execution.model.model_file_loader import ModelFileLoader from scenario_execution.model.model_resolver import resolve_internal_model from scenario_execution.utils.logging import Logger diff --git a/scenario_execution_rviz/package.xml b/scenario_execution_rviz/package.xml index 917f75eb..e83475c6 100644 --- a/scenario_execution_rviz/package.xml +++ b/scenario_execution_rviz/package.xml @@ -14,6 +14,7 @@ qtbase5-dev nav_msgs + std_srvs geometry_msgs scenario_execution_interfaces py_trees_ros_interfaces