diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 659b7d5..fd23c7f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM ros:jazzy AS dev +FROM ros@sha256:9749355a0760334fa5ea3c660f2de1ecc5e8a5af8047e4bbaa9afa8a7843da80 AS dev # ******************************************************** # * Install Dependencies * @@ -20,11 +20,6 @@ RUN apt-get update -q && \ apt-get install -y gnupg2 iputils-ping usbutils \ python3-argcomplete python3-colcon-common-extensions python3-networkx python3-pip python3-rosdep python3-vcstool -# Set up the ROS 2 environment -# This ensures that ROS 2 commands are available in the shell -RUN grep -F "source /opt/ros/jazzy/setup.bash" /root/.bashrc || echo "source /opt/ros/jazzy/setup.bash" >> /root/.bashrc && \ - grep -F "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" /root/.bashrc || echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> /root/.bashrc - # Install Linux packages # xterm: to use teleop_twist_keyboard RUN apt-get update && apt-get install -y \ @@ -56,8 +51,11 @@ RUN apt-get update && apt-get install -y \ ros-jazzy-teleop-twist-joy \ ros-jazzy-fuse +# Install cyclonedds rmw and set up the configuration RUN apt-get update && apt-get install -y \ ros-jazzy-rmw-cyclonedds-cpp +ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp +ENV RMW_CYCLONEDDS_URI=file:///home/ws/config/cyclonedds.xml # ******************************************************** # * Set up the user * @@ -82,9 +80,15 @@ RUN apt-get update && apt-get upgrade -y RUN apt-get install -y python3-pip ENV SHELL=/bin/bash +# Define entrypoint +RUN mkdir -p /home/ws +COPY .devcontainer/entrypoint.sh /home/ws +RUN chmod +x /home/ws/entrypoint.sh +ENTRYPOINT [ "/home/ws/entrypoint.sh" ] +CMD ["/bin/bash"] + # [Optional] Set the default user. Omit if you want to keep the default as root. USER $USERNAME -CMD ["/bin/bash"] # ******************************************************** # * Production * @@ -93,11 +97,8 @@ FROM dev AS prod # Copy our packages into the image and build them USER root -RUN mkdir -p /ws/src -WORKDIR /ws +WORKDIR /home/ws COPY config config COPY src src RUN source /opt/ros/jazzy/setup.bash \ - && colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release - -ENTRYPOINT ["python3", "/ws/config/append_to_bashrc.py"] \ No newline at end of file + && colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8f4c197..4b088d4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,6 +4,7 @@ "remoteUser": "htkz", "build": { "dockerfile": "Dockerfile", + "context": "..", "target": "dev", "args": { "USERNAME": "htkz" @@ -44,5 +45,5 @@ "source=/dev/dri,target=/dev/dri,type=bind,consistency=cached", "source=/dev/input,target=/dev/input,type=bind,consistency=delegated" ], - "postCreateCommand": "sudo apt-get update && sudo rosdep update && sudo rosdep install --from-paths src --ignore-src -y && sudo chown -R $(whoami) /home/ws/ && python3 /home/ws/config/append_to_bashrc.py" + "postCreateCommand": "sudo chown -R $(whoami) /home/ws/ && python3 /home/ws/config/append_to_bashrc.py" } \ No newline at end of file diff --git a/.devcontainer/entrypoint.sh b/.devcontainer/entrypoint.sh new file mode 100755 index 0000000..3ed87e1 --- /dev/null +++ b/.devcontainer/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set +e + +# Source environment variables +source /opt/ros/jazzy/setup.bash +source /home/ws/install/setup.bash +source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash + +# Execute the provided command +exec "$@" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2953dcd..75e77c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,14 @@ -name: docker build & colcon-test +name: Build Image and Run Tests on: push: branches: - - microservices - pull_request: - branches: - - microservices + - '**' + - '!humble' + - '!jazzy' jobs: - docker-build: + Build-image: runs-on: ubuntu-latest steps: @@ -31,26 +30,38 @@ jobs: with: context: . file: .devcontainer/Dockerfile - tags: ghcr.io/helbling-technik/github_actions:latest - cache-from: type=gha - cache-to: type=gha,mode=max + tags: ghcr.io/helbling-technik/helmoro-software-ros2:${{ github.ref_name }}-latest push: true + cache-from: | + type=gha + type=registry,ref=ghcr.io/helbling-technik/helmoro-software-ros2:${{ github.ref_name }}-buildcache + cache-to: | + type=gha,mode=max + type=registry,ref=ghcr.io/helbling-technik/helmoro-software-ros2:${{ github.ref_name }}-buildcache,mode=max + - colcon-test: - needs: docker-build + Run-tests: + needs: Build-image runs-on: ubuntu-latest + defaults: + run: + working-directory: /home/ws container: - image: ghcr.io/helbling-technik/github_actions:latest + image: ghcr.io/helbling-technik/helmoro-software-ros2:${{ github.ref_name }}-latest + steps: - - uses: ros-tooling/action-ros-ci@v0.4 - id: action_ros_ci_step - with: - package-name: helmoro_description - target-ros2-distro: jazzy - coverage-result: false + - name: Run tests + shell: bash + run: | + set -e + source entrypoint.sh + colcon test + colcon test-result --all --verbose - - uses: actions/upload-artifact@v4 + - name: Upload logs + uses: actions/upload-artifact@v4 with: name: colcon-logs - path: ${{ steps.action_ros_ci_step.outputs.ros-workspace-directory-name }}/log - if: always() # upload the logs even when the build fails + path: /home/ws/log + if-no-files-found: error + if: always() # upload the logs even when the test fails diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml new file mode 100644 index 0000000..c0eae36 --- /dev/null +++ b/.github/workflows/debug.yml @@ -0,0 +1,34 @@ +name: Run Tests + +on: + push: + branches: + - '**' + - '!humble' + - '!jazzy' + +jobs: + colcon-test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: /home/ws + container: + image: ghcr.io/helbling-technik/helmoro-software-ros2:${{ github.ref_name }}-latest + + steps: + - name: Run tests + shell: bash + run: | + set -e + source entrypoint.sh + colcon test + colcon test-result --all --verbose + + - name: Upload logs + uses: actions/upload-artifact@v4 + with: + name: colcon-logs + path: /home/ws/log + if-no-files-found: error + if: always() # upload the logs even when the test fails diff --git a/config/append_to_bashrc.py b/config/append_to_bashrc.py index 726fefe..79ce07d 100644 --- a/config/append_to_bashrc.py +++ b/config/append_to_bashrc.py @@ -26,8 +26,7 @@ def append_unique_lines_to_bashrc(source_file): print("ℹ️ No new lines were added. All lines already exist.") if __name__ == "__main__": - append_unique_lines_to_bashrc(os.path.expanduser("/ws/config/append_to_bashrc.txt")) - + append_unique_lines_to_bashrc(os.path.abspath("/home/ws/config/append_to_bashrc.txt")) # If there are arguments passed to this container (i.e., CMD), run them if len(sys.argv) > 1: diff --git a/config/append_to_bashrc.txt b/config/append_to_bashrc.txt index 510fe5e..d3d43a2 100644 --- a/config/append_to_bashrc.txt +++ b/config/append_to_bashrc.txt @@ -1,10 +1,5 @@ # Custom Alias alias src='source /opt/ros/jazzy/setub.bash && source /home/ws/install/setup.bash' -# Environment Setup -source /opt/ros/jazzy/setup.bash -source /home/ws/install/setup.bash -export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp -export CYCLONEDDS_URI=file:///home/ws/config/cyclonedds.xml - - +# Integrate entrypoint.sh +source /home/ws/.devcontainer/entrypoint.sh \ No newline at end of file diff --git a/src/helmoro_description/test/launch_description.test.py b/src/helmoro_description/test/launch_description.test.py index 725babb..5ad1107 100644 --- a/src/helmoro_description/test/launch_description.test.py +++ b/src/helmoro_description/test/launch_description.test.py @@ -48,7 +48,7 @@ def generate_test_description(): ld.add_action(ready_to_test) return ld -class TestRobotStatePublisher(unittest.TestCase): +class TestCollection(unittest.TestCase): """Test suite for checking the robot_state_publisher functionality.""" def setUp(self): @@ -61,7 +61,7 @@ def tearDown(self): self.node.destroy_node() rclpy.shutdown() - def test_node_start(self, proc_output: ActiveIoHandler): + def test_robot_state_publisher_node_start(self, proc_output: ActiveIoHandler): """Test if the robot_state_publisher node has started.""" found = False print('Waiting for node...') @@ -75,7 +75,7 @@ def test_node_start(self, proc_output: ActiveIoHandler): # Assert that the node was found assert found, 'Node not found!' - def test_advertise_topic(self, proc_output: ActiveIoHandler): + def test_robot_state_publisher_advertise_topic(self, proc_output: ActiveIoHandler): """Test if the robot_description topic is advertised by the node.""" received = False print("Listening for topics...") @@ -94,7 +94,7 @@ def test_advertise_topic(self, proc_output: ActiveIoHandler): # Assert that the topic was advertised assert received, 'Topic not advertised!' - def test_publish_msgs(self, proc_output: ActiveIoHandler): + def test_robot_state_publisher_publish_msgs(self, proc_output: ActiveIoHandler): """Test if messages are published to the correct topic.""" msgs_rx = [] # List to store received messages @@ -128,20 +128,7 @@ def test_publish_msgs(self, proc_output: ActiveIoHandler): # Ensure that the subscription is destroyed after the test self.node.destroy_subscription(sub) -class TestJointStatePublisher(unittest.TestCase): - """Test suite for checking the joint_state_publisher functionality.""" - - def setUp(self): - """Initialize the ROS node before each test.""" - rclpy.init() - self.node = rclpy.create_node('test_node') - - def tearDown(self): - """Shut down the ROS node after each test.""" - self.node.destroy_node() - rclpy.shutdown() - - def test_node_start(self, proc_output: ActiveIoHandler): + def test_joint_state_publisher_node_start(self, proc_output: ActiveIoHandler): """Test if the joint_state_publisher node has started.""" found = False print('Waiting for node...') @@ -155,7 +142,7 @@ def test_node_start(self, proc_output: ActiveIoHandler): # Assert that the node was found assert found, 'Node not found!' - def test_advertise_topic(self, proc_output: ActiveIoHandler): + def test_joint_state_publisher_advertise_topic(self, proc_output: ActiveIoHandler): """Test if the joint_state topic is advertised by the node.""" received = False print("Listening for topics...")