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