├── .github └── workflows │ └── cmake.yml ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── include └── polym │ ├── Msg.hpp │ └── Queue.hpp ├── src ├── Msg.cpp └── Queue.cpp └── test ├── Test.cpp └── Tester.hpp /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally 16 | # well on Windows or Mac. You can convert this to a matrix build if you need 17 | # cross-platform coverage. 18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | os: [macos-latest, ubuntu-latest, windows-latest] 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Configure CMake 28 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 29 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 30 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DPOLYM_BUILD_TEST=ON 31 | 32 | - name: Build 33 | # Build your program with the given configuration 34 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 35 | 36 | - name: Test 37 | working-directory: ${{github.workspace}}/build 38 | # Execute tests defined by the CMake configuration. 39 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 40 | run: ctest -C ${{env.BUILD_TYPE}} 41 | 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | option(POLYM_BUILD_TEST "Build test with Polym" OFF) 4 | 5 | project(PolyM) 6 | 7 | add_library(polym 8 | src/Msg.cpp 9 | src/Queue.cpp 10 | 11 | include/polym/Msg.hpp 12 | include/polym/Queue.hpp 13 | ) 14 | 15 | target_compile_features(polym PUBLIC cxx_std_11) 16 | 17 | find_package(Threads) 18 | target_link_libraries(polym PUBLIC Threads::Threads) 19 | target_include_directories(polym PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/) 20 | 21 | # Test program 22 | if(POLYM_BUILD_TEST) 23 | enable_testing() 24 | add_executable(testpolym test/Test.cpp) 25 | target_link_libraries(testpolym polym) 26 | target_include_directories(testpolym PRIVATE test/) 27 | add_test(NAME PolyM COMMAND testpolym) 28 | endif() -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kalle Huttunen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PolyM 2 | 3 | PolyM is a very simple C++ message queue intended for inter-thread communication. There are three major requirement driving the design of PolyM: 4 | 5 | - The design should be simple and lightweight 6 | - The design should support delivering any kind of data as the message payload 7 | - There should be no copies made of the message payload data when passing the message through the queue 8 | 9 | PolyM (**Poly**morphic **M**essage Queue) fulfills these requirements by usage of C++ move semantics. 10 | 11 | ## Moving Messages Through the Queue 12 | 13 | When a message with payload data (`PolyM::DataMsg`) is created, the payload is allocated from the heap. When the message is put to the queue, it is moved there with so called "virtual move constructor". The virtual move constructor is what enables the polymorphism of the message queue: any message type derived from the base `PolyM::Msg` type can be moved to the queue. When a message is read from the queue, it is moved out from the queue to be the responsibility of the receiver. 14 | 15 | All the message types are movable, but not copyable. Once the message is put to the queue, the sender cannot access the message data anymore. A message always has a single owner, and on send-receive scenario, the ownership is transferred 16 | 17 | Sender -> Queue -> Receiver 18 | 19 | ## Messaging Patterns 20 | 21 | PolyM supports two kinds of messaging patterns: 22 | 23 | - One way 24 | - Messages are put to the queue with `PolyM::Queue::put()`. `put()` will return immediately. 25 | - Messages are read from the queue with `PolyM::Queue::get()`. `get()` will block until there is at least one message available in the queue (or until timeout occurs, if specified). 26 | - Request-response 27 | - A request is made with `PolyM::Queue::request()`. `request()` will block until a response is given. The request message can be read from the queue just as any other message with `get()`. 28 | - A response is given with `PolyM::Queue::respondTo()`. `respondTo()` will return immediately. 29 | -------------------------------------------------------------------------------- /include/polym/Msg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace PolyM { 6 | 7 | /** Type for Msg unique identifiers */ 8 | using MsgUID = unsigned long long; 9 | 10 | /** 11 | * Msg represents a simple message that doesn't have any payload data. 12 | * Msg ID identifies the type of the message. Msg ID can be queried with getMsgId(). 13 | */ 14 | class Msg 15 | { 16 | public: 17 | /** 18 | * Construct a Msg. 19 | * 20 | * @param msgId Msg ID of this Msg. 21 | */ 22 | Msg(int msgId); 23 | 24 | virtual ~Msg() = default; 25 | Msg(const Msg&) = delete; 26 | Msg& operator=(const Msg&) = delete; 27 | 28 | /** "Virtual move constructor" */ 29 | virtual std::unique_ptr move(); 30 | 31 | /** 32 | * Get Msg ID. 33 | * Msg ID identifies message type. 34 | * Multiple Msg instances can have the same Msg ID. 35 | */ 36 | int getMsgId() const; 37 | 38 | /** 39 | * Get Msg UID. 40 | * Msg UID is the unique ID associated with this message. 41 | * All Msg instances have a unique Msg UID. 42 | */ 43 | MsgUID getUniqueId() const; 44 | 45 | protected: 46 | Msg(Msg&&) = default; 47 | Msg& operator=(Msg&&) = default; 48 | 49 | private: 50 | int msgId_; 51 | MsgUID uniqueId_; 52 | }; 53 | 54 | /** 55 | * DataMsg is a Msg with payload of type PayloadType. 56 | * Payload is constructed when DataMsg is created and the DataMsg instance owns the payload data. 57 | */ 58 | template 59 | class DataMsg : public Msg 60 | { 61 | public: 62 | /** 63 | * Construct DataMsg 64 | * @param msgId Msg ID 65 | * @param args Arguments for PayloadType ctor 66 | */ 67 | template 68 | DataMsg(int msgId, Args&& ... args) 69 | : Msg(msgId), 70 | pl_(new PayloadType(std::forward(args) ...)) 71 | { 72 | } 73 | 74 | virtual ~DataMsg() = default; 75 | DataMsg(const DataMsg&) = delete; 76 | DataMsg& operator=(const DataMsg&) = delete; 77 | 78 | /** "Virtual move constructor" */ 79 | virtual std::unique_ptr move() override 80 | { 81 | return std::unique_ptr(new DataMsg(std::move(*this))); 82 | } 83 | 84 | /** Get the payload data */ 85 | PayloadType& getPayload() const 86 | { 87 | return *pl_; 88 | } 89 | 90 | protected: 91 | DataMsg(DataMsg&&) = default; 92 | DataMsg& operator=(DataMsg&&) = default; 93 | 94 | private: 95 | std::unique_ptr pl_; 96 | }; 97 | 98 | } 99 | -------------------------------------------------------------------------------- /include/polym/Queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Msg.hpp" 3 | #include 4 | 5 | namespace PolyM { 6 | 7 | /** 8 | * Queue is a thread-safe message queue. 9 | * It supports one-way messaging and request-response pattern. 10 | */ 11 | class Queue 12 | { 13 | public: 14 | Queue(); 15 | 16 | ~Queue(); 17 | 18 | /** 19 | * Put Msg to the end of the queue. 20 | * 21 | * @param msg Msg to put to the queue. 22 | */ 23 | void put(Msg&& msg); 24 | 25 | /** 26 | * Get message from the head of the queue. 27 | * Blocks until at least one message is available in the queue, or until timeout happens. 28 | * If get() returns due to timeout, returns a nullptr. 29 | * 30 | * @param timeoutMillis How many ms to wait for message until timeout happens. 31 | * 0 = wait indefinitely. 32 | */ 33 | std::unique_ptr get(int timeoutMillis = 0); 34 | 35 | /** 36 | * Get message from the head of the queue. 37 | * Returns an empty pointer if no message is available. 38 | */ 39 | std::unique_ptr tryGet(); 40 | 41 | /** 42 | * Make a request. 43 | * Call will block until response is given with respondTo(). 44 | * If request() returns due to timeout, returns a nullptr. 45 | * 46 | * @param msg Request message. Is put to the queue so it can be retrieved from it with get(). 47 | * @param timeoutMillis How many ms to wait for response until timeout happens. 48 | * 0 = wait indefinitely. 49 | */ 50 | std::unique_ptr request(Msg&& msg, int timeoutMillis = 0); 51 | 52 | /** 53 | * Respond to a request previously made with request(). 54 | * If the requestID has been found, return true. 55 | * 56 | * @param reqUid Msg UID of the request message. 57 | * @param responseMsg Response message. The requester will receive it as the return value of 58 | * request(). 59 | */ 60 | bool respondTo(MsgUID reqUid, Msg&& responseMsg); 61 | 62 | /** 63 | * Get the size of the queue 64 | */ 65 | size_t size(); 66 | private: 67 | class Impl; 68 | std::unique_ptr impl_; 69 | }; 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Msg.cpp: -------------------------------------------------------------------------------- 1 | #include "polym/Msg.hpp" 2 | 3 | #include 4 | 5 | namespace PolyM { 6 | 7 | namespace { 8 | 9 | MsgUID generateUniqueId() 10 | { 11 | static std::atomic i(0); 12 | return ++i; 13 | } 14 | 15 | } 16 | 17 | Msg::Msg(int msgId) 18 | : msgId_(msgId), uniqueId_(generateUniqueId()) 19 | { 20 | } 21 | 22 | std::unique_ptr Msg::move() 23 | { 24 | return std::unique_ptr(new Msg(std::move(*this))); 25 | } 26 | 27 | int Msg::getMsgId() const 28 | { 29 | return msgId_; 30 | } 31 | 32 | MsgUID Msg::getUniqueId() const 33 | { 34 | return uniqueId_; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Queue.cpp: -------------------------------------------------------------------------------- 1 | #include "polym/Queue.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace PolyM { 11 | 12 | class Queue::Impl 13 | { 14 | struct Request 15 | { 16 | Request() {}; 17 | 18 | std::unique_ptr response; 19 | std::condition_variable condVar; 20 | }; 21 | 22 | public: 23 | Impl() 24 | : queue_(), queueMutex_(), queueCond_(), responseMap_(), responseMapMutex_() 25 | { 26 | } 27 | 28 | void put(Msg&& msg) 29 | { 30 | { 31 | std::lock_guard lock(queueMutex_); 32 | queue_.push(msg.move()); 33 | } 34 | 35 | queueCond_.notify_one(); 36 | } 37 | 38 | std::unique_ptr get(int timeoutMillis) 39 | { 40 | std::unique_lock lock(queueMutex_); 41 | 42 | if (timeoutMillis <= 0) 43 | queueCond_.wait(lock, [this]{return !queue_.empty();}); 44 | else 45 | { 46 | // wait_for returns false if the return is due to timeout 47 | auto timeoutOccured = !queueCond_.wait_for( 48 | lock, 49 | std::chrono::milliseconds(timeoutMillis), 50 | [this]{return !queue_.empty();}); 51 | 52 | if (timeoutOccured) 53 | return nullptr; 54 | } 55 | 56 | auto msg = queue_.front()->move(); 57 | queue_.pop(); 58 | return msg; 59 | } 60 | 61 | std::unique_ptr tryGet() 62 | { 63 | std::unique_lock lock(queueMutex_); 64 | if (!queue_.empty()) 65 | { 66 | auto msg = queue_.front()->move(); 67 | queue_.pop(); 68 | return msg; 69 | } 70 | else 71 | { 72 | return{ nullptr }; 73 | } 74 | } 75 | 76 | std::unique_ptr request(Msg&& msg, int timeoutMillis) 77 | { 78 | Request req; 79 | 80 | // emplace the request in the map 81 | std::unique_lock lock(responseMapMutex_); 82 | auto it = responseMap_.emplace( 83 | std::make_pair(msg.getUniqueId(), &req)).first; 84 | 85 | put(std::move(msg)); 86 | 87 | if (timeoutMillis <= 0) 88 | req.condVar.wait(lock, [&req]{return req.response.get();}); 89 | else 90 | { 91 | // wait_for returns false if the return is due to timeout 92 | auto timeoutOccured = !req.condVar.wait_for( 93 | lock, 94 | std::chrono::milliseconds(timeoutMillis), 95 | [&req] {return req.response.get();} 96 | ); 97 | 98 | if (timeoutOccured) 99 | { 100 | responseMap_.erase(it); 101 | return nullptr; 102 | } 103 | } 104 | 105 | auto response = std::move(it->second->response); 106 | responseMap_.erase(it); 107 | 108 | return response; 109 | } 110 | 111 | bool respondTo(MsgUID reqUid, Msg&& responseMsg) 112 | { 113 | std::unique_lock lock(responseMapMutex_); 114 | auto it = responseMap_.find(reqUid); 115 | if(it == responseMap_.end()) 116 | return false; 117 | 118 | it->second->response = responseMsg.move(); 119 | it->second->condVar.notify_one(); 120 | return true; 121 | } 122 | 123 | size_t size() 124 | { 125 | std::unique_lock lock(queueMutex_); 126 | auto size = queue_.size(); 127 | return size; 128 | } 129 | 130 | private: 131 | // Queue for the Msgs 132 | std::queue> queue_; 133 | 134 | // Mutex to protect access to the queue 135 | std::mutex queueMutex_; 136 | 137 | // Condition variable to wait for when getting Msgs from the queue 138 | std::condition_variable queueCond_; 139 | 140 | // Map to keep track of which request IDs are associated with which request Msgs 141 | std::map responseMap_; 142 | 143 | // Mutex to protect access to response map 144 | std::mutex responseMapMutex_; 145 | }; 146 | 147 | Queue::Queue() 148 | : impl_(new Impl) 149 | { 150 | } 151 | 152 | Queue::~Queue() 153 | { 154 | } 155 | 156 | void Queue::put(Msg&& msg) 157 | { 158 | impl_->put(std::move(msg)); 159 | } 160 | 161 | std::unique_ptr Queue::get(int timeoutMillis) 162 | { 163 | return impl_->get(timeoutMillis); 164 | } 165 | 166 | std::unique_ptr Queue::tryGet() 167 | { 168 | return impl_->tryGet(); 169 | } 170 | 171 | std::unique_ptr Queue::request(Msg&& msg, int timeoutMillis) 172 | { 173 | return impl_->request(std::move(msg), timeoutMillis); 174 | } 175 | 176 | bool Queue::respondTo(MsgUID reqUid, Msg&& responseMsg) 177 | { 178 | return impl_->respondTo(reqUid, std::move(responseMsg)); 179 | } 180 | 181 | size_t Queue::size() 182 | { 183 | return impl_->size(); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /test/Test.cpp: -------------------------------------------------------------------------------- 1 | #include "polym/Queue.hpp" 2 | #include "Tester.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Test that MsgUIDs generated in two different threads simultaneously are unique 10 | void testMsgUID() 11 | { 12 | const int N = 1000; 13 | std::vector uids1, uids2; 14 | 15 | auto createMsgs = [](int count, std::vector& uids) 16 | { 17 | for (int i = 0; i < count; ++i) 18 | uids.push_back(PolyM::Msg(1).getUniqueId()); 19 | }; 20 | 21 | std::thread t1(createMsgs, N, std::ref(uids1)); 22 | std::thread t2(createMsgs, N, std::ref(uids2)); 23 | t1.join(); 24 | t2.join(); 25 | 26 | for (auto uid1 : uids1) 27 | { 28 | for (auto uid2 : uids2) 29 | { 30 | TEST_NOT_EQUALS(uid1, uid2); 31 | } 32 | } 33 | } 34 | 35 | // Test that messages are received in order in 1-to-1 one-way messaging scenario 36 | void testMsgOrder() 37 | { 38 | const int N = 1000; 39 | PolyM::Queue queue; 40 | 41 | auto sender = [](int count, PolyM::Queue& q) 42 | { 43 | for (int i = 0; i < count; ++i) 44 | q.put(PolyM::Msg(i)); 45 | }; 46 | 47 | auto receiver = [](int count, PolyM::Queue& q) 48 | { 49 | for (int i = 0; i < count; ++i) 50 | { 51 | auto m = q.get(); 52 | TEST_EQUALS(m->getMsgId(), i); 53 | } 54 | }; 55 | 56 | std::thread t1(sender, N, std::ref(queue)); 57 | std::thread t2(receiver, N, std::ref(queue)); 58 | t1.join(); 59 | t2.join(); 60 | } 61 | 62 | // Test that messages are received in order in 2-to-1 one-way messaging scenario 63 | void test2To1MsgOrder() 64 | { 65 | const int N = 1000; 66 | PolyM::Queue queue; 67 | 68 | auto sender = [](int msgId, int count, PolyM::Queue& q) 69 | { 70 | for (int i = 0; i < count; ++i) 71 | q.put(PolyM::DataMsg(msgId, i)); 72 | }; 73 | 74 | auto receiver = [](int count, PolyM::Queue& q) 75 | { 76 | int expectedData1 = 0; 77 | int expectedData2 = 0; 78 | 79 | for (int i = 0; i < count; ++i) 80 | { 81 | auto m = q.get(); 82 | auto& dm = dynamic_cast&>(*m); 83 | 84 | if (dm.getMsgId() == 1) 85 | { 86 | TEST_EQUALS(dm.getPayload(), expectedData1); 87 | ++expectedData1; 88 | } 89 | else if (dm.getMsgId() == 2) 90 | { 91 | TEST_EQUALS(dm.getPayload(), expectedData2); 92 | ++expectedData2; 93 | } 94 | else 95 | { 96 | TEST_FAIL("Unexpected message id"); 97 | } 98 | } 99 | }; 100 | 101 | std::thread t1(sender, 1, N, std::ref(queue)); 102 | std::thread t2(sender, 2, N, std::ref(queue)); 103 | std::thread t3(receiver, 2 * N, std::ref(queue)); 104 | t1.join(); 105 | t2.join(); 106 | t3.join(); 107 | } 108 | 109 | // Test putting DataMsg through the queue 110 | void testDataMsg() 111 | { 112 | PolyM::Queue q; 113 | q.put(PolyM::DataMsg(42, "foo")); 114 | auto m = q.get(); 115 | auto& dm = dynamic_cast&>(*m); 116 | TEST_EQUALS(dm.getMsgId(), 42); 117 | TEST_EQUALS(dm.getPayload(), std::string("foo")); 118 | // Test modifying the payload data 119 | dm.getPayload() += "bar"; 120 | TEST_EQUALS(dm.getPayload(), std::string("foobar")); 121 | } 122 | 123 | // Test timeout when getting message from the queue 124 | void testReceiveTimeout() 125 | { 126 | PolyM::Queue q; 127 | 128 | // Test first with a Msg in the queue that specifying timeout for get() doesn't have an effect 129 | q.put(PolyM::Msg(1)); 130 | 131 | auto start = std::chrono::steady_clock::now(); 132 | auto m = q.get(10); 133 | auto end = std::chrono::steady_clock::now(); 134 | auto dur = std::chrono::duration_cast(end - start).count(); 135 | 136 | TEST_EQUALS(m->getMsgId(), 1); 137 | //TEST_LESS_THAN(dur, 5LL); 138 | 139 | // Then test with empty queue 140 | start = std::chrono::steady_clock::now(); 141 | auto m2 = q.get(10); 142 | end = std::chrono::steady_clock::now(); 143 | dur = std::chrono::duration_cast(end - start).count(); 144 | 145 | TEST_EQUALS(m2.get(), (PolyM::Msg*) nullptr); 146 | //TEST_LESS_THAN(5LL, dur); 147 | //TEST_LESS_THAN(dur, 15LL); 148 | } 149 | 150 | // Test tryGet() method 151 | void testTryGet() 152 | { 153 | PolyM::Queue q; 154 | 155 | auto m1 = q.tryGet(); 156 | TEST_EQUALS(m1.get(), (PolyM::Msg*) nullptr); 157 | 158 | q.put(PolyM::Msg(1)); 159 | 160 | auto m2 = q.tryGet(); 161 | TEST_NOT_EQUALS(m2.get(), (PolyM::Msg*) nullptr); 162 | } 163 | 164 | // Test 2-to-1 request-response scenario 165 | void testRequestResponse() 166 | { 167 | const int N = 1000; 168 | 169 | PolyM::Queue queue; 170 | 171 | auto requester1 = [](int count, PolyM::Queue& q) 172 | { 173 | for (int i = 0; i < count; ++i) 174 | { 175 | TEST_EQUALS(q.request(PolyM::Msg(i))->getMsgId(), i + count); 176 | } 177 | }; 178 | 179 | auto requester2 = [](int count, PolyM::Queue& q) 180 | { 181 | for (int i = 0; i < count; ++i) 182 | { 183 | TEST_EQUALS(q.request(PolyM::Msg(i + 2 * count))->getMsgId(), i + 3 * count); 184 | } 185 | }; 186 | 187 | auto responder = [](int count, PolyM::Queue& q) 188 | { 189 | for (int i = 0; i < 2 * count; ++i) 190 | { 191 | auto m = q.get(); 192 | q.respondTo(m->getUniqueId(), PolyM::Msg(m->getMsgId() + count)); 193 | } 194 | }; 195 | 196 | std::thread t1(requester1, N, std::ref(queue)); 197 | std::thread t2(requester2, N, std::ref(queue)); 198 | std::thread t3(responder, N, std::ref(queue)); 199 | t1.join(); 200 | t2.join(); 201 | t3.join(); 202 | } 203 | 204 | void testRequestExpired() 205 | { 206 | PolyM::Queue queue; 207 | auto m1 = queue.request(PolyM::Msg(42), 50); 208 | TEST_EQUALS(m1.get(), (PolyM::Msg*) nullptr); 209 | 210 | auto m2 = queue.get(); 211 | TEST_EQUALS(m2->getMsgId(), 42); 212 | } 213 | 214 | int main() 215 | { 216 | // Statically assert that messages can't be copied or moved 217 | static_assert(!std::is_move_constructible::value, "Msg can't be copyable"); 218 | static_assert(!std::is_move_assignable::value, "Msg can't be copyable"); 219 | static_assert(!std::is_move_constructible>::value, "DataMsg can't be copyable"); 220 | static_assert(!std::is_move_assignable>::value, "DataMsg can't be copyable"); 221 | 222 | Tester tester("Test PolyM"); 223 | tester.addTest(testMsgUID, "Test MsgUID generation"); 224 | tester.addTest(testMsgOrder, "Test 1-to-1 message order"); 225 | tester.addTest(test2To1MsgOrder, "Test 2-to-1 message order"); 226 | tester.addTest(testDataMsg, "Test DataMsg"); 227 | tester.addTest(testReceiveTimeout, "Test receive timeout"); 228 | tester.addTest(testTryGet, "Test tryGet"); 229 | tester.addTest(testRequestResponse, "Test 2-to-1 request-response"); 230 | tester.addTest(testRequestExpired, "Test request() timeout"); 231 | tester.runTests(); 232 | } 233 | -------------------------------------------------------------------------------- /test/Tester.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // This file contains an implementation for *very* simple testing framework. 11 | 12 | // Use these macros in your test functions to test for various things. 13 | // The test program will print an error message and exit if the tests fail. 14 | 15 | /** Test that boolean condition a is true */ 16 | #define TEST(a) test((a), #a, __FILE__, __LINE__); 17 | /** Test that a == b */ 18 | #define TEST_EQUALS(a, b) testEquals((a), (b), __FILE__, __LINE__); 19 | /** Test that a != b */ 20 | #define TEST_NOT_EQUALS(a, b) testNotEquals((a), (b), __FILE__, __LINE__); 21 | /** Test that a < b */ 22 | #define TEST_LESS_THAN(a, b) testLessThan((a), (b), __FILE__, __LINE__); 23 | /** Fail the test with error message msg */ 24 | #define TEST_FAIL(msg) testFail((msg), __FILE__, __LINE__); 25 | 26 | void test(bool a, const char* exp, const char* file, int line) 27 | { 28 | if (!a) 29 | { 30 | std::cout << file << ":" << line << ": TEST FAILED: " << exp << std::endl; 31 | exit(1); 32 | } 33 | } 34 | 35 | template void testEquals(C a, C b, const char* file, int line) 36 | { 37 | if (a != b) 38 | { 39 | std::cout << file << ":" << line << ": TEST FAILED: " << a << " == " << b << std::endl; 40 | exit(1); 41 | } 42 | } 43 | 44 | template void testNotEquals(C a, C b, const char* file, int line) 45 | { 46 | if (a == b) 47 | { 48 | std::cout << file << ":" << line << ": TEST FAILED: " << a << " != " << b << std::endl; 49 | exit(1); 50 | } 51 | } 52 | 53 | template void testLessThan(C a, C b, const char* file, int line) 54 | { 55 | if (a >= b) 56 | { 57 | std::cout << file << ":" << line << ": TEST FAILED: " << a << " < " << b << std::endl; 58 | exit(1); 59 | } 60 | } 61 | 62 | void testFail(const char* msg, const char* file, int line) 63 | { 64 | std::cout << file << ":" << line << ": TEST FAILED: " << msg << std::endl; 65 | exit(1); 66 | } 67 | 68 | /** 69 | * Tester is a container for tests. 70 | * Use addTest() to add new test. 71 | * Use runTests() to run all added tests. 72 | */ 73 | class Tester 74 | { 75 | public: 76 | /** 77 | * Construct a Tester. 78 | * 79 | * @param name Name for the Tester instance. Will be printed when running tests. 80 | */ 81 | Tester(const std::string& name) 82 | : name_(name), tests_() 83 | { 84 | } 85 | 86 | /** 87 | * Add test. 88 | * 89 | * @param testFunc Function containing the test. 90 | * @param testDesc Description for the test. Will be printed when running the test. 91 | */ 92 | void addTest(const std::function& testFunc, const std::string& testDesc) 93 | { 94 | tests_.push_back(std::make_pair(testFunc, testDesc)); 95 | } 96 | 97 | /** 98 | * Run all added tests. 99 | */ 100 | void runTests() const 101 | { 102 | std::cout << name_ << ": Running " << tests_.size() << " tests" << std::endl; 103 | for (auto& test : tests_) 104 | { 105 | std::cout << test.second << "... "; 106 | test.first(); 107 | std::cout << "OK!" << std::endl; 108 | } 109 | } 110 | 111 | private: 112 | std::string name_; 113 | std::vector, std::string>> tests_; 114 | }; 115 | --------------------------------------------------------------------------------