diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile
new file mode 100644
index 00000000..3794d1e6
--- /dev/null
+++ b/.github/docker/Dockerfile
@@ -0,0 +1,22 @@
+FROM ubuntu:22.04
+LABEL author=juanlopez@eprosima.com
+
+# Avoid interactuation with installation of some package that needs the locale.
+ENV TZ=Europe/Madrid
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+# Avoids using interactions during building
+ENV DEBIAN_FRONTEND=noninteractive
+
+# Use a bash shell so it is possigle to run things like `source` (required for colcon builds)
+SHELL ["/bin/bash", "-c"]
+
+COPY linux_bisource_installation.bash /tmp/linux_bisource_installation.bash
+RUN chmod +x /tmp/linux_bisource_installation.bash
+RUN /tmp/linux_bisource_installation.bash
+RUN rm /tmp/linux_bisource_installation.bash
+
+COPY run.bash /root/run.bash
+RUN chmod +x /root/run.bash
+
+ENTRYPOINT ["/root/run.bash" ]
diff --git a/.github/docker/linux_bisource_installation.bash b/.github/docker/linux_bisource_installation.bash
new file mode 100755
index 00000000..ba75e510
--- /dev/null
+++ b/.github/docker/linux_bisource_installation.bash
@@ -0,0 +1,142 @@
+#!/bin/bash
+
+set -e
+
+if (( $EUID == 0 )); then
+ shopt -s expand_aliases
+ alias sudo=''
+fi
+
+if !(locale | grep -e 'utf8' -e 'UTF-8') >/dev/null 2>&1; then
+
+##LINUX_BINARY_LOCALE
+locale # check for UTF-8
+
+sudo apt update && sudo apt install -y locales
+# Any UTF-8 locale will work. Using en_US as an example
+sudo locale-gen en_US en_US.UTF-8
+sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
+export LANG=en_US.UTF-8
+##!
+
+locale
+
+fi
+
+##LINUX_BINARY_UBUNTU_UNIVERSE
+apt-cache policy | grep universe
+
+# This should print something similar to:
+#
+# 500 http://us.archive.ubuntu.com/ubuntu jammy/universe amd64 Packages
+# release v=22.04,o=Ubuntu,a=jammy,n=jammy,l=Ubuntu,c=universe,b=amd64
+#
+# Otherwise run
+
+sudo apt install -y software-properties-common
+sudo add-apt-repository universe -y
+##!
+
+##LINUX_BINARY_KEYSTORE
+sudo apt update && sudo apt install -y curl gnupg lsb-release
+sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
+##!
+
+##LINUX_BINARY_REPO_SOURCELIST
+echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
+##!
+
+##LINUX_BINARY_ROS_UPDATE
+sudo apt update -y
+##!
+
+##LINUX_BINARY_ROS_INSTALL
+sudo apt install -y ros-iron-desktop
+##!
+
+##SETUP_ROS_ENV
+source "/opt/ros/iron/setup.bash"
+##!
+
+##CREATE_WORKSPACE
+mkdir -p ~/vulcanexus_iron/src
+cd ~/vulcanexus_iron
+##!
+
+##LINUX_SOURCE_ROS2_DEPS
+sudo apt update && sudo apt install -y \
+ build-essential \
+ cmake \
+ git \
+ python3-colcon-common-extensions \
+ python3-flake8 \
+ python3-flake8-blind-except \
+ python3-flake8-builtins \
+ python3-flake8-class-newline \
+ python3-flake8-comprehensions \
+ python3-flake8-deprecated \
+ python3-flake8-docstrings \
+ python3-flake8-import-order \
+ python3-flake8-quotes \
+ python3-pip \
+ python3-pytest \
+ python3-pytest-cov \
+ python3-pytest-repeat \
+ python3-pytest-rerunfailures \
+ python3-rosdep \
+ python3-setuptools \
+ python3-vcstool \
+ wget
+##!
+
+##LINUX_SOURCE_VULCA_DEPS
+sudo apt update && sudo apt install -y \
+ libasio-dev \
+ libdocopt-dev \
+ libengine-pkcs11-openssl \
+ liblog4cxx-dev \
+ liblz4-dev \
+ libp11-dev \
+ libqt5charts5-dev \
+ libssl-dev \
+ libtinyxml2-dev \
+ libxerces-c-dev \
+ libyaml-cpp-dev \
+ libzstd-dev \
+ openjdk-8-jdk \
+ python3-sphinx \
+ python3-sphinx-rtd-theme \
+ qtbase5-dev \
+ qtdeclarative5-dev \
+ qtquickcontrols2-5-dev \
+ swig
+##!
+
+# Get Vulcanexus sources
+wget https://raw.githubusercontent.com/eProsima/vulcanexus/iron/vulcanexus.repos
+wget https://raw.githubusercontent.com/eProsima/vulcanexus/iron/colcon.meta
+vcs import --force src < vulcanexus.repos
+
+# Avoid compilation of some documentation and demo packages
+touch src/eProsima/Fast-DDS-QoS-Profiles-Manager/docs/COLCON_IGNORE
+touch src/eProsima/Vulcanexus-Base/docs/COLCON_IGNORE
+touch src/eProsima/Vulcanexus-Base/code/COLCON_IGNORE
+##!
+
+#################################################################################################
+#################################################################################################
+#################################################################################################
+###### TEMP ######
+cd /tmp
+git clone https://github.com/eProsima/vulcanexus.git
+cd vulcanexus
+git checkout feature/fastdds-cli-package
+mv fastdds_cli ~/vulcanexus_iron/src/eProsima/Vulcanexus-Base
+#################################################################################################
+#################################################################################################
+#################################################################################################
+
+##LINUX_SOURCE_VULCA_COMPILE
+cd ~/vulcanexus_iron
+colcon build --cmake-args -DBUILD_TESTING=OFF
+##!
diff --git a/.github/docker/run.bash b/.github/docker/run.bash
new file mode 100644
index 00000000..b866d7d1
--- /dev/null
+++ b/.github/docker/run.bash
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# Setup environment
+source "/opt/ros/iron/setup.bash"
+source "/root/vulcanexus_iron/install/setup.bash"
+
+exec "$@"
diff --git a/.github/workflows/test_docker.yaml b/.github/workflows/test_docker.yaml
new file mode 100644
index 00000000..5671269c
--- /dev/null
+++ b/.github/workflows/test_docker.yaml
@@ -0,0 +1,65 @@
+name: test-docker-workflow
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+ - humble
+ - iron
+ - jazzy
+
+ schedule:
+ - cron: '0 0 * * *'
+
+jobs:
+ docker-tests:
+ runs-on: ubuntu-22.04
+
+ env:
+ VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE: "vulcanexus:ci"
+
+ steps:
+
+ - name: Sync repository
+ uses: eProsima/eProsima-CI/external/checkout@v0
+ with:
+ path: src
+ # ref: main
+ ref: feature/fastdds-cli-package
+
+ - name: Build Vulcanexus Docker image
+ run: |
+ cd ./src/.github/docker
+ docker build \
+ --no-cache \
+ -t ${{ env.VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE }} \
+ -f Dockerfile .
+
+ - name: Check if Docker images exist
+ run: |
+ [ -n "$(docker images -q ${{ env.VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE }})" ] || echo "Vulcanexus Docker image does not exist"
+
+ - name: Install colcon
+ uses: eProsima/eProsima-CI/ubuntu/install_colcon@main
+
+ - name: Install GTest
+ uses: eProsima/eProsima-CI/ubuntu/install_gtest@main
+
+ - name: Compile docker tests
+ uses: eProsima/eProsima-CI/multiplatform/colcon_build@v0
+ with:
+ workspace: ${{ github.workspace }}
+ colcon_build_args: --packages-up-to vulcanexus_test
+ cmake_args: -DBUILD_COMPOSE_TESTS=ON
+
+ - name: Run tests
+ run: |
+ export VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE=${{ env.VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE }}
+ source install/setup.bash
+ colcon test \
+ --packages-select vulcanexus_test \
+ --event-handlers console_direct+ \
+ --return-code-on-test-failure \
+ --ctest-args \
+ --timeout 120
diff --git a/docs/resources/scripts/linux_bisource_installation.bash b/docs/resources/scripts/linux_bisource_installation.bash
new file mode 100755
index 00000000..ba75e510
--- /dev/null
+++ b/docs/resources/scripts/linux_bisource_installation.bash
@@ -0,0 +1,142 @@
+#!/bin/bash
+
+set -e
+
+if (( $EUID == 0 )); then
+ shopt -s expand_aliases
+ alias sudo=''
+fi
+
+if !(locale | grep -e 'utf8' -e 'UTF-8') >/dev/null 2>&1; then
+
+##LINUX_BINARY_LOCALE
+locale # check for UTF-8
+
+sudo apt update && sudo apt install -y locales
+# Any UTF-8 locale will work. Using en_US as an example
+sudo locale-gen en_US en_US.UTF-8
+sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
+export LANG=en_US.UTF-8
+##!
+
+locale
+
+fi
+
+##LINUX_BINARY_UBUNTU_UNIVERSE
+apt-cache policy | grep universe
+
+# This should print something similar to:
+#
+# 500 http://us.archive.ubuntu.com/ubuntu jammy/universe amd64 Packages
+# release v=22.04,o=Ubuntu,a=jammy,n=jammy,l=Ubuntu,c=universe,b=amd64
+#
+# Otherwise run
+
+sudo apt install -y software-properties-common
+sudo add-apt-repository universe -y
+##!
+
+##LINUX_BINARY_KEYSTORE
+sudo apt update && sudo apt install -y curl gnupg lsb-release
+sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
+##!
+
+##LINUX_BINARY_REPO_SOURCELIST
+echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
+##!
+
+##LINUX_BINARY_ROS_UPDATE
+sudo apt update -y
+##!
+
+##LINUX_BINARY_ROS_INSTALL
+sudo apt install -y ros-iron-desktop
+##!
+
+##SETUP_ROS_ENV
+source "/opt/ros/iron/setup.bash"
+##!
+
+##CREATE_WORKSPACE
+mkdir -p ~/vulcanexus_iron/src
+cd ~/vulcanexus_iron
+##!
+
+##LINUX_SOURCE_ROS2_DEPS
+sudo apt update && sudo apt install -y \
+ build-essential \
+ cmake \
+ git \
+ python3-colcon-common-extensions \
+ python3-flake8 \
+ python3-flake8-blind-except \
+ python3-flake8-builtins \
+ python3-flake8-class-newline \
+ python3-flake8-comprehensions \
+ python3-flake8-deprecated \
+ python3-flake8-docstrings \
+ python3-flake8-import-order \
+ python3-flake8-quotes \
+ python3-pip \
+ python3-pytest \
+ python3-pytest-cov \
+ python3-pytest-repeat \
+ python3-pytest-rerunfailures \
+ python3-rosdep \
+ python3-setuptools \
+ python3-vcstool \
+ wget
+##!
+
+##LINUX_SOURCE_VULCA_DEPS
+sudo apt update && sudo apt install -y \
+ libasio-dev \
+ libdocopt-dev \
+ libengine-pkcs11-openssl \
+ liblog4cxx-dev \
+ liblz4-dev \
+ libp11-dev \
+ libqt5charts5-dev \
+ libssl-dev \
+ libtinyxml2-dev \
+ libxerces-c-dev \
+ libyaml-cpp-dev \
+ libzstd-dev \
+ openjdk-8-jdk \
+ python3-sphinx \
+ python3-sphinx-rtd-theme \
+ qtbase5-dev \
+ qtdeclarative5-dev \
+ qtquickcontrols2-5-dev \
+ swig
+##!
+
+# Get Vulcanexus sources
+wget https://raw.githubusercontent.com/eProsima/vulcanexus/iron/vulcanexus.repos
+wget https://raw.githubusercontent.com/eProsima/vulcanexus/iron/colcon.meta
+vcs import --force src < vulcanexus.repos
+
+# Avoid compilation of some documentation and demo packages
+touch src/eProsima/Fast-DDS-QoS-Profiles-Manager/docs/COLCON_IGNORE
+touch src/eProsima/Vulcanexus-Base/docs/COLCON_IGNORE
+touch src/eProsima/Vulcanexus-Base/code/COLCON_IGNORE
+##!
+
+#################################################################################################
+#################################################################################################
+#################################################################################################
+###### TEMP ######
+cd /tmp
+git clone https://github.com/eProsima/vulcanexus.git
+cd vulcanexus
+git checkout feature/fastdds-cli-package
+mv fastdds_cli ~/vulcanexus_iron/src/eProsima/Vulcanexus-Base
+#################################################################################################
+#################################################################################################
+#################################################################################################
+
+##LINUX_SOURCE_VULCA_COMPILE
+cd ~/vulcanexus_iron
+colcon build --cmake-args -DBUILD_TESTING=OFF
+##!
diff --git a/docs/resources/scripts/linux_source_installation.bash b/docs/resources/scripts/linux_source_installation.bash
index 98b06229..0b1f31bb 100755
--- a/docs/resources/scripts/linux_source_installation.bash
+++ b/docs/resources/scripts/linux_source_installation.bash
@@ -119,8 +119,8 @@ vcs import --force src < vulcanexus.repos
# Avoid compilation of some documentation and demo packages
touch src/eProsima/Fast-DDS-QoS-Profiles-Manager/docs/COLCON_IGNORE
-touch src/eProsima/Vulcanexus-Base/docs/COLCON_IGNORE
-touch src/eProsima/Vulcanexus-Base/code/COLCON_IGNORE
+touch src/eProsima/Vulcanexus/docs/COLCON_IGNORE
+touch src/eProsima/Vulcanexus/code/COLCON_IGNORE
##!
##LINUX_SOURCE_VULCA_DEPS
diff --git a/fastdds_cli/fastdds_cli/__init__.py b/fastdds_cli/fastdds_cli/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/fastdds_cli/fastdds_cli/discovery_server.py b/fastdds_cli/fastdds_cli/discovery_server.py
new file mode 100644
index 00000000..b589f1ee
--- /dev/null
+++ b/fastdds_cli/fastdds_cli/discovery_server.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+from fastdds_cli.executor import Executor
+
+def main():
+ exec = Executor("discovery")
+ exec.execute()
+
+if __name__ == '__main__':
+ main()
diff --git a/fastdds_cli/fastdds_cli/executor.py b/fastdds_cli/fastdds_cli/executor.py
new file mode 100644
index 00000000..9f84732c
--- /dev/null
+++ b/fastdds_cli/fastdds_cli/executor.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+import os
+import signal
+import subprocess
+import sys
+
+class Executor:
+
+ def __init__(self, command_arg):
+ self.command_arg = command_arg
+
+ def execute(self):
+
+ def signal_handler(sig, frame):
+ print("\nSignal captured, ending node execution...")
+
+ signal.signal(signal.SIGINT, signal_handler)
+ signal.signal(signal.SIGTERM, signal_handler)
+
+ try:
+
+ result = subprocess.run(
+ ["fastdds", self.command_arg] + sys.argv[1:],
+ env=os.environ
+ )
+
+ except Exception as e:
+ print('Error:', e)
+ return
diff --git a/fastdds_cli/fastdds_cli/shm.py b/fastdds_cli/fastdds_cli/shm.py
new file mode 100644
index 00000000..3da1c289
--- /dev/null
+++ b/fastdds_cli/fastdds_cli/shm.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+from fastdds_cli.executor import Executor
+
+def main():
+ exec = Executor("shm")
+ exec.execute()
+
+if __name__ == '__main__':
+ main()
diff --git a/fastdds_cli/fastdds_cli/xml.py b/fastdds_cli/fastdds_cli/xml.py
new file mode 100644
index 00000000..343d9b99
--- /dev/null
+++ b/fastdds_cli/fastdds_cli/xml.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+from fastdds_cli.executor import Executor
+
+def main():
+ exec = Executor("xml")
+ exec.execute()
+
+if __name__ == '__main__':
+ main()
diff --git a/fastdds_cli/package.xml b/fastdds_cli/package.xml
new file mode 100644
index 00000000..1fb6a0de
--- /dev/null
+++ b/fastdds_cli/package.xml
@@ -0,0 +1,23 @@
+
+
+
+ fastdds_cli
+ 0.0.1
+
+ A command line interface for Fast DDS.
+
+ juan
+ Apache 2.0
+
+ https://www.eprosima.com/
+ https://github.com/eProsima/vulcanexus/issues
+ https://github.com/eProsima/vulcanexus
+
+ fastdds
+
+ python3-pytest
+
+
+ ament_python
+
+
diff --git a/fastdds_cli/resource/fastdds_cli b/fastdds_cli/resource/fastdds_cli
new file mode 100644
index 00000000..e69de29b
diff --git a/fastdds_cli/setup.cfg b/fastdds_cli/setup.cfg
new file mode 100644
index 00000000..e5786b9b
--- /dev/null
+++ b/fastdds_cli/setup.cfg
@@ -0,0 +1,4 @@
+[develop]
+script_dir=$base/lib/fastdds_cli
+[install]
+install_scripts=$base/lib/fastdds_cli
diff --git a/fastdds_cli/setup.py b/fastdds_cli/setup.py
new file mode 100644
index 00000000..8dc75c90
--- /dev/null
+++ b/fastdds_cli/setup.py
@@ -0,0 +1,28 @@
+from setuptools import find_packages, setup
+
+package_name = 'fastdds_cli'
+
+setup(
+ name=package_name,
+ version='0.0.1',
+ packages=find_packages(exclude=['test']),
+ data_files=[
+ ('share/ament_index/resource_index/packages',
+ ['resource/' + package_name]),
+ ('share/' + package_name, ['package.xml']),
+ ],
+ install_requires=['setuptools'],
+ zip_safe=True,
+ maintainer='eprosima',
+ maintainer_email='juanlopez@eprosima.com',
+ description='Fast DDS CLI',
+ license='Apache License, Version 2.0',
+ tests_require=['pytest'],
+ entry_points={
+ 'console_scripts': [
+ 'discovery_server = fastdds_cli.discovery_server:main',
+ 'shm = fastdds_cli.shm:main',
+ 'xml = fastdds_cli.xml:main'
+ ],
+ },
+)
diff --git a/vulcanexus.repos b/vulcanexus.repos
index df59cbbb..42800a5c 100644
--- a/vulcanexus.repos
+++ b/vulcanexus.repos
@@ -83,7 +83,7 @@ repositories:
type: git
url: https://github.com/eProsima/ShapesDemo-TypeSupport.git
version: v1.0.0
- eProsima/Vulcanexus-Base:
+ eProsima/Vulcanexus:
type: git
url: https://github.com/eProsima/vulcanexus.git
version: v3.3.0
diff --git a/vulcanexus_test/CMakeLists.txt b/vulcanexus_test/CMakeLists.txt
new file mode 100644
index 00000000..a19e4a4f
--- /dev/null
+++ b/vulcanexus_test/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# 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.
+
+cmake_minimum_required(VERSION 3.8)
+project(vulcanexus_test)
+
+###############################################################################
+# Test
+###############################################################################
+# Compile tests if CMake options requires it
+option(BUILD_COMPOSE_TESTS "Compile compose tests" OFF)
+
+if(BUILD_COMPOSE_TESTS)
+ enable_testing()
+ message(STATUS "Compiling Compose Tests of ${PROJECT_NAME}")
+ add_subdirectory(compose)
+endif()
diff --git a/vulcanexus_test/compose/CMakeLists.txt b/vulcanexus_test/compose/CMakeLists.txt
new file mode 100644
index 00000000..9201aa1d
--- /dev/null
+++ b/vulcanexus_test/compose/CMakeLists.txt
@@ -0,0 +1,51 @@
+# Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# 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.
+
+# Name of files to test
+set(TESTS
+ fastdds_cli/discovery_server
+ fastdds_cli/shm
+ fastdds_cli/xml
+)
+
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test_cases DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docker-compose.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/docker-compose.sh
+ COPYONLY)
+
+file(
+ COPY
+ ${CMAKE_CURRENT_SOURCE_DIR}/scripts
+ DESTINATION
+ ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+find_program (BASH_PROGRAM bash)
+
+# Populate the tests
+foreach(TEST IN LISTS TESTS)
+
+ set(TEST_NAME "vulcanexus.compose.${TEST}")
+ string(REPLACE "/" "_" TEST_NAME ${TEST_NAME})
+
+ message(STATUS "Building test ${TEST_NAME}")
+
+ add_test(
+ NAME ${TEST_NAME}
+ COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_BINARY_DIR}/docker-compose.sh
+ -t ${TEST_NAME}
+ -f ${CMAKE_CURRENT_BINARY_DIR}/test_cases/${TEST}/compose.yml)
+
+endforeach()
diff --git a/vulcanexus_test/compose/docker-compose.sh b/vulcanexus_test/compose/docker-compose.sh
new file mode 100755
index 00000000..49f65e46
--- /dev/null
+++ b/vulcanexus_test/compose/docker-compose.sh
@@ -0,0 +1,129 @@
+#!/bin/bash
+
+# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# 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.
+
+print_usage()
+{
+ echo "----------------------------------------------------------------------------------"
+ echo "Script that retrieves the exit code of the Vulcanexus docker compose tests."
+ echo "----------------------------------------------------------------------------------"
+ echo "REQUIRED ARGUMENTS:"
+ echo " -t --test-name [string] Name of the Vulcanexus test."
+ echo " -f --compose-file [filename] The Docker compose file"
+ echo ""
+ echo "OPTIONAL ARGUMENTS:"
+ echo " -d --debug Print debug traces."
+ echo " -h --help Print this same help."
+ echo ""
+ echo "EXAMPLE: bash docker-compose.sh -t -f "
+ echo ""
+ exit ${1}
+}
+
+parse_options()
+{
+
+ if (($# == 0)) ; then print_usage 1 ; fi
+ if [ $? != 0 ] ; then print_usage 1 ; fi
+
+ TEMP=`getopt \
+ -o t:f:hd \
+ --long test-name:,compose-file:,help,debug \
+ -n 'Error' \
+ -- "$@"`
+
+ eval set -- "${TEMP}"
+
+ TEST_NAME=
+ COMPOSE_FILE=
+ DEBUG=false
+ while true; do
+ case "$1" in
+ # Mandatory args
+ -t | --test-name ) TEST_NAME="$2"; shift 2;;
+ -f | --compose-file ) COMPOSE_FILE="$2"; shift 2;;
+ # Optional args
+ -h | --help ) print_usage 0; shift ;;
+ -d | --debug ) DEBUG=true; shift ;;
+ # Wrong args
+ -- ) shift; break ;;
+ * ) break ;;
+ esac
+ done
+
+ if [[ ${TEST_NAME} == "" ]]
+ then
+ echo "----------------------------------------------------------------------------------"
+ echo "No test name defined"
+ print_usage 1
+ fi
+
+ if [[ ${COMPOSE_FILE} == "" ]]
+ then
+ echo "----------------------------------------------------------------------------------"
+ echo "No Docker compose file provided"
+ print_usage 1
+ fi
+
+ if [[ ! -f "${COMPOSE_FILE}" ]]
+ then
+ echo "----------------------------------------------------------------------------------"
+ echo "-f --compose-file must specify an existing file"
+ print_usage 1
+ fi
+}
+
+full_path ()
+{
+ if [[ -f ${1} ]]
+ then
+ echo "$(realpath ${1})"
+ else
+ echo "$(dirname ${1})"
+ fi
+}
+
+main ()
+{
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
+ parse_options ${@}
+ EXIT_CODE=0
+
+ set -e
+
+ echo "Docker compose file: ${COMPOSE_FILE}"
+
+ # Run docker compose
+ docker compose -f ${COMPOSE_FILE} up
+
+ # First this command gets the ids of every container listed on the docker compose file.
+ # Then it checks the exist code of every container listed before, remove the exit codes equal
+ # to 0 and count how many containers exited with an exit code different than 0.
+ # As a result, the EXIT_CODE is the number of containers that exited with an exit code
+ # different than 0.
+ EXIT_CODE=$(docker compose -f ${COMPOSE_FILE} ps -aq |
+ xargs docker inspect -f '{{ .State.ExitCode }}' |
+ grep -vx "^0$" | wc -l | tr -d ' ')
+
+ echo "${TEST_NAME} exited with code ${EXIT_CODE}"
+
+ # Clean containers and networks before exiting and do not prompt for confirmation
+ docker container prune --force
+ docker network prune --force
+
+ exit ${EXIT_CODE}
+}
+
+main ${@}
diff --git a/vulcanexus_test/compose/scripts/execute_and_validate.py b/vulcanexus_test/compose/scripts/execute_and_validate.py
new file mode 100644
index 00000000..d8dd53b0
--- /dev/null
+++ b/vulcanexus_test/compose/scripts/execute_and_validate.py
@@ -0,0 +1,173 @@
+# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# 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.
+
+from enum import Enum
+from typing import List
+import argparse
+import signal
+import subprocess
+import time
+
+import log
+
+import utils
+
+
+DESCRIPTION = """Script to run commands and validate their output."""
+USAGE = ('python3 execute_and_validate.py '
+ '[-c ] [-t ] [-d]')
+
+
+class ReturnCode(Enum):
+ """Enumeration for return codes of this script."""
+
+ SUCCESS = 0
+ TIMEOUT = 1
+ HARD_TIMEOUT = 2
+ COMMAND_FAIL = 3
+ STDERR_OUTPUT = 4
+
+
+def parse_options():
+ """
+ Parse arguments.
+
+ :return: The arguments parsed.
+ """
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ add_help=True,
+ description=(DESCRIPTION),
+ usage=(USAGE)
+ )
+ parser.add_argument(
+ '-c',
+ '--command',
+ type=str,
+ help='Command to execute.'
+ )
+ parser.add_argument(
+ '-t',
+ '--timeout',
+ type=int,
+ default=5,
+ help='Timeout for the command execution.'
+ )
+ parser.add_argument(
+ '--delay',
+ type=float,
+ default=0,
+ help='Time to wait before starting execution.'
+ )
+ parser.add_argument(
+ '-d',
+ '--debug',
+ action='store_true',
+ help='Print test debugging info.'
+ )
+
+ return parser.parse_args()
+
+
+def run_command(
+ command: 'list[str]',
+ timeout: float,
+ delay: float = 0,
+ timeout_as_error: bool = True):
+ """
+ Run command with timeout.
+
+ :param command: Command to run in list format
+ :param timeout: Timeout for the process
+ :return:
+ - ret_code - The process exit code
+ - stdout - Output of the process
+ - stderr - Error output of the process
+ """
+ ret_code = ReturnCode.SUCCESS
+
+ # Delay
+ utils.delay(delay)
+
+ log.logger.debug(f'Running command: {command}')
+
+ proc = subprocess.Popen(command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True)
+
+ try:
+ proc.wait(timeout=timeout)
+
+ except subprocess.TimeoutExpired:
+ if timeout_as_error:
+ log.logger.error(
+ 'Timeout expired, killing process...')
+ proc.send_signal(signal.SIGINT)
+ ret_code = ReturnCode.TIMEOUT
+
+ else:
+ proc.send_signal(signal.SIGINT)
+
+ else:
+ if not timeout_as_error:
+ log.logger.error('Command finished before expected.')
+ ret_code = ReturnCode.COMMAND_FAIL
+
+ # Wait a minimum elapsed time to the signal to be received
+ time.sleep(0.2)
+
+ stdout, stderr = proc.communicate()
+
+ # Check whether SIGINT was able to terminate the process
+ if proc.poll() is None:
+ # SIGINT couldn't terminate the process
+ log.logger.error(
+ 'SIGINT could not kill process. '
+ 'Killing process hardly...')
+ proc.kill()
+ ret_code = ReturnCode.HARD_TIMEOUT
+
+ log.logger.info(f'Process exited with code {proc.returncode}')
+
+ # Set return code to COMMAND_FAIL if the process returned 1
+ ret_code = ReturnCode.COMMAND_FAIL if proc.returncode == 1 else ret_code
+
+ if not stdout:
+ stdout = ''
+ if not stderr:
+ stderr = ''
+
+ return (ret_code, stdout, stderr)
+
+
+if __name__ == '__main__':
+
+ # Parse arguments
+ args = parse_options()
+
+ # Set log level
+ if args.debug:
+ log.activate_debug()
+
+ command = args.command.split()
+
+ (ret_code, stdout, stderr) = run_command(
+ command=command,
+ timeout=args.timeout,
+ delay=args.delay)
+
+ log.logger.info(f'Command exited with code {ret_code}')
+
+ exit(ret_code.value)
diff --git a/vulcanexus_test/compose/scripts/log.py b/vulcanexus_test/compose/scripts/log.py
new file mode 100644
index 00000000..8ff5d317
--- /dev/null
+++ b/vulcanexus_test/compose/scripts/log.py
@@ -0,0 +1,32 @@
+# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# 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.
+
+import logging
+
+# Create a custom logger
+logger = logging.getLogger('VALIDATION')
+# Create handlers
+l_handler = logging.StreamHandler()
+# Create formatters and add it to handlers
+l_format = '[%(asctime)s][%(name)s][%(levelname)s] %(message)s'
+l_format = logging.Formatter(l_format)
+l_handler.setFormatter(l_format)
+# Add handlers to the logger
+logger.addHandler(l_handler)
+logger.setLevel(logging.INFO)
+
+
+def activate_debug():
+ """Activate debug mode."""
+ logger.setLevel(logging.DEBUG)
diff --git a/vulcanexus_test/compose/scripts/utils.py b/vulcanexus_test/compose/scripts/utils.py
new file mode 100644
index 00000000..605d8dd8
--- /dev/null
+++ b/vulcanexus_test/compose/scripts/utils.py
@@ -0,0 +1,39 @@
+# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# 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.
+
+import random
+import time
+
+
+def sleep_random_time(
+ min_time_seconds: int,
+ max_time_seconds: int):
+ """Sleep a random time between min_time_seconds and max_time in seconds."""
+ time.sleep(
+ min_time_seconds +
+ ((max_time_seconds - min_time_seconds) * random.random()))
+
+
+def print_with_timestamp(
+ msg: str):
+ """Print a message with a timestamp."""
+ print(f'{time.time()}$ {msg}')
+ # This has been made by copilot... Respect
+
+
+def delay(
+ time_s: float):
+ """Wait for time_s seconds."""
+ if (time_s > 0):
+ time.sleep(time_s)
diff --git a/vulcanexus_test/compose/test_cases/fastdds_cli/discovery_server/compose.yml b/vulcanexus_test/compose/test_cases/fastdds_cli/discovery_server/compose.yml
new file mode 100644
index 00000000..03e46ace
--- /dev/null
+++ b/vulcanexus_test/compose/test_cases/fastdds_cli/discovery_server/compose.yml
@@ -0,0 +1,12 @@
+# Test description:
+# This test checks that running fastdds CLI discovery option results in no error (process return code is not 1).
+#
+
+services:
+
+ vulcanexus:
+ image: ${VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE}
+ container_name: vulcanexus
+ volumes:
+ - ../../../scripts:/scripts
+ command: python3 /scripts/execute_and_validate.py --command "timeout 1 ros2 run fastdds_cli discovery_server"
diff --git a/vulcanexus_test/compose/test_cases/fastdds_cli/shm/compose.yml b/vulcanexus_test/compose/test_cases/fastdds_cli/shm/compose.yml
new file mode 100644
index 00000000..6afc73e5
--- /dev/null
+++ b/vulcanexus_test/compose/test_cases/fastdds_cli/shm/compose.yml
@@ -0,0 +1,12 @@
+# Test description:
+# This test checks that running fastdds CLI shm option results in no error (process return code is not 1).
+#
+
+services:
+
+ vulcanexus:
+ image: ${VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE}
+ container_name: vulcanexus
+ volumes:
+ - ../../../scripts:/scripts
+ command: python3 /scripts/execute_and_validate.py --command "ros2 run fastdds_cli shm"
diff --git a/vulcanexus_test/compose/test_cases/fastdds_cli/xml/compose.yml b/vulcanexus_test/compose/test_cases/fastdds_cli/xml/compose.yml
new file mode 100644
index 00000000..90b1f876
--- /dev/null
+++ b/vulcanexus_test/compose/test_cases/fastdds_cli/xml/compose.yml
@@ -0,0 +1,12 @@
+# Test description:
+# This test checks that running fastdds CLI xml option results in no error (process return code is not 1).
+#
+
+services:
+
+ vulcanexus:
+ image: ${VULCANEXUS_COMPOSE_TEST_DOCKER_IMAGE}
+ container_name: vulcanexus
+ volumes:
+ - ../../../scripts:/scripts
+ command: python3 /scripts/execute_and_validate.py --command "ros2 run fastdds_cli xml"
diff --git a/vulcanexus_test/package.xml b/vulcanexus_test/package.xml
new file mode 100644
index 00000000..f996be43
--- /dev/null
+++ b/vulcanexus_test/package.xml
@@ -0,0 +1,21 @@
+
+
+
+ vulcanexus_test
+ 0.0.1
+
+ Vulcanexus tests.
+
+ Juan López
+ Apache 2.0
+
+ https://www.eprosima.com/
+ https://github.com/eProsima/vulcanexus/issues
+ https://github.com/eProsima/vulcanexus
+
+ cmake
+
+
+ cmake
+
+