Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Unit Test for Republisher Node on Jazzy Branch #42

Open
wants to merge 21 commits into
base: jazzy-devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
78ab386
Working on a republisher node test using a rosbag to harcode data on…
IrinaTerebiznik Dec 13, 2024
3eb6cec
publisher test works when running rosbag from cmd yet not from launch…
IrinaTerebiznik Dec 17, 2024
a603e78
Trying to fix launch file error, unsuccessfully
IrinaTerebiznik Dec 17, 2024
2c8308b
Added a gitignore file
IrinaTerebiznik Dec 18, 2024
43133c5
sample_data.launch.xml file raise an exception in launch, still fixin…
IrinaTerebiznik Dec 18, 2024
c44ebe1
rosbag test on Humble working properly
IrinaTerebiznik Dec 18, 2024
b827a28
corrected colcon sentence as it was missing an 'l' in install
IrinaTerebiznik Dec 18, 2024
7df9e31
Rosbag sample test for Jazzy working
IrinaTerebiznik Dec 18, 2024
5782f90
Deleted irrelevant folders
IrinaTerebiznik Dec 18, 2024
8a2aca2
Deleted irrelevant files
IrinaTerebiznik Dec 18, 2024
5642afc
Added a README file and modified .gitignore file
IrinaTerebiznik Dec 23, 2024
e8313dd
Deleted .history folder and updated gitignore file
IrinaTerebiznik Dec 23, 2024
976496b
Updated gitignore file
IrinaTerebiznik Dec 23, 2024
7e4f8f2
Revert "Deleted irrelevant folders"
IrinaTerebiznik Dec 23, 2024
dcbada9
Revert "Deleted irrelevant files"
IrinaTerebiznik Dec 23, 2024
3b91470
Added missing files
IrinaTerebiznik Dec 23, 2024
bf52bd3
Added a README file that was missing, corrected extra line on config …
IrinaTerebiznik Dec 23, 2024
682d379
Updated README to echo another topic
IrinaTerebiznik Dec 23, 2024
001912b
Added resource folder as it was not running the package without it
IrinaTerebiznik Dec 23, 2024
eca3638
Updated README, i added a first step to build a docker before running…
IrinaTerebiznik Dec 26, 2024
ba35871
Added a unit test to the republisher
IrinaTerebiznik Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# Launch files
*build/
*devel/
*install/
*log/

# Python files
__pycache__/
*.pyc
*.pyo
*.pyd

# VS files
.vscode/
.idea/
*.swp

# Dependencies
venv/
*.egg-info/
*.eggs/
pip-log.txt
2 changes: 1 addition & 1 deletion inorbit_republisher/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ Changelog for package inorbit_republisher
------------------
* First pass at porting republisher to ROS 2 (`#20 <https://github.com/inorbit-ai/ros_inorbit_samples/issues/20>`_)
Ported from noetic-devel
* Contributors: Julian Cerruti, Elvio Aruta
* Contributors: Julian Cerruti, Elvio Aruta
14 changes: 7 additions & 7 deletions inorbit_republisher/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# InOrbit republisher for ROS 2

This directory includes a republisher that allows mapping from arbitrary ROS2 values to ``InOrbit`` [custom data](https://www.inorbit.ai/faq#publish-custom-data) key/value pairs for application-specific observability.
This directory includes a republisher that allows mapping from arbitrary ROS 2 values to ``InOrbit`` [custom data](https://www.inorbit.ai/faq#publish-custom-data) key/value pairs for application-specific observability.

Currently only mapping from ROS2 topics is supported. The republisher could be extended to map actions, services and parameters.
Currently only mapping from ROS 2 topics is supported. The republisher could be extended to map actions, services and parameters.

## Usage

Expand Down Expand Up @@ -75,9 +75,9 @@ A suggested way to organize this is by creating the config file and launch file
</launch>
```

## Mapping ROS2 topics
## Mapping ROS 2 topics

The republisher can map the ROS2 values to single field (e.g. ``'fruit=apple'``) or to an array of fields (e.g. ``'fruits=[{fruit1: apple, fruit2: orange}, {fruit1: melon, fruit2: apple}]'``). The former is useful to capture simple fields and the latter to get data from an array of values.
The republisher can map the ROS 2 values to single field (e.g. ``'fruit=apple'``) or to an array of fields (e.g. ``'fruits=[{fruit1: apple, fruit2: orange}, {fruit1: melon, fruit2: apple}]'``). The former is useful to capture simple fields and the latter to get data from an array of values.

### Single field: mapping options

Expand Down Expand Up @@ -173,15 +173,15 @@ These values will be published as latched and delivered only once every time a s

Find below instructions for building the package and running the node using the the code on the workspace (see also [colcon](https://colcon.readthedocs.io/en/released/reference/verb/build.html)).

### Start ROS2 docker container (optional)
### Start ROS 2 docker container (optional)

You can run the commands below for building and running the republisher inside a docker container.

```bash
docker run -ti --rm \
--workdir /root/ros2_ws/ \
-v .:/root/ros2_ws/src/inorbit_republisher \
osrf/ros:foxy-desktop
osrf/ros:jazzy-desktop
```

### Build
Expand All @@ -207,4 +207,4 @@ ros2 launch inorbit_republisher example.launch.xml
* Error handling
* Proper logging
* Latched topic support
* Mapping of ROS parameters
* Mapping of ROS parameters
29 changes: 29 additions & 0 deletions inorbit_republisher/config/example_unit_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
republishers:
- topic: "/input_topic"
msg_type: "std_msgs/String"
mappings:
- field: "data"
mapping_type: "single_field"
out:
topic: "/output_topic"
key: "input_to_output"
static_publishers:
- value_from:
package_version: "rospy"
out:
topic: "/inorbit/custom_data"
key: "rospy_version"
- value_from:
package_version: "inorbit_republisher"
out:
topic: "/inorbit/custom_data"
key: "inorbit_republisher_version"
- value_from:
environment_variable: "PYTHONPATH"
out:
topic: "/inorbit/custom_data"
key: "python_path"
- value: "let's republish in orbit"
out:
topic: "/inorbit/custom_data"
key: "greeting"
6 changes: 6 additions & 0 deletions inorbit_republisher/launch/sample_data.launch.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<launch>
<node name="inorbit_republisher" pkg="inorbit_republisher" exec="republisher">
<param name="config" value="/root/ros2_ws/src/inorbit_republisher/test/sample_data/config.yaml" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute paths can be replaced by dynamic substitutions, see https://design.ros2.org/articles/roslaunch_xml.html#built-in-substitutions

</node>
<executable cmd="ros2 bag play ros2_ws/src/inorbit_republisher/test/sample_data/hardcodedRosbag/rosbag2_2024_12_13-19_33_34_0.db3 -l" output="screen" />
</launch>
5 changes: 5 additions & 0 deletions inorbit_republisher/launch/test_republisher.launch.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<launch>
<node name="inorbit_republisher" pkg="inorbit_republisher" exec="republisher">
<param name="config" value="$(dirname)/config/example_unit_test.yaml" />
</node>
</launch>
2 changes: 1 addition & 1 deletion inorbit_republisher/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
<export>
<build_type>ament_python</build_type>
</export>
</package>
</package>
2 changes: 1 addition & 1 deletion inorbit_republisher/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[develop]
script-dir=$base/lib/inorbit_republisher
[install]
install-scripts=$base/lib/inorbit_republisher
install-scripts=$base/lib/inorbit_republisher
2 changes: 1 addition & 1 deletion inorbit_republisher/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
'republisher = inorbit_republisher.republisher:main'
],
},
)
)
65 changes: 65 additions & 0 deletions inorbit_republisher/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Sample data for testing

A very simple ``rosbag`` with hardcoded data for smoke testing the transformer node.

```bash
$root@7c94d2cf9659:~# ros2 bag info ros2_ws/src/inorbit_republisher/test/sample_data/hardcodedRosbag/rosbag2_2024_12_13-19_33_34_0.db3

closing.
[INFO] [1734553121.938063653] [rosbag2_storage]: Opened database 'ros2_ws/src/inorbit_republisher/test/sample_data/hardcodedRosbag/rosbag2_2024_12_13-19_33_34_0.db3' for READ_ONLY.

Files: ros2_ws/src/inorbit_republisher/test/sample_data/hardcodedRosbag/rosbag2_2024_12_13-19_33_34_0.db3
Bag size: 24.0 KiB
Storage id: sqlite3
Duration: 16.005861016s
Start: Dec 13 2024 19:45:00.085674077 (1734119100.085674077)
End: Dec 13 2024 19:45:16.091535093 (1734119116.091535093)
Messages: 34
Topic information: Topic: /my_magnetic_field | Type: sensor_msgs/msg/MagneticField | Count: 17 | Serialization Format: cdr
Topic: /my_temperature | Type: sensor_msgs/msg/Temperature | Count: 17 | Serialization Format: cdr

```

To validate the node works launch the sample by using the ``sample_data.launch.xml`` launch file and look at ``out`` topic: ``ros2 topic echo my_temperature``.
1. **Start ROS2 docker container (optional)**:
You can run the commands below for building and running the republisher inside a docker container.
```bash
docker run -ti --rm \
--workdir /root/ros2_ws/ \
-v .:/root/ros2_ws/src/inorbit_republisher \
osrf/ros:jazzy-desktop
```
2. **Build the Workspace**:
Ensure the workspace is built and the environment is sourced:
```bash
cd ~/ros2_ws
rosdep install --from-paths src -y --ignore-src
colcon build --packages-select inorbit_republisher --symlink-install
```
3. **Source and Run the Workspace**:
```bash
cd ros2_ws/src
colcon build --packages-select inorbit_republisher --symlink-install
source install/setup.bash
cd
ros2 launch inorbit_republisher sample_data.launch.xml
# On a different terminal windows
source /opt/ros/jazzy/setup.bash
ros2 topic echo inorbit/custom_data
data: my_magnetic_field_x=1.233536973711507
---
data: my_magnetic_field_y=6.557449696128689
---
data: my_magnetic_field_z=-1.6201375987982995
---
data: my_temperature=29.562268283427592
---
data: my_magnetic_field_x=-3.071746389301242
---
data: my_magnetic_field_y=6.080682955949868
---
data: my_magnetic_field_z=3.952488443512035
---
data: my_temperature=21.159168808582983
---
```
23 changes: 23 additions & 0 deletions inorbit_republisher/test/sample_data/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
republishers:
- topic: "/my_magnetic_field"
msg_type: "sensor_msgs/MagneticField"
mappings:
- field: "magnetic_field.x"
out:
topic: "/inorbit/custom_data"
key: "my_magnetic_field_x"
- field: "magnetic_field.y"
out:
topic: "/inorbit/custom_data"
key: "my_magnetic_field_y"
- field: "magnetic_field.z"
out:
topic: "/inorbit/custom_data"
key: "my_magnetic_field_z"
- topic: "/my_temperature"
msg_type: "sensor_msgs/Temperature"
mappings:
- field: "temperature"
out:
topic: "/inorbit/custom_data"
key: "my_temperature"
32 changes: 32 additions & 0 deletions inorbit_republisher/test/sample_data/hardcodedRosbag/metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
rosbag2_bagfile_information:
version: 5
storage_identifier: sqlite3
duration:
nanoseconds: 16005861016
starting_time:
nanoseconds_since_epoch: 1734119100085674077
message_count: 34
topics_with_message_count:
- topic_metadata:
name: /my_temperature
type: sensor_msgs/msg/Temperature
serialization_format: cdr
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
message_count: 17
- topic_metadata:
name: /my_magnetic_field
type: sensor_msgs/msg/MagneticField
serialization_format: cdr
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
message_count: 17
compression_format: ""
compression_mode: ""
relative_file_paths:
- rosbag2_2024_12_13-19_33_34_0.db3
files:
- path: rosbag2_2024_12_13-19_33_34_0.db3
starting_time:
nanoseconds_since_epoch: 1734119100085674077
duration:
nanoseconds: 16005861016
message_count: 34
Binary file not shown.
38 changes: 38 additions & 0 deletions inorbit_republisher/test/unit_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Unit Test for ROS Republisher Node

This repository contains a unit test for a ROS node that republishes messages from `/input_topic` to `/output_topic`. The test ensures the republisher node processes and republishes messages correctly.

## Test Description

The unit test is implemented in `test_republisher.py` and uses the `unittest` framework along with `rostest`. It includes the following test cases:

- **`test_message_republishing`**:
- Publishes a test message (`key=value`) to `/input_topic`.
- Verifies that the republisher node republishes the message with the correct prefix (`input_to_output=`) to `/output_topic`.

- **`test_no_message`**:
- Ensures that no messages are received on `/output_topic` at the start of the test.

The test subscribes to `/output_topic` and collects received messages for validation.

## Running the Unit Test
1. **Start ROS 2 docker container (optional)**:
You can run the commands below for building and running the republisher inside a docker container.
```bash
docker run -ti --rm \
--workdir /root/ros2_ws/ \
-v .:/root/ros2_ws/src/inorbit_republisher \
osrf/ros:jazzy-desktop
```
2. **Build the Workspace**:
Ensure the workspace is built and the environment is sourced:
```bash
cd ~/ros2_ws
rosdep install --from-paths src -y --ignore-src
colcon build --packages-select inorbit_republisher --symlink-install
```
3. **Source and Run the Workspace**:
```bash
source install/local_setup.bash
colcon test --packages-select inorbit_republisher --event-handlers console_cohesion+ console_direct+
```
75 changes: 75 additions & 0 deletions inorbit_republisher/test/unit_test/test_republisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
import os
import time
import unittest
import pytest
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import AnyLaunchDescriptionSource
import launch_testing
from ament_index_python.packages import get_package_share_directory


@pytest.mark.launch_test
def generate_test_description():
# Use dynamic path instead of hardcoded absolute path
launch_file_path = os.path.join(
get_package_share_directory("inorbit_republisher"),
"test_republisher.launch.xml"
)
return LaunchDescription([
IncludeLaunchDescription(
AnyLaunchDescriptionSource(launch_file_path)
),
launch_testing.actions.ReadyToTest(),
])


class TestRepublisher(unittest.TestCase):

def setUp(self):
rclpy.init()
self.node = rclpy.create_node('test_republisher')
self.test_pub = self.node.create_publisher(String, '/input_topic', 10)
self.received_messages = []

self.node.create_subscription(String, '/output_topic', self.callback, 10)
# Wait up to 5 seconds for at least one subscriber on /input_topic
end_time = time.time() + 5
while time.time() < end_time:
if self.test_pub.get_subscription_count() > 0:
break
rclpy.spin_once(self.node, timeout_sec=0.1)
else:
self.fail("Test setup failed: Publisher connection timeout.")

def callback(self, msg):
self.received_messages.append(msg.data)

def test_message_republishing(self):
test_message = "key=value"
expected_message = f"input_to_output={test_message}"
self.node.get_logger().info(f"Publishing: {test_message} to /input_topic")

self.test_pub.publish(String(data=test_message))

# Spin for up to 5 seconds waiting for the republished message
end_time = time.time() + 5
while time.time() < end_time:
rclpy.spin_once(self.node, timeout_sec=0.1)
if expected_message in self.received_messages:
break

self.node.get_logger().info(f"Messages received: {self.received_messages}")
self.assertIn(expected_message, self.received_messages)

def tearDown(self):
self.node.destroy_node()
rclpy.shutdown()


if __name__ == '__main__':
unittest.main()