The ExSce Management is an approach to store, query and generate test scenarios for ROS-based multi-robot systems. Provenance data about test scenarios and their executions is modeled using PROV and stored on a property graph. Runtime information is obtained from recorded bag files. Metamorphic testing is used to generate new scenarios and validate the system's requirements.
The process, shown in Figure 1, starts by having specified a scenario to use as a base, here we use the YAML specification of scenario_c for our example:
Listing 1: Scenario speficication for scenario_c
id: scenario_c
mission:
id: mission_c
type: parallel
allocation:
- robot: tiago1
pkg: metamorphic_testing
file_path: config/tasks/delivery_a.yaml
- robot: tiago2
pkg: metamorphic_testing
file_path: config/tasks/delivery_b.yaml
- robot: tiago1
pkg: metamorphic_testing
file_path: config/tasks/navigate_home_1.yaml
- robot: tiago2
pkg: metamorphic_testing
file_path: config/tasks/navigate_home_2.yaml
environment:
id: brsu_building_c_with_doors
models:
map:
map_name: brsu_building_c_with_doors
pkg: floorplan-DSL-environments
relative_path: maps/
gazebo_world:
model_name: brsu_building_c_with_doors
pkg: floorplan-DSL-environments
relative_path: worlds/
robots:
tiago1:
start_pose:
id: classroom_c025-w001
x: 44.80387496948242
y: 37.15502166748047
z: 0.0
roll: 0.0
pitch: 0.0
yaw: 0.0
tiago2:
start_pose:
id: classroom_c025-w002
x: 43.432926177978516
y: 38.493873596191406
z: 0.0
roll: 0.0
pitch: 0.0
yaw: 0.0
robots:
- robot_id: tiago1
robot_namespace: tiago1
robot_type: tiago
- robot_id: tiago2
robot_namespace: tiago2
robot_type: tiago
Next, we execute the scenario and record $n$ number of runs where the SUT performs their behaviour correctly. This requires three terminals.
First launching the scenario:
roslaunch metamorphic_testing scenario_c.launch
Start the record_exsce_run
script:
rosrun metamorphic_testing record_exsce_run
Start the mission dispatcher:
rosrun metamorphic_testing mission_dispatcher
The robots will proceed with the scenario execution. When the run ends,
the record_exsce_run
script will save three files, following the
format <ISO Date>_<scenario ID>_<run ID>.<ext>
, for example:
2023-06-13T15-48-07_scenario_c_run-e30df0b6.bag
2023-06-13T15-48-07_scenario_c_run-e30df0b6.prov.json
2023-06-13T15-48-07_scenario_c_run-e30df0b6.rosparams
The results of these runs and their provenance are obtained from the run artefacts and added to the Neo4j database. We define the first part of the oracle config to obtain the run results and store their PROV in the database in Listing 2.
Listing 2: Configuration for the bag-to-PROV transformation of
scenario_c
id: scenario_c # The ID of the scenario
# The baseline are the metrics are collected among runs of the same scenario
baseline:
base_scenario: scenario_c # This should always be the same as the scenario ID
# Metrics that apply to the overall mission
mission:
mission_duration: # Metric ID
topics: # Data sources from which we derive the metrics
- /tiago1/waypoint_dispatcher/feedback
- /tiago2/waypoint_dispatcher/feedback
distance_between_robots:
topics:
- /tiago1/robot_pose
- /tiago2/robot_pose
# Metrics that apply to individual robots
robots:
tiago1: # Robot ID from the scenario specification
task_duration: # Metric name
topics: # Data sources from which we derive the metric
- /tiago1/waypoint_dispatcher/feedback
waypoints_visited:
topics:
- /tiago1/waypoint_dispatcher/result
distance_travelled:
topics:
- /tiago1/distance_travelled
distance_to_obstacles:
topics:
- /tiago1/scan
max_velocity:
topics:
- /tiago1/nav_vel
tiago2:
task_duration:
topics:
- /tiago2/waypoint_dispatcher/feedback
waypoints_visited:
topics:
- /tiago2/waypoint_dispatcher/result
distance_travelled:
topics:
- /tiago2/distance_travelled
distance_to_obstacles:
topics:
- /tiago2/scan
max_velocity:
topics:
- /tiago2/nav_vel
Then we write the monitors that can process the data from the bag file
and return the metric. An excerpt for the max_velocity
metric is shown
in Listing 3.
Listing 3: Excerpt of the robot monitors that computes the
max_velocity
metric
class RobotMetrics:
def __init__(self, robot_id) -> None:
self.robot_id = robot_id
self.max_velocity = 0.0 # This must match the metric name in the oracle config
self.goals = dict()
self.path = ["start-pose"]
self.path_segments = dict()
# This must match the pattern `get_<metric name>`
def get_max_velocity(self, msg):
vel = math.sqrt(
math.pow(msg.linear.x, 2)
+ math.pow(msg.linear.y, 2)
+ math.pow(msg.linear.z, 2)
)
if vel > self.max_velocity:
self.max_velocity = vel
Note that the class attribute matches the metric name used in the oracle config, and the method used to compute the metric is a getter for said attribute, as the oracle and rosbag transform implementation leverages Python’s dynamic imports and instantiation.
To transform the runs and store them in the PROV database, make sure you have the Neo4j database running, and use the following command:
cd scripts
./transform_rosbag ../runs/2023-06-13T15-48-07_scenario_c_run-e30df0b6.bag --config ../config/oracle/scenario_c.yaml
After we have added a sufficient number of baseline runs to our PROV database, we now update the baseline oracle config file by adding the invariant relations for the safety metrics (which are constant, as described in the Oracles section, as shown in Listing 4.
Listing 4: Baseline oracle configuration for scenario_c
id: scenario_c
baseline:
base_scenario: scenario_c
mission:
mission_duration:
topics:
- /tiago1/waypoint_dispatcher/feedback
- /tiago2/waypoint_dispatcher/feedback
relationship:
package: exsce.metamorphic.relationships
name: invariant
distance_between_robots:
topics:
- /tiago1/robot_pose
- /tiago2/robot_pose
baseline:
limit: 0.1
relationship:
package: exsce.metamorphic.relationships
name: above_min
robots:
tiago1:
task_duration:
topics:
- /tiago1/waypoint_dispatcher/feedback
relationship:
package: exsce.metamorphic.relationships
name: invariant
waypoints_visited:
topics:
- /tiago1/waypoint_dispatcher/result
relationship:
package: exsce.metamorphic.relationships
name: invariant
distance_travelled:
topics:
- /tiago1/distance_travelled
relationship:
package: exsce.metamorphic.relationships
name: invariant
distance_to_obstacles:
topics:
- /tiago1/scan
baseline:
limit: 0.1
relationship:
package: exsce.metamorphic.relationships
name: above_min
max_velocity:
topics:
- /tiago1/nav_vel
baseline:
limit: 1.0
relationship:
package: exsce.metamorphic.relationships
name: below_max
tiago2:
task_duration:
topics:
- /tiago2/waypoint_dispatcher/feedback
relationship:
package: exsce.metamorphic.relationships
name: invariant
waypoints_visited:
topics:
- /tiago2/waypoint_dispatcher/result
relationship:
package: exsce.metamorphic.relationships
name: invariant
distance_travelled:
topics:
- /tiago2/distance_travelled
relationship:
package: exsce.metamorphic.relationships
name: invariant
distance_to_obstacles:
topics:
- /tiago2/scan
baseline:
limit: 0.1
relationship:
package: exsce.metamorphic.relationships
name: above_min
max_velocity:
topics:
- /tiago2/nav_vel
baseline:
limit: 1.0
relationship:
package: exsce.metamorphic.relationships
name: below_max
Finally, we can use the baseline test oracle to automatically validate new baseline runs based on their output relation:
cd scripts
./transform_rosbag ../runs/2023-06-13T15-48-07_scenario_c_run-e30df0b6.bag --config ../config/oracle/scenario_c.yaml --validate
This classifies the results based on whether the baseline relationship holds or not.
Let us manually define one new scenario based on scenario_c.yaml
. In
this minimal example, we’ll be removing tiago2
and reassigning its
tasks to tiago1
.
First we update the mission, as shown in Listing 5, by changing the
allocation of delivery_b.yaml
to tiago1
and removing the
navigate_home_2.yaml
task for tiago2
.
Listing 5: A new mission specification after removing tiago2
manually
mission:
id: mission_c
type: parallel
allocation:
- robot: tiago1
pkg: metamorphic_testing
file_path: config/tasks/delivery_a.yaml
- robot: tiago1
pkg: metamorphic_testing
file_path: config/tasks/delivery_b.yaml
- robot: tiago1
pkg: metamorphic_testing
file_path: config/tasks/navigate_home_1.yaml
We also remove tiago2
from the list of robots
, and its starting
position from the environment.robots
(as shown in Listing 6):
Listing 6: Environment specification after removing tiago2
in a
scenario speciication
environment:
robots:
tiago1:
start_pose:
id: classroom_c025-w001
x: 44.80387496948242
y: 37.15502166748047
z: 0.0
roll: 0.0
pitch: 0.0
yaw: 0.0
robots:
- robot_id: tiago1
robot_namespace: tiago1
robot_type: tiago
Finally, the oracle config is updated similarly to the above, by
removing topics and metrics related to tiago2
.
As mentioned in Metamorphic Relations, we have pre-defined some transformations. We can write a script to use these functions and recreate the example from the previous section, for example:
Listing 7: Example of a scenario transformation script that removes
tiago2
and reassigns its tasks to tiago1
# scenario_config is the scenario specification.
# limits is a list of metrics that we consider constant,
# and which should not change between the original and derived scenario
scenario = ScenarioTransformations(scenario_config, prov_api, limits)
# Input transformation
scenario.remove_robot("tiago2", assignment={"tiago2": "tiago1"})
# Calling `generate() creates and updates the PROV documents,
# and generates a new oracle config file for the new scenario
scenario.generate()
The script above will create the new scenario, its oracle configuration, and save both of them in the PROV database.