ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
UnifiedBackendTest.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#pragma once
4
5#include "Device.h"
6#include "DeviceBackendImpl.h"
7
8#include <boost/fusion/include/at_key.hpp>
9#include <boost/test/unit_test.hpp>
10
11#include <functional>
12#include <list>
13#include <numeric>
14#include <string>
15#include <thread>
16#include <utility>
17
18// disable shadow warning, boost::mpl::for_each is triggering this warning on Ubuntu 16.04
19#pragma GCC diagnostic ignored "-Wshadow"
20
21namespace ChimeraTK {
22
26 enum class TestCapability {
28 enabled,
30 };
31
44 template<TestCapability _syncRead = TestCapability::enabled,
45 TestCapability _forceDataLossWrite = TestCapability::unspecified,
46 TestCapability _asyncReadInconsistency = TestCapability::unspecified,
49 TestCapability _writeNeverLosesData = TestCapability::unspecified,
53 TestCapability _setRemoteValueIncrementsVersion = TestCapability::enabled,
54 TestCapability _testPartialAccessor = TestCapability::enabled>
56 constexpr TestCapabilities() = default;
57
63 constexpr TestCapabilities<TestCapability::disabled, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
64 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
65 _setRemoteValueIncrementsVersion, _testPartialAccessor>
67 return {};
68 }
69
71 constexpr TestCapabilities<_syncRead, TestCapability::enabled, _asyncReadInconsistency, _switchReadOnly,
72 _switchWriteOnly, TestCapability::disabled, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
73 _setRemoteValueIncrementsVersion, _testPartialAccessor>
75 static_assert(_writeNeverLosesData != TestCapability::enabled,
76 "enableTestWriteNeverLosesData() and enableForceDataLossWrite() are mutually exclusive.");
77 return {};
78 }
79 constexpr TestCapabilities<_syncRead, TestCapability::disabled, _asyncReadInconsistency, _switchReadOnly,
80 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
81 _setRemoteValueIncrementsVersion, _testPartialAccessor>
83 return {};
84 }
85
87 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, TestCapability::enabled, _switchReadOnly,
88 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
89 _setRemoteValueIncrementsVersion, _testPartialAccessor>
91 return {};
92 }
93 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, TestCapability::disabled, _switchReadOnly,
94 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
95 _setRemoteValueIncrementsVersion, _testPartialAccessor>
97 return {};
98 }
99
101 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, TestCapability::enabled,
102 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
103 _setRemoteValueIncrementsVersion, _testPartialAccessor>
105 return {};
106 }
107 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, TestCapability::disabled,
108 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
109 _setRemoteValueIncrementsVersion, _testPartialAccessor>
111 return {};
112 }
113
115 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
116 TestCapability::enabled, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
117 _setRemoteValueIncrementsVersion, _testPartialAccessor>
119 return {};
120 }
121 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
122 TestCapability::disabled, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
123 _setRemoteValueIncrementsVersion, _testPartialAccessor>
125 return {};
126 }
127
131 constexpr TestCapabilities<_syncRead, TestCapability::disabled, _asyncReadInconsistency, _switchReadOnly,
132 _switchWriteOnly, TestCapability::enabled, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
133 _setRemoteValueIncrementsVersion, _testPartialAccessor>
135 static_assert(_forceDataLossWrite != TestCapability::enabled,
136 "enableTestWriteNeverLosesData() and enableForceDataLossWrite() are mutualy exclusive.");
137 return {};
138 }
139 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
140 _switchWriteOnly, TestCapability::disabled, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
141 _setRemoteValueIncrementsVersion, _testPartialAccessor>
143 return {};
144 }
145
147 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
148 _switchWriteOnly, _writeNeverLosesData, TestCapability::enabled, _testReadOnly, _testRawTransfer,
149 _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor>
151 return {};
152 }
153 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
154 _switchWriteOnly, _writeNeverLosesData, TestCapability::disabled, _testReadOnly, _testRawTransfer,
155 _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor>
157 return {};
158 }
159
161 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
162 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, TestCapability::enabled, _testRawTransfer,
163 _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor>
165 return {};
166 }
167 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
168 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, TestCapability::disabled, _testRawTransfer,
169 _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor>
171 return {};
172 }
173
175 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
176 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, TestCapability::disabled, _testCatalogue,
177 _setRemoteValueIncrementsVersion, _testPartialAccessor>
179 return {};
180 }
181 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
182 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, TestCapability::enabled, _testCatalogue,
183 _setRemoteValueIncrementsVersion, _testPartialAccessor>
185 return {};
186 }
187
189 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
190 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer,
191 TestCapability::disabled, _setRemoteValueIncrementsVersion, _testPartialAccessor>
193 return {};
194 }
195 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
196 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer,
197 TestCapability::enabled, _setRemoteValueIncrementsVersion, _testPartialAccessor>
199 return {};
200 }
201
203 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
204 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
205 TestCapability::disabled, _testPartialAccessor>
207 return {};
208 }
209 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
210 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
211 TestCapability::enabled, _testPartialAccessor>
213 return {};
214 }
215
217 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
218 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
219 _setRemoteValueIncrementsVersion, TestCapability::disabled>
221 return {};
222 }
223 constexpr TestCapabilities<_syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly,
224 _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue,
225 _setRemoteValueIncrementsVersion, TestCapability::enabled>
227 return {};
228 }
229
230 static constexpr TestCapability syncRead{_syncRead};
231 static constexpr TestCapability forceDataLossWrite{_forceDataLossWrite};
232 static constexpr TestCapability asyncReadInconsistency{_asyncReadInconsistency};
233 static constexpr TestCapability switchReadOnly{_switchReadOnly};
234 static constexpr TestCapability switchWriteOnly{_switchWriteOnly};
235 static constexpr TestCapability writeNeverLosesData{_writeNeverLosesData};
236 static constexpr TestCapability testWriteOnly{_testWriteOnly};
237 static constexpr TestCapability testReadOnly{_testReadOnly};
238 static constexpr TestCapability testRawTransfer{_testRawTransfer};
239 static constexpr TestCapability testCatalogue{_testCatalogue};
240 static constexpr TestCapability setRemoteValueIncrementsVersion{_setRemoteValueIncrementsVersion};
241 static constexpr TestCapability testPartialAccessor{_testPartialAccessor};
242 };
243
274 template<typename VECTOR_OF_REGISTERS_T = boost::mpl::vector<>>
276 public:
367 template<typename REG_T>
373
377 class EnableDisableActionList : public std::list<std::pair<std::function<void(void)>, std::function<void(void)>>> {
378 public:
379 using std::list<std::pair<std::function<void(void)>, std::function<void(void)>>>::list;
380 };
381
388 void runTests(const std::string& cdd_, const std::string& cdd2_ = "");
389
399
400 protected:
401 void test_B_3_1_2_1();
403 void test_NOSPEC_write();
405 void test_B_3_2_1_2();
406 void test_B_3_2_2();
407 void test_B_4_2_4();
408 void test_B_4_2_5();
409 void test_B_6_4();
410 void test_B_7_2();
411 void test_B_8_2();
412 void test_B_8_2_1();
413 void test_B_8_3();
414 void test_B_8_4();
415 void test_B_8_5();
416 void test_B_8_5_1();
417 void test_B_8_5_2();
418 void test_B_8_5_3();
419 void test_B_8_5_4_3();
420 void test_B_8_6_6();
421 void test_B_9_1();
422 void test_B_9_2_2();
423 void test_B_9_3_1();
424 void test_B_9_3_2();
425 void test_B_9_4_1();
426 void test_B_9_5();
428 void test_B_11_2_1();
429 void test_B_11_2_2();
430 void test_B_11_6();
431 void test_B_12_1_3_1();
432 void test_B_12_1_5_1();
433 void test_C_5_2_1_2();
434 void test_C_5_2_2_2();
435 void test_C_5_2_3_2();
436 void test_C_5_2_5_2();
437 void test_C_5_2_6_2();
438 void test_C_5_2_7_2();
439 void test_C_5_3();
440 void test_C_5_3_2();
441 void test_C_5_3_3();
447
450
452 template<typename REG_T>
453 bool isRead(REG_T x = {}) {
454 if(x.capabilities.syncRead == TestCapability::disabled) return false;
455 if(x.capabilities.testWriteOnly == TestCapability::enabled) return false;
456 return x.isReadable();
457 }
458 template<typename REG_T>
459 bool isWrite(REG_T x = {}) {
460 if(x.capabilities.testReadOnly == TestCapability::enabled) return false;
461 return x.isWriteable();
462 }
463 template<typename REG_T>
464 bool isSyncRead(REG_T x = {}) {
465 if(x.capabilities.testWriteOnly == TestCapability::enabled) return false;
466 return x.isReadable() && !x.supportedFlags().has(ChimeraTK::AccessMode::wait_for_new_data);
467 }
468 template<typename REG_T>
469 bool isAsyncRead(REG_T x = {}) {
470 if(x.capabilities.testWriteOnly == TestCapability::enabled) return false;
471 return x.isReadable() && x.supportedFlags().has(ChimeraTK::AccessMode::wait_for_new_data);
472 }
473 template<typename REG_T>
474 bool isRaw(REG_T x = {}) {
475 return x.supportedFlags().has(ChimeraTK::AccessMode::raw);
476 }
477 template<typename REG_T>
478 bool isReadOnly(REG_T x = {}) {
479 return !x.isWriteable() && x.isReadable();
480 }
481 template<typename REG_T>
482 bool isWriteOnly(REG_T x = {}) {
483 return x.isWriteable() && !x.isReadable();
484 }
485
487 VECTOR_OF_REGISTERS_T registers;
488
490 std::string cdd, cdd2;
491
494
497 explicit ExceptionReportingBackend(boost::shared_ptr<DeviceBackend> target) : _target(std::move(target)) {}
498 ~ExceptionReportingBackend() override = default;
499
500 void setExceptionImpl() noexcept override {
501 _hasSeenException = true;
502 _target->setException(getActiveExceptionMessage());
503 // do not keep the ExceptionReportingBackend in exception state, otherwise further exceptions do not cause this
504 // function to be executed.
506 }
507
510 bool ret = _hasSeenException;
511 _hasSeenException = false;
512 return ret;
513 }
514
515 void open() override {}
516 void close() override {}
517 std::string readDeviceInfo() override { return ""; }
518
519 RegisterCatalogue getRegisterCatalogue() const override { throw; }
520
521 private:
522 boost::shared_ptr<DeviceBackend> _target;
523 bool _hasSeenException{false};
524 };
525
526 // Proxy for calling setForceDataLossWrite() only if allowed by capabilities.
527 template<typename T, bool condition = (T::capabilities.forceDataLossWrite == TestCapability::enabled)>
529 setForceDataLossWrite_proxy_helper(T t, bool enable) { t.setForceDataLossWrite(enable); }
530 };
531
532 template<typename T>
535 std::cout << "Unexpected use of disabled capability." << std::endl;
536 std::terminate();
537 }
538 };
539
540 template<typename T>
541 void setForceDataLossWrite(T t, bool enable) {
543 }
544
545 // Proxy for calling forceAsyncReadInconsistency() only if allowed by capabilities.
546 template<typename T, bool condition = (T::capabilities.asyncReadInconsistency == TestCapability::enabled)>
548 explicit forceAsyncReadInconsistency_proxy_helper(T t) { t.forceAsyncReadInconsistency(); }
549 };
550
551 template<typename T>
554 std::cout << "Unexpected use of disabled capability." << std::endl;
555 std::terminate();
556 }
557 };
558
559 template<typename T>
563
564 // Proxy for calling switchReadOnly() only if allowed by capabilities.
565 template<typename T, bool condition = (T::capabilities.switchReadOnly == TestCapability::enabled)>
567 switchReadOnly_proxy_helper(T t, bool enable) { t.switchReadOnly(enable); }
568 };
569
570 template<typename T>
573 std::cout << "Unexpected use of disabled capability." << std::endl;
574 std::terminate();
575 }
576 };
577
578 template<typename T>
579 void switchReadOnly(T t, bool enable) {
581 }
582
583 // Proxy for calling switchWriteOnly() only if allowed by capabilities.
584 template<typename T, bool condition = (T::capabilities.switchWriteOnly == TestCapability::enabled)>
586 switchWriteOnly_proxy_helper(T t, bool enable) { t.switchWriteOnly(enable); }
587 };
588
589 template<typename T>
592 std::cout << "Unexpected use of disabled capability." << std::endl;
593 std::terminate();
594 }
595 };
596
597 template<typename T>
598 void switchWriteOnly(T t, bool enable) {
600 }
601
602 // Proxy for getting the writeQueueLength only if allowed by capabilities.
603 template<typename T,
604 bool condition = (T::capabilities.forceDataLossWrite == TestCapability::enabled ||
605 T::capabilities.writeNeverLosesData == TestCapability::enabled)>
607 explicit writeQueueLength_proxy_helper(T t) { result = t.writeQueueLength(); }
608 size_t result;
609 };
610
611 template<typename T>
614 std::cout << "Unexpected use of disabled capability." << std::endl;
615 std::terminate();
616 }
617 size_t result{0};
618 };
619
620 template<typename T>
621 size_t writeQueueLength(T t) {
623 }
624
625 // Proxy for getting nValuesToTest() if defined, and otherwise use the default
626 template<typename T>
628 using one = char;
629 struct two {
630 char x[2];
631 };
632
633 template<typename C>
634 static one test(decltype(&C::nValuesToTest));
635 template<typename C>
636 static two test(...);
637
638 public:
639 enum { value = sizeof(test<T>(0)) == sizeof(char) };
640 };
641
642 template<typename T, bool hasFn = has_nValuesToTest<T>::value>
644 explicit nValuesToTest_proxy_helper(T t) { result = t.nValuesToTest(); }
645 size_t result;
646 };
647 template<typename T>
648 struct nValuesToTest_proxy_helper<T, false> {
650 size_t result;
651 };
652
653 template<typename T>
654 size_t nValuesToTest(T t) {
656 }
657 };
658
659 /********************************************************************************************************************/
660 /********************************************************************************************************************/
661 /*
662 * Implementations below this point.
663 *
664 * This is made header-only to avoid the need to link against BOOST unit test libraries for the DeviceAccess
665 * runtime library, or to introduce another library shipped with DeviceAccess just for this class.
666 */
667 /********************************************************************************************************************/
668 /********************************************************************************************************************/
669
670 // Helper template function to compare values appropriately for the type
671 template<typename UserType>
672 bool compareHelper(UserType a, UserType b) {
673 return a == b;
674 }
675
676 template<>
677 inline bool compareHelper<double>(double a, double b) {
678 return std::abs(a - b) <= std::numeric_limits<double>::epsilon() * 10. * std::max(std::abs(a), std::abs(b));
679 }
680
681 template<>
682 inline bool compareHelper<float>(float a, float b) {
683 return std::abs(a - b) <= std::numeric_limits<float>::epsilon() * 10.F * std::max(std::abs(a), std::abs(b));
684 }
685
686 // Turn off the linter warning. It shows for strings only and we can't change the template signature here.
687 template<>
688 inline bool compareHelper<std::string>(std::string a, std::string b) { // NOLINT(performance-unnecessary-value-param)
689 return a == b;
690 }
691} // namespace ChimeraTK
692
693namespace std {
694 inline std::string to_string(const std::string& v) {
695 return v;
696 }
697 inline std::string to_string(const char*& v) {
698 return {v};
699 }
700} // namespace std
701
702namespace ChimeraTK {
703
704 /********************************************************************************************************************/
705
706 // Helper macro to compare the value on an accessor and the expected 2D value
707 // Note: we use a macro and not a function, so BOOST_ERROR prints us the line number of the actual test!
708
709 // As the function only works with the correct objects and it is unlikely that expressions are used as input
710 // parameters, we turn off the linter warning about parentheses around the macro arguments. The parentheses would
711 // make the code harder to read.
712
713 // NOLINTBEGIN(bugprone-macro-parentheses)
714#define CHECK_EQUALITY(accessor, expectedValue) \
715 { \
716 typedef typename decltype(expectedValue)::value_type::value_type CHECK_EQUALITY_UserType; \
717 std::string fail; \
718 BOOST_CHECK_EQUAL(accessor.getNChannels(), expectedValue.size()); \
719 BOOST_CHECK_EQUAL(accessor.getNElementsPerChannel(), expectedValue[0].size()); \
720 bool CHECK_EQUALITY_warnExpectedZero = true; \
721 for(size_t CHECK_EQUALITY_i = 0; CHECK_EQUALITY_i < expectedValue.size(); ++CHECK_EQUALITY_i) { \
722 for(size_t CHECK_EQUALITY_k = 0; CHECK_EQUALITY_k < expectedValue[0].size(); ++CHECK_EQUALITY_k) { \
723 if(CHECK_EQUALITY_warnExpectedZero && \
724 !compareHelper(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k], CHECK_EQUALITY_UserType())) { \
725 /* non-zero value found in expectedValue, no need to warn about all-zero expected value */ \
726 CHECK_EQUALITY_warnExpectedZero = false; \
727 } \
728 if(!compareHelper( \
729 accessor[CHECK_EQUALITY_i][CHECK_EQUALITY_k], expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k])) { \
730 if(fail.empty()) { \
731 fail = "Accessor content differs from expected value. First difference at index [" + \
732 std::to_string(CHECK_EQUALITY_i) + "][" + std::to_string(CHECK_EQUALITY_k) + \
733 "]: " + std::to_string(accessor[CHECK_EQUALITY_i][CHECK_EQUALITY_k]) + \
734 " != " + std::to_string(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k]); \
735 } \
736 } \
737 } \
738 } \
739 if(!fail.empty()) { \
740 BOOST_ERROR(fail); \
741 } \
742 if(CHECK_EQUALITY_warnExpectedZero && !std::is_same<CHECK_EQUALITY_UserType, ChimeraTK::Boolean>::value) { \
743 BOOST_ERROR("Comparison with all-zero expectedValue! Test may be insensitive! Check the " \
744 "generateValue() implementations!"); \
745 } \
746 } \
747 (void)(0)
748
749// Similar to CHECK_EQUALITY, but compares two 2D vectors
750#define CHECK_EQUALITY_VECTOR(foundValue, expectedValue) \
751 { \
752 typedef typename decltype(expectedValue)::value_type::value_type CHECK_EQUALITY_UserType; \
753 std::string fail; \
754 BOOST_CHECK_EQUAL(foundValue.size(), expectedValue.size()); \
755 BOOST_CHECK_EQUAL(foundValue[0].size(), expectedValue[0].size()); \
756 bool CHECK_EQUALITY_warnExpectedZero = true; \
757 for(size_t CHECK_EQUALITY_i = 0; CHECK_EQUALITY_i < expectedValue.size(); ++CHECK_EQUALITY_i) { \
758 for(size_t CHECK_EQUALITY_k = 0; CHECK_EQUALITY_k < expectedValue[0].size(); ++CHECK_EQUALITY_k) { \
759 if(CHECK_EQUALITY_warnExpectedZero && \
760 !compareHelper(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k], CHECK_EQUALITY_UserType())) { \
761 /* non-zero value found in expectedValue, no need to warn about all-zero expected value */ \
762 CHECK_EQUALITY_warnExpectedZero = false; \
763 } \
764 if(!compareHelper( \
765 foundValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k], expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k])) { \
766 if(fail.empty()) { \
767 fail = "Data content differs from expected value. First difference at index [" + \
768 std::to_string(CHECK_EQUALITY_i) + "][" + std::to_string(CHECK_EQUALITY_k) + \
769 "]: " + std::to_string(foundValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k]) + \
770 " != " + std::to_string(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k]); \
771 } \
772 } \
773 } \
774 } \
775 if(!fail.empty()) { \
776 BOOST_ERROR(fail); \
777 } \
778 if(CHECK_EQUALITY_warnExpectedZero && !std::is_same<CHECK_EQUALITY_UserType, ChimeraTK::Boolean>::value) { \
779 BOOST_ERROR("Comparison with all-zero expectedValue! Test may be insensitive! Check the " \
780 "generateValue() implementations!"); \
781 } \
782 } \
783 (void)(0)
784
785// Similar to CHECK_EQUALITY, but runs readLatest() on the accessor in a loop until the expected value has arrived, for
786// at most maxMilliseconds. If the expected value is not seen within that time, an error is risen.
787#define CHECK_EQUALITY_TIMEOUT(accessor, expectedValue, maxMilliseconds) \
788 { \
789 typedef typename decltype(expectedValue)::value_type::value_type CHECK_EQUALITY_UserType; \
790 std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \
791 bool CHECK_EQUALITY_warnExpectedZero = true; \
792 while(true) { \
793 accessor.readLatest(); \
794 std::string fail; \
795 BOOST_CHECK_EQUAL(accessor.getNChannels(), expectedValue.size()); \
796 BOOST_CHECK_EQUAL(accessor.getNElementsPerChannel(), expectedValue[0].size()); \
797 for(size_t CHECK_EQUALITY_i = 0; CHECK_EQUALITY_i < expectedValue.size(); ++CHECK_EQUALITY_i) { \
798 for(size_t CHECK_EQUALITY_k = 0; CHECK_EQUALITY_k < expectedValue[0].size(); ++CHECK_EQUALITY_k) { \
799 if(CHECK_EQUALITY_warnExpectedZero && \
800 !compareHelper(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k], CHECK_EQUALITY_UserType())) { \
801 /* non-zero value found in expectedValue, no need to warn about all-zero expected value */ \
802 CHECK_EQUALITY_warnExpectedZero = false; \
803 } \
804 if(!compareHelper( \
805 accessor[CHECK_EQUALITY_i][CHECK_EQUALITY_k], expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k])) { \
806 if(fail.empty()) { \
807 fail = "Accessor content differs from expected value. First difference at index [" + \
808 std::to_string(CHECK_EQUALITY_i) + "][" + std::to_string(CHECK_EQUALITY_k) + \
809 "]: " + std::to_string(accessor[CHECK_EQUALITY_i][CHECK_EQUALITY_k]) + \
810 " != " + std::to_string(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k]); \
811 } \
812 } \
813 } \
814 } \
815 if(fail.empty()) break; \
816 bool timeout_reached = (std::chrono::steady_clock::now() - t0) > std::chrono::milliseconds(maxMilliseconds); \
817 BOOST_CHECK_MESSAGE(!timeout_reached, fail); \
818 if(timeout_reached) break; \
819 usleep(10000); \
820 } \
821 if(CHECK_EQUALITY_warnExpectedZero && !std::is_same<CHECK_EQUALITY_UserType, ChimeraTK::Boolean>::value) { \
822 BOOST_ERROR("Comparison with all-zero expectedValue! Test may be insensitive! Check the " \
823 "generateValue() implementations!"); \
824 } \
825 } \
826 (void)(0)
827
828// Similar to CHECK_EQUALITY_TIMEOUT, but compares two 2D vectors
829#define CHECK_EQUALITY_VECTOR_TIMEOUT(foundValue, expectedValue, maxMilliseconds) \
830 { \
831 typedef typename decltype(expectedValue)::value_type::value_type CHECK_EQUALITY_UserType; \
832 std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \
833 bool CHECK_EQUALITY_warnExpectedZero = true; \
834 while(true) { \
835 std::string fail; \
836 auto CHECK_EQUALITY_value = foundValue; /* Copy value for consistency and performance in the following code */ \
837 BOOST_CHECK_EQUAL(theValue.size(), expectedValue.size()); \
838 BOOST_CHECK_EQUAL(theValue[0].size(), expectedValue[0].size()); \
839 for(size_t CHECK_EQUALITY_i = 0; CHECK_EQUALITY_i < expectedValue.size(); ++CHECK_EQUALITY_i) { \
840 for(size_t CHECK_EQUALITY_k = 0; CHECK_EQUALITY_k < expectedValue[0].size(); ++CHECK_EQUALITY_k) { \
841 if(CHECK_EQUALITY_warnExpectedZero && \
842 !compareHelper(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k], CHECK_EQUALITY_UserType())) { \
843 /* non-zero value found in expectedValue, no need to warn about all-zero expected value */ \
844 CHECK_EQUALITY_warnExpectedZero = false; \
845 } \
846 if(!compareHelper(CHECK_EQUALITY_value[CHECK_EQUALITY_i][CHECK_EQUALITY_k], \
847 expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k])) { \
848 if(fail.empty()) { \
849 fail = "Data content differs from expected value. First difference at index [" + \
850 std::to_string(CHECK_EQUALITY_i) + "][" + std::to_string(CHECK_EQUALITY_k) + \
851 "]: " + std::to_string(CHECK_EQUALITY_value[CHECK_EQUALITY_i][CHECK_EQUALITY_k]) + \
852 " != " + std::to_string(expectedValue[CHECK_EQUALITY_i][CHECK_EQUALITY_k]); \
853 } \
854 } \
855 } \
856 } \
857 if(fail.empty()) break; \
858 bool timeout_reached = (std::chrono::steady_clock::now() - t0) > std::chrono::milliseconds(maxMilliseconds); \
859 BOOST_CHECK_MESSAGE(!timeout_reached, fail); \
860 if(timeout_reached) break; \
861 usleep(10000); \
862 } \
863 if(CHECK_EQUALITY_warnExpectedZero && !std::is_same<CHECK_EQUALITY_UserType, ChimeraTK::Boolean>::value) { \
864 BOOST_ERROR("Comparison with all-zero expectedValue! Test may be insensitive! Check the " \
865 "generateValue() implementations!"); \
866 } \
867 } \
868 (void)(0)
869 // NOLINTEND(bugprone-macro-parentheses)
870
871#define CHECK_TIMEOUT(condition, maxMilliseconds) \
872 { \
873 std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \
874 while(!(condition)) { \
875 bool timeout_reached = (std::chrono::steady_clock::now() - t0) > std::chrono::milliseconds(maxMilliseconds); \
876 BOOST_CHECK(!timeout_reached); \
877 if(timeout_reached) break; \
878 usleep(1000); \
879 } \
880 } \
881 (void)0
882
883 /********************************************************************************************************************/
884
885 template<typename VECTOR_OF_REGISTERS_T>
886 void UnifiedBackendTest<VECTOR_OF_REGISTERS_T>::runTests(const std::string& cdd_, const std::string& cdd2_) {
887 cdd = cdd_;
888 cdd2 = cdd2_;
889 std::cout << "=== UnifiedBackendTest for " << cdd;
890 if(!cdd2.empty()) std::cout << " and " << cdd2;
891 std::cout << std::endl;
892
893 size_t nSyncReadRegisters = 0;
894 size_t nAsyncReadRegisters = 0;
895 size_t nWriteRegisters = 0;
896 size_t nRawRegisters = 0;
897 size_t nReadOnlyRegisters = 0;
898 size_t nWriteOnlyRegisters = 0;
899 size_t nPartialReadRegisters = 0; // need at least 4 elements for partial read test
900 size_t nPartialWriteRegisters = 0;
901 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
902 if(this->isAsyncRead(x)) ++nAsyncReadRegisters;
903 if(this->isSyncRead(x)) ++nSyncReadRegisters;
904 if(this->isWrite(x)) ++nWriteRegisters;
905 if(this->isWrite(x) && !this->isRead(x)) ++nWriteOnlyRegisters;
906 if(!this->isWrite(x) && this->isRead(x)) ++nReadOnlyRegisters;
907 if(this->isRaw(x)) ++nRawRegisters;
908 if(this->isRead(x) && (x.nElementsPerChannel() > 4) &&
909 (x.capabilities.testPartialAccessor == TestCapability::enabled)) {
910 ++nPartialReadRegisters;
911 }
912 if(this->isWrite(x) && (x.nElementsPerChannel() > 4) &&
913 (x.capabilities.testPartialAccessor == TestCapability::enabled)) {
914 ++nPartialWriteRegisters;
915 }
916
917 if(x.capabilities.forceDataLossWrite == TestCapability::unspecified) {
918 std::cout << "WARNING: Register " << x.path() << " has unspecified capability forceDataLossWrite!" << std::endl;
919 }
920 if(x.capabilities.asyncReadInconsistency == TestCapability::unspecified) {
921 std::cout << "WARNING: Register " << x.path() << " has unspecified capability asyncReadInconsistency!"
922 << std::endl;
923 }
924 if(x.capabilities.switchReadOnly == TestCapability::unspecified) {
925 std::cout << "WARNING: Register " << x.path() << " has unspecified capability switchReadOnly!" << std::endl;
926 }
927 if(x.capabilities.switchWriteOnly == TestCapability::unspecified) {
928 std::cout << "WARNING: Register " << x.path() << " has unspecified capability switchWriteOnly!" << std::endl;
929 }
930 if(x.capabilities.writeNeverLosesData == TestCapability::unspecified) {
931 std::cout << "WARNING: Register " << x.path() << " has unspecified capability writeNeverLosesData!"
932 << std::endl;
933 }
934 });
935
936 std::cout << "Using " << nSyncReadRegisters << " synchronous and " << nAsyncReadRegisters
937 << " asynchronous read and " << nWriteRegisters << " write test registers." << std::endl;
938 std::cout << "Of those are " << nRawRegisters << " supporting raw mode, " << nReadOnlyRegisters
939 << " are read-only and " << nWriteOnlyRegisters << " write-only." << std::endl;
940
941 if(nSyncReadRegisters + nAsyncReadRegisters + nWriteRegisters == 0) {
942 std::cout << "ERROR: No test registers specified. Cannot perform tests." << std::endl;
943 std::exit(1);
944 }
945
946 if(nSyncReadRegisters + nAsyncReadRegisters == 0) {
947 std::cout << "WARNING: No read test registers specified. This is acceptable only if the backend does not "
948 << "support reading at all." << std::endl;
949 }
950 else if(nSyncReadRegisters == 0) {
951 std::cout
952 << "WARNING: No synchronous read test registers specified. This is acceptable only if the backend has only "
953 << "registers which support AccessMode::wait_for_new_data." << std::endl;
954 }
955 else if(nAsyncReadRegisters == 0) {
956 std::cout
957 << "WARNING: No asynchronous read test registers specified. This is acceptable only if the backend does not "
958 << "support AccessMode::wait_for_new_data at all." << std::endl;
959 }
960 if(nWriteRegisters == 0) {
961 std::cout << "WARNING: No write test registers specified. This is acceptable only if the backend does not "
962 << "support writing at all." << std::endl;
963 }
964
965 if(nRawRegisters == 0) {
966 std::cout << "WARNING: No raw registers specified. This is acceptable only if the backend does not "
967 << "support raw access mode at all." << std::endl;
968 }
969 if(nReadOnlyRegisters == 0) {
970 std::cout << "WARNING: No read-only registers specified." << std::endl;
971 }
972 if(nWriteOnlyRegisters == 0) {
973 std::cout << "WARNING: No write-only registers specified." << std::endl;
974 }
975 if(nPartialReadRegisters == 0) {
976 std::cout << "WARNING: No read registers large enough to test partial reading (>= 4 elements)." << std::endl;
977 }
978 if(nPartialWriteRegisters == 0) {
979 std::cout << "WARNING: No partial write registers defined. This is acceptable only if the backend does not "
980 << "support partial write." << std::endl;
981 }
982 // run the tests
983 test_B_3_1_2_1();
984 test_NOSPEC_partial_read();
985 test_NOSPEC_write();
986 test_NOSPEC_partial_write();
987 test_B_3_2_1_2();
988 test_B_3_2_2();
989 test_B_4_2_4();
990 test_B_4_2_5();
991 test_B_6_4();
992 test_B_7_2();
993 test_B_8_2();
994 test_B_8_2_1();
995 test_B_8_3();
996 test_B_8_4();
997 test_B_8_5();
998 test_B_8_5_1();
999 test_B_8_5_2();
1000 test_B_8_5_3();
1001 test_B_8_5_4_3();
1002 test_B_8_6_6();
1003 test_B_9_1();
1004 test_B_9_2_2();
1005 test_B_9_3_1();
1006 test_B_9_3_2();
1007 test_B_9_4_1();
1008 test_B_9_5();
1009 test_NOSPEC_newVersionAfterOpen();
1010 test_B_11_2_1();
1011 test_B_11_2_2();
1012 test_B_11_6();
1013 test_B_12_1_3_1();
1014 test_B_12_1_5_1();
1015 test_C_5_2_1_2();
1016 test_C_5_2_2_2();
1017 test_C_5_2_3_2();
1018 test_C_5_2_5_2();
1019 test_C_5_2_6_2();
1020 test_C_5_2_7_2();
1021 test_C_5_3();
1022 test_C_5_3_2();
1023 test_C_5_3_3();
1024 test_NOSPEC_valueAfterConstruction();
1025 test_NOSPEC_backendNotClosedAfterException();
1026 test_NOSPEC_rawTransfer();
1027 test_NOSPEC_catalogueRaw();
1028 test_NOSPEC_catalogueReadWrite();
1029 }
1030
1031 /********************************************************************************************************************/
1032
1033 template<typename VECTOR_OF_REGISTERS_T>
1035 auto stopTime = std::chrono::steady_clock::now() + std::chrono::seconds(60);
1036 for(size_t i = 0;; ++i) {
1037 try {
1038 d.open();
1039 break;
1040 }
1041 catch(ChimeraTK::runtime_error&) {
1042 usleep(10000); // 10ms
1043 // try minimum 10 times for at least 60 seconds
1044 if((i > 10) && (std::chrono::steady_clock::now() > stopTime)) {
1045 BOOST_FAIL("Device did not recover within 60 seconds after forced ChimeraTK::runtime_error.");
1046 }
1047 }
1048 }
1049 }
1050
1051 /********************************************************************************************************************/
1052
1057 template<typename VECTOR_OF_REGISTERS_T>
1059 std::cout << "--- test_B_3_1_2_1 - synchronous read" << std::endl;
1060 Device d(cdd);
1061
1062 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1063 if(!this->isRead(x)) return;
1064 typedef typename decltype(x)::minimumUserType UserType;
1065 auto registerName = x.path();
1066 std::cout << "... registerName = " << registerName << std::endl;
1067 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1068
1069 // Set remote value to be read.
1070 x.setRemoteValue();
1071 std::vector<std::vector<UserType>> v1 = x.template getRemoteValue<UserType>();
1072
1073 // open the device
1074 d.open();
1075
1076 // Read value
1077 reg.read();
1078
1079 // Check application buffer
1080 CHECK_EQUALITY(reg, v1);
1081 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1082
1083 // Set an intermediate remote value to be overwritten next
1084 x.setRemoteValue();
1085 usleep(100000); // give potential race conditions a chance to pop up more easily...
1086
1087 std::vector<std::vector<UserType>> v2;
1088 for(size_t iter = 0; iter < this->nValuesToTest(x); ++iter) {
1089 // Set another remote value to be read.
1090 x.setRemoteValue();
1091 v2 = x.template getRemoteValue<UserType>();
1092
1093 // Read second value
1094 reg.read();
1095
1096 // Check application buffer
1097 CHECK_EQUALITY(reg, v2);
1098 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1099 }
1100
1101 // Reading again without changing remote value does not block and gives the same value
1102 reg.read();
1103
1104 // Check application buffer
1105 CHECK_EQUALITY(reg, v2);
1106 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1107
1108 // close device again
1109 d.close();
1110 });
1111 }
1112
1113 /********************************************************************************************************************/
1114
1119 template<typename VECTOR_OF_REGISTERS_T>
1121 std::cout << "--- test_NOSPEC_partial_read" << std::endl;
1122 Device d(cdd);
1123
1124 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1125 if(!this->isRead(x)) return;
1126 if(x.nElementsPerChannel() < 4) return;
1127 typedef typename decltype(x)::minimumUserType UserType;
1128 auto registerName = x.path();
1129 std::cout << "... registerName = " << registerName << std::endl;
1130 // partial accessor with two elements and offset 1
1131 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 2, 1);
1132
1133 // open the device
1134 d.open();
1135
1136 // Set an intermediate remote value to be overwritten next
1137 x.setRemoteValue();
1138 usleep(100000); // give potential race conditions a chance to pop up more easily...
1139
1140 std::vector<std::vector<UserType>> partialV1;
1141 for(size_t iter = 0; iter < this->nValuesToTest(x); ++iter) {
1142 // Set another remote value to be read.
1143 x.setRemoteValue();
1144
1145 partialV1 = x.template getRemoteValue<UserType>();
1146 for(auto& chan : partialV1) {
1147 auto val1 = chan[1]; // cache the two entries we need
1148 auto val2 = chan[2];
1149 chan.resize(2); // resize to two elements
1150 chan[0] = val1; // put back the cached elements
1151 chan[1] = val2;
1152 }
1153
1154 // Read second value
1155 reg.read();
1156
1157 // Check application buffer
1158 CHECK_EQUALITY(reg, partialV1);
1159 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1160 }
1161
1162 // Reading again without changing remote value does not block and gives the same value
1163 reg.read();
1164
1165 // Check application buffer
1166 CHECK_EQUALITY(reg, partialV1);
1167 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1168
1169 // close device again
1170 d.close();
1171 });
1172 }
1173
1174 /********************************************************************************************************************/
1175
1180 template<typename VECTOR_OF_REGISTERS_T>
1182 std::cout << "--- test_NOSPEC_write - write" << std::endl;
1183 Device d(cdd);
1184
1185 // open the device
1186 d.open();
1187
1188 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1189 if(!this->isWrite(x)) return;
1190
1191 typedef typename decltype(x)::minimumUserType UserType;
1192 auto registerName = x.path();
1193
1194 std::cout << "... registerName = " << registerName << std::endl;
1195 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1196
1197 // write some value
1198 for(size_t iter = 0; iter < this->nValuesToTest(x); ++iter) {
1199 auto theValue = x.template generateValue<UserType>();
1200
1201 reg = theValue;
1202 reg.write();
1203
1204 // check remote value (with timeout, because the write might complete asynchronously)
1205 CHECK_EQUALITY_VECTOR_TIMEOUT(x.template getRemoteValue<UserType>(), theValue, 10000);
1206 }
1207 });
1208
1209 // close device again
1210 d.close();
1211 }
1212
1213 /********************************************************************************************************************/
1214
1219 template<typename VECTOR_OF_REGISTERS_T>
1221 std::cout << "--- test_NOSPEC_partial_write" << std::endl;
1222 Device d(cdd);
1223
1224 // open the device
1225 d.open();
1226
1227 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1228 if(!this->isWrite(x)) return;
1229 if(x.nElementsPerChannel() < 4) return;
1230 if(x.capabilities.testPartialAccessor != TestCapability::enabled) return;
1231
1232 typedef typename decltype(x)::minimumUserType UserType;
1233 auto registerName = x.path();
1234
1235 std::cout << "... registerName = " << registerName << std::endl;
1236 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 2, 1);
1237
1238 // write some value
1239 for(size_t iter = 0; iter < this->nValuesToTest(x); ++iter) {
1240 // set a remote value and get it. It will be partially modified.
1241 x.setRemoteValue();
1242 auto theValue = x.template getRemoteValue<UserType>();
1243 //
1244 auto partialValue = x.template generateValue<UserType>();
1245 for(size_t i = 0; i < partialValue.size(); ++i) {
1246 auto& chan = partialValue[i];
1247 auto val1 = chan[1];
1248 auto val2 = chan[2];
1249 chan.resize(2);
1250 chan[0] = val1;
1251 chan[1] = val2;
1252 // the partial replacement of the expected value
1253 theValue[i][1] = val1;
1254 theValue[i][2] = val2;
1255 }
1256
1257 reg = partialValue;
1258 reg.write();
1259
1260 // Check remote value (with timeout, because the write might complete asynchronously).
1261 // We compare the complete value including the values that must not have changed.
1262 CHECK_EQUALITY_VECTOR_TIMEOUT(x.template getRemoteValue<UserType>(), theValue, 10000);
1263 }
1264 });
1265
1266 // close device again
1267 d.close();
1268 }
1269
1270 /********************************************************************************************************************/
1271
1278 template<typename VECTOR_OF_REGISTERS_T>
1280 std::cout << "--- test_B_3_2_1_2 - write() does not destroy application buffer" << std::endl;
1281 Device d(cdd);
1282
1283 // open the device
1284 d.open();
1285
1286 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1287 if(!this->isWrite(x)) return;
1288 typedef typename decltype(x)::minimumUserType UserType;
1289 auto registerName = x.path();
1290 std::cout << "... registerName = " << registerName << std::endl;
1291 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1292
1293 // write some value
1294 auto theValue = x.template generateValue<UserType>();
1295 reg = theValue;
1296 VersionNumber ver;
1297 reg.write(ver);
1298
1299 // check that application data buffer is not changed (non-destructive write, B.3.2.1.2)
1300 BOOST_CHECK(reg.getNChannels() == theValue.size());
1301 BOOST_CHECK(reg.getNElementsPerChannel() == theValue[0].size());
1302 CHECK_EQUALITY(reg, theValue);
1303
1304 // check the version number
1305 BOOST_CHECK(reg.getVersionNumber() == ver);
1306 });
1307
1308 // close device again
1309 d.close();
1310 }
1311
1312 /********************************************************************************************************************/
1313
1318 template<typename VECTOR_OF_REGISTERS_T>
1320 std::cout << "--- test_B_3_2_2 - destructive write" << std::endl;
1321 Device d(cdd);
1322
1323 // open the device
1324 d.open();
1325
1326 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1327 if(!this->isWrite(x)) return;
1328 typedef typename decltype(x)::minimumUserType UserType;
1329 auto registerName = x.path();
1330
1331 std::cout << "... registerName = " << registerName << std::endl;
1332 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1333
1334 // write some value destructively
1335 auto theValue = x.template generateValue<UserType>();
1336 reg = theValue;
1337 VersionNumber ver;
1338 reg.writeDestructively(ver);
1339
1340 // check that application data buffer shape is not changed (content may be lost)
1341 BOOST_CHECK(reg.getNChannels() == theValue.size());
1342 BOOST_CHECK(reg.getNElementsPerChannel() == theValue[0].size());
1343
1344 // check the version number
1345 BOOST_CHECK(reg.getVersionNumber() == ver);
1346
1347 // check remote value
1348 CHECK_EQUALITY_VECTOR_TIMEOUT(x.template getRemoteValue<UserType>(), theValue, 10000);
1349 });
1350
1351 // close device again
1352 d.close();
1353 }
1354
1355/**********************************************************************************************************************/
1356
1357// Turn off the warning about parameter parentheses. They probably are even wrong for template arguments,
1358// and clutter the readability for variable names.
1359// NOLINTBEGIN(bugprone-macro-parentheses)
1360
1362#define STORE_APPLICATION_DATA_BUFFER(UserType, accessor) \
1363 std::vector<std::vector<UserType>> STORE_APPLICATION_BUFFER_data; \
1364 for(size_t i = 0; i < accessor.getNChannels(); ++i) { \
1365 STORE_APPLICATION_BUFFER_data.push_back(accessor[i]); \
1366 } \
1367 (void)0
1368
1369#define ALTER_AND_STORE_APPLICATION_BUFFER(UserType, accessor) \
1370 for(size_t i = 0; i < accessor.getNChannels(); ++i) { \
1371 if constexpr(std::is_arithmetic_v<UserType>) \
1372 std::iota(accessor[i].begin(), accessor[i].end(), std::numeric_limits<UserType>::min() + 1); \
1373 if constexpr(std::is_same_v<std::string, UserType>) std::fill(accessor[i].begin(), accessor[i].end(), "FACECAFE"); \
1374 } \
1375 STORE_APPLICATION_DATA_BUFFER(UserType, accessor); \
1376 VersionNumber STORE_APPLICATION_BUFFER_version; \
1377 DataValidity STORE_APPLICATION_BUFFER_validity; \
1378 STORE_APPLICATION_BUFFER_version = accessor.getVersionNumber(); \
1379 STORE_APPLICATION_BUFFER_validity = accessor.dataValidity()
1380
1381#define CHECK_APPLICATION_DATA_BUFFER(UserType, accessor) CHECK_EQUALITY(accessor, STORE_APPLICATION_BUFFER_data)
1382
1383#define CHECK_APPLICATION_BUFFER(UserType, accessor) \
1384 CHECK_APPLICATION_DATA_BUFFER(UserType, accessor); \
1385 BOOST_CHECK(STORE_APPLICATION_BUFFER_version == accessor.getVersionNumber()); \
1386 BOOST_CHECK(STORE_APPLICATION_BUFFER_validity == accessor.dataValidity())
1387
1388 // NOLINTEND(bugprone-macro-parentheses)
1389
1394 template<typename VECTOR_OF_REGISTERS_T>
1396 std::cout << "--- test_B_4_2_4 - transfer implementations do not change the application buffer" << std::endl;
1397 Device d(cdd);
1398
1399 // open the device
1400 d.open();
1401
1402 std::cout << "... writeTransfer()" << std::endl;
1403 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1404 if(!this->isWrite(x)) return;
1405 typedef typename decltype(x)::minimumUserType UserType;
1406 auto registerName = x.path();
1407 std::cout << "... registerName = " << registerName << std::endl;
1408 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1409 auto te = reg.getHighLevelImplElement();
1410
1411 // write some value, calling the stages manually
1412 auto theValue = x.template generateValue<UserType>();
1413 reg = theValue;
1414 VersionNumber ver;
1415 te->preWrite(TransferType::write, ver);
1417 te->writeTransfer(ver);
1418 CHECK_APPLICATION_BUFFER(UserType, reg);
1419 te->postWrite(TransferType::write, ver);
1420 });
1421
1422 std::cout << "... writeTransferDestructively()" << std::endl;
1423 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1424 if(!this->isWrite(x)) return;
1425 typedef typename decltype(x)::minimumUserType UserType;
1426 auto registerName = x.path();
1427 std::cout << "... registerName = " << registerName << std::endl;
1428 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1429 auto te = reg.getHighLevelImplElement();
1430
1431 // write some value, calling the stages manually
1432 auto theValue = x.template generateValue<UserType>();
1433 reg = theValue;
1434 VersionNumber ver;
1435 te->preWrite(TransferType::writeDestructively, ver);
1437 te->writeTransferDestructively(ver);
1438 CHECK_APPLICATION_BUFFER(UserType, reg);
1439 te->postWrite(TransferType::writeDestructively, ver);
1440 });
1441
1442 std::cout << "... readTransferSynchronously()" << std::endl;
1443 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1444 if(!this->isRead(x)) return;
1445 typedef typename decltype(x)::minimumUserType UserType;
1446 auto registerName = x.path();
1447 std::cout << "... registerName = " << registerName << std::endl;
1448 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1449 auto te = reg.getHighLevelImplElement();
1450
1451 // read some value, calling the stages manually
1452 auto theValue = x.template generateValue<UserType>();
1453 reg = theValue;
1454 VersionNumber ver;
1456 te->preRead(TransferType::read);
1457 CHECK_APPLICATION_BUFFER(UserType, reg);
1458 te->readTransfer();
1459 CHECK_APPLICATION_BUFFER(UserType, reg);
1460 te->postRead(TransferType::read, true);
1461 });
1462
1463 // close device again
1464 d.close();
1465 }
1466
1467 /********************************************************************************************************************/
1468
1473 template<typename VECTOR_OF_REGISTERS_T>
1475 std::cout << "--- test_B_4_2_5 - xxxTransferYyy() can be skipped between preXxx() and postXxx()" << std::endl;
1476 Device d(cdd);
1477
1478 // open the device
1479 d.open();
1480
1481 std::cout << "... writeTransfer()" << std::endl;
1482 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1483 if(!this->isWrite(x)) {
1484 return;
1485 }
1486 using UserType = typename decltype(x)::minimumUserType;
1487 auto registerName = x.path();
1488 std::cout << "... registerName = " << registerName << std::endl;
1489 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1490 auto te = reg.getHighLevelImplElement();
1491
1492 // write some value, calling the stages manually
1493 auto theValue = x.template generateValue<UserType>();
1494 reg = theValue;
1495 VersionNumber ver;
1496 STORE_APPLICATION_DATA_BUFFER(UserType, reg);
1497 te->preWrite(TransferType::write, ver);
1498 te->postWrite(TransferType::write, ver);
1499 CHECK_APPLICATION_DATA_BUFFER(UserType, reg);
1500 });
1501
1502 std::cout << "... writeTransferDestructively()" << std::endl;
1503 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1504 if(!this->isWrite(x)) {
1505 return;
1506 }
1507 using UserType = typename decltype(x)::minimumUserType;
1508 auto registerName = x.path();
1509 std::cout << "... registerName = " << registerName << std::endl;
1510 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1511 auto te = reg.getHighLevelImplElement();
1512
1513 // write some value, calling the stages manually
1514 auto theValue = x.template generateValue<UserType>();
1515 reg = theValue;
1516 VersionNumber ver;
1517 STORE_APPLICATION_DATA_BUFFER(UserType, reg);
1518 te->preWrite(TransferType::writeDestructively, ver);
1519 te->postWrite(TransferType::writeDestructively, ver);
1520 CHECK_APPLICATION_DATA_BUFFER(UserType, reg);
1521 });
1522
1523 std::cout << "... readTransferSynchronously()" << std::endl;
1524 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1525 if(!this->isRead(x)) {
1526 return;
1527 }
1528 using UserType = typename decltype(x)::minimumUserType;
1529 auto registerName = x.path();
1530 std::cout << "... registerName = " << registerName << std::endl;
1531 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1532 auto te = reg.getHighLevelImplElement();
1533
1534 // read some value, calling the stages manually
1535 auto theValue = x.template generateValue<UserType>();
1536 reg = theValue;
1537 STORE_APPLICATION_DATA_BUFFER(UserType, reg);
1538 te->preRead(TransferType::read);
1539 te->postRead(TransferType::read, false);
1540 CHECK_APPLICATION_DATA_BUFFER(UserType, reg);
1541 });
1542
1543 // close device again
1544 d.close();
1545 }
1546
1547 /********************************************************************************************************************/
1548
1553 template<typename VECTOR_OF_REGISTERS_T>
1555 if(_testOnlyTransferElement) return;
1556 std::cout << "--- test_B_6_4 - application buffer unchanged after exception" << std::endl;
1557 Device d(cdd);
1558
1559 std::cout << "... synchronous read " << std::endl;
1560 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1561 if(!this->isRead(x)) return;
1562 typedef typename decltype(x)::minimumUserType UserType;
1563 auto registerName = x.path();
1564 int someNumber = 42;
1565
1566 std::cout << " registerName = " << registerName << std::endl;
1567 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1568
1569 // alter the application buffer to make sure it is not changed under exception
1570 reg[0][0] = numericToUserType<UserType>(someNumber);
1571 reg.setDataValidity(DataValidity::ok);
1572 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr)); // (quasi-assertion for the test)
1573
1574 // trigger logic error
1575 BOOST_CHECK_THROW(reg.read(), logic_error); // (no check intended, just catch)
1576
1577 // check that the application buffer has not changed after exception
1578 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1579 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1580 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
1581
1582 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
1583 std::cout << " -> runtime_error case: " << i << std::endl;
1584 // open the device, then let it throw runtime_error exceptions
1585 d.open();
1586
1587 // enable runtime errors
1588 x.setForceRuntimeError(true, i);
1589
1590 // trigger runtime_error
1591 BOOST_CHECK_THROW(reg.read(), runtime_error); // (no check intended, just catch)
1592
1593 // check that the application buffer has not changed after exception
1594 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1595 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1596 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
1597
1598 // disable runtime errors
1599 x.setForceRuntimeError(false, i);
1600
1601 // recover
1602 this->recoverDevice(d);
1603
1604 // close device again
1605 d.close();
1606 }
1607 });
1608
1609 std::cout << "... asynchronous read " << std::endl;
1610 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1611 if(!this->isAsyncRead(x)) return;
1612 typedef typename decltype(x)::minimumUserType UserType;
1613 auto registerName = x.path();
1614 int someNumber = 42;
1615
1616 std::cout << " registerName = " << registerName << std::endl;
1617 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
1618
1619 // alter the application buffer to make sure it is not changed under exception
1620 reg[0][0] = numericToUserType<UserType>(someNumber);
1621 reg.setDataValidity(DataValidity::ok);
1622 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr)); // (quasi-assertion for the test)
1623
1624 // trigger logic error via read()
1625 BOOST_CHECK_THROW(reg.read(), logic_error); // (no check intended, just catch)
1626
1627 // check that the application buffer has not changed after exception
1628 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1629 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1630 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
1631
1632 // trigger logic error via readNonBlocking()
1633 BOOST_CHECK_THROW(reg.readNonBlocking(), logic_error); // (no check intended, just catch)
1634
1635 // check that the application buffer has not changed after exception
1636 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1637 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1638 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
1639
1640 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
1641 std::cout << " -> runtime_error case: " << i << std::endl;
1642 // open the device, then let it throw runtime_error exceptions
1643 d.open();
1645 reg.read(); // initial value
1646
1647 // enable runtime errors
1648 x.setForceRuntimeError(true, i);
1649
1650 // alter the application buffer to make sure it is not changed under exception
1651 reg[0][0] = numericToUserType<UserType>(someNumber);
1652 reg.setDataValidity(DataValidity::ok);
1653 auto ver = reg.getVersionNumber();
1654
1655 // trigger runtime_error via read
1656 BOOST_CHECK_THROW(reg.read(), runtime_error); // (no check intended, just catch)
1657
1658 // check that the application buffer has not changed after exception
1659 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1660 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1661 BOOST_CHECK(reg.getVersionNumber() == ver);
1662
1663 // recover to get another exception
1664 x.setForceRuntimeError(false, i);
1665 this->recoverDevice(d);
1667 reg.read(); // initial value
1668 x.setForceRuntimeError(true, i);
1669
1670 // alter the application buffer to make sure it is not changed under exception
1671 reg[0][0] = numericToUserType<UserType>(someNumber);
1672 reg.setDataValidity(DataValidity::ok);
1673 ver = reg.getVersionNumber();
1674
1675 // trigger runtime_error via readNonBlocking
1676 try {
1677 while(!reg.readNonBlocking()) usleep(10000);
1678 }
1679 catch(runtime_error&) {
1680 }
1681
1682 // check that the application buffer has not changed after exception
1683 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1684 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1685 BOOST_CHECK(reg.getVersionNumber() == ver);
1686
1687 // disable exceptions on read
1688 x.setForceRuntimeError(false, i);
1689
1690 // recover
1691 this->recoverDevice(d);
1692
1693 // close device again
1694 d.close();
1695 }
1696 });
1697
1698 std::cout << "... write " << std::endl;
1699 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1700 if(!this->isWrite(x)) return;
1701 typedef typename decltype(x)::minimumUserType UserType;
1702 auto registerName = x.path();
1703 int someNumber = 42;
1704
1705 std::cout << " registerName = " << registerName << std::endl;
1706 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1707
1708 // alter the application buffer to make sure it is not changed under exception
1709 reg[0][0] = numericToUserType<UserType>(someNumber);
1710 reg.setDataValidity(DataValidity::ok);
1711 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr)); // (quasi-assertion for the test)
1712
1713 // trigger logic error
1714 BOOST_CHECK_THROW(reg.write(), logic_error); // (no check intended, just catch)
1715
1716 // check that the application buffer has not changed after exception
1717 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1718 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1719 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
1720
1721 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
1722 std::cout << " -> runtime_error case: " << i << std::endl;
1723 // open the device, then let it throw runtime_error exceptions
1724 d.open();
1725
1726 // enable exceptions on read
1727 x.setForceRuntimeError(true, i);
1728
1729 // trigger runtime_error
1730 BOOST_CHECK_THROW(reg.write(), runtime_error); // (no check intended, just catch)
1731
1732 // check that the application buffer has not changed after exception
1733 BOOST_CHECK(reg[0][0] == numericToUserType<UserType>(someNumber));
1734 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1735 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
1736
1737 // disable exceptions on read
1738 x.setForceRuntimeError(false, i);
1739
1740 // recover
1741 this->recoverDevice(d);
1742
1743 // close device again
1744 d.close();
1745 }
1746 });
1747 }
1748
1749 /********************************************************************************************************************/
1750
1755 template<typename VECTOR_OF_REGISTERS_T>
1757 std::cout << "--- test_B_7_2 - data loss in write" << std::endl;
1758 Device d(cdd);
1759
1760 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1761 if(!this->isWrite(x)) return;
1762 if(x.capabilities.forceDataLossWrite == TestCapability::enabled) {
1763 typedef typename decltype(x)::minimumUserType UserType;
1764 auto registerName = x.path();
1765 std::cout << "... registerName = " << registerName << " (data loss expected)" << std::endl;
1766
1767 // enable test condition
1768 size_t attempts = this->writeQueueLength(x) + 1;
1769 this->setForceDataLossWrite(x, true);
1770
1771 // open the device
1772 d.open();
1773
1774 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1775
1776 // write some value the requested number of attempts
1777 for(size_t i = 0; i < attempts; ++i) {
1778 auto theValue = x.template generateValue<UserType>();
1779 reg = theValue;
1780 VersionNumber someVersion;
1781 bool dataLost = reg.write(someVersion);
1782 if(i < attempts - 1) {
1783 BOOST_CHECK(dataLost == false);
1784 }
1785 else {
1786 BOOST_CHECK(dataLost == true);
1787 }
1788 // User buffer must be intact even when value was lost somewhere
1789 CHECK_EQUALITY(reg, theValue);
1790 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1791 BOOST_CHECK(reg.getVersionNumber() == someVersion);
1792 }
1793
1794 // disable test condition
1795 this->setForceDataLossWrite(x, false);
1796
1797 // check remote value, must be the last written value
1798 auto v1 = x.template getRemoteValue<UserType>();
1799 CHECK_EQUALITY(reg, v1);
1800
1801 // close device again
1802 d.close();
1803 }
1804 else if(x.capabilities.writeNeverLosesData == TestCapability::enabled) {
1805 typedef typename decltype(x)::minimumUserType UserType;
1806 auto registerName = x.path();
1807 std::cout << "... registerName = " << registerName << " (data loss never expected)" << std::endl;
1808
1809 // obtain number of attempts to make
1810 size_t attempts = this->writeQueueLength(x) + 1;
1811
1812 // open the device
1813 d.open();
1814
1815 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
1816
1817 // write some value the requested number of attempts
1818 for(size_t i = 0; i < attempts; ++i) {
1819 auto theValue = x.template generateValue<UserType>();
1820 reg = theValue;
1821 VersionNumber someVersion;
1822 bool dataLost = reg.write(someVersion);
1823 BOOST_CHECK(dataLost == false);
1824 }
1825
1826 // check remote value, must be the last written value
1827 auto v1 = x.template getRemoteValue<UserType>();
1828 CHECK_EQUALITY(reg, v1);
1829
1830 // close device again
1831 d.close();
1832 }
1833 });
1834 }
1835
1836 /********************************************************************************************************************/
1837
1845 template<typename VECTOR_OF_REGISTERS_T>
1847 std::cout << "--- test_B_8_2 - async read fills _readQueue" << std::endl;
1848 Device d(cdd);
1849
1850 // open the device and activate asynchronous reads
1851 d.open();
1853
1854 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1855 if(!this->isAsyncRead(x)) return;
1856 typedef typename decltype(x)::minimumUserType UserType;
1857 auto registerName = x.path();
1858 VersionNumber someVersion{nullptr};
1859
1860 std::cout << "... registerName = " << registerName << std::endl;
1861 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
1862 // a helper to the same register to make sure the sending thread has distributed into the queues before
1863 // sending the next data
1864 auto helper = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
1865
1866 // Initial value (stays unchecked here)
1867 reg.read();
1868 usleep(100000); // give potential race conditions a chance to pop up more easily...
1869 BOOST_CHECK(reg.readNonBlocking() == false);
1870
1871 // Set remote value to be read.
1872 x.setRemoteValue();
1873 auto v1 = x.template getRemoteValue<UserType>();
1874
1875 // Read the value
1876 reg.read();
1877 usleep(100000); // give potential race conditions a chance to pop up more easily...
1878 BOOST_CHECK(reg.readNonBlocking() == false);
1879
1880 // Check application buffer
1881 CHECK_EQUALITY(reg, v1);
1882 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1883 BOOST_CHECK(reg.getVersionNumber() > someVersion);
1884 someVersion = reg.getVersionNumber();
1885
1886 // Clear the queue of the helper so it afterwards will block until the data from the next setRemoteValue() has arrived
1887 helper.readLatest();
1888
1889 // Set multiple remote values in a row - they will be queued
1890 x.setRemoteValue();
1891 auto v2 = x.template getRemoteValue<UserType>();
1892 helper.read();
1893
1894 x.setRemoteValue();
1895 auto v3 = x.template getRemoteValue<UserType>();
1896 helper.read();
1897
1898 x.setRemoteValue();
1899 auto v4 = x.template getRemoteValue<UserType>();
1900 helper.read();
1901
1902 // Read and check second value
1903 reg.read();
1904 CHECK_EQUALITY(reg, v2);
1905 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1906 BOOST_CHECK(reg.getVersionNumber() > someVersion);
1907 someVersion = reg.getVersionNumber();
1908
1909 // Read and check third value
1910 reg.read();
1911 CHECK_EQUALITY(reg, v3);
1912 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1913 BOOST_CHECK(reg.getVersionNumber() > someVersion);
1914 someVersion = reg.getVersionNumber();
1915
1916 // Read and check fourth value
1917 reg.read();
1918 CHECK_EQUALITY(reg, v4);
1919 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1920 BOOST_CHECK(reg.getVersionNumber() > someVersion);
1921 someVersion = reg.getVersionNumber();
1922
1923 // No more data available
1924 BOOST_CHECK(reg.readNonBlocking() == false);
1925 CHECK_EQUALITY(reg, v4); // application buffer is unchanged (SPEC???)
1926 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
1927 BOOST_CHECK(reg.getVersionNumber() == someVersion);
1928 });
1929
1930 // close device again
1931 d.close();
1932 }
1933
1934 /********************************************************************************************************************/
1935
1944 template<typename VECTOR_OF_REGISTERS_T>
1946 std::cout << "--- test_B_8_2_1 - _readQueue overrun" << std::endl;
1947 Device d(cdd);
1948
1949 // open the device
1950 d.open();
1951
1952 // Activate async read
1954
1955 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
1956 if(!this->isAsyncRead(x)) return;
1957 typedef typename decltype(x)::minimumUserType UserType;
1958 auto registerName = x.path();
1959 VersionNumber someVersion{nullptr};
1960
1961 std::cout << "... registerName = " << registerName << std::endl;
1962 auto overrunningReg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
1963 // same register as the overrunningReg. They are both filled by the same thread, so we know that data has been
1964 // pushed into the queue where we want to force an overrun
1965 auto referenceReg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
1966
1967 // Initial value (stays unchecked here)
1968 overrunningReg.read();
1969 referenceReg.read();
1970 usleep(100000); // give potential race conditions a chance to pop up more easily...
1971 BOOST_CHECK(overrunningReg.readNonBlocking() == false);
1972
1973 // Provoke queue overflow by filling many values. We are only interested in the last one.
1974 for(size_t i = 0; i < 10; ++i) {
1975 x.setRemoteValue();
1976 // wait until it arrives in the reference reg so we are sure the other queue is actually overrunning
1977 referenceReg.read();
1978 }
1979 auto val = x.template getRemoteValue<UserType>();
1980
1981 // Read last written value (B.8.2.1)
1982 BOOST_CHECK(overrunningReg.readLatest() == true);
1983 // Use check with timeout. It might be that the referenceReg was filled, but the overrunning reg not yet because
1984 // the thread was scheduled out
1985 CHECK_EQUALITY_TIMEOUT(overrunningReg, val, 10000);
1986 BOOST_CHECK(overrunningReg.dataValidity() == DataValidity::ok);
1987 BOOST_CHECK(overrunningReg.getVersionNumber() > someVersion);
1988 someVersion = overrunningReg.getVersionNumber();
1989 });
1990
1991 // close device again
1992 d.close();
1993 }
1994
1995 /********************************************************************************************************************/
1996
2001 template<typename VECTOR_OF_REGISTERS_T>
2003 std::cout << "--- test_B_8_3 - new runtime errors are put to _readQueue in async reads" << std::endl;
2004 Device d(cdd);
2005 d.open();
2006
2007 // Activate async read
2009
2010 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2011 if(!this->isAsyncRead(x)) return;
2012 typedef typename decltype(x)::minimumUserType UserType;
2013 auto registerName = x.path();
2014 VersionNumber someVersion{nullptr};
2015
2016 std::cout << "... registerName = " << registerName << std::endl;
2017
2018 // obtain accessor for the test
2019 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2020
2021 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2022 std::cout << " -> runtime_error case: " << i << std::endl;
2023 // read initial value
2024 reg.read();
2025
2026 // execute preRead without exception state
2027 reg.getHighLevelImplElement()->preRead(TransferType::read);
2028
2029 // enable exceptions on read
2030 x.setForceRuntimeError(true, i);
2031
2032 // Check for runtime_error as it is popped of the queue
2033 BOOST_CHECK_THROW(reg.getHighLevelImplElement()->readTransfer(), ChimeraTK::runtime_error);
2034
2035 // Need to report the exception to the exception backend, because this is normally done in
2036 // TransferElement::read() etc.
2037 d.setException("Some message");
2038
2039 // complete the operation
2040 reg.getHighLevelImplElement()->postRead(TransferType::read, false);
2041
2042 // disable exceptions on read
2043 x.setForceRuntimeError(false, i);
2044
2045 // recover
2046 this->recoverDevice(d);
2047 // Activate async read again
2049 }
2050 });
2051
2052 // close device again
2053 d.close();
2054 }
2055
2056 /********************************************************************************************************************/
2057
2062 template<typename VECTOR_OF_REGISTERS_T>
2064 if(_testOnlyTransferElement) return;
2065 std::cout << "--- test_B_8_4 - async read consistency heartbeat" << std::endl;
2066
2067 Device d(cdd);
2068
2069 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2070 if(!this->isAsyncRead(x) || x.capabilities.asyncReadInconsistency != TestCapability::enabled) return;
2071 typedef typename decltype(x)::minimumUserType UserType;
2072 auto registerName = x.path();
2073 VersionNumber someVersion{nullptr};
2074
2075 std::cout << "... registerName = " << registerName << std::endl;
2076
2077 // open the device
2078 d.open();
2079
2080 // Activate async read
2082
2083 // Set remote value to be read.
2084 x.setRemoteValue();
2085 auto v1 = x.template getRemoteValue<UserType>();
2086
2087 // Obtain accessor
2088 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2089
2090 // Read and check initial value
2091 reg.read();
2092 CHECK_EQUALITY(reg, v1);
2093 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
2094 BOOST_CHECK(reg.getVersionNumber() > someVersion);
2095 someVersion = reg.getVersionNumber();
2096
2097 // Provoke inconsistency
2098 this->forceAsyncReadInconsistency(x);
2099
2100 // Wait for the exception which informs about the problem
2101 BOOST_CHECK_THROW(reg.read(), ChimeraTK::runtime_error);
2102
2103 // Recover the device
2104 this->recoverDevice(d);
2105 auto v2 = x.template getRemoteValue<UserType>();
2106
2107 // Activate async read again
2109
2110 // Read and check value
2111 reg.read();
2112 CHECK_EQUALITY(reg, v2);
2113 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
2114 BOOST_CHECK(reg.getVersionNumber() > someVersion);
2115 someVersion = reg.getVersionNumber();
2116
2117 // close device again
2118 d.close();
2119 });
2120 }
2121 /********************************************************************************************************************/
2122
2128 template<typename VECTOR_OF_REGISTERS_T>
2130 if(_testOnlyTransferElement) return;
2131 std::cout << "--- test_B_8_5 - no async transfers until activateAsyncRead()" << std::endl;
2132 Device d(cdd);
2133
2134 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2135 if(!this->isAsyncRead(x)) return;
2136 typedef typename decltype(x)::minimumUserType UserType;
2137 auto registerName = x.path();
2138 std::cout << "... registerName = " << registerName << std::endl;
2139
2140 // First step: measure time until initial value arrives, so we know how long to wait to exclude that an initial
2141 // value arrives wrongly.
2142 std::chrono::duration<double> timeToInitialValue{};
2143 {
2144 // start time measurement
2145 auto t0 = std::chrono::steady_clock::now();
2146
2147 // open the device
2148 d.open();
2150
2151 // obtain accessor
2152 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2153
2154 // make a successful read (initial value) to make sure the exception state is gone
2155 reg.read();
2156
2157 // finish time measurement
2158 auto t1 = std::chrono::steady_clock::now();
2159 timeToInitialValue = t1 - t0;
2160
2161 // close device again
2162 d.close();
2163 }
2164
2165 // Second step: Check if no data arrives without activateAsyncRead()
2166 {
2167 // open the device, but don't call activateAsyncRead() yet
2168 d.open();
2169 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2170
2171 // wait 2 times longer than the time until initial value was received before
2172 std::this_thread::sleep_for(timeToInitialValue * 2);
2173
2174 // no value must have arrived
2175 BOOST_CHECK(reg.readNonBlocking() == false);
2176
2177 // Check again for possible side effects (automatic subscription on read) of reg.readNonBlocking()
2178 // wait 2 times longer than the time until initial value was received before
2179 std::this_thread::sleep_for(timeToInitialValue * 2);
2180
2181 // no value must have arrived
2182 BOOST_CHECK(reg.readNonBlocking() == false);
2183
2184 // close device again
2185 d.close();
2186 }
2187 });
2188 }
2189
2190 /********************************************************************************************************************/
2191
2196 template<typename VECTOR_OF_REGISTERS_T>
2198 if(_testOnlyTransferElement) return;
2199 std::cout << "--- test_B_8_5_1 - activateAsynchronousRead" << std::endl;
2200 Device d(cdd);
2201 Device d2;
2202 if(!cdd2.empty()) {
2203 d2.open(cdd2);
2204 d2.close();
2205 }
2206
2207 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2208 if(!this->isAsyncRead(x)) return;
2209 typedef typename decltype(x)::minimumUserType UserType;
2210 auto registerName = x.path();
2211 std::cout << "... registerName = " << registerName << std::endl;
2212 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2213
2215 if(!cdd2.empty()) {
2216 d2.open();
2217 reg2.replace(d2.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data}));
2218 if(!cdd2.empty()) BOOST_CHECK(reg2.readNonBlocking() == false);
2219 }
2220
2221 // Set remote value to be read.
2222 x.setRemoteValue();
2223 auto v1 = x.template getRemoteValue<UserType>();
2224
2225 // open the device
2226 d.open();
2227 if(!cdd2.empty()) BOOST_CHECK(reg2.readNonBlocking() == false);
2228
2229 // Activate async read
2231
2232 // Read initial value
2233 reg.read();
2234
2235 // Check application buffer
2236 CHECK_EQUALITY(reg, v1);
2237
2238 if(!cdd2.empty()) {
2239 // wait a bit, check that accessor of second device does not receive data
2240 using namespace std::chrono_literals;
2241 std::this_thread::sleep_for(10ms);
2242 BOOST_CHECK(reg2.readNonBlocking() == false);
2243
2244 // activate async read on second device and check again
2245 d2.activateAsyncRead();
2246 reg2.read();
2247 CHECK_EQUALITY(reg2, v1);
2248 }
2249
2250 // close device again
2251 d.close();
2252 });
2253 }
2254
2255 /********************************************************************************************************************/
2256
2261 template<typename VECTOR_OF_REGISTERS_T>
2263 if(_testOnlyTransferElement) return;
2264 std::cout << "--- test_B_8_5_2 - initial value" << std::endl;
2265 Device d(cdd);
2266 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2267 if(!this->isAsyncRead(x)) return;
2268 typedef typename decltype(x)::minimumUserType UserType;
2269 auto registerName = x.path();
2270 std::cout << "... registerName = " << registerName << std::endl;
2271
2272 // First check: initial value is correctly arriving
2273 {
2274 // Set remote value to be read.
2275 x.setRemoteValue();
2276 auto v1 = x.template getRemoteValue<UserType>();
2277
2278 // open the device and activate async read
2279 d.open();
2281
2282 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2283
2284 // Read initial value
2285 reg.read();
2286
2287 // Check application buffer
2288 CHECK_EQUALITY(reg, v1);
2289
2290 // close device again
2291 d.close();
2292 }
2293
2294 // Second check: Concurrent updates do not cause inconsistency. Note: This test cannot possibly cover all
2295 // potential scenarios for race conditions, hence only one simple scenario is tested.
2296 {
2297 // Set initial remote value, to make sure it is different from the next remote value set below
2298 x.setRemoteValue();
2299
2300 // open the device and activate async read
2301 d.open();
2303
2304 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2305
2306 // Concurrently set another remote value to be read (while presumably the subscription is still being made).
2307 x.setRemoteValue();
2308 auto v2 = x.template getRemoteValue<UserType>();
2309
2310 // Check that the second value arrives at some point (with timeout)
2311 CHECK_EQUALITY_TIMEOUT(reg, v2, 30000);
2312
2313 // close device again
2314 d.close();
2315 }
2316 });
2317 }
2318
2319 /********************************************************************************************************************/
2320
2325 template<typename VECTOR_OF_REGISTERS_T>
2327 std::cout << "--- test_B_8_5_3 - accessors created after activateAsyncRead() are immediately active" << std::endl;
2328 Device d(cdd);
2329
2330 // open the device and activate async read
2331 d.open();
2333
2334 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2335 if(!this->isAsyncRead(x)) return;
2336 typedef typename decltype(x)::minimumUserType UserType;
2337 auto registerName = x.path();
2338
2339 std::cout << "... registerName = " << registerName << " (activated async read)" << std::endl;
2340 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2341
2342 // Initial value should arrive
2343 CHECK_TIMEOUT(reg.readNonBlocking() == true, 30000);
2344 });
2345
2346 // close device again
2347 d.close();
2348 }
2349
2350 /********************************************************************************************************************/
2351
2356 template<typename VECTOR_OF_REGISTERS_T>
2358 std::cout << "--- test_B_8_5_4_3 - calling activateAsyncRead() when already active has no effect" << std::endl;
2359 Device d(cdd);
2360
2361 // open the device and activate async read
2362 d.open();
2364
2365 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2366 if(!this->isAsyncRead(x)) {
2367 return;
2368 }
2369 using UserType = typename decltype(x)::minimumUserType;
2370 auto registerName = x.path();
2371
2372 std::cout << "... registerName = " << registerName << " (activated async read)" << std::endl;
2373 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2374
2375 // Read the initial value to empty the queue. We use CHECK_TIMEOUT here so the test does not block in case this
2376 // does not happen. This is not the actual test here (see B_8_5_3).
2377 CHECK_TIMEOUT(reg.readNonBlocking(), 30000);
2378
2379 // The actual test: calling activateAsyncRead again does not produce new "initial" values
2381 usleep(100000); // Sleep a bit (100 ms) to wait for data to arrive, which it should not. So don't wait too long.
2382 BOOST_CHECK(reg.readNonBlocking() == false); // no new data
2383 });
2384
2385 // close device again
2386 d.close();
2387 }
2388
2389 /********************************************************************************************************************/
2390
2395 template<typename VECTOR_OF_REGISTERS_T>
2397 std::cout << "--- test_B_8_6_6 - interrupt()" << std::endl;
2398
2399 Device d(cdd);
2400 auto backend = d.getBackend();
2401 d.open();
2402
2403 // Activate async read
2405
2406 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2407 if(!this->isAsyncRead(x)) return;
2408 typedef typename decltype(x)::minimumUserType UserType;
2409 auto registerName = x.path();
2410 VersionNumber someVersion{nullptr};
2411
2412 std::cout << "... registerName = " << registerName << std::endl;
2413 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2414 reg.read(); // initial value
2415
2416 for(size_t i = 0; i < 2; ++i) {
2417 // execute blocking read in another thread
2418 boost::thread anotherThread([&] {
2419 reg.read();
2420 BOOST_ERROR("boost::thread_interrupt exception expected but not thrown.");
2421 });
2422
2423 // interrupt the blocking operation
2424 reg.getHighLevelImplElement()->interrupt();
2425
2426 // make sure the other thread can terminate
2427 anotherThread.join();
2428
2429 // check accessor is still working
2430 x.setRemoteValue();
2431 auto v1 = x.template getRemoteValue<UserType>();
2432 reg.read();
2433 CHECK_EQUALITY(reg, v1);
2434 BOOST_CHECK(reg.dataValidity() == DataValidity::ok);
2435 BOOST_CHECK(reg.getVersionNumber() > someVersion);
2436 someVersion = reg.getVersionNumber();
2437 }
2438 });
2439
2440 d.close();
2441 }
2442
2443 /********************************************************************************************************************/
2444
2450 template<typename VECTOR_OF_REGISTERS_T>
2452 if(_testOnlyTransferElement) return;
2453 std::cout << "--- test_B_9_1 - reporting exceptions to exception backend" << std::endl;
2454 Device d(cdd);
2455
2456 // open the device, then let it throw runtime_error exceptions
2457 d.open();
2458
2459 std::cout << "... synchronous read" << std::endl;
2460 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2461 if(!this->isRead(x)) return;
2462 if(x.nRuntimeErrorCases() == 0) return;
2463 typedef typename decltype(x)::minimumUserType UserType;
2464 auto registerName = x.path();
2465 std::cout << " registerName = " << registerName << std::endl;
2466 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2467
2468 // set exception reporting backend
2469 auto erb = boost::make_shared<ExceptionReportingBackend>(d.getBackend());
2470 reg.getHighLevelImplElement()->setExceptionBackend(erb);
2471
2472 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2473 std::cout << " -> runtime_error case: " << i << std::endl;
2474 // enable exceptions on read
2475 x.setForceRuntimeError(true, i);
2476
2477 // Runtime error should be reported via setException()
2478 BOOST_CHECK(!erb->hasSeenException());
2479 BOOST_CHECK_THROW(reg.read(), runtime_error);
2480 BOOST_CHECK(erb->hasSeenException());
2481
2482 // disable exceptions on read
2483 x.setForceRuntimeError(false, i);
2484
2485 // recover
2486 this->recoverDevice(d);
2487
2488 // make a successful read to make sure the exception state is gone. no reporting must take place here.
2489 BOOST_CHECK_NO_THROW(reg.read());
2490 BOOST_CHECK(!erb->hasSeenException());
2491 }
2492 });
2493
2494 std::cout << "... asynchronous read" << std::endl;
2496 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2497 if(!this->isAsyncRead(x)) return;
2498 if(x.nRuntimeErrorCases() == 0) return;
2499 typedef typename decltype(x)::minimumUserType UserType;
2500 auto registerName = x.path();
2501 std::cout << " registerName = " << registerName << std::endl;
2502 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2503 reg.read(); // initial value
2504
2505 // set exception reporting backend
2506 auto erb = boost::make_shared<ExceptionReportingBackend>(d.getBackend());
2507 reg.getHighLevelImplElement()->setExceptionBackend(erb);
2508
2509 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2510 std::cout << " -> runtime_error case: " << i << std::endl;
2511 // enable exceptions on read
2512 x.setForceRuntimeError(true, i);
2513
2514 // Runtime error should be reported via setException()
2515 BOOST_CHECK(!erb->hasSeenException());
2516 BOOST_CHECK_THROW(reg.read(), runtime_error);
2517 BOOST_CHECK(erb->hasSeenException());
2518
2519 // disable exceptions on read
2520 x.setForceRuntimeError(false, i);
2521
2522 // recover
2523 this->recoverDevice(d);
2524 d.activateAsyncRead(); // turn async read back on
2525
2526 // make a successful readNonBlocking (no data) to make sure the exception state is gone. no reporting must take
2527 // place here.
2528 BOOST_CHECK_NO_THROW(reg.readNonBlocking());
2529 BOOST_CHECK(!erb->hasSeenException());
2530 }
2531 });
2532
2533 std::cout << "... write" << std::endl;
2534 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2535 if(!this->isWrite(x)) return;
2536 if(x.nRuntimeErrorCases() == 0) return;
2537 typedef typename decltype(x)::minimumUserType UserType;
2538 auto registerName = x.path();
2539 std::cout << " registerName = " << registerName << std::endl;
2540 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2541
2542 // set exception reporting backend
2543 auto erb = boost::make_shared<ExceptionReportingBackend>(d.getBackend());
2544 reg.getHighLevelImplElement()->setExceptionBackend(erb);
2545
2546 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2547 std::cout << " -> runtime_error case: " << i << std::endl;
2548 // enable exceptions on write
2549 x.setForceRuntimeError(true, i);
2550
2551 // Runtime error should be reported via setException()
2552 BOOST_CHECK(!erb->hasSeenException());
2553 BOOST_CHECK_THROW(reg.write(), runtime_error);
2554 BOOST_CHECK(erb->hasSeenException());
2555
2556 // disable exceptions on write
2557 x.setForceRuntimeError(false, i);
2558
2559 // recover
2560 this->recoverDevice(d);
2561
2562 // make a successful write to make sure the exception state is gone. no reporting must take place here.
2563 BOOST_CHECK_NO_THROW(reg.write());
2564 BOOST_CHECK(!erb->hasSeenException());
2565 }
2566 });
2567
2568 std::cout << "... isReadable" << std::endl;
2569 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2570 if(x.nRuntimeErrorCases() == 0) return;
2571 typedef typename decltype(x)::minimumUserType UserType;
2572 auto registerName = x.path();
2573 std::cout << " registerName = " << registerName;
2574 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2575
2576 // set exception reporting backend
2577 auto erb = boost::make_shared<ExceptionReportingBackend>(d.getBackend());
2578 reg.getHighLevelImplElement()->setExceptionBackend(erb);
2579
2580 bool didThrow = false;
2581 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2582 std::cout << " -> runtime_error case: " << i << std::endl;
2583 // enable exceptions on write
2584 x.setForceRuntimeError(true, i);
2585
2586 // Runtime error should be reported via setException()
2587 BOOST_CHECK(!erb->hasSeenException());
2588 try {
2589 [[maybe_unused]] auto result = reg.isReadable();
2590 }
2591 catch(...) {
2592 didThrow = true;
2593 BOOST_CHECK(erb->hasSeenException());
2594 }
2595
2596 // disable exceptions on write
2597 x.setForceRuntimeError(false, i);
2598
2599 // recover
2600 this->recoverDevice(d);
2601 }
2602
2603 if(!didThrow) {
2604 std::cout << " (doesn't throw)" << std::endl;
2605 }
2606 else {
2607 std::cout << " (throws)" << std::endl;
2608 }
2609 });
2610
2611 std::cout << "... isWriteable" << std::endl;
2612 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2613 if(x.nRuntimeErrorCases() == 0) return;
2614 typedef typename decltype(x)::minimumUserType UserType;
2615 auto registerName = x.path();
2616 std::cout << " registerName = " << registerName;
2617 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2618
2619 // set exception reporting backend
2620 auto erb = boost::make_shared<ExceptionReportingBackend>(d.getBackend());
2621 reg.getHighLevelImplElement()->setExceptionBackend(erb);
2622
2623 bool didThrow = false;
2624 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2625 std::cout << " -> runtime_error case: " << i << std::endl;
2626 // enable exceptions on write
2627 x.setForceRuntimeError(true, i);
2628
2629 // Runtime error should be reported via setException()
2630 BOOST_CHECK(!erb->hasSeenException());
2631 try {
2632 [[maybe_unused]] auto result = reg.isWriteable();
2633 }
2634 catch(...) {
2635 didThrow = true;
2636 BOOST_CHECK(erb->hasSeenException());
2637 }
2638
2639 // disable exceptions on write
2640 x.setForceRuntimeError(false, i);
2641
2642 // recover
2643 this->recoverDevice(d);
2644 }
2645
2646 if(!didThrow) {
2647 std::cout << " (doesn't throw)" << std::endl;
2648 }
2649 else {
2650 std::cout << " (throws)" << std::endl;
2651 }
2652 });
2653
2654 std::cout << "... isReadOnly" << std::endl;
2655 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2656 typedef typename decltype(x)::minimumUserType UserType;
2657 auto registerName = x.path();
2658 std::cout << " registerName = " << registerName;
2659 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2660
2661 // set exception reporting backend
2662 auto erb = boost::make_shared<ExceptionReportingBackend>(d.getBackend());
2663 reg.getHighLevelImplElement()->setExceptionBackend(erb);
2664
2665 bool didThrow = false;
2666 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
2667 std::cout << " -> runtime_error case: " << i << std::endl;
2668 // enable exceptions on write
2669 x.setForceRuntimeError(true, i);
2670
2671 // Runtime error should be reported via setException()
2672 BOOST_CHECK(!erb->hasSeenException());
2673 try {
2674 [[maybe_unused]] auto result = reg.isReadOnly();
2675 }
2676 catch(...) {
2677 didThrow = true;
2678 BOOST_CHECK(erb->hasSeenException());
2679 }
2680
2681 // disable exceptions on write
2682 x.setForceRuntimeError(false, i);
2683
2684 // recover
2685 this->recoverDevice(d);
2686 }
2687
2688 if(!didThrow) {
2689 std::cout << " (doesn't throw)" << std::endl;
2690 }
2691 else {
2692 std::cout << " (throws)" << std::endl;
2693 }
2694 });
2695 // close device again
2696 d.close();
2697 }
2698
2699 /********************************************************************************************************************/
2700
2705 template<typename VECTOR_OF_REGISTERS_T>
2707 if(_testOnlyTransferElement) return;
2708 std::cout << "--- test_B_9_2_2 - repeated setException() has no effect" << std::endl;
2709 Device d(cdd);
2710 d.open();
2712
2713 // obtain accessors and read initial value
2714 std::list<TransferElementAbstractor> accessors;
2715 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2716 if(!this->isAsyncRead(x)) return;
2717 typedef typename decltype(x)::minimumUserType UserType;
2718 auto registerName = x.path();
2719 std::cout << "... registerName = " << registerName << std::endl;
2720
2721 // obtain accessor for the test
2722 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2723 accessors.push_back(reg);
2724
2725 // read initial value
2726 reg.read();
2727 });
2728
2729 // enter exception state
2730 d.setException("Some message");
2731
2732 // each accessor has now an exception in the queue -> remove from queue
2733 for(auto& accessor : accessors) {
2734 BOOST_CHECK_THROW(accessor.read(), runtime_error); // (no test intended, just catch)
2735 }
2736
2737 // call setException repeatedly
2738 d.setException("Some message");
2739 d.setException("Some message");
2740
2741 // give potential race conditions a chance...
2742 usleep(10000);
2743
2744 // each accessor still must not have any more exceptions in the queue
2745 for(auto& accessor : accessors) {
2746 BOOST_CHECK(accessor.readNonBlocking() == false);
2747 }
2748
2749 // close device again
2750 d.close();
2751 }
2752
2753 /********************************************************************************************************************/
2754
2759 template<typename VECTOR_OF_REGISTERS_T>
2761 if(_testOnlyTransferElement) return;
2762 std::cout << "--- test_B_9_3_1 - setException() disables asynchronous read transfers" << std::endl;
2763 Device d(cdd);
2764 d.open();
2766
2767 // obtain accessors and read initial value
2768 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2769 if(!this->isAsyncRead(x)) return;
2770 typedef typename decltype(x)::minimumUserType UserType;
2771 auto registerName = x.path();
2772 std::cout << "... registerName = " << registerName << std::endl;
2773
2774 // obtain accessor for the test
2775 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2776
2777 // read initial value
2778 reg.read();
2779
2780 // enter exception state
2781 d.setException("Some message");
2782
2783 // get the exception off the queue. From this point on no other data must be received.
2784 BOOST_CHECK_THROW(reg.read(), runtime_error); // (no test intended, just catch)
2785
2786 // send value, must not be received
2787 x.setRemoteValue();
2788
2789 // give potential race conditions a chance...
2790 usleep(100000);
2791
2792 // no value expected
2793 BOOST_CHECK(reg.readNonBlocking() == false);
2794
2795 // recover device
2796 this->recoverDevice(d);
2797 d.activateAsyncRead(); // re-activate async read after recovery
2798 });
2799
2800 // close device again
2801 d.close();
2802 }
2803
2804 /********************************************************************************************************************/
2805
2810 template<typename VECTOR_OF_REGISTERS_T>
2812 if(_testOnlyTransferElement) return;
2813 std::cout << "--- test_B_9_3_2 - exactly one runtime_error in the _readQueue per async read accessor" << std::endl;
2814 Device d(cdd);
2815 d.open();
2817
2818 // obtain accessors and read initial value
2819 std::list<TransferElementAbstractor> accessors;
2820 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2821 if(!this->isAsyncRead(x)) return;
2822 typedef typename decltype(x)::minimumUserType UserType;
2823 auto registerName = x.path();
2824 std::cout << "... registerName = " << registerName << std::endl;
2825
2826 // obtain accessor for the test
2827 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2828 accessors.push_back(reg);
2829
2830 // read initial value
2831 reg.read();
2832 });
2833
2834 // enter exception state
2835 d.setException("Some message");
2836
2837 usleep(10000); // give potential race conditions a chance...
2838
2839 // each accessor must have exactly one exception in the queue
2840 for(auto& accessor : accessors) {
2841 // call the read stages explicitly to make sure the exception is thrown in the right place
2842 accessor.getHighLevelImplElement()->preRead(TransferType::read);
2843 BOOST_CHECK_THROW(accessor.getHighLevelImplElement()->readTransfer(), runtime_error);
2844 accessor.getHighLevelImplElement()->postRead(TransferType::read, false);
2845 // no more exceptions in the queue are allowed
2846 BOOST_CHECK(accessor.readNonBlocking() == false);
2847 }
2848
2849 // close device again
2850 d.close();
2851 }
2852
2853 /********************************************************************************************************************/
2854
2859 template<typename VECTOR_OF_REGISTERS_T>
2861 if(_testOnlyTransferElement) return;
2862 std::cout
2863 << "--- test_B_9_4_1 - doReadTransferSynchronously throws runtime_error after setException() until recovery"
2864 << std::endl;
2865 Device d(cdd);
2866 d.open();
2867
2868 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2869 if(!this->isRead(x)) return;
2870 typedef typename decltype(x)::minimumUserType UserType;
2871 auto registerName = x.path();
2872 std::cout << "... registerName = " << registerName << std::endl;
2873 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2874
2875 // put backend into exception state
2876 d.setException("Some message");
2877
2878 // Check for runtime_error where it is now expected
2879 BOOST_CHECK_THROW(reg.read(), runtime_error);
2880
2881 // recover
2882 this->recoverDevice(d);
2883
2884 // make a successful read to make sure the exception state is gone
2885 BOOST_CHECK_NO_THROW(reg.read());
2886 });
2887
2888 d.close();
2889 }
2890
2891 /********************************************************************************************************************/
2892
2897 template<typename VECTOR_OF_REGISTERS_T>
2899 if(_testOnlyTransferElement) return;
2900 std::cout << "--- test_B_9_5 - write operations throw after setException()" << std::endl;
2901 Device d(cdd);
2902 d.open();
2903
2904 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2905 if(!this->isWrite(x)) return;
2906 typedef typename decltype(x)::minimumUserType UserType;
2907 auto registerName = x.path();
2908 std::cout << "... registerName = " << registerName << std::endl;
2909 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2910
2911 // put backend into exception state
2912 d.setException("Some message");
2913
2914 // Check for runtime_error where it is now expected
2915 BOOST_CHECK_THROW(reg.write(), runtime_error);
2916
2917 // recover
2918 this->recoverDevice(d);
2919
2920 // make a successful read to make sure the exception state is gone
2921 BOOST_CHECK_NO_THROW(reg.write());
2922 });
2923
2924 d.close();
2925 }
2926
2927 /********************************************************************************************************************/
2928
2933 template<typename VECTOR_OF_REGISTERS_T>
2935 std::cout << "--- test_B_11_2_1 - version number bigger for newer values" << std::endl;
2936 Device d(cdd);
2937
2938 // open the device
2939 d.open();
2940
2941 // synchronous read
2942 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2943 if(!this->isRead(x)) return;
2944 if(x.capabilities.setRemoteValueIncrementsVersion == TestCapability::disabled) return;
2945 typedef typename decltype(x)::minimumUserType UserType;
2946 auto registerName = x.path();
2947 VersionNumber someVersion{nullptr};
2948
2949 std::cout << "... registerName = " << registerName << std::endl;
2950 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
2951
2952 for(size_t i = 0; i < 2; ++i) {
2953 // Set remote value to be read.
2954 x.setRemoteValue();
2955
2956 // Read value
2957 reg.read();
2958
2959 // Check application buffer
2960 BOOST_CHECK(reg.getVersionNumber() > someVersion);
2961 someVersion = reg.getVersionNumber();
2962 }
2963 });
2964
2965 // asynchronous read
2967 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
2968 if(!this->isAsyncRead(x)) return;
2969 typedef typename decltype(x)::minimumUserType UserType;
2970 auto registerName = x.path();
2971
2972 std::cout << "... registerName = " << registerName << " (async)" << std::endl;
2973 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
2974
2975 reg.read(); // initial value
2976 VersionNumber someVersion = reg.getVersionNumber();
2977
2978 // Backends do not guarantee that the same data and version number are not send twice. Especially for the polled
2979 // initial value there is the intrinsic race condition that the subscription and the according pull are happening
2980 // concurrently and the data and version number are seen twice.
2981 // For the polling backends there is no guarantee that the poll and the data are really matching, so it
2982 // might be that the same data is seen twice for different version numbers.
2983 //
2984 // What is guaranteed if we read after each setRemoteValue(): Once we see the new data, it has to have a new
2985 // version number.
2986
2987 for(size_t i = 0; i < 2; ++i) {
2988 x.setRemoteValue();
2989 auto val = x.template getRemoteValue<UserType>();
2990 CHECK_EQUALITY_TIMEOUT(reg, val, 30000);
2991 BOOST_TEST(reg.getVersionNumber() > someVersion);
2992 someVersion = reg.getVersionNumber();
2993 }
2994 });
2995
2996 // close device
2997 d.close();
2998 }
2999
3000 /********************************************************************************************************************/
3001
3009 template<typename VECTOR_OF_REGISTERS_T>
3011 std::cout << "--- test_NOSPEC_newVersionsAfterOpen - version numbers after open() are newer" << std::endl;
3012 Device d(cdd);
3013
3014 // Application can create version numbers any time.
3015 VersionNumber someVersion{};
3016
3017 // Open the device. All versions from the backend must be newer than someVersion from now on.
3018 d.open();
3019
3020 // synchronous read
3021 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3022 if(!this->isRead(x)) return;
3023 typedef typename decltype(x)::minimumUserType UserType;
3024 auto registerName = x.path();
3025
3026 std::cout << "... registerName = " << registerName << std::endl;
3027 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3028
3029 // Set remote value to be read.
3030 x.setRemoteValue();
3031
3032 // Read value
3033 reg.read();
3034
3035 // Check application buffer
3036 BOOST_CHECK(reg.getVersionNumber() > someVersion);
3037 });
3038
3039 // asynchronous read 1: activate before creating accessor
3041 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3042 if(!this->isAsyncRead(x)) return;
3043 typedef typename decltype(x)::minimumUserType UserType;
3044 auto registerName = x.path();
3045
3046 std::cout << "... registerName = " << registerName << " (async1)" << std::endl;
3047
3048 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
3049
3050 reg.read();
3051
3052 // Check application buffer
3053 BOOST_CHECK(reg.getVersionNumber() > someVersion);
3054 });
3055
3056 // close device
3057 d.close();
3058
3059 // asynchronous read 2: activate after creating accessor
3060 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3061 if(!this->isAsyncRead(x)) return;
3062 typedef typename decltype(x)::minimumUserType UserType;
3063 auto registerName = x.path();
3064
3065 someVersion = {};
3066 d.open();
3067
3068 std::cout << "... registerName = " << registerName << " (async2)" << std::endl;
3069
3070 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
3071
3073
3074 reg.read();
3075
3076 // Check application buffer
3077 BOOST_CHECK(reg.getVersionNumber() > someVersion);
3078
3079 d.close();
3080 });
3081 }
3082
3083 /********************************************************************************************************************/
3084
3093 template<typename VECTOR_OF_REGISTERS_T>
3095 if(_testOnlyTransferElement) return;
3096 std::cout << "--- test_B_11_2_2 - consistent data gets same VersionNumber" << std::endl;
3097 Device d(cdd);
3098
3099 // open the device and activate async read
3100 d.open();
3102
3103 // CASE 1: consistency with the same register in async read
3104 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3105 if(!this->isAsyncRead(x)) return;
3106 typedef typename decltype(x)::minimumUserType UserType;
3107 auto registerName = x.path();
3108 std::cout << "... registerName = " << registerName << std::endl;
3109
3110 // Set remote value to be read.
3111 x.setRemoteValue();
3112
3113 // Obtain accessor
3114 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
3115
3116 // Read the initial value
3117 reg.read();
3118
3119 // Read through second accessor
3120 auto reg2 = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
3121 reg2.read();
3122
3123 // Version must be identical to the version of the first accessor
3124 // Initial values are not necessarily consistent, so this check is skipped.
3125 // BOOST_CHECK_EQUAL(reg2.getVersionNumber(), reg.getVersionNumber());
3126
3127 // Change value, must be seen by both accessors, again same version expected
3128 x.setRemoteValue();
3129
3130 // We have no guarantee that there is only one value in the queue. The setRemoteValue also send the data, and
3131 // depending on the subscription status of reg and reg2 they might see this value in addition to initial value,
3132 // with unknown timing (extra threads and maybe processes involved). We wait until we get the data that was last
3133 // written on both accessors, and then compare the version number.
3134 auto val = x.template getRemoteValue<UserType>();
3135
3136 CHECK_EQUALITY_TIMEOUT(reg, val, 30000);
3137 CHECK_EQUALITY_TIMEOUT(reg2, val, 30000);
3138 BOOST_CHECK_EQUAL(reg.getVersionNumber(), reg2.getVersionNumber());
3139 });
3140
3141 // close device again
3142 d.close();
3143 }
3144
3145 /********************************************************************************************************************/
3146
3151 template<typename VECTOR_OF_REGISTERS_T>
3153 std::cout << "--- B.11.6 - value after construction for the version number in the application buffer" << std::endl;
3154 Device d(cdd);
3155
3156 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3157 typedef typename decltype(x)::minimumUserType UserType;
3158 auto registerName = x.path();
3159 std::cout << "... registerName = " << registerName << std::endl;
3160 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3161
3162 // check "value after construction" for VersionNumber
3163 BOOST_CHECK(reg.getVersionNumber() == VersionNumber(nullptr));
3164 });
3165 }
3166
3167 /********************************************************************************************************************/
3168
3175 template<typename VECTOR_OF_REGISTERS_T>
3177 std::cout << "--- B.12.1.3.1 - call replaceTransferElement() with internal element does nothing" << std::endl;
3178 Device d(cdd);
3179
3180 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3181 using UserType = typename decltype(x)::minimumUserType;
3182 auto registerName = x.path();
3183 std::cout << "... registerName = " << registerName << std::endl;
3184 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3185
3186 auto internalElements = reg.getInternalElements();
3187 for(auto& elem : internalElements) {
3188 reg.replaceTransferElement(elem);
3189 }
3190
3191 // check: List of internal elements is unchanged
3192 BOOST_TEST(reg.getInternalElements() == internalElements);
3193 });
3194 }
3195
3196 /********************************************************************************************************************/
3197
3202 template<typename VECTOR_OF_REGISTERS_T>
3204 std::cout << "--- B.12.1.5.1 - mayReplaceOther() of itself returns \"false\"" << std::endl;
3205 Device d(cdd);
3206
3207 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3208 using UserType = typename decltype(x)::minimumUserType;
3209 auto registerName = x.path();
3210 std::cout << "... registerName = " << registerName << std::endl;
3211 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3212 auto transferElement = reg.getHighLevelImplElement();
3213 BOOST_CHECK(transferElement->mayReplaceOther(transferElement) == false);
3214 });
3215 }
3216
3217 /********************************************************************************************************************/
3218
3223 template<typename VECTOR_OF_REGISTERS_T>
3225 if(_testOnlyTransferElement) return;
3226 std::cout << "--- test_C_5_2_1_2 - logic_error for non-existing register" << std::endl;
3227
3228 // Constructor must throw when device is closed
3229 {
3230 Device d(cdd);
3231 BOOST_CHECK_THROW(
3232 auto reg = d.getTwoDRegisterAccessor<int>("This_register_name_does_not_exist_for_sure/whileClosed"),
3233 logic_error);
3234 }
3235
3236 // Constructor must throw when device is open
3237 {
3238 Device d(cdd);
3239 d.open();
3240 BOOST_CHECK_THROW(
3241 auto reg = d.getTwoDRegisterAccessor<int>("This_register_name_does_not_exist_for_sure/whileOpened"),
3242 logic_error);
3243 d.close();
3244 }
3245 }
3246
3247 /********************************************************************************************************************/
3248
3253 template<typename VECTOR_OF_REGISTERS_T>
3255 if(_testOnlyTransferElement) return;
3256 std::cout << "--- test_C_5_2_2_2 - logic_error for exceeding register size" << std::endl;
3257
3258 // Collect register sizes
3259 std::map<std::string, size_t> sizeMap;
3260 {
3261 Device d(cdd);
3262 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3263 typedef typename decltype(x)::minimumUserType UserType;
3264 auto registerName = x.path();
3265 std::cout << "... registerName = " << registerName << std::endl;
3266 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3267 sizeMap[registerName] = reg.getNElementsPerChannel();
3268 });
3269 }
3270
3271 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3272 typedef typename decltype(x)::minimumUserType UserType;
3273 auto registerName = x.path();
3274 std::cout << "... registerName = " << registerName << std::endl;
3275 // number of elements too big
3276 {
3277 Device d(cdd);
3278 BOOST_CHECK_THROW(
3279 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, sizeMap[registerName] + 1, 0), logic_error);
3280 }
3281 // one element, but behind the end
3282 {
3283 Device d(cdd);
3284 BOOST_CHECK_THROW(
3285 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 1, sizeMap[registerName]), logic_error);
3286 }
3287 // full length but offset by 1 element (so 1 element too long)
3288 {
3289 Device d(cdd);
3290 BOOST_CHECK_THROW(
3291 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, sizeMap[registerName], 1), logic_error);
3292 }
3293 // full length by default(=0) but offset by 1 element (so 1 element too long)
3294 {
3295 Device d(cdd);
3296 BOOST_CHECK_THROW(auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 1), logic_error);
3297 }
3298 // does not throw when full length and no offset specified
3299 {
3300 Device d(cdd);
3301 BOOST_CHECK_NO_THROW(auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, sizeMap[registerName], 0));
3302 }
3303 // does not throw when one element shorter and offset of 1 specified (only if register is long enough)
3304 if((sizeMap[registerName] > 1) && (x.capabilities.testPartialAccessor == TestCapability::enabled)) {
3305 Device d(cdd);
3306 BOOST_CHECK_NO_THROW(
3307 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, sizeMap[registerName] - 1, 1));
3308 }
3309 });
3310 }
3311
3312 /********************************************************************************************************************/
3313
3318 template<typename VECTOR_OF_REGISTERS_T>
3320 std::cout << "--- test_C_5_2_3_2 - logic_error for wrong access mode flags" << std::endl;
3321
3322 Device d(cdd);
3323 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3324 if(x.supportedFlags().has(ChimeraTK::AccessMode::wait_for_new_data)) return;
3325 typedef typename decltype(x)::minimumUserType UserType;
3326 auto registerName = x.path();
3327 std::cout << "... registerName = " << registerName << " (wait_for_new_data throws)" << std::endl;
3328 BOOST_CHECK_THROW(
3329 d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data}), logic_error);
3330 });
3331 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3332 if(x.supportedFlags().has(ChimeraTK::AccessMode::raw)) return;
3333 typedef typename decltype(x)::minimumUserType UserType;
3334 auto registerName = x.path();
3335 std::cout << "... registerName = " << registerName << " (raw throws)" << std::endl;
3336 BOOST_CHECK_THROW(d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::raw}), logic_error);
3337 });
3338 }
3339
3340 /********************************************************************************************************************/
3341
3346 template<typename VECTOR_OF_REGISTERS_T>
3348 if(_testOnlyTransferElement) return;
3349 std::cout << "--- test_C_5_2_5_2 - logic_error on operation while backend closed" << std::endl;
3350 Device d(cdd);
3351
3352 std::cout << "... synchronous read" << std::endl;
3353 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3354 if(!this->isRead(x)) return;
3355 typedef typename decltype(x)::minimumUserType UserType;
3356 auto registerName = x.path();
3357 std::cout << " registerName = " << registerName << std::endl;
3358 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3359 BOOST_CHECK_THROW(reg.read(), logic_error);
3360 });
3361
3362 std::cout << "... asynchronous read" << std::endl;
3363 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3364 if(!this->isAsyncRead(x)) return;
3365 typedef typename decltype(x)::minimumUserType UserType;
3366 auto registerName = x.path();
3367 std::cout << " registerName = " << registerName << std::endl;
3368 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
3369 BOOST_CHECK_THROW(reg.read(), logic_error);
3370 BOOST_CHECK_THROW(reg.readNonBlocking(), logic_error);
3371 });
3372
3373 std::cout << "... write" << std::endl;
3374 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3375 if(!this->isWrite(x)) return;
3376 typedef typename decltype(x)::minimumUserType UserType;
3377 auto registerName = x.path();
3378 std::cout << " registerName = " << registerName << std::endl;
3379 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3380 BOOST_CHECK_THROW(reg.write(), logic_error);
3381 });
3382 }
3383
3384 /********************************************************************************************************************/
3385
3390 template<typename VECTOR_OF_REGISTERS_T>
3392 std::cout << "--- test_C_5_2_6_2 - logic_error on read operation on write-only register" << std::endl;
3393 Device d(cdd);
3394
3395 std::cout << "... synchronous read" << std::endl;
3396 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3397 if(!this->isWriteOnly(x)) return;
3398 typedef typename decltype(x)::minimumUserType UserType;
3399 auto registerName = x.path();
3400 std::cout << " registerName = " << registerName << std::endl;
3401 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3402 BOOST_CHECK_THROW(reg.read(), logic_error);
3403 });
3404
3405 std::cout << "... asynchronous read" << std::endl;
3406 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3407 if(!this->isWriteOnly(x)) return;
3408 typedef typename decltype(x)::minimumUserType UserType;
3409 auto registerName = x.path();
3410 std::cout << " registerName = " << registerName << std::endl;
3411 BOOST_CHECK_THROW(
3412 d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data}), logic_error);
3413 });
3414 }
3415
3416 /********************************************************************************************************************/
3417
3422 template<typename VECTOR_OF_REGISTERS_T>
3424 std::cout << "--- test_C_5_2_7_2 - logic_error on write operation on read-only register" << std::endl;
3425 Device d(cdd);
3426
3427 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3428 if(!this->isReadOnly(x)) return;
3429 typedef typename decltype(x)::minimumUserType UserType;
3430 auto registerName = x.path();
3431 std::cout << " registerName = " << registerName << std::endl;
3432 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3433 BOOST_CHECK_THROW(reg.write(), logic_error);
3434 });
3435 }
3436
3437 /********************************************************************************************************************/
3438
3444 template<typename VECTOR_OF_REGISTERS_T>
3446 std::cout << "--- test_C_5_3 - read-only/write-only information changes after runtime_error" << std::endl;
3447 Device d(cdd);
3448 d.open();
3449
3450 // switch to read-only
3451 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3452 if(!this->isRead(x) || !this->isWrite(x) || x.capabilities.switchReadOnly != TestCapability::enabled) return;
3453 typedef typename decltype(x)::minimumUserType UserType;
3454 auto registerName = x.path();
3455 std::cout << " registerName = " << registerName << std::endl;
3456 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3457 this->switchReadOnly(x, true);
3458 BOOST_CHECK(reg.isWriteable() == true); // C.5.3
3459 BOOST_CHECK_THROW(reg.write(), runtime_error); // C.5.3.1
3460 BOOST_CHECK(reg.isWriteable() == false); // C.5.3
3461 this->switchReadOnly(x, false);
3462 });
3463
3464 // switch to write-only (note: this test is untested, no backend supports this!)
3465 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3466 if(!this->isRead(x) || !this->isWrite(x) || x.capabilities.switchWriteOnly != TestCapability::enabled) return;
3467 typedef typename decltype(x)::minimumUserType UserType;
3468 auto registerName = x.path();
3469 std::cout << " registerName = " << registerName << std::endl;
3470 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3471 this->switchWriteOnly(x, true);
3472 BOOST_CHECK(reg.isReadable() == true); // C.5.3
3473 BOOST_CHECK_THROW(reg.read(), runtime_error); // C.5.3.1
3474 BOOST_CHECK(reg.isReadable() == false); // C.5.3
3475 this->switchWriteOnly(x, false);
3476 });
3477 }
3478
3479 /********************************************************************************************************************/
3480
3485 template<typename VECTOR_OF_REGISTERS_T>
3487 std::cout << "--- test_C_5_3_2 - read-only/write-only information cached per accessor" << std::endl;
3488 Device d(cdd);
3489 d.open();
3490
3491 // switch to read-only
3492 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3493 if(!this->isRead(x) || !this->isWrite(x) || x.capabilities.switchReadOnly != TestCapability::enabled) return;
3494 typedef typename decltype(x)::minimumUserType UserType;
3495 auto registerName = x.path();
3496 std::cout << " registerName = " << registerName << std::endl;
3497 auto reg1 = d.getTwoDRegisterAccessor<UserType>(registerName);
3498 auto reg2 = d.getTwoDRegisterAccessor<UserType>(registerName);
3499 this->switchReadOnly(x, true);
3500 BOOST_CHECK_THROW(reg1.write(), runtime_error); // no check intended, just catch
3501 BOOST_CHECK(reg2.isWriteable() == true);
3502 this->switchReadOnly(x, false);
3503 });
3504
3505 // switch to write-only (note: this test is untested, no backend supports this!)
3506 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3507 if(!this->isRead(x) || !this->isWrite(x) || x.capabilities.switchWriteOnly != TestCapability::enabled) return;
3508 typedef typename decltype(x)::minimumUserType UserType;
3509 auto registerName = x.path();
3510 std::cout << " registerName = " << registerName << std::endl;
3511 auto reg1 = d.getTwoDRegisterAccessor<UserType>(registerName);
3512 auto reg2 = d.getTwoDRegisterAccessor<UserType>(registerName);
3513 this->switchWriteOnly(x, true);
3514 BOOST_CHECK_THROW(reg1.read(), runtime_error); // no check intended, just catch
3515 BOOST_CHECK(reg2.isReadable() == true);
3516 this->switchWriteOnly(x, false);
3517 });
3518 }
3519
3520 /********************************************************************************************************************/
3521
3526 template<typename VECTOR_OF_REGISTERS_T>
3528 std::cout << "--- test_C_5_3_3 - read-only/write-only information always returned from cache if available"
3529 << std::endl;
3530 Device d(cdd);
3531 d.open();
3532
3533 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3534 typedef typename decltype(x)::minimumUserType UserType;
3535 auto registerName = x.path();
3536 std::cout << " registerName = " << registerName << std::endl;
3537 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3538
3539 // Obtain information. This also makes sure the TE caches it.
3540 auto isReadable = reg.isReadable();
3541 auto isWriteable = reg.isWriteable();
3542
3543 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3544 std::cout << " -> runtime_error case: " << i << std::endl;
3545 // enable exceptions on read
3546 x.setForceRuntimeError(true, i);
3547
3548 // Now isReadable and isWriteable are not able to communicate with the device but still should give the same
3549 // result.
3550 BOOST_CHECK(reg.isReadable() == isReadable);
3551 BOOST_CHECK(reg.isWriteable() == isWriteable);
3552
3553 // disable exceptions on read
3554 x.setForceRuntimeError(false, i);
3555
3556 // recover shouldn't even be necessary, since no communication happened
3557 BOOST_CHECK(d.isFunctional() == true);
3558 }
3559 });
3560 }
3561
3562 /********************************************************************************************************************/
3563
3568 template<typename VECTOR_OF_REGISTERS_T>
3570 std::cout << "--- test_NOSPEC_valueAfterConstruction - content of the application data buffer after construction."
3571 << std::endl;
3572 Device d(cdd);
3573
3574 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3575 typedef typename decltype(x)::minimumUserType UserType;
3576 auto registerName = x.path();
3577 std::cout << "... registerName = " << registerName << std::endl;
3578 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3579
3580 // check "value after construction" for value buffer
3581 std::vector<UserType> v(reg.getNElementsPerChannel(), UserType());
3582 for(size_t i = 0; i < reg.getNChannels(); ++i) BOOST_CHECK(reg[i] == v);
3583 });
3584 }
3585
3586 /********************************************************************************************************************/
3587
3592 template<typename VECTOR_OF_REGISTERS_T>
3594 if(_testOnlyTransferElement) return;
3595 std::cout << "--- test_NOSPEC_backendNotClosedAfterException - backend not closed after exception" << std::endl;
3596 Device d(cdd);
3597
3598 // open the device, then let it throw runtime_error exceptions
3599 d.open();
3600
3601 std::cout << "... synchronous read" << std::endl;
3602 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3603 if(!this->isRead(x)) return;
3604 if(x.nRuntimeErrorCases() == 0) return;
3605 typedef typename decltype(x)::minimumUserType UserType;
3606 auto registerName = x.path();
3607 std::cout << " registerName = " << registerName << std::endl;
3608 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3609
3610 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3611 std::cout << " -> runtime_error case: " << i << std::endl;
3612 // enable exceptions on read
3613 x.setForceRuntimeError(true, i);
3614
3615 // trigger runtime error
3616 BOOST_CHECK_THROW(reg.read(), runtime_error); // no test intended, just catch
3617
3618 // check device is still open but in error state
3619 BOOST_CHECK(d.isOpened());
3620 BOOST_CHECK(!d.isFunctional());
3621
3622 // check a failed attempt to recover does not change this
3623 BOOST_CHECK_THROW(d.open(), runtime_error); // no test intended, just catch
3624 BOOST_CHECK(d.isOpened());
3625 BOOST_CHECK(!d.isFunctional());
3626
3627 // disable exceptions on read
3628 x.setForceRuntimeError(false, i);
3629
3630 // recover
3631 this->recoverDevice(d);
3632 }
3633 });
3634
3635 std::cout << "... asynchronous read" << std::endl;
3637 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3638 if(!this->isAsyncRead(x)) return;
3639 if(x.nRuntimeErrorCases() == 0) return;
3640 typedef typename decltype(x)::minimumUserType UserType;
3641 auto registerName = x.path();
3642 std::cout << " registerName = " << registerName << std::endl;
3643 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName, 0, 0, {AccessMode::wait_for_new_data});
3644
3645 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3646 std::cout << " -> runtime_error case: " << i << std::endl;
3647 reg.read(); // initial value
3648
3649 // enable exceptions on read
3650 x.setForceRuntimeError(true, i);
3651
3652 // trigger runtime error
3653 BOOST_CHECK_THROW(reg.read(), runtime_error); // no test intended, just catch
3654
3655 // check device is still open but in error state
3656 BOOST_CHECK(d.isOpened());
3657 BOOST_CHECK(!d.isFunctional());
3658
3659 // check a failed attempt to recover does not change this
3660 BOOST_CHECK_THROW(d.open(), runtime_error); // no test intended, just catch
3661 BOOST_CHECK(d.isOpened());
3662 BOOST_CHECK(!d.isFunctional());
3663
3664 // disable exceptions on read
3665 x.setForceRuntimeError(false, i);
3666
3667 // recover
3668 this->recoverDevice(d);
3669 d.activateAsyncRead(); // turn async read back on
3670 }
3671 });
3672
3673 std::cout << "... write" << std::endl;
3674 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3675 if(!this->isWrite(x)) return;
3676 if(x.nRuntimeErrorCases() == 0) return;
3677 typedef typename decltype(x)::minimumUserType UserType;
3678 auto registerName = x.path();
3679 std::cout << " registerName = " << registerName << std::endl;
3680 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3681
3682 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3683 std::cout << " -> runtime_error case: " << i << std::endl;
3684 // enable exceptions on write
3685 x.setForceRuntimeError(true, i);
3686
3687 // trigger runtime error
3688 BOOST_CHECK_THROW(reg.write(), runtime_error); // no test intended, just catch
3689
3690 // check device is still open but in error state
3691 BOOST_CHECK(d.isOpened());
3692 BOOST_CHECK(!d.isFunctional());
3693
3694 // check a failed attempt to recover does not change this
3695 BOOST_CHECK_THROW(d.open(), runtime_error); // no test intended, just catch
3696 BOOST_CHECK(d.isOpened());
3697 BOOST_CHECK(!d.isFunctional());
3698
3699 // disable exceptions on write
3700 x.setForceRuntimeError(false, i);
3701
3702 // recover
3703 this->recoverDevice(d);
3704 }
3705 });
3706
3707 std::cout << "... isReadable" << std::endl;
3708 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3709 if(x.nRuntimeErrorCases() == 0) return;
3710 typedef typename decltype(x)::minimumUserType UserType;
3711 auto registerName = x.path();
3712 std::cout << " registerName = " << registerName;
3713 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3714
3715 bool didThrow = false;
3716 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3717 std::cout << " -> runtime_error case: " << i << std::endl;
3718 // enable exceptions on write
3719 x.setForceRuntimeError(true, i);
3720
3721 // attempt to trigger runtime error (no obligation to throw...)
3722 try {
3723 [[maybe_unused]] auto result = reg.isReadable();
3724 }
3725 catch(...) {
3726 didThrow = true;
3727 // check device is still open but in error state
3728 BOOST_CHECK(d.isOpened());
3729 BOOST_CHECK(!d.isFunctional());
3730
3731 // check a failed attempt to recover does not change this
3732 BOOST_CHECK_THROW(d.open(), runtime_error); // no test intended, just catch
3733 BOOST_CHECK(d.isOpened());
3734 BOOST_CHECK(!d.isFunctional());
3735 }
3736
3737 // disable exceptions on write
3738 x.setForceRuntimeError(false, i);
3739
3740 // recover
3741 this->recoverDevice(d);
3742 }
3743
3744 if(!didThrow) {
3745 std::cout << " (doesn't throw)" << std::endl;
3746 }
3747 else {
3748 std::cout << " (throws)" << std::endl;
3749 }
3750 });
3751
3752 std::cout << "... isWriteable" << std::endl;
3753 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3754 if(x.nRuntimeErrorCases() == 0) return;
3755 typedef typename decltype(x)::minimumUserType UserType;
3756 auto registerName = x.path();
3757 std::cout << " registerName = " << registerName;
3758 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3759
3760 bool didThrow = false;
3761 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3762 std::cout << " -> runtime_error case: " << i << std::endl;
3763 // enable exceptions on write
3764 x.setForceRuntimeError(true, i);
3765
3766 // attempt to trigger runtime error (no obligation to throw...)
3767 try {
3768 [[maybe_unused]] auto result = reg.isWriteable();
3769 }
3770 catch(...) {
3771 didThrow = true;
3772 // check device is still open but in error state
3773 BOOST_CHECK(d.isOpened());
3774 BOOST_CHECK(!d.isFunctional());
3775
3776 // check a failed attempt to recover does not change this
3777 BOOST_CHECK_THROW(d.open(), runtime_error); // no test intended, just catch
3778 BOOST_CHECK(d.isOpened());
3779 BOOST_CHECK(!d.isFunctional());
3780 }
3781
3782 // disable exceptions on write
3783 x.setForceRuntimeError(false, i);
3784
3785 // recover
3786 this->recoverDevice(d);
3787 }
3788
3789 if(!didThrow) {
3790 std::cout << " (doesn't throw)" << std::endl;
3791 }
3792 else {
3793 std::cout << " (throws)" << std::endl;
3794 }
3795 });
3796
3797 std::cout << "... isReadOnly" << std::endl;
3798 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3799 typedef typename decltype(x)::minimumUserType UserType;
3800 auto registerName = x.path();
3801 std::cout << " registerName = " << registerName;
3802 auto reg = d.getTwoDRegisterAccessor<UserType>(registerName);
3803
3804 bool didThrow = false;
3805 for(size_t i = 0; i < x.nRuntimeErrorCases(); ++i) {
3806 std::cout << " -> runtime_error case: " << i << std::endl;
3807 // enable exceptions on write
3808 x.setForceRuntimeError(true, i);
3809
3810 // attempt to trigger runtime error (no obligation to throw...)
3811 try {
3812 [[maybe_unused]] auto result = reg.isReadOnly();
3813 }
3814 catch(...) {
3815 didThrow = true;
3816 // check device is still open but in error state
3817 BOOST_CHECK(d.isOpened());
3818 BOOST_CHECK(!d.isFunctional());
3819
3820 // check a failed attempt to recover does not change this
3821 BOOST_CHECK_THROW(d.open(), runtime_error); // no test intended, just catch
3822 BOOST_CHECK(d.isOpened());
3823 BOOST_CHECK(!d.isFunctional());
3824 }
3825
3826 // disable exceptions on write
3827 x.setForceRuntimeError(false, i);
3828
3829 // recover
3830 this->recoverDevice(d);
3831 }
3832
3833 if(!didThrow) {
3834 std::cout << " (doesn't throw)" << std::endl;
3835 }
3836 else {
3837 std::cout << " (throws)" << std::endl;
3838 }
3839 });
3840 // close device again
3841 d.close();
3842 }
3843
3844 /********************************************************************************************************************/
3845
3850 template<typename VECTOR_OF_REGISTERS_T>
3852 std::cout << "--- test_NOSPEC_rawTransfer - test creation and reading/writing with access mode raw." << std::endl;
3853 Device d(cdd);
3854 d.open();
3855
3856 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3857 // the test itself requires an extended interface, so the test can be disabled
3858 if constexpr(x.capabilities.testRawTransfer == TestCapability::enabled) {
3859 auto registerName = x.path();
3860 std::cout << "... registerName = " << registerName << std::endl;
3861
3862 BOOST_REQUIRE_MESSAGE(this->isRaw(x),
3863 "Test configuration error: testRawTransfer is enabled for register without AccessMode::raw!");
3864
3865 typedef typename decltype(x)::minimumUserType UserType;
3866 typedef typename decltype(x)::rawUserType RawType;
3867 // Use double as example for a not working user type
3868 BOOST_CHECK_THROW(
3869 d.getTwoDRegisterAccessor<double>(registerName, 0, 0, {AccessMode::raw}), ChimeraTK::logic_error);
3870 try {
3871 // test creation
3872 auto reg = d.getTwoDRegisterAccessor<RawType>(registerName, 0, 0, {AccessMode::raw});
3873 // the test itself requires an extended interface, so the test can be disabled
3874 if(x.isReadable()) {
3875 x.setRemoteValue();
3876 reg.read();
3877 auto expectedRawValue = x.template getRemoteValue<RawType>(/* getRaw = */ true);
3878 CHECK_EQUALITY(reg, expectedRawValue);
3879
3880 auto expectedCookedValue = x.template getRemoteValue<UserType>();
3881 // fill into a vector<vector> and use CHECK_EQUALITY_VECTOR. This stops at the first mismatch, prints a good
3882 // error message, checks for all elements 0 etc.
3883 std::vector<std::vector<UserType>> readCookedValue;
3884 for(size_t channel = 0; channel < reg.getNChannels(); ++channel) {
3885 std::vector<UserType> readCookedChannel;
3886 for(size_t element = 0; element < reg.getNElementsPerChannel(); ++element) {
3887 readCookedChannel.push_back(reg.template getAsCooked<UserType>(channel, element));
3888 }
3889 readCookedValue.push_back(readCookedChannel);
3890 }
3891 CHECK_EQUALITY_VECTOR(readCookedValue, expectedCookedValue);
3892 }
3893 if(x.isWriteable()) {
3894 auto newValue = x.template generateValue<RawType>(/* getRaw = */ true);
3895 reg = newValue;
3896 reg.write();
3897 auto readbackValue = x.template getRemoteValue<RawType>(/* getRaw = */ true);
3898 CHECK_EQUALITY_VECTOR(readbackValue, newValue);
3899
3900 // test setting as cooked
3901 auto newCookedValue = x.template generateValue<UserType>();
3902 for(size_t channel = 0; channel < reg.getNChannels(); ++channel) {
3903 for(size_t element = 0; element < reg.getNElementsPerChannel(); ++element) {
3904 reg.template setAsCooked<UserType>(channel, element, newCookedValue[channel][element]);
3905 }
3906 }
3907 reg.write();
3908
3909 auto readbackCookedValue = x.template getRemoteValue<UserType>();
3910 CHECK_EQUALITY_VECTOR(readbackCookedValue, newCookedValue);
3911 }
3912 }
3913 catch(std::exception& e) {
3914 BOOST_CHECK_MESSAGE(false, std::string("Unexpected expeption: ") + e.what());
3915 }
3916 } // end of constexpr if
3917 if(this->isRaw(x)) {
3918 if(x.capabilities.testRawTransfer == TestCapability::disabled) {
3919 BOOST_REQUIRE_MESSAGE(false,
3920 "Test configuration error: testRawTransfer is disabled for register '" + std::string(x.path()) +
3921 "' with AccessMode::raw!");
3922 }
3923 else if(x.capabilities.testRawTransfer == TestCapability::unspecified) {
3924 std::cout << "WARNING: testRawTransfer capability unspecified for register '" + std::string(x.path()) +
3925 "' with AccessMode::raw. This will turn into a test configuration error in a future release!"
3926 << std::endl;
3927 }
3928 }
3929 else {
3930 if(x.capabilities.testRawTransfer == TestCapability::unspecified) {
3931 std::cout << "Warning: testRawTransfer capability unspecified for register '" + std::string(x.path()) +
3932 "' without AccessMode::raw. Please explicitly disable this test."
3933 << std::endl;
3934 }
3935 }
3936 });
3937 }
3938
3939 /********************************************************************************************************************/
3940
3945 template<typename VECTOR_OF_REGISTERS_T>
3947 std::cout << "--- test_NOSPEC_catalogueRaw - test catalogue entries for access mode raw." << std::endl;
3948 Device d(cdd);
3949
3950 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3951 if(x.capabilities.testCatalogue == TestCapability::disabled) {
3952 return;
3953 }
3954
3955 auto registerName = x.path();
3956 std::cout << "... registerName = " << registerName << std::endl;
3957
3958 // workaround for DUMMY_WRITABLE not having information in the catalogue yet
3959 if(std::string(registerName).find("DUMMY_WRITEABLE") != std::string::npos) {
3960 return;
3961 }
3962
3963 if(std::string(registerName).find("DUMMY_INTERRUPT_") != std::string::npos) {
3964 return;
3965 }
3966
3967 auto registerInfo = d.getRegisterCatalogue().getRegister(registerName);
3968
3969 if(this->isRaw(x)) {
3970 BOOST_CHECK(registerInfo.getSupportedAccessModes().has(AccessMode::raw));
3971 BOOST_TEST(registerInfo.getDataDescriptor().rawDataType() != DataType::none);
3972 }
3973 else {
3974 BOOST_CHECK(not registerInfo.getSupportedAccessModes().has(AccessMode::raw));
3975 BOOST_CHECK((registerInfo.getDataDescriptor().rawDataType() == DataType::none) ||
3976 (registerInfo.getDataDescriptor().rawDataType() == DataType::Void));
3977 }
3978 });
3979 }
3980
3985 template<typename VECTOR_OF_REGISTERS_T>
3987 std::cout << "--- test_NOSPEC_catalogueReadWrite- test catalogue and accessor entries for read/write." << std::endl;
3988 Device d(cdd);
3989
3990 boost::mpl::for_each<VECTOR_OF_REGISTERS_T>([&](auto x) {
3991 if(x.capabilities.testCatalogue == TestCapability::disabled) {
3992 return;
3993 }
3994
3995 typedef typename decltype(x)::minimumUserType UserType;
3996
3997 auto registerName = x.path();
3998 std::cout << "... registerName = " << registerName << std::endl;
3999
4000 auto registerInfo = d.getRegisterCatalogue().getRegister(registerName);
4001 auto accessor = d.getTwoDRegisterAccessor<UserType>(registerName);
4002
4003 BOOST_CHECK_EQUAL(this->isRead(x), registerInfo.isReadable());
4004 BOOST_CHECK_EQUAL(this->isRead(x), accessor.isReadable());
4005 BOOST_CHECK_EQUAL(this->isWrite(x), registerInfo.isWriteable());
4006 BOOST_CHECK_EQUAL(this->isWrite(x), accessor.isWriteable());
4007 });
4008 }
4009
4010 /********************************************************************************************************************/
4019 /********************************************************************************************************************/
4020
4027 /********************************************************************************************************************/
4028
4037 /********************************************************************************************************************/
4038
4039} // namespace ChimeraTK
#define CHECK_APPLICATION_BUFFER(UserType, accessor)
#define CHECK_EQUALITY_TIMEOUT(accessor, expectedValue, maxMilliseconds)
#define CHECK_EQUALITY_VECTOR(foundValue, expectedValue)
#define CHECK_TIMEOUT(condition, maxMilliseconds)
#define CHECK_EQUALITY_VECTOR_TIMEOUT(foundValue, expectedValue, maxMilliseconds)
#define CHECK_EQUALITY(accessor, expectedValue)
#define ALTER_AND_STORE_APPLICATION_BUFFER(UserType, accessor)
#define CHECK_APPLICATION_DATA_BUFFER(UserType, accessor)
#define STORE_APPLICATION_DATA_BUFFER(UserType, accessor)
Helper macros for test_B_4_2_4 and test_B_4_2_5.
DeviceBackendImpl implements some basic functionality which should be available for all backends.
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
std::string getActiveExceptionMessage() noexcept
Class allows to read/write registers from device.
Definition Device.h:39
TwoDRegisterAccessor< UserType > getTwoDRegisterAccessor(const RegisterPath &registerPathName, size_t numberOfElements=0, size_t elementsOffset=0, const AccessModeFlags &flags=AccessModeFlags({})) const
Get a TwoDRegisterAccessor object for the given register.
Definition Device.h:286
bool isOpened() const
Check if the device is currently opened.
Definition Device.cc:73
void close()
Close the device.
Definition Device.cc:66
void setException(const std::string &message)
Set the device into an exception state.
Definition Device.cc:97
boost::shared_ptr< DeviceBackend > getBackend()
Obtain the backend.
Definition Device.cc:111
RegisterCatalogue getRegisterCatalogue() const
Return the register catalogue with detailed information on all registers.
Definition Device.cc:22
bool isFunctional() const
Return wether a device is working as intended, usually this means it is opened and does not have any ...
Definition Device.cc:82
void activateAsyncRead() noexcept
Activate asyncronous read for all transfer elements where AccessMode::wait_for_new_data is set.
Definition Device.cc:91
void open(std::string const &aliasName)
Open a device by the given alias name from the DMAP file.
Definition Device.cc:58
Catalogue of register information.
RegisterInfo getRegister(const RegisterPath &registerPathName) const
Get register information for a given full path name.
Accessor class to read and write 2D registers.
"Strong typedef" for list of pairs of functors for enabling and disabling a test condition.
Class to test any backend for correct behaviour.
void test_B_8_4()
Test async read consistency heartbeat.
void test_B_11_6()
Test the value after construction for the version number in the application buffer.
void switchWriteOnly(T t, bool enable)
void test_B_8_2_1()
Test _readQueue overrun.
VECTOR_OF_REGISTERS_T registers
boost::mpl::vector with all register descriptors
void test_B_9_3_2()
Test exactly one runtime_error in the _readQueue per async read accessor.
void test_NOSPEC_valueAfterConstruction()
Test the content of the application data buffer after construction.
void test_C_5_3_2()
Test read-only/write-only information cached per accessor.
void test_NOSPEC_partial_write()
Test partial write.
void test_B_12_1_5_1()
mayReplaceOther() of itself returns "false"
void test_NOSPEC_catalogueRaw()
Test that the catalogue information for the raw accessor is correct.
void recoverDevice(ChimeraTK::Device &d)
Utility functions for recurring tasks.
void test_B_8_5_1()
Test activateAsynchronousRead.
void test_C_5_2_5_2()
Test logic_error on operation while backend closed.
void test_B_6_4()
Test application buffer unchanged after exception.
void test_B_9_5()
Test write operations throw after setException()
void test_B_9_2_2()
Test repeated setException() has no effect (in particular, no additional exceptions in async transfer...
void test_NOSPEC_rawTransfer()
Test that the backend does not close itself after seeing an exception.
void test_B_9_3_1()
Test setException() disables asynchronous read transfers.
void test_NOSPEC_backendNotClosedAfterException()
Test that the backend does not close itself after seeing an exception.
bool isRead(REG_T x={})
Utility functions for register traits.
void test_B_4_2_4()
Test transfer implementations do not change the application buffer.
UnifiedBackendTest< VECTOR_OF_REGISTERS_T > & testOnlyTransferElement()
Call if not a real backend is tested but just a special TransferElement implementation.
void test_B_3_2_2()
Test destructive write.
void test_B_8_3()
Test new runtime errors are put to _readQueue in async reads.
void test_B_8_5_2()
Test initial value.
void test_B_4_2_5()
Test that xxxTransferYyy() can be skipped between preXxx() and postXxx()
void test_B_8_6_6()
Test interrupt()
void test_B_11_2_2()
Test consistent data gets same VersionNumber.
void test_B_3_2_1_2()
Test write() does not destroy application buffer.
void test_NOSPEC_newVersionAfterOpen()
Test versions after calling open() are newer than any version before.
void test_C_5_3()
Test read-only/write-only information changes after runtime_error.
void runTests(const std::string &cdd_, const std::string &cdd2_="")
Execute all tests.
void test_NOSPEC_partial_read()
Partial accessor read.
void test_C_5_2_2_2()
Test logic_error for exceeding register size.
void test_B_9_1()
Test reporting exceptions to exception backend.
void test_B_11_2_1()
Test version number bigger for newer values.
void test_B_7_2()
Test data loss in write.
void test_C_5_2_7_2()
Test logic_error on write operation on read-only register.
void test_C_5_3_3()
Test read-only/write-only information always returned from cache if available.
void test_B_8_2()
Test async read fills _readQueue.
void test_C_5_2_6_2()
Test logic_error on read operation on write-only register.
void setForceDataLossWrite(T t, bool enable)
void test_B_8_5()
Test no async transfers until activateAsyncRead().
void test_B_3_1_2_1()
Test synchronous read.
std::string cdd
CDD for backend to test.
void test_B_9_4_1()
Test doReadTransferSynchronously throws runtime_error after setException() until recovery.
UnifiedBackendTest< typename boost::mpl::push_back< VECTOR_OF_REGISTERS_T, REG_T >::type > addRegister()
Add a register to be used by the test.
void test_C_5_2_3_2()
Test logic_error for wrong access mode flags.
bool _testOnlyTransferElement
Flag whether to disable tests for the backend itself.
void test_B_8_5_4_3()
Calling activateAsyncRead() when already active has no effect.
void test_B_12_1_3_1()
If an element that is already in the list of internal elements is passed to TransferElement::replaceT...
void switchReadOnly(T t, bool enable)
void test_NOSPEC_catalogueReadWrite()
Test that the catalogue and accessor information for read and write are correct.
void test_C_5_2_1_2()
Test logic_error for non-existing register.
void test_B_8_5_3()
Accessors created after activateAsyncRead() are immediately active.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
Exception thrown when a runtime error has occured.
Definition Exception.h:18
bool compareHelper< float >(float a, float b)
bool compareHelper< double >(double a, double b)
bool compareHelper< std::string >(std::string a, std::string b)
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
@ raw
Raw access: disable any possible conversion from the original hardware data type into the given UserT...
TestCapability
Used by the Capabilities descriptor.
@ disabled
Disable tests requiring this capability and do not warn.
@ enabled
Enable tests requiring this capability.
@ unspecified
Capability is not specified, hence it is disabled and a warning is printed. Usually default.
bool compareHelper(UserType a, UserType b)
STL namespace.
std::string to_string(const std::string &v)
Descriptor for the test capabilities for each register.
static constexpr TestCapability writeNeverLosesData
static constexpr TestCapability testPartialAccessor
static constexpr TestCapability switchReadOnly
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, TestCapability::enabled, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableTestReadOnly() const
Enable/disable testing only read operations, even if the register is readable.
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, TestCapability::disabled, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableSwitchReadOnly() const
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, TestCapability::disabled, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableTestRawTransfer() const
Enable/disable testing the raw accessors.
constexpr TestCapabilities< _syncRead, TestCapability::enabled, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, TestCapability::disabled, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableForceDataLossWrite() const
See setForceDataLossWrite() function in the register descriptor.
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, TestCapability::disabled, _testPartialAccessor > disableSetRemoteValueIncrementsVersion() const
Enable/disable testing of version number increment in read operations after setRemoteValue.
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, TestCapability::enabled, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableTestCatalogue() const
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, TestCapability::disabled, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableTestWriteOnly() const
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, TestCapability::enabled, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableTestWriteOnly() const
Enable/disable testing only write operations, even if the register is readable.
static constexpr TestCapability forceDataLossWrite
constexpr TestCapabilities< _syncRead, TestCapability::disabled, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableForceDataLossWrite() const
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, TestCapability::disabled, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableAsyncReadInconsistency() const
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, TestCapability::disabled > disableTestPartialAccessor() const
Enable/disable testing for writing with a partial accessor.
constexpr TestCapabilities< _syncRead, TestCapability::disabled, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, TestCapability::enabled, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableTestWriteNeverLosesData() const
Enable/disable test whether write transfers never report lost data (part of B.7.2).
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, TestCapability::enabled, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableTestRawTransfer() const
static constexpr TestCapability syncRead
constexpr TestCapabilities()=default
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, TestCapability::enabled, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableSwitchWriteOnly() const
See switchWriteOnly() function in the register descriptor.
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, TestCapability::disabled, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableTestWriteNeverLosesData() const
static constexpr TestCapability testRawTransfer
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, TestCapability::enabled, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableSwitchReadOnly() const
See switchReadOnly() function in the register descriptor.
static constexpr TestCapability switchWriteOnly
constexpr TestCapabilities< TestCapability::disabled, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableSyncRead() const
Allows to prevent the test from executing any synchronous read tests.
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, TestCapability::disabled, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableTestReadOnly() const
static constexpr TestCapability testWriteOnly
static constexpr TestCapability setRemoteValueIncrementsVersion
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, TestCapability::disabled, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableSwitchWriteOnly() const
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, TestCapability::enabled, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > enableAsyncReadInconsistency() const
See forceAsyncReadInconsistency() function in the register descriptor.
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, TestCapability::enabled > enableTestPartialAccessor() const
static constexpr TestCapability testCatalogue
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, TestCapability::disabled, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableTestCatalogue() const
Enable/disable testing of catalogue content.
static constexpr TestCapability testReadOnly
static constexpr TestCapability asyncReadInconsistency
constexpr TestCapabilities< _syncRead, _forceDataLossWrite, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, TestCapability::enabled, _testPartialAccessor > enableSetRemoteValueIncrementsVersion() const
Special DeviceBackend used for testing the exception reporting to the backend.
void setExceptionImpl() noexcept override
Function to be (optionally) implemented by backends if additional actions are needed when switching t...
bool hasSeenException()
Check whether setException() has been called since the last call to hasSeenException().
RegisterCatalogue getRegisterCatalogue() const override
Return the register catalogue with detailed information on all registers.
std::string readDeviceInfo() override
Return a device information string containing hardware details like the firmware version number or th...
ExceptionReportingBackend(boost::shared_ptr< DeviceBackend > target)
std::string cdd