ros1_foxglove_bridge/tests/smoke_test.cpp
Go to the documentation of this file.
1 #define ASIO_STANDALONE
2 
3 #include <chrono>
4 #include <future>
5 #include <thread>
6 
7 #include <gtest/gtest.h>
8 #include <ros/ros.h>
10 #include <std_srvs/SetBool.h>
11 #include <websocketpp/config/asio_client.hpp>
12 
15 
16 constexpr char URI[] = "ws://localhost:9876";
17 
18 // Binary representation of std_msgs/String for "hello world"
19 constexpr uint8_t HELLO_WORLD_BINARY[] = {11, 0, 0, 0, 104, 101, 108, 108,
20  111, 32, 119, 111, 114, 108, 100};
21 
22 constexpr auto ONE_SECOND = std::chrono::seconds(1);
23 constexpr auto DEFAULT_TIMEOUT = std::chrono::seconds(8);
24 
25 class ParameterTest : public ::testing::Test {
26 public:
27  using PARAM_1_TYPE = std::string;
28  inline static const std::string PARAM_1_NAME = "/node_1/string_param";
29  inline static const PARAM_1_TYPE PARAM_1_DEFAULT_VALUE = "hello";
30 
31  using PARAM_2_TYPE = std::vector<double>;
32  inline static const std::string PARAM_2_NAME = "/node_2/int_array_param";
33  inline static const PARAM_2_TYPE PARAM_2_DEFAULT_VALUE = {1.2, 2.1, 3.3};
34 
35 protected:
36  void SetUp() override {
37  _nh = ros::NodeHandle();
38  _nh.setParam(PARAM_1_NAME, PARAM_1_DEFAULT_VALUE);
39  _nh.setParam(PARAM_2_NAME, PARAM_2_DEFAULT_VALUE);
40 
41  _wsClient = std::make_shared<foxglove::Client<websocketpp::config::asio_client>>();
42  ASSERT_EQ(std::future_status::ready, _wsClient->connect(URI).wait_for(DEFAULT_TIMEOUT));
43  }
44 
46  std::shared_ptr<foxglove::Client<websocketpp::config::asio_client>> _wsClient;
47 };
48 
49 class ServiceTest : public ::testing::Test {
50 public:
51  inline static const std::string SERVICE_NAME = "/foo_service";
52 
53 protected:
54  void SetUp() override {
55  _nh = ros::NodeHandle();
56  _service = _nh.advertiseService<std_srvs::SetBool::Request, std_srvs::SetBool::Response>(
57  SERVICE_NAME, [&](auto& req, auto& res) {
58  res.message = "hello";
59  res.success = req.data;
60  return true;
61  });
62  }
63 
64 private:
67 };
68 
69 TEST(SmokeTest, testConnection) {
71  EXPECT_EQ(std::future_status::ready, wsClient.connect(URI).wait_for(DEFAULT_TIMEOUT));
72 }
73 
74 TEST(SmokeTest, testSubscription) {
75  // Publish a string message on a latched ros topic
76  const std::string topic_name = "/pub_topic";
77  ros::NodeHandle nh;
78  auto pub = nh.advertise<std_msgs::String>(topic_name, 10, true);
79  pub.publish(std::string("hello world"));
80 
81  // Connect a few clients and make sure that they receive the correct message
82  const auto clientCount = 3;
83  for (auto i = 0; i < clientCount; ++i) {
84  std::vector<uint8_t> msgData;
85  ASSERT_NO_THROW(msgData = foxglove::connectClientAndReceiveMsg(URI, topic_name));
86  ASSERT_EQ(sizeof(HELLO_WORLD_BINARY), msgData.size());
87  EXPECT_EQ(0, std::memcmp(HELLO_WORLD_BINARY, msgData.data(), msgData.size()));
88  }
89 }
90 
91 TEST(SmokeTest, testSubscriptionParallel) {
92  // Publish a string message on a latched ros topic
93  const std::string topic_name = "/pub_topic";
94  ros::NodeHandle nh;
95  auto pub = nh.advertise<std_msgs::String>(topic_name, 10, true);
96  pub.publish(std::string("hello world"));
97 
98  // Connect a few clients (in parallel) and make sure that they receive the correct message
99  std::vector<std::future<std::vector<uint8_t>>> futures;
100  const auto clientCount = 3;
101  for (auto i = 0; i < clientCount; ++i) {
102  futures.push_back(
103  std::async(std::launch::async, foxglove::connectClientAndReceiveMsg, URI, topic_name));
104  }
105 
106  for (auto& future : futures) {
107  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
108  auto msgData = future.get();
109  ASSERT_EQ(sizeof(HELLO_WORLD_BINARY), msgData.size());
110  EXPECT_EQ(0, std::memcmp(HELLO_WORLD_BINARY, msgData.data(), msgData.size()));
111  }
112 }
113 
114 TEST(SmokeTest, testPublishing) {
116 
117  foxglove::ClientAdvertisement advertisement;
118  advertisement.channelId = 1;
119  advertisement.topic = "/foo";
120  advertisement.encoding = "ros1";
121  advertisement.schemaName = "std_msgs/String";
122 
123  // Set up a ROS node with a subscriber
124  ros::NodeHandle nh;
125  std::promise<std::string> msgPromise;
126  auto msgFuture = msgPromise.get_future();
127  auto subscriber = nh.subscribe<std_msgs::String>(
128  advertisement.topic, 10, [&msgPromise](const std_msgs::String::ConstPtr& msg) {
129  msgPromise.set_value(msg->data);
130  });
131 
132  // Set up the client, advertise and publish the binary message
133  ASSERT_EQ(std::future_status::ready, wsClient.connect(URI).wait_for(DEFAULT_TIMEOUT));
134  wsClient.advertise({advertisement});
135  std::this_thread::sleep_for(ONE_SECOND);
136  wsClient.publish(advertisement.channelId, HELLO_WORLD_BINARY, sizeof(HELLO_WORLD_BINARY));
137  wsClient.unadvertise({advertisement.channelId});
138 
139  // Ensure that we have received the correct message via our ROS subscriber
140  const auto msgResult = msgFuture.wait_for(ONE_SECOND);
141  ASSERT_EQ(std::future_status::ready, msgResult);
142  EXPECT_EQ("hello world", msgFuture.get());
143 }
144 
145 TEST_F(ParameterTest, testGetAllParams) {
146  const std::string requestId = "req-testGetAllParams";
147  auto future = foxglove::waitForParameters(_wsClient, requestId);
148  _wsClient->getParameters({}, requestId);
149  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
150  std::vector<foxglove::Parameter> params = future.get();
151 
152  EXPECT_GE(params.size(), 2UL);
153 }
154 
155 TEST_F(ParameterTest, testGetNonExistingParameters) {
156  const std::string requestId = "req-testGetNonExistingParameters";
157  auto future = foxglove::waitForParameters(_wsClient, requestId);
158  _wsClient->getParameters(
159  {"/foo_1/non_existing_parameter", "/foo_2/non_existing/nested_parameter"}, requestId);
160  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
161  std::vector<foxglove::Parameter> params = future.get();
162 
163  EXPECT_TRUE(params.empty());
164 }
165 
166 TEST_F(ParameterTest, testGetParameters) {
167  const std::string requestId = "req-testGetParameters";
168  auto future = foxglove::waitForParameters(_wsClient, requestId);
169  _wsClient->getParameters({PARAM_1_NAME, PARAM_2_NAME}, requestId);
170  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
171  std::vector<foxglove::Parameter> params = future.get();
172 
173  EXPECT_EQ(2UL, params.size());
174  auto p1Iter = std::find_if(params.begin(), params.end(), [](const auto& param) {
175  return param.getName() == PARAM_1_NAME;
176  });
177  auto p2Iter = std::find_if(params.begin(), params.end(), [](const auto& param) {
178  return param.getName() == PARAM_2_NAME;
179  });
180  ASSERT_NE(p1Iter, params.end());
181  EXPECT_EQ(PARAM_1_DEFAULT_VALUE, p1Iter->getValue().getValue<PARAM_1_TYPE>());
182  ASSERT_NE(p2Iter, params.end());
183 
184  std::vector<double> double_array_val;
185  const auto array_params = p2Iter->getValue().getValue<std::vector<foxglove::ParameterValue>>();
186  for (const auto& paramValue : array_params) {
187  double_array_val.push_back(paramValue.getValue<double>());
188  }
189  EXPECT_EQ(double_array_val, PARAM_2_DEFAULT_VALUE);
190 }
191 
192 TEST_F(ParameterTest, testSetParameters) {
193  const PARAM_1_TYPE newP1value = "world";
194  const std::vector<foxglove::ParameterValue> newP2value = {4.1, 5.5, 6.6};
195 
196  const std::vector<foxglove::Parameter> parameters = {
197  foxglove::Parameter(PARAM_1_NAME, newP1value),
198  foxglove::Parameter(PARAM_2_NAME, newP2value),
199  };
200 
201  _wsClient->setParameters(parameters);
202  const std::string requestId = "req-testSetParameters";
203  auto future = foxglove::waitForParameters(_wsClient, requestId);
204  _wsClient->getParameters({PARAM_1_NAME, PARAM_2_NAME}, requestId);
205  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
206  std::vector<foxglove::Parameter> params = future.get();
207 
208  EXPECT_EQ(2UL, params.size());
209  auto p1Iter = std::find_if(params.begin(), params.end(), [](const auto& param) {
210  return param.getName() == PARAM_1_NAME;
211  });
212  auto p2Iter = std::find_if(params.begin(), params.end(), [](const auto& param) {
213  return param.getName() == PARAM_2_NAME;
214  });
215  ASSERT_NE(p1Iter, params.end());
216  EXPECT_EQ(newP1value, p1Iter->getValue().getValue<PARAM_1_TYPE>());
217  ASSERT_NE(p2Iter, params.end());
218 
219  std::vector<double> double_array_val;
220  const auto array_params = p2Iter->getValue().getValue<std::vector<foxglove::ParameterValue>>();
221  for (const auto& paramValue : array_params) {
222  double_array_val.push_back(paramValue.getValue<double>());
223  }
224  const std::vector<double> expected_value = {4.1, 5.5, 6.6};
225  EXPECT_EQ(double_array_val, expected_value);
226 }
227 
228 TEST_F(ParameterTest, testSetParametersWithReqId) {
229  const PARAM_1_TYPE newP1value = "world";
230  const std::vector<foxglove::Parameter> parameters = {
231  foxglove::Parameter(PARAM_1_NAME, newP1value),
232  };
233 
234  const std::string requestId = "req-testSetParameters";
235  auto future = foxglove::waitForParameters(_wsClient, requestId);
236  _wsClient->setParameters(parameters, requestId);
237  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
238  std::vector<foxglove::Parameter> params = future.get();
239 
240  EXPECT_EQ(1UL, params.size());
241 }
242 
243 TEST_F(ParameterTest, testUnsetParameter) {
244  const std::vector<foxglove::Parameter> parameters = {
246  };
247 
248  const std::string requestId = "req-testUnsetParameter";
249  auto future = foxglove::waitForParameters(_wsClient, requestId);
250  _wsClient->setParameters(parameters, requestId);
251  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
252  std::vector<foxglove::Parameter> params = future.get();
253 
254  EXPECT_EQ(0UL, params.size());
255 }
256 
257 TEST_F(ParameterTest, testParameterSubscription) {
258  auto future = foxglove::waitForParameters(_wsClient);
259 
260  _wsClient->subscribeParameterUpdates({PARAM_1_NAME});
261  _wsClient->setParameters({foxglove::Parameter(PARAM_1_NAME, "foo")});
262  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
263  std::vector<foxglove::Parameter> params = future.get();
264 
265  ASSERT_EQ(1UL, params.size());
266  EXPECT_EQ(params.front().getName(), PARAM_1_NAME);
267 
268  _wsClient->unsubscribeParameterUpdates({PARAM_1_NAME});
269  _wsClient->setParameters({foxglove::Parameter(PARAM_1_NAME, "bar")});
270 
272  ASSERT_EQ(std::future_status::timeout, future.wait_for(ONE_SECOND));
273 }
274 
275 TEST_F(ParameterTest, testGetParametersParallel) {
276  // Connect a few clients (in parallel) and make sure that they all receive parameters
277  auto clients = {
278  std::make_shared<foxglove::Client<websocketpp::config::asio_client>>(),
280  std::make_shared<foxglove::Client<websocketpp::config::asio_client>>(),
281  };
282 
283  std::vector<std::future<std::vector<foxglove::Parameter>>> futures;
284  for (auto client : clients) {
285  futures.push_back(
286  std::async(std::launch::async, [client]() -> std::vector<foxglove::Parameter> {
287  if (std::future_status::ready == client->connect(URI).wait_for(DEFAULT_TIMEOUT)) {
288  const std::string requestId = "req-123";
289  auto future = foxglove::waitForParameters(client, requestId);
290  client->getParameters({}, requestId);
291  future.wait_for(DEFAULT_TIMEOUT);
292  if (future.valid()) {
293  return future.get();
294  }
295  }
296  return {};
297  }));
298  }
299 
300  for (auto& future : futures) {
301  ASSERT_EQ(std::future_status::ready, future.wait_for(DEFAULT_TIMEOUT));
302  std::vector<foxglove::Parameter> parameters;
303  EXPECT_NO_THROW(parameters = future.get());
304  EXPECT_GE(parameters.size(), 2UL);
305  }
306 }
307 
308 TEST_F(ServiceTest, testCallServiceParallel) {
309  // Connect a few clients (in parallel) and make sure that they can all call the service
310  auto clients = {
311  std::make_shared<foxglove::Client<websocketpp::config::asio_client>>(),
313  std::make_shared<foxglove::Client<websocketpp::config::asio_client>>(),
314  };
315 
316  auto serviceFuture = foxglove::waitForService(*clients.begin(), SERVICE_NAME);
317  for (auto client : clients) {
318  ASSERT_EQ(std::future_status::ready, client->connect(URI).wait_for(std::chrono::seconds(5)));
319  }
320  ASSERT_EQ(std::future_status::ready, serviceFuture.wait_for(std::chrono::seconds(5)));
321  const foxglove::Service service = serviceFuture.get();
322 
323  foxglove::ServiceRequest request;
324  request.serviceId = service.id;
325  request.callId = 123lu;
326  request.encoding = "ros1";
327  request.data = {1}; // Serialized boolean "True"
328 
329  const std::vector<uint8_t> expectedSerializedResponse = {1, 5, 0, 0, 0, 104, 101, 108, 108, 111};
330 
331  std::vector<std::future<foxglove::ServiceResponse>> futures;
332  for (auto client : clients) {
333  futures.push_back(foxglove::waitForServiceResponse(client));
334  client->sendServiceRequest(request);
335  }
336 
337  for (auto& future : futures) {
338  ASSERT_EQ(std::future_status::ready, future.wait_for(std::chrono::seconds(5)));
340  EXPECT_NO_THROW(response = future.get());
341  EXPECT_EQ(response.serviceId, request.serviceId);
342  EXPECT_EQ(response.callId, request.callId);
343  EXPECT_EQ(response.encoding, request.encoding);
344  EXPECT_EQ(response.data, expectedSerializedResponse);
345  }
346 }
347 
348 // Run all the tests that were declared with TEST()
349 int main(int argc, char** argv) {
350  testing::InitGoogleTest(&argc, argv);
351  ros::init(argc, argv, "tester");
352  ros::NodeHandle nh;
353 
354  // Give the server some time to start
355  std::this_thread::sleep_for(std::chrono::seconds(2));
356 
357  ros::AsyncSpinner spinner(1);
358  spinner.start();
359  const auto testResult = RUN_ALL_TESTS();
360  spinner.stop();
361 
362  return testResult;
363 }
bool param(const std::string &param_name, T &param_val, const T &default_val)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
std::shared_ptr< foxglove::Client< websocketpp::config::asio_client > > _wsClient
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
static const PARAM_1_TYPE PARAM_1_DEFAULT_VALUE
constexpr uint8_t HELLO_WORLD_BINARY[]
std::future< Service > waitForService(std::shared_ptr< ClientInterface > client, const std::string &serviceName)
Definition: test_client.cpp:97
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
int main(int argc, char **argv)
void advertise(const std::vector< ClientAdvertisement > &channels) override
TEST(SmokeTest, testConnection)
std::vector< uint8_t > data
Definition: common.hpp:132
TEST_F(ParameterTest, testGetAllParams)
void publish(const boost::shared_ptr< M > &message) const
constexpr auto DEFAULT_TIMEOUT
void connect(const std::string &uri, std::function< void(websocketpp::connection_hdl)> onOpenHandler, std::function< void(websocketpp::connection_hdl)> onCloseHandler=nullptr) override
static const std::string PARAM_2_NAME
void publish(ClientChannelId channelId, const uint8_t *buffer, size_t size) override
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
void unadvertise(const std::vector< ClientChannelId > &channelIds) override
std::vector< uint8_t > connectClientAndReceiveMsg(const std::string &uri, const std::string &topic_name)
Definition: test_client.cpp:14
void setParam(const std::string &key, const XmlRpc::XmlRpcValue &v) const
static const PARAM_2_TYPE PARAM_2_DEFAULT_VALUE
constexpr auto ONE_SECOND
static const std::string PARAM_1_NAME
ClientChannelId channelId
Definition: common.hpp:76
constexpr char URI[]
std::future< ServiceResponse > waitForServiceResponse(std::shared_ptr< ClientInterface > client)
Definition: test_client.cpp:80
const std::string response
std::future< std::vector< Parameter > > waitForParameters(std::shared_ptr< ClientInterface > client, const std::string &requestId=std::string())
Definition: test_client.cpp:60


foxglove_bridge
Author(s): Foxglove
autogenerated on Mon Jul 3 2023 02:12:22