Skip to content
This repository has been archived by the owner on Jun 10, 2021. It is now read-only.

Commit

Permalink
Bump action-ros-ci to 0.1.0 (#186)
Browse files Browse the repository at this point in the history
* Upgrade CI actions to latest version to fix build 
* upgrade tests to match foxy api
* rewrite e2e tests as launch tests to remove process management logic 

Signed-off-by: Emerson Knapp <eknapp@amazon.com>
  • Loading branch information
emersonknapp authored Dec 2, 2020
1 parent fc24b02 commit bf375a5
Show file tree
Hide file tree
Showing 19 changed files with 263 additions and 565 deletions.
20 changes: 0 additions & 20 deletions .github/workflows/e2e_test.yml

This file was deleted.

1 change: 0 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ jobs:
matrix:
linter: [copyright, cppcheck, cpplint, flake8, pep257, uncrustify, xmllint]
steps:
- run: sudo chown -R rosbuild:rosbuild "$HOME" .
- uses: actions/checkout@v2
- uses: ros-tooling/action-ros-lint@0.0.6
with:
Expand Down
10 changes: 4 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ jobs:
container:
image: rostooling/setup-ros-docker:ubuntu-focal-latest
steps:
# TODO(setup-ros-docker#7): calling chown is necessary for now
- run: sudo chown -R rosbuild:rosbuild "$HOME" .
- uses: ros-tooling/action-ros-ci@0.0.17
- uses: ros-tooling/action-ros-ci@0.1.0
with:
package-name: system_metrics_collector
colcon-mixin-name: coverage-gcc
colcon-mixin-repository: https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml
target-ros2-distro: foxy
- uses: codecov/codecov-action@v1.0.14
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand All @@ -40,13 +39,12 @@ jobs:
container:
image: rostooling/setup-ros-docker:ubuntu-focal-latest
steps:
# TODO(setup-ros-docker#7): calling chown is necessary for now
- run: sudo chown -R rosbuild:rosbuild "$HOME" .
- uses: ros-tooling/action-ros-ci@0.0.17
- uses: ros-tooling/action-ros-ci@0.1.0
with:
colcon-mixin-name: asan
colcon-mixin-repository: https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml
package-name: system_metrics_collector
target-ros2-distro: foxy
- uses: actions/upload-artifact@v1
with:
name: colcon-logs-ubuntu-asan
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
11 changes: 11 additions & 0 deletions system_metrics_collector/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ ament_target_dependencies(topic_statistics_node)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
find_package(ament_cmake_gtest REQUIRED)
find_package(launch_testing_ament_cmake REQUIRED)
find_package(class_loader REQUIRED)
find_package(lifecycle_msgs REQUIRED)

Expand Down Expand Up @@ -149,6 +150,16 @@ if(BUILD_TESTING)
ament_target_dependencies(test_subscriber_topic_statistics rcl rclcpp)

rosidl_target_interfaces(test_subscriber_topic_statistics system_metrics_collector_test_msgs "rosidl_typesupport_cpp")

add_launch_test(
test/test_system_metrics_e2e.py
TARGET test_system_metrics_e2e
TIMEOUT 60)

add_launch_test(
test/test_topic_statistics_e2e.py
TARGET test_topic_statistics_e2e
TIMEOUT 60)
endif()

# To enable use of dummy_message.hpp in executables
Expand Down
10 changes: 5 additions & 5 deletions system_metrics_collector/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@

<!--Required for example launch file-->
<exec_depend>demo_nodes_cpp</exec_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<exec_depend>std_msgs</exec_depend>

<test_depend>ament_cmake_gtest</test_depend>
<test_depend>launch_testing_ament_cmake</test_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>class_loader</test_depend>
<test_depend>launch_testing</test_depend>
<test_depend>lifecycle_msgs</test_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<exec_depend>std_msgs</exec_depend>

<!--Required for e2e test file-->
<test_depend>python3-retrying</test_depend>
<test_depend>rclpy</test_depend>
<test_depend>ros2launch</test_depend>
<test_depend>ros2node</test_depend>
<test_depend>ros2lifecycle</test_depend>
<test_depend>ros2topic</test_depend>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ def generate_launch_description():
return LaunchDescription([
launch_ros.actions.Node(
package='system_metrics_collector',
node_executable='dummy_talker',
executable='dummy_talker',
output='screen'),
])
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ def generate_launch_description():
ld.add_action(
Node(
package='system_metrics_collector',
node_executable='linux_cpu_collector',
executable='linux_cpu_collector',
name=LaunchConfiguration(CPU_NODE_NAME),
parameters=node_parameters,
remappings=[('system_metrics', LaunchConfiguration(PUBLISH_TOPIC))],
output='screen'))
ld.add_action(
Node(
package='system_metrics_collector',
node_executable='linux_memory_collector',
executable='linux_memory_collector',
name=LaunchConfiguration(MEMORY_NODE_NAME),
parameters=node_parameters,
remappings=[('system_metrics', LaunchConfiguration(PUBLISH_TOPIC))],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,20 @@ def generate_launch_description():

# Collect, aggregate, and measure system CPU % used
system_cpu_node = LifecycleNode(
executable='linux_cpu_collector',
package='system_metrics_collector',
name='linux_system_cpu_collector',
node_executable='linux_cpu_collector',
namespace='',
output='screen',
parameters=node_parameters,
)

# Collect, aggregate, and measure system memory % used
system_memory_node = LifecycleNode(
executable='linux_memory_collector',
package='system_metrics_collector',
name='linux_system_memory_collector',
node_executable='linux_memory_collector',
namespace='',
output='screen',
parameters=node_parameters,
)
Expand All @@ -74,7 +76,7 @@ def generate_launch_description():
name='listener_container',
namespace='',
package='rclcpp_components',
node_executable='component_container',
executable='component_container',
composable_node_descriptions=[
ComposableNode(
package='demo_nodes_cpp',
Expand All @@ -101,7 +103,7 @@ def generate_launch_description():
name='talker_container',
namespace='',
package='rclcpp_components',
node_executable='component_container',
executable='component_container',
composable_node_descriptions=[
ComposableNode(
package='demo_nodes_cpp',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
# Default argument values
DEFAULT_COLLECTOR_NODE_NAME = 'topic_stats_collector'
DEFAULT_MONITORED_TOPIC_NAME = ['dummy_topic']
DEFAULT_PUBLISH_PERIOD_IN_MS = '30000'
DEFAULT_PUBLISH_PERIOD_IN_MS = '1000'
DEFAULT_PUBLISH_TOPIC = 'system_metrics'


Expand Down Expand Up @@ -68,7 +68,7 @@ def generate_launch_description():
ld.add_action(
Node(
package='system_metrics_collector',
node_executable='topic_statistics_node',
executable='topic_statistics_node',
name=LaunchConfiguration(COLLECTOR_NODE_NAME),
parameters=node_parameters,
remappings=[('system_metrics', LaunchConfiguration(PUBLISH_TOPIC_NAME))],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class DummyTalker : public rclcpp::Node
publisher_ = this->create_publisher<system_metrics_collector::msg::DummyMessage>(
"dummy_data",
10 /* QoS history_depth */);
timer_ = this->create_wall_timer(1s, publish_lambda);
timer_ = this->create_wall_timer(100ms, publish_lambda);
}

private:
Expand Down
108 changes: 108 additions & 0 deletions system_metrics_collector/test/base_metrics_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# 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 collections import Counter
from pathlib import Path
from threading import Lock
from typing import Iterable
from typing import Set
import unittest

from ament_index_python.packages import get_package_share_directory
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from lifecycle_msgs.msg import State
import rclpy
from rclpy.task import Future
import retrying
import ros2lifecycle.api
import ros2node.api
from statistics_msgs.msg import MetricsMessage


def include_python_launch_file(package: str, launchfile: str) -> IncludeLaunchDescription:
package_dir = Path(get_package_share_directory(package))
launchfile_path = str(package_dir / launchfile)
return IncludeLaunchDescription(PythonLaunchDescriptionSource(launchfile_path))


class TestMetricsBase(unittest.TestCase):

@classmethod
def setUpClass(cls):
rclpy.init()
cls.node = rclpy.create_node('test_system_metrics_nodes')

@classmethod
def tearDownClass(cls):
cls.node.destroy_node()
rclpy.shutdown()

@retrying.retry(
stop_max_attempt_number=10,
wait_exponential_multiplier=1000,
wait_exponential_max=10000)
def _test_nodes_exist(self, expected_nodes: Set[str]):
node_names = ros2node.api.get_node_names(node=self.node)
full_names = {n.full_name for n in node_names}
self.assertTrue(expected_nodes.issubset(full_names))

@retrying.retry(
stop_max_attempt_number=10,
wait_exponential_multiplier=1000,
wait_exponential_max=10000)
def _test_lifecycle_nodes_exist(self, expected_nodes: Set[str]) -> None:
node_names = ros2lifecycle.api.get_node_names(node=self.node)
full_names = {n.full_name for n in node_names}
self.assertTrue(expected_nodes.issubset(full_names))

def _test_lifecycle_nodes_active(self, expected_lifecycle_nodes: Iterable[str]) -> None:
states = ros2lifecycle.api.call_get_states(
node=self.node,
node_names=expected_lifecycle_nodes)
self.assertTrue(all(s.id == State.PRIMARY_STATE_ACTIVE for s in states.values()))

def _test_topic_exists(self, topic_name: str) -> None:
topics_and_types = self.node.get_topic_names_and_types()
found = False
for name, types in topics_and_types:
if name == topic_name:
found = True
assert all(t == 'statistics_msgs/msg/MetricsMessage' for t in types)
self.assertTrue(found, f'No topic named {topic_name}')

def _test_statistic_publication(self, topic_name: str, expected_nodes: Iterable[str]):
future = Future()
message_counter = Counter()
lock = Lock()
# arbitrary choice, just tells if it's working for a little while
expected_messages_per_node = 3
# we are receiving stats every 10 seconds, so this should pass in 30s
timeout_sec = 180

def message_callback(msg):
node_name = '/' + msg.measurement_source_name
with lock:
message_counter[node_name] += 1
if all(
message_counter[node] >= expected_messages_per_node
for node in expected_nodes
):
print('Successfully received all expected messages')
future.set_result(True)

sub = self.node.create_subscription(
MetricsMessage, topic_name, message_callback, qos_profile=10)
rclpy.spin_until_future_complete(self.node, future, timeout_sec=timeout_sec)
self.assertTrue(future.done(), f'Timed out, received message count: {message_counter}')
self.node.destroy_subscription(sub)
64 changes: 64 additions & 0 deletions system_metrics_collector/test/test_system_metrics_e2e.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# 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 os
import sys

from launch import LaunchDescription
import launch_testing
import pytest

# Allow relative import even though we are not in a real module
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from base_metrics_test import include_python_launch_file, TestMetricsBase # noqa: E402, I100


EXPECTED_LIFECYCLE_NODES = {
'/linux_system_cpu_collector',
'/linux_system_memory_collector',
'/listener_process_cpu_node',
'/listener_process_memory_node',
'/talker_process_cpu_node',
'/talker_process_memory_node',
}
EXPECTED_REGULAR_NODES = {
'/listener',
'/talker',
}


@pytest.mark.launch_test
def generate_test_description():
return LaunchDescription([
include_python_launch_file(
'system_metrics_collector', 'examples/talker_listener_example.launch.py'),
launch_testing.actions.ReadyToTest(),
])


class TestSystemMetricsLaunch(TestMetricsBase):

def test_nodes_exist(self):
return self._test_nodes_exist(EXPECTED_LIFECYCLE_NODES.union(EXPECTED_REGULAR_NODES))

def test_lifecycle_nodes_exist(self):
return self._test_lifecycle_nodes_exist(EXPECTED_LIFECYCLE_NODES)

def test_lifecycle_nodes_active(self):
return self._test_lifecycle_nodes_active(EXPECTED_LIFECYCLE_NODES)

def test_topics_exist(self):
return self._test_topic_exists('/system_metrics')

def test_statistic_publication(self):
return self._test_statistic_publication('/system_metrics', EXPECTED_LIFECYCLE_NODES)
Loading

0 comments on commit bf375a5

Please sign in to comment.