Files
2026-07-27 13:51:19 +08:00

712 lines
22 KiB
C++

// Copyright (C) 2018, 2019 Kevin Hallenbeck, Joshua Whitley
// All rights reserved.
//
// Software License Agreement (BSD License 2.0)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of {copyright_holder} nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <gtest/gtest.h>
#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <sensor_msgs/LaserScan.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
// Define our own PointCloud type for easy use
typedef struct
{
float x; // x
float y; // y
float z; // z
float i; // intensity
uint16_t r; // ring
}
Point;
typedef struct
{
std_msgs::Header header;
std::vector<Point> points;
}
PointCloud;
// Global variables
ros::Publisher g_pub;
ros::Subscriber g_sub;
sensor_msgs::LaserScan g_scan;
volatile bool g_scan_new = false;
// Convert WallTime to Time
static inline ros::Time rosTime(const ros::WallTime &stamp)
{
return ros::Time(stamp.sec, stamp.nsec);
}
// Subscriber receive callback
void recv(const sensor_msgs::LaserScanConstPtr& msg)
{
g_scan = *msg;
g_scan_new = true;
}
// Wait for incoming LaserScan message
bool waitForScan(ros::WallDuration dur)
{
const ros::WallTime start = ros::WallTime::now();
while (!g_scan_new)
{
if ((ros::WallTime::now() - start) > dur)
{
return false;
}
ros::WallDuration(0.001).sleep();
ros::spinOnce();
}
return true;
}
// Build and publish PointCloud2 messages of various structures
void publishXYZIR1(const PointCloud &cloud)
{
g_scan_new = false;
const uint32_t POINT_STEP = 32;
sensor_msgs::PointCloud2 msg;
msg.header.frame_id = cloud.header.frame_id;
msg.header.stamp = cloud.header.stamp;
msg.fields.resize(5);
msg.fields[0].name = "x";
msg.fields[0].offset = 0;
msg.fields[0].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[0].count = 1;
msg.fields[1].name = "y";
msg.fields[1].offset = 4;
msg.fields[1].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[1].count = 1;
msg.fields[2].name = "z";
msg.fields[2].offset = 8;
msg.fields[2].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[2].count = 1;
msg.fields[3].name = "intensity";
msg.fields[3].offset = 16;
msg.fields[3].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[3].count = 1;
msg.fields[4].name = "ring";
msg.fields[4].offset = 20;
msg.fields[4].datatype = sensor_msgs::PointField::UINT16;
msg.fields[4].count = 1;
msg.data.resize(std::max((size_t)1, cloud.points.size()) * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
msg.is_bigendian = false;
msg.is_dense = true;
uint8_t *ptr = msg.data.data();
for (size_t i = 0; i < cloud.points.size(); i++)
{
*(reinterpret_cast<float*>(ptr + 0)) = cloud.points[i].x;
*(reinterpret_cast<float*>(ptr + 4)) = cloud.points[i].y;
*(reinterpret_cast<float*>(ptr + 8)) = cloud.points[i].z;
*(reinterpret_cast<float*>(ptr + 16)) = cloud.points[i].i;
*(reinterpret_cast<uint16_t*>(ptr + 20)) = cloud.points[i].r;
ptr += POINT_STEP;
}
g_pub.publish(msg);
}
void publishXYZIR2(const PointCloud &cloud)
{
g_scan_new = false;
const uint32_t POINT_STEP = 19;
sensor_msgs::PointCloud2 msg;
msg.header.frame_id = cloud.header.frame_id;
msg.header.stamp = cloud.header.stamp;
msg.fields.resize(5);
msg.fields[0].name = "z";
msg.fields[0].offset = 4;
msg.fields[0].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[0].count = 1;
msg.fields[1].name = "y";
msg.fields[1].offset = 8;
msg.fields[1].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[1].count = 1;
msg.fields[2].name = "x";
msg.fields[2].offset = 12;
msg.fields[2].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[2].count = 1;
msg.fields[3].name = "intensity";
msg.fields[3].offset = 0;
msg.fields[3].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[3].count = 1;
msg.fields[4].name = "ring";
msg.fields[4].offset = 16;
msg.fields[4].datatype = sensor_msgs::PointField::UINT16;
msg.fields[4].count = 1;
msg.data.resize(std::max((size_t)1, cloud.points.size()) * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
msg.is_bigendian = false;
msg.is_dense = true;
uint8_t *ptr = msg.data.data();
for (size_t i = 0; i < cloud.points.size(); i++)
{
*(reinterpret_cast<float*>(ptr + 0)) = cloud.points[i].i;
*(reinterpret_cast<float*>(ptr + 4)) = cloud.points[i].z;
*(reinterpret_cast<float*>(ptr + 8)) = cloud.points[i].y;
*(reinterpret_cast<float*>(ptr + 12)) = cloud.points[i].x;
*(reinterpret_cast<uint16_t*>(ptr + 16)) = cloud.points[i].r;
ptr += POINT_STEP;
}
g_pub.publish(msg);
}
void publishXYZR(const PointCloud &cloud)
{
g_scan_new = false;
const uint32_t POINT_STEP = 15;
sensor_msgs::PointCloud2 msg;
msg.header.frame_id = cloud.header.frame_id;
msg.header.stamp = cloud.header.stamp;
msg.fields.resize(4);
msg.fields[0].name = "x";
msg.fields[0].offset = 0;
msg.fields[0].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[0].count = 1;
msg.fields[1].name = "y";
msg.fields[1].offset = 4;
msg.fields[1].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[1].count = 1;
msg.fields[2].name = "z";
msg.fields[2].offset = 8;
msg.fields[2].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[2].count = 1;
msg.fields[3].name = "ring";
msg.fields[3].offset = 12;
msg.fields[3].datatype = sensor_msgs::PointField::UINT16;
msg.fields[3].count = 1;
msg.data.resize(std::max((size_t)1, cloud.points.size()) * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
msg.is_bigendian = false;
msg.is_dense = true;
uint8_t *ptr = msg.data.data();
for (size_t i = 0; i < cloud.points.size(); i++)
{
*(reinterpret_cast<float*>(ptr + 0)) = cloud.points[i].x;
*(reinterpret_cast<float*>(ptr + 4)) = cloud.points[i].y;
*(reinterpret_cast<float*>(ptr + 8)) = cloud.points[i].z;
*(reinterpret_cast<uint16_t*>(ptr + 12)) = cloud.points[i].r;
ptr += POINT_STEP;
}
g_pub.publish(msg);
}
void publishR(const PointCloud &cloud)
{
g_scan_new = false;
const uint32_t POINT_STEP = 2;
sensor_msgs::PointCloud2 msg;
msg.header.stamp = rosTime(ros::WallTime::now());
msg.fields.resize(1);
msg.fields[0].name = "ring";
msg.fields[0].offset = 0;
msg.fields[0].datatype = sensor_msgs::PointField::UINT16;
msg.fields[0].count = 1;
msg.data.resize(std::max((size_t)1, cloud.points.size()) * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
uint8_t *ptr = msg.data.data();
for (size_t i = 0; i < cloud.points.size(); i++)
{
*(reinterpret_cast<uint16_t*>(ptr + 0)) = cloud.points[i].r;
ptr += POINT_STEP;
}
g_pub.publish(msg);
}
void publishXYZR32(const PointCloud &cloud)
{
g_scan_new = false;
const uint32_t POINT_STEP = 16;
sensor_msgs::PointCloud2 msg;
msg.header.frame_id = cloud.header.frame_id;
msg.header.stamp = cloud.header.stamp;
msg.fields.resize(4);
msg.fields[0].name = "x";
msg.fields[0].offset = 0;
msg.fields[0].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[0].count = 1;
msg.fields[1].name = "y";
msg.fields[1].offset = 4;
msg.fields[1].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[1].count = 1;
msg.fields[2].name = "z";
msg.fields[2].offset = 8;
msg.fields[2].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[2].count = 1;
msg.fields[3].name = "ring";
msg.fields[3].offset = 12;
msg.fields[3].datatype = sensor_msgs::PointField::UINT32;
msg.fields[3].count = 1;
msg.data.resize(std::max((size_t)1, cloud.points.size()) * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
msg.is_bigendian = false;
msg.is_dense = true;
uint8_t *ptr = msg.data.data();
for (size_t i = 0; i < cloud.points.size(); i++)
{
*(reinterpret_cast<float*>(ptr + 0)) = cloud.points[i].x;
*(reinterpret_cast<float*>(ptr + 4)) = cloud.points[i].y;
*(reinterpret_cast<float*>(ptr + 8)) = cloud.points[i].z;
*(reinterpret_cast<uint32_t*>(ptr + 12)) = cloud.points[i].r;
ptr += POINT_STEP;
}
g_pub.publish(msg);
}
void publishXYZ(const PointCloud &cloud)
{
g_scan_new = false;
const uint32_t POINT_STEP = 12;
sensor_msgs::PointCloud2 msg;
msg.header.stamp = rosTime(ros::WallTime::now());
msg.fields.resize(3);
msg.fields[0].name = "x";
msg.fields[0].offset = 0;
msg.fields[0].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[0].count = 1;
msg.fields[1].name = "y";
msg.fields[1].offset = 4;
msg.fields[1].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[1].count = 1;
msg.fields[2].name = "z";
msg.fields[2].offset = 8;
msg.fields[2].datatype = sensor_msgs::PointField::FLOAT32;
msg.fields[2].count = 1;
msg.data.resize(std::max((size_t)1, cloud.points.size()) * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
uint8_t *ptr = msg.data.data();
for (size_t i = 0; i < cloud.points.size(); i++)
{
*(reinterpret_cast<float*>(ptr + 0)) = cloud.points[i].x;
*(reinterpret_cast<float*>(ptr + 4)) = cloud.points[i].y;
*(reinterpret_cast<float*>(ptr + 8)) = cloud.points[i].z;
ptr += POINT_STEP;
}
g_pub.publish(msg);
}
void publishNone()
{
g_scan_new = false;
const uint32_t POINT_STEP = 16;
sensor_msgs::PointCloud2 msg;
msg.header.stamp = rosTime(ros::WallTime::now());
msg.data.resize(1 * POINT_STEP, 0x00);
msg.point_step = POINT_STEP;
msg.row_step = msg.data.size();
msg.height = 1;
msg.width = msg.row_step / POINT_STEP;
g_pub.publish(msg);
}
// Find the index of the point in the PointCloud with the shortest 2d distance to the point (x,y)
static inline float SQUARE(float x)
{
return x * x;
}
size_t findClosestIndex(const PointCloud &cloud, uint16_t ring, float x, float y)
{
size_t index = SIZE_MAX;
float delta = INFINITY;
for (size_t i = 0; i < cloud.points.size(); i++)
{
if (cloud.points[i].r == ring)
{
float dist = SQUARE(x - cloud.points[i].x) + SQUARE(y - cloud.points[i].y);
if (dist < delta)
{
delta = dist;
index = i;
}
}
}
return index;
}
// Verify that all LaserScan header values are values are passed through, and other values are default
void verifyScanEmpty(const PointCloud &cloud, bool intensity = true)
{
ASSERT_EQ(cloud.header.stamp, g_scan.header.stamp);
EXPECT_EQ(cloud.header.frame_id, g_scan.header.frame_id);
for (size_t i = 0; i < g_scan.ranges.size(); i++)
{
EXPECT_EQ(INFINITY, g_scan.ranges[i]);
}
if (!intensity)
{
EXPECT_EQ(0, g_scan.intensities.size());
}
else
{
EXPECT_EQ(g_scan.ranges.size(), g_scan.intensities.size());
for (size_t i = 0; i < g_scan.intensities.size(); i++)
{
EXPECT_EQ(0.0, g_scan.intensities[i]);
}
}
}
// Verify that every PointCloud point made it to the LaserScan and other values are default
void verifyScanSparse(const PointCloud &cloud, uint16_t ring, uint16_t ring_count, bool intensity = true)
{
ASSERT_EQ(cloud.header.stamp, g_scan.header.stamp);
EXPECT_EQ(cloud.header.frame_id, g_scan.header.frame_id);
EXPECT_EQ(intensity ? g_scan.ranges.size() : 0, g_scan.intensities.size());
size_t count = 0;
for (size_t i = 0; i < g_scan.ranges.size(); i++)
{
double r = g_scan.ranges[i];
if (std::isfinite(r))
{
float a = g_scan.angle_min + i * g_scan.angle_increment;
float x = g_scan.ranges[i] * cosf(a);
float y = g_scan.ranges[i] * sinf(a);
float e = g_scan.ranges[i] * g_scan.angle_increment + static_cast<float>(1e-3); // allowable error
size_t index = findClosestIndex(cloud, ring, x, y);
if (index < cloud.points.size())
{
count++;
EXPECT_NEAR(cloud.points[index].x, x, e);
EXPECT_NEAR(cloud.points[index].y, y, e);
if (i < g_scan.intensities.size())
{
EXPECT_EQ(cloud.points[index].i, g_scan.intensities[i]);
}
}
else
{
EXPECT_TRUE(false); // LaserScan point not found in PointCloud
}
}
else
{
EXPECT_EQ(INFINITY, r);
}
}
if (ring_count > 0)
{
EXPECT_EQ(cloud.points.size() / ring_count, count); // Make sure that all points were converted to ranges
}
}
// Verify that every LaserScan point is not default, and every point came from the PointCloud
void verifyScanDense(const PointCloud &cloud, uint16_t ring, bool intensity = true)
{
ASSERT_EQ(cloud.header.stamp, g_scan.header.stamp);
EXPECT_EQ(cloud.header.frame_id, g_scan.header.frame_id);
EXPECT_EQ(intensity ? g_scan.ranges.size() : 0, g_scan.intensities.size());
for (size_t i = 0; i < g_scan.ranges.size(); i++)
{
double r = g_scan.ranges[i];
if (std::isfinite(r))
{
float a = g_scan.angle_min + i * g_scan.angle_increment;
float x = g_scan.ranges[i] * cosf(a);
float y = g_scan.ranges[i] * sinf(a);
float e = g_scan.ranges[i] * g_scan.angle_increment + static_cast<float>(1e-3); // allowable error
size_t index = findClosestIndex(cloud, ring, x, y);
if (index < cloud.points.size())
{
EXPECT_NEAR(cloud.points[index].x, x, e);
EXPECT_NEAR(cloud.points[index].y, y, e);
// @TODO: Test for matching intensity
}
else
{
EXPECT_TRUE(false); // LaserScan point not found in PointCloud
}
}
else
{
EXPECT_TRUE(false); // Dense PointCloud should populate every range in LaserScan
}
}
}
// Verify that no LaserScan is generated when the PointCloud2 message is missing required fields
TEST(System, missing_fields)
{
// Make sure system is connected
ASSERT_EQ(1, g_sub.getNumPublishers());
ASSERT_EQ(1, g_pub.getNumSubscribers());
// Create PointCloud with 16 rings
PointCloud cloud;
cloud.points.resize(1);
cloud.points[0].x = 0.0;
cloud.points[0].y = 0.0;
cloud.points[0].z = 0.0;
cloud.points[0].i = 0.0;
cloud.points[0].r = 15;
// Verify no LaserScan when PointCloud2 fields are empty
publishNone();
EXPECT_FALSE(waitForScan(ros::WallDuration(0.5)));
// Verify no LaserScan when PointCloud2 fields are missing 'ring'
publishXYZ(cloud);
EXPECT_FALSE(waitForScan(ros::WallDuration(0.5)));
// Verify no LaserScan when PointCloud2 field 'ring' is the incorrect type
publishXYZR32(cloud);
EXPECT_FALSE(waitForScan(ros::WallDuration(0.5)));
// Verify no LaserScan when PointCloud2 fields are missing 'x' and 'y'
publishR(cloud);
EXPECT_FALSE(waitForScan(ros::WallDuration(0.5)));
// Verify that the node hasn't crashed by sending normal PointCloud2 fields
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR1(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
ASSERT_EQ(cloud.header.stamp, g_scan.header.stamp);
}
// Verify that non-point fields are passed through unmodified
TEST(System, empty_data)
{
// Make sure system is connected
ASSERT_EQ(1, g_sub.getNumPublishers());
ASSERT_EQ(1, g_pub.getNumSubscribers());
// Create PointCloud with 16 rings
PointCloud cloud;
cloud.header.frame_id = "abcdefghijklmnopqrstuvwxyz";
cloud.points.resize(1);
cloud.points[0].x = 0.0;
cloud.points[0].y = 0.0;
cloud.points[0].z = 0.0;
cloud.points[0].i = 0.0;
cloud.points[0].r = 15;
// Verify that all three PointCloud2 types create proper default values
// PointXYZIR (expected format)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR1(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanEmpty(cloud, true);
// PointXYZIR (unexpected format with intensity)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR2(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanEmpty(cloud, true);
// PointXYZR (unexpected format without intensity)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZR(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanEmpty(cloud, false);
}
// Verify that every piece of a small amount of random data is passed through
TEST(System, random_data_sparse)
{
// Make sure system is connected
ASSERT_EQ(1, g_sub.getNumPublishers());
ASSERT_EQ(1, g_pub.getNumSubscribers());
// Create PointCloud with sparse random data
PointCloud cloud;
cloud.header.frame_id = "velodyne";
const size_t RANGE_COUNT = 100;
const size_t RING_COUNT = 16;
const double RANGE_MAX = 20.0;
const double INTENSITY_MAX = 1.0;
for (size_t i = 0; i < RANGE_COUNT; i++)
{
double angle_y = i * 1.99 * M_PI / RANGE_COUNT; // yaw
for (size_t j = 0; j < RING_COUNT; j++)
{
double angle_p = j * 0.2 * M_PI / RING_COUNT - 0.1 * M_PI; // pitch
double range = std::rand() * (RANGE_MAX / RAND_MAX);
Point point;
point.x = range * cos(angle_p) * cos(angle_y);
point.y = range * cos(angle_p) * sin(angle_y);
point.z = range * sin(angle_p);
point.i = std::rand() * (INTENSITY_MAX / RAND_MAX);
point.r = j;
cloud.points.push_back(point);
}
}
// Verify that all three PointCloud2 types are handled correctly
// PointXYZIR (expected format)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR1(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanSparse(cloud, 8, RING_COUNT, true);
// PointXYZIR (unexpected format with intensity)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR2(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanSparse(cloud, 8, RING_COUNT, true);
// PointXYZR (unexpected format without intensity)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZR(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanSparse(cloud, 8, RING_COUNT, false);
}
// Verify that every LaserScan range is valid when given an extra large amount of random data
TEST(System, random_data_dense)
{
// Make sure system is connected
ASSERT_EQ(1, g_sub.getNumPublishers());
ASSERT_EQ(1, g_pub.getNumSubscribers());
// Create PointCloud with dense random data
PointCloud cloud;
cloud.header.frame_id = "velodyne";
const size_t RANGE_COUNT = 2500;
const size_t RING_COUNT = 16;
const double RANGE_MAX = 20.0;
const double INTENSITY_MAX = 1.0;
for (size_t i = 0; i < RANGE_COUNT; i++)
{
double angle_y = i * 2.0 * M_PI / RANGE_COUNT; // yaw
for (size_t j = 0; j < RING_COUNT; j++)
{
double angle_p = j * 0.2 * M_PI / RING_COUNT - 0.1 * M_PI; // pitch
double range = std::rand() * (RANGE_MAX / RAND_MAX);
Point point;
point.x = range * cos(angle_p) * cos(angle_y);
point.y = range * cos(angle_p) * sin(angle_y);
point.z = range * sin(angle_p);
point.i = std::rand() * (INTENSITY_MAX / RAND_MAX);
point.r = j;
cloud.points.push_back(point);
}
}
// Verify that all three PointCloud2 types are handled correctly
// PointXYZIR (expected format)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR1(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanDense(cloud, 8, true);
// PointXYZIR (unexpected format with intensity)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZIR2(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanDense(cloud, 8, true);
// PointXYZR (unexpected format without intensity)
cloud.header.stamp = rosTime(ros::WallTime::now());
publishXYZR(cloud);
ASSERT_TRUE(waitForScan(ros::WallDuration(1.0)));
verifyScanDense(cloud, 8, false);
}
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
// Initialize ROS
ros::init(argc, argv, "test_lazy_subscriber");
ros::NodeHandle nh;
// Setup publisher and subscriber
g_pub = nh.advertise<sensor_msgs::PointCloud2>("velodyne_points", 2);
g_sub = nh.subscribe("scan", 2, recv);
// Wait for other nodes to startup
ros::WallDuration(1.0).sleep();
ros::spinOnce();
// Run all the tests that were declared with TEST()
return RUN_ALL_TESTS();
}