Skip to content

Commit 8cd43ca

Browse files
committed
[#24620] Added regression tests
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
1 parent 00e1965 commit 8cd43ca

2 files changed

Lines changed: 223 additions & 0 deletions

File tree

ddsrouter_core/test/blackbox/ddsrouter_core/dds/repeater/CMakeLists.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,37 @@ add_blackbox_executable(
4545
"${TEST_LIST}"
4646
"${TEST_NEEDED_SOURCES}"
4747
"${TEST_EXTRA_HEADERS}")
48+
49+
#################################
50+
# DDS Test Repeater Dispose Key #
51+
#################################
52+
53+
set(TEST_NAME
54+
DDSTestRepeaterDisposeKey)
55+
56+
set(TEST_SOURCES
57+
DDSTestRepeaterDisposeKey.cpp
58+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorld/HelloWorldPubSubTypes.cxx
59+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorld/HelloWorldCdrAux.ipp
60+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorld/HelloWorldTypeObjectSupport.cxx
61+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorldKeyed/HelloWorldKeyedPubSubTypes.cxx
62+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorldKeyed/HelloWorldKeyedCdrAux.ipp
63+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorldKeyed/HelloWorldKeyedTypeObjectSupport.cxx)
64+
65+
set(TEST_LIST
66+
end_to_end_xml_repeater_key_dispose
67+
)
68+
69+
set(TEST_NEEDED_SOURCES
70+
)
71+
72+
set(TEST_EXTRA_HEADERS
73+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types/HelloWorldKeyed
74+
${PROJECT_SOURCE_DIR}/test/blackbox/ddsrouter_core/dds/types)
75+
76+
add_blackbox_executable(
77+
"${TEST_NAME}"
78+
"${TEST_SOURCES}"
79+
"${TEST_LIST}"
80+
"${TEST_NEEDED_SOURCES}"
81+
"${TEST_EXTRA_HEADERS}")
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <atomic>
16+
#include <thread>
17+
18+
#include <fastdds/dds/core/detail/DDSReturnCode.hpp>
19+
20+
#include <cpp_utils/Log.hpp>
21+
#include <cpp_utils/testing/gtest_aux.hpp>
22+
23+
#include <ddspipe_core/types/topic/filter/WildcardDdsFilterTopic.hpp>
24+
25+
#include <ddspipe_participants/configuration/SimpleParticipantConfiguration.hpp>
26+
#include <ddspipe_participants/configuration/XmlParticipantConfiguration.hpp>
27+
28+
#include <ddsrouter_core/core/DdsRouter.hpp>
29+
#include <ddsrouter_core/types/ParticipantKind.hpp>
30+
31+
#include <test_participants.hpp>
32+
33+
#include <gtest/gtest.h>
34+
35+
using namespace eprosima;
36+
using namespace eprosima::ddspipe;
37+
using namespace eprosima::ddsrouter::core;
38+
39+
namespace test {
40+
41+
constexpr const uint32_t DEFAULT_SAMPLES_TO_RECEIVE = 5;
42+
constexpr const uint32_t DEFAULT_MILLISECONDS_PUBLISH_LOOP = 100;
43+
constexpr const uint32_t DEFAULT_MESSAGE_SIZE = 1; // x50 bytes
44+
45+
// Maximum time to wait for the forwarded dispose to be received by the subscriber.
46+
constexpr const uint32_t MAX_MILLISECONDS_WAIT_DISPOSE = 5000;
47+
constexpr const uint32_t MILLISECONDS_WAIT_STEP = 100;
48+
49+
/**
50+
* @brief Create a DDS Router configuration that bridges \c TOPIC_NAME between:
51+
* - a Simple participant in domain 0 (publisher side), and
52+
* - an XML (DDS) participant in domain 1 (subscriber side) configured as a REPEATER.
53+
*
54+
* The XML participant is the one that exercises the buggy code path: as a repeater its
55+
* DataWriter installs a \c dds::RepeaterDataFilter prefilter. When the dispose/unregister
56+
* sample produced by the publisher is forwarded to the subscriber through this writer, the
57+
* prefilter is evaluated for a sample whose \c user_write_data is null (dispose/unregister
58+
* are sent through DataWriter::dispose / unregister_instance, which cannot carry WriteParams).
59+
*
60+
* Note: no participant_profile is set on the XML participant, so XmlParticipant falls back to
61+
* the configured domain (1) — this keeps the test self-contained (no external XML profile file).
62+
*/
63+
DdsRouterConfiguration xml_repeater_dispose_configuration()
64+
{
65+
DdsRouterConfiguration conf;
66+
67+
// -- Filter the test topic by name ---------------------------------------
68+
// (any type, keyed or not)
69+
core::types::WildcardDdsFilterTopic topic;
70+
topic.topic_name.set_value(TOPIC_NAME);
71+
conf.ddspipe_configuration.allowlist.insert(
72+
utils::Heritable<core::types::WildcardDdsFilterTopic>::make_heritable(topic));
73+
74+
// -- Create the participants ---------------------------------------------
75+
76+
// Simple participant in domain 0 (publisher side)
77+
{
78+
auto part = std::make_shared<participants::SimpleParticipantConfiguration>();
79+
part->id = core::types::ParticipantId("simple_participant_0");
80+
part->domain.domain_id = 0u;
81+
conf.participants_configurations.insert({types::ParticipantKind::simple, part});
82+
}
83+
84+
// XML (DDS) participant in domain 1 (subscriber side), in REPEATER mode.
85+
{
86+
auto part = std::make_shared<participants::XmlParticipantConfiguration>();
87+
part->id = core::types::ParticipantId("xml_repeater_participant_1");
88+
part->domain.domain_id = 1u;
89+
part->is_repeater = true;
90+
conf.participants_configurations.insert({types::ParticipantKind::xml, part});
91+
}
92+
93+
return conf;
94+
}
95+
96+
/**
97+
* Regression test for the segfault in dds::RepeaterDataFilter::evaluate when forwarding a
98+
* dispose/unregister sample (null user_write_data) through a repeater XML participant.
99+
*
100+
* Without the fix the router process crashes (SIGSEGV) while forwarding the dispose, so this
101+
* test aborts. With the fix the dispose is forwarded and received by the subscriber.
102+
*/
103+
void test_xml_repeater_key_dispose(
104+
DdsRouterConfiguration ddsrouter_configuration,
105+
uint32_t samples_to_receive = DEFAULT_SAMPLES_TO_RECEIVE,
106+
uint32_t time_between_samples = DEFAULT_MILLISECONDS_PUBLISH_LOOP,
107+
uint32_t msg_size = DEFAULT_MESSAGE_SIZE)
108+
{
109+
uint32_t samples_sent = 0;
110+
std::atomic<uint32_t> samples_received(0);
111+
112+
HelloWorldKeyed msg;
113+
HelloWorldKeyedPubSubType type;
114+
115+
std::string msg_str;
116+
for (uint32_t i = 0; i < msg_size; i++)
117+
{
118+
msg_str += "Testing DdsRouter Blackbox Repeater Dispose ...";
119+
}
120+
msg.message(msg_str);
121+
msg.id(666);
122+
123+
// -- Create keyed Publisher, Subscriber & Router -------------------------
124+
125+
// Publisher (domain 0)
126+
TestPublisher<HelloWorldKeyed> publisher(type.is_compute_key_provided);
127+
ASSERT_TRUE(publisher.init(0));
128+
129+
// Subscriber (domain 1)
130+
TestSubscriber<HelloWorldKeyed> subscriber(type.is_compute_key_provided);
131+
ASSERT_TRUE(subscriber.init(1, &msg, &samples_received));
132+
133+
// Create and start the DDS Router
134+
DdsRouter router(ddsrouter_configuration);
135+
router.start();
136+
137+
// -- Publish some messages -----------------------------------------------
138+
while (samples_received.load() < samples_to_receive)
139+
{
140+
msg.index(++samples_sent);
141+
ASSERT_EQ(publisher.publish(msg), fastdds::dds::RETCODE_OK) << samples_sent;
142+
143+
if (time_between_samples > 0)
144+
{
145+
std::this_thread::sleep_for(std::chrono::milliseconds(time_between_samples));
146+
}
147+
}
148+
149+
// -- Dispose the keyed publisher -----------------------------------------
150+
151+
// The dispose is routed to the XML repeater participant's writer,
152+
// whose RepeaterDataFilter prefilter is evaluated with a null user_write_data.
153+
// >>> Without the fix the router segfaults at this point. <<<
154+
ASSERT_EQ(publisher.dispose_key(msg), fastdds::dds::RETCODE_OK);
155+
156+
// With the fix, the dispose is considered relevant and forwarded to the subscriber
157+
uint32_t waited = 0;
158+
while (subscriber.n_disposed() < 1u && waited < MAX_MILLISECONDS_WAIT_DISPOSE)
159+
{
160+
std::this_thread::sleep_for(std::chrono::milliseconds(MILLISECONDS_WAIT_STEP));
161+
waited += MILLISECONDS_WAIT_STEP;
162+
}
163+
164+
ASSERT_GE(subscriber.n_disposed(), 1u);
165+
166+
router.stop();
167+
}
168+
169+
} /* namespace test */
170+
171+
/**
172+
* Test that a dispose forwarded through a repeater XML (DDS) participant does not crash the router
173+
* and is correctly received by the subscriber.
174+
*
175+
* Regression test for the null user_write_data dereference in dds::RepeaterDataFilter::evaluate.
176+
*/
177+
TEST(DDSTestRepeaterDisposeKey, end_to_end_xml_repeater_key_dispose)
178+
{
179+
test::test_xml_repeater_key_dispose(
180+
test::xml_repeater_dispose_configuration());
181+
}
182+
183+
int main(
184+
int argc,
185+
char** argv)
186+
{
187+
::testing::InitGoogleTest(&argc, argv);
188+
return RUN_ALL_TESTS();
189+
}

0 commit comments

Comments
 (0)