RTC Toolkit 6.0.0-pre2
Loading...
Searching...
No Matches
repositorySubscriberIfTestSuite.hpp
Go to the documentation of this file.
1
12
13#ifndef RTCTK_COMPONENTFRAMEWORK_TEST_REPOSITORYSUBSCRIBERIFTESTSUITE_HPP
14#define RTCTK_COMPONENTFRAMEWORK_TEST_REPOSITORYSUBSCRIBERIFTESTSUITE_HPP
15
16#include <memory>
18
19#include <gtest/gtest.h>
20
21#include <chrono>
22#include <stdexcept>
23#include <thread>
24
26
27static std::shared_ptr<RepositorySubscriberIf> MakeRepository();
28
29template <typename T>
31public:
32 void PushBack(const T& path) {
33 std::lock_guard lock{m_mutex};
34 m_data.push_back(path);
35 }
36
37 T operator[](size_t idx) {
38 std::lock_guard lock{m_mutex};
39 return m_data.at(idx);
40 }
41
42 size_t Size() {
43 std::lock_guard lock{m_mutex};
44 return m_data.size();
45 }
46
47 bool Contains(const T& val) {
48 std::lock_guard lock{m_mutex};
49 auto it = std::find(m_data.begin(), m_data.end(), val);
50 return it != m_data.end();
51 }
52
53 void AwaitSize(size_t target_size, std::chrono::seconds timeout = std::chrono::seconds(5)) {
54 auto t_start = std::chrono::steady_clock::now();
55 while (Size() < target_size) {
56 std::this_thread::sleep_for(std::chrono::microseconds(100));
57 if ((std::chrono::steady_clock::now() - t_start) > timeout) {
58 FAIL() << "ThreadSafeQ::AwaitSize: Timed out waiting for size "
59 << std::to_string(target_size);
60 }
61 }
62 }
63
64private:
65 std::mutex m_mutex;
66 std::vector<T> m_data;
67};
68
69std::chrono::milliseconds g_sleep_duration = std::chrono::milliseconds(0);
70
71void Sleep() {
72 if (g_sleep_duration > std::chrono::milliseconds(0)) {
73 std::this_thread::sleep_for(g_sleep_duration);
74 }
75}
76
77class Subscription : public testing::Test {
78public:
79 void SetUp() override {
80 repo = MakeRepository();
81 path1 = "/foo"_dppath;
82 path2 = "/bar"_dppath;
83 link = "/link"_dppath;
84 }
85
86 void TearDown() override {
87 if (repo == nullptr) {
88 // in case we could not connect in the first place
89 return;
90 }
91 if (repo->DataPointExists(link)) {
92 repo->DeleteDataPoint(link);
93 }
94 if (repo->DataPointExists(path1)) {
95 repo->DeleteDataPoint(path1);
96 }
97 if (repo->DataPointExists(path2)) {
98 repo->DeleteDataPoint(path2);
99 }
100 }
101
102protected:
103 std::shared_ptr<RepositorySubscriberIf> repo;
107};
108
109TEST_F(Subscription, ValueSubscription) {
110 {
111 int int_create = 0;
113 req.CreateDataPoint(path1, int_create);
114 req.CreateDataPoint(path2, int_create);
115 repo->SendRequest(req).Wait();
116 }
117
119 ThreadSafeQ<int> value_q_1;
122 int num_cb_q_1 = 0;
123 int num_cb_q_2 = 0;
124
125 auto sub1 = repo->Subscribe<int>(
126 path1,
127 [&](const DataPointPath& path,
128 const int& data,
129 const RepositoryIf::MetaData& metadata) {
130 path_q_1.PushBack(path);
131 value_q_1.PushBack(data);
132 },
133 // NOLINTNEXTLINE(performance-unnecessary-value-param)
134 [&](std::exception_ptr error) { error_q_1.PushBack(error); });
135
136 auto sub2 = repo->Subscribe(
137 path2,
138 [&](const DataPointPath& path, const RepositoryIf::MetaData& metadata) {
139 path_q_2.PushBack(path);
140 },
141 nullptr);
142
143 // Wait a moment for the subscription to complete before updating the data point.
144 Sleep();
145
146 {
147 int int_write = 3;
149 req.WriteDataPoint(path1, int_write);
150 req.WriteDataPoint(path2, int_write);
151 repo->SendRequest(req).Wait();
152
153 path_q_1.AwaitSize(1);
154 value_q_1.AwaitSize(1);
155 path_q_2.AwaitSize(1);
156 EXPECT_TRUE(path_q_1.Contains(path1));
157 EXPECT_TRUE(value_q_1.Contains(int_write));
158 EXPECT_TRUE(path_q_2.Contains(path2));
159 EXPECT_EQ(error_q_1.Size(), 0);
160 }
161
162 sub1.Unsubscribe();
163 sub2.Unsubscribe();
164
165 num_cb_q_1 = path_q_1.Size();
166 num_cb_q_2 = path_q_2.Size();
167
168 {
169 int int_write = 4;
171 req.WriteDataPoint(path1, int_write);
172 req.WriteDataPoint(path2, int_write);
173 repo->SendRequest(req).Wait();
174
175 // best effort: sleep a short time and check that really no more callbacks were triggered
176 std::this_thread::sleep_for(std::chrono::milliseconds(200));
177
178 // we ensure that no new callbacks have arrived
179 EXPECT_EQ(error_q_1.Size(), 0);
180 EXPECT_EQ(path_q_1.Size(), num_cb_q_1);
181 EXPECT_EQ(value_q_1.Size(), num_cb_q_1);
182 EXPECT_EQ(path_q_2.Size(), num_cb_q_2);
183 }
184}
185
186TEST_F(Subscription, MultipleSubscriptionsToSameDataPoint) {
187 repo->CreateDataPoint(path1, 0);
188
191 int num_cb_q_1 = 0;
192 int num_cb_q_2 = 0;
193
194 auto sub1 = repo->Subscribe(
195 path1,
196 [&](const DataPointPath& path, const RepositoryIf::MetaData& metadata) {
197 path_q_1.PushBack(path);
198 },
199 nullptr);
200
201 {
202 auto sub2 = repo->Subscribe(
203 path1,
204 [&](const DataPointPath& path, const RepositoryIf::MetaData& metadata) {
205 path_q_2.PushBack(path);
206 },
207 nullptr);
208
209 // Wait a moment for the subscription to complete before updating the data point.
210 Sleep();
211
212 {
213 repo->WriteDataPoint(path1, 3);
214
215 path_q_1.AwaitSize(1);
216 path_q_2.AwaitSize(1);
217 EXPECT_TRUE(path_q_1.Contains(path1));
218 EXPECT_TRUE(path_q_2.Contains(path1));
219 }
220 } // here sub 2 goes out of scope and we unsubscribe
221
222 num_cb_q_1 = path_q_1.Size();
223 num_cb_q_2 = path_q_2.Size();
224
225 {
226 repo->WriteDataPoint(path1, 4);
227
228 path_q_1.AwaitSize(num_cb_q_1 + 1);
229
230 // best effort: sleep a short time and check that really no more callbacks were triggered
231 std::this_thread::sleep_for(std::chrono::milliseconds(200));
232
233 EXPECT_EQ(path_q_1.Size(), (num_cb_q_1 + 1));
234 EXPECT_EQ(path_q_2.Size(), num_cb_q_2);
235 }
236}
237
238TEST_F(Subscription, NotifySubscriptionCausesNoDataRace) {
239 {
240 int int_create = 0;
242 req.CreateDataPoint(path1, int_create);
243 repo->SendRequest(req).Wait();
244 }
245
246 std::atomic_bool stop = false;
247
248 // this thread keeps writing the datapoint with high frequency
249 auto writer_func = [&] {
250 int value = 3;
251 while (stop == false) {
252 repo->WriteDataPoint(path1, value);
253 value++;
254 }
255 };
256
257 // this thread subscribes, waits for notification and unsubscribes with high frequency
258 auto subscriber_func = [&] {
259 for (unsigned i = 0; i < 1000; i++) {
261
262 auto sub = repo->Subscribe(
263 path1,
264 [&](const DataPointPath& path, const RepositoryIf::MetaData& metadata) {
265 path_q_1.PushBack(path);
266 },
267 nullptr);
268
269 path_q_1.AwaitSize(1);
270 } // here the subscription goes out of scope and we unsubscribe
271
272 stop = true;
273 };
274
275 // let the threads to their work
276 std::thread t1{writer_func};
277 std::thread t2{subscriber_func};
278 t1.join();
279 t2.join();
280}
281
282TEST_F(Subscription, ValueSubscriptionToSymlink) {
283 {
284 int int_create = 0;
286 req.CreateDataPoint(path1, int_create);
287 req.CreateSymlink(path1, link);
288 repo->SendRequest(req).Wait();
289 }
290
292 ThreadSafeQ<int> value_q;
294
295 {
296 auto sub = repo->Subscribe<int>(
297 link,
298 [&](const DataPointPath& path,
299 const int& data,
300 const RepositoryIf::MetaData& metadata) {
301 path_q.PushBack(path);
302 value_q.PushBack(data);
303 },
304 // NOLINTNEXTLINE(performance-unnecessary-value-param)
305 [&](std::exception_ptr error) { error_q.PushBack(error); });
306
307 // Wait a moment for the subscription to complete before updating the data point.
308 Sleep();
309
310 {
311 // Check that writing to target triggers the callback
312 int int_write = 3;
313 repo->WriteDataPoint(path1, int_write);
314
315 path_q.AwaitSize(1);
316 value_q.AwaitSize(1);
317 EXPECT_TRUE(path_q.Contains(link));
318 EXPECT_TRUE(value_q.Contains(int_write));
319 EXPECT_EQ(error_q.Size(), 0);
320 }
321
322 {
323 // Check that writing to link triggers the callback
324 int int_write = 4;
325 repo->WriteDataPoint(link, int_write);
326
327 path_q.AwaitSize(2);
328 value_q.AwaitSize(2);
329 EXPECT_TRUE(path_q.Contains(link));
330 EXPECT_TRUE(value_q.Contains(int_write));
331 EXPECT_EQ(error_q.Size(), 0);
332 }
333 } // here the subscription goes out of scope and we unsubscribe
334
335 {
336 // Check that no more callbacks are triggered after unsubcribing from link
337
338 int num_cb_q = path_q.Size();
339
340 int int_write = 5;
341 repo->WriteDataPoint(path1, int_write);
342
343 // best effort: sleep a short time and check that really no more callbacks were triggered
344 std::this_thread::sleep_for(std::chrono::milliseconds(200));
345
346 EXPECT_EQ(error_q.Size(), 0);
347 EXPECT_EQ(path_q.Size(), num_cb_q);
348 EXPECT_EQ(value_q.Size(), num_cb_q);
349 }
350}
351
352TEST_F(Subscription, ValueSubscriptionToDanglingSymlink) {
353 {
354 int int_create = 0;
356 req.CreateDataPoint(path1, int_create);
357 req.CreateSymlink(path1, link);
358 repo->SendRequest(req).Wait();
359 }
360
362 ThreadSafeQ<int> value_q;
364
365 auto sub = repo->Subscribe<int>(
366 link,
367 [&](const DataPointPath& path,
368 const int& data,
369 const RepositoryIf::MetaData& metadata) {
370 path_q.PushBack(path);
371 value_q.PushBack(data);
372 },
373 // NOLINTNEXTLINE(performance-unnecessary-value-param)
374 [&](std::exception_ptr error) { error_q.PushBack(error); });
375
376 // Wait a moment for the subscription to complete before updating the data point.
377 Sleep();
378
379 // Check that writing to target triggers the callback
380 int int_write = 3;
381 repo->WriteDataPoint(path1, int_write);
382
383 path_q.AwaitSize(1);
384 value_q.AwaitSize(1);
385 EXPECT_TRUE(path_q.Contains(link));
386 EXPECT_TRUE(value_q.Contains(int_write));
387 EXPECT_EQ(error_q.Size(), 0);
388
389 // Check that no more callbacks are triggered after deleting the target
390 int num_cb_q = path_q.Size();
391 repo->DeleteDataPoint(path1);
392
393 // best effort: sleep a short time and check that really no more callbacks were triggered
394 std::this_thread::sleep_for(std::chrono::milliseconds(200));
395
396 EXPECT_EQ(error_q.Size(), 0);
397 EXPECT_EQ(path_q.Size(), num_cb_q);
398 EXPECT_EQ(value_q.Size(), num_cb_q);
399}
400
401TEST_F(Subscription, ValueSubscriptionToUpdatedSymlink) {
402 {
403 int int_create = 0;
404 int int_create_2 = 100;
406 req.CreateDataPoint(path1, int_create);
407 req.CreateDataPoint(path2, int_create_2);
408 req.CreateSymlink(path1, link);
409 repo->SendRequest(req).Wait();
410 }
411
413 ThreadSafeQ<int> value_q;
415
416 auto sub = repo->Subscribe<int>(
417 link,
418 [&](const DataPointPath& path,
419 const int& data,
420 const RepositoryIf::MetaData& metadata) {
421 path_q.PushBack(path);
422 value_q.PushBack(data);
423 },
424 // NOLINTNEXTLINE(performance-unnecessary-value-param)
425 [&](std::exception_ptr error) { error_q.PushBack(error); });
426
427 // Wait a moment for the subscription to complete before updating the data point.
428 Sleep();
429
430 // Check that writing to target triggers the callback
431 int int_write = 3;
432 repo->WriteDataPoint(path1, int_write);
433
434 path_q.AwaitSize(1);
435 value_q.AwaitSize(1);
436 EXPECT_TRUE(path_q.Contains(link));
437 EXPECT_TRUE(value_q.Contains(int_write));
438 EXPECT_EQ(error_q.Size(), 0);
439
440 // Check that updating the target triggers the callback
441 {
443 req.UpdateSymlink(path2, link);
444 repo->SendRequest(req).Wait();
445 }
446
447 path_q.AwaitSize(2);
448 value_q.AwaitSize(2);
449 EXPECT_TRUE(path_q.Contains(link));
450 EXPECT_TRUE(value_q.Contains(100));
451 EXPECT_EQ(error_q.Size(), 0);
452
453 // Check that no more callbacks are triggered after updating to dangling link
454 int num_cb_q = path_q.Size();
455 {
457 req.UpdateSymlink("/dangling/link"_dppath, link);
458 repo->SendRequest(req).Wait();
459 }
460
461 repo->WriteDataPoint(path1, int_write);
462 repo->WriteDataPoint(path2, int_write);
463
464 // best effort: sleep a short time and check that really no more callbacks were triggered
465 std::this_thread::sleep_for(std::chrono::milliseconds(200));
466
467 EXPECT_EQ(error_q.Size(), 0);
468 EXPECT_EQ(path_q.Size(), num_cb_q);
469 EXPECT_EQ(value_q.Size(), num_cb_q);
470}
471
472TEST_F(Subscription, CreateDeleteSubscription) {
476
477 {
478 auto subscription =
479 repo->Subscribe([&](const DataPointPath& path) { create_q.PushBack(path); },
480 [&](const DataPointPath& path) { delete_q.PushBack(path); },
481 // NOLINTNEXTLINE(performance-unnecessary-value-param)
482 [&](std::exception_ptr error) { error_q.PushBack(error); });
483
484 {
485 int int_create = 0;
487 req.CreateDataPoint(path1, int_create);
488 req.CreateDataPoint(path2, int_create);
489 repo->SendRequest(req).Wait();
490
491 create_q.AwaitSize(2);
492 EXPECT_TRUE(create_q.Contains(path1));
493 EXPECT_TRUE(create_q.Contains(path2));
494 EXPECT_EQ(delete_q.Size(), 0);
495 EXPECT_EQ(error_q.Size(), 0);
496 }
497
498 {
500 req.DeleteDataPoint(path2);
501 req.DeleteDataPoint(path1);
502 repo->SendRequest(req).Wait();
503
504 delete_q.AwaitSize(2);
505 EXPECT_EQ(create_q.Size(), 2);
506 EXPECT_TRUE(delete_q.Contains(path1));
507 EXPECT_TRUE(delete_q.Contains(path2));
508 EXPECT_EQ(error_q.Size(), 0);
509 }
510 } // here the subscription goes out of scope and we unsubscribe
511
512 {
513 int int_create = 0;
515 req.CreateDataPoint(path1, int_create);
516 req.CreateDataPoint(path2, int_create);
517 repo->SendRequest(req).Wait();
518
519 // best effort: sleep a short time and check that really no more callbacks were triggered
520 std::this_thread::sleep_for(std::chrono::milliseconds(200));
521
522 EXPECT_EQ(create_q.Size(), 2);
523 EXPECT_EQ(delete_q.Size(), 2);
524 EXPECT_EQ(error_q.Size(), 0);
525 }
526}
527
528TEST_F(Subscription, CreateDeleteSubscriptionToSymlink) {
532
533 {
534 auto subscription =
535 repo->Subscribe([&](const DataPointPath& path) { create_q.PushBack(path); },
536 [&](const DataPointPath& path) { delete_q.PushBack(path); },
537 // NOLINTNEXTLINE(performance-unnecessary-value-param)
538 [&](std::exception_ptr error) { error_q.PushBack(error); });
539
540 {
541 int int_create = 0;
543 req.CreateDataPoint(path1, int_create);
544 req.CreateSymlink(path1, path2);
545 repo->SendRequest(req).Wait();
546
547 create_q.AwaitSize(2);
548 EXPECT_TRUE(create_q.Contains(path1));
549 EXPECT_TRUE(create_q.Contains(path2));
550 EXPECT_EQ(delete_q.Size(), 0);
551 EXPECT_EQ(error_q.Size(), 0);
552 }
553
554 {
556 req.DeleteDataPoint(path2);
557 req.DeleteDataPoint(path1);
558 repo->SendRequest(req).Wait();
559
560 delete_q.AwaitSize(2);
561 EXPECT_EQ(create_q.Size(), 2);
562 EXPECT_TRUE(delete_q.Contains(path1));
563 EXPECT_TRUE(delete_q.Contains(path2));
564 EXPECT_EQ(error_q.Size(), 0);
565 }
566 } // here the subscription goes out of scope and we unsubscribe
567
568 {
569 int int_create = 0;
571 req.CreateDataPoint(path1, int_create);
572 req.CreateSymlink(path1, path2);
573 repo->SendRequest(req).Wait();
574
575 // best effort: sleep a short time and check that really no more callbacks were triggered
576 std::this_thread::sleep_for(std::chrono::milliseconds(200));
577
578 EXPECT_EQ(create_q.Size(), 2);
579 EXPECT_EQ(delete_q.Size(), 2);
580 EXPECT_EQ(error_q.Size(), 0);
581 }
582}
583
584TEST_F(Subscription, SubscribeShouldThrowForNonAbsolutePath) {
585 EXPECT_THROW(
586 {
587 auto sub = repo->Subscribe<int>(
588 "relative/dp/path"_dppath,
589 [&](const DataPointPath& path,
590 const int& data,
591 const RepositoryIf::MetaData& metadata) {},
592 // NOLINTNEXTLINE(performance-unnecessary-value-param)
593 [&](std::exception_ptr error) {});
594 },
596}
597
598TEST_F(Subscription, SubscribeShouldThrowForNonExistingDataPoint) {
599 EXPECT_THROW(
600 {
601 auto sub = repo->Subscribe<int>(
602 path1,
603 [&](const DataPointPath& path,
604 const int& data,
605 const RepositoryIf::MetaData& metadata) {},
606 // NOLINTNEXTLINE(performance-unnecessary-value-param)
607 [&](std::exception_ptr error) {});
608 },
610}
611
612} // namespace rtctk::componentFramework::test
613
614#endif // RTCTK_COMPONENTFRAMEWORK_TEST_REPOSITORYSUBSCRIBERIFTESTSUITE_HPP
This class provides a wrapper for a data point path.
Definition dataPointPath.hpp:77
An object representing one or more asynchronous I/O requests to a repository.
Definition repositoryIf.hpp:634
void WriteDataPoint(const DataPointPath &path, const T &buffer, std::optional< std::reference_wrapper< MetaData > > metadata=std::nullopt, const CallbackType &callback=nullptr)
Definition repositoryIf.ipp:1538
void DeleteDataPoint(const DataPointPath &path, const CallbackType &callback=nullptr)
Definition repositoryIf.cpp:343
void CreateSymlink(const DataPointPath &dp, const DataPointPath &link, const CallbackType &callback=nullptr)
Definition repositoryIf.cpp:383
void CreateDataPoint(const DataPointPath &path, const T &initial_value, std::optional< std::reference_wrapper< const MetaData > > metadata=std::nullopt, const CallbackType &callback=nullptr)
Definition repositoryIf.ipp:1385
void UpdateSymlink(const DataPointPath &dp, const DataPointPath &link, const CallbackType &callback=nullptr)
Definition repositoryIf.cpp:392
Class for passing/receiving metadata to/from the repository.
Definition repositoryIf.hpp:146
Definition repositorySubscriberIfTestSuite.hpp:77
void TearDown() override
Definition repositorySubscriberIfTestSuite.hpp:86
DataPointPath path2
Definition repositorySubscriberIfTestSuite.hpp:105
void SetUp() override
Definition repositorySubscriberIfTestSuite.hpp:79
std::shared_ptr< RepositorySubscriberIf > repo
Definition repositorySubscriberIfTestSuite.hpp:103
DataPointPath path1
Definition repositorySubscriberIfTestSuite.hpp:104
DataPointPath link
Definition repositorySubscriberIfTestSuite.hpp:106
Definition repositorySubscriberIfTestSuite.hpp:30
size_t Size()
Definition repositorySubscriberIfTestSuite.hpp:42
void AwaitSize(size_t target_size, std::chrono::seconds timeout=std::chrono::seconds(5))
Definition repositorySubscriberIfTestSuite.hpp:53
void PushBack(const T &path)
Definition repositorySubscriberIfTestSuite.hpp:32
T operator[](size_t idx)
Definition repositorySubscriberIfTestSuite.hpp:37
bool Contains(const T &val)
Definition repositorySubscriberIfTestSuite.hpp:47
Definition fakeClock.cpp:15
std::chrono::milliseconds g_sleep_duration
Definition repositorySubscriberIfTestSuite.hpp:69
TEST_F(Callbacks, CreateDataPointCallback)
Definition repositoryIfTestSuite.hpp:1662
void Sleep()
Definition repositorySubscriberIfTestSuite.hpp:71
Header file for RepositorySubscriberIf and related base classes.