├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build.sh ├── include └── libsharedmemory │ └── libsharedmemory.hpp ├── package-lock.json ├── package.json ├── screenshot.png ├── test.sh └── test ├── CMakeLists.txt ├── lest.hpp └── test.cc /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vscode 3 | build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | cmake_policy(SET CMP0042 NEW) 3 | set(CMAKE_CXX_STANDARD 11) 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | 6 | project(LSM) 7 | 8 | set(header_files ${CMAKE_CURRENT_SOURCE_DIR}/include/libsharedmemory/libsharedmemory.hpp) 9 | 10 | add_library(lsm INTERFACE) 11 | target_sources(lsm INTERFACE "$") 12 | 13 | target_include_directories(lsm INTERFACE $) 14 | target_include_directories(lsm SYSTEM INTERFACE $/include>) 15 | 16 | if (${CMAKE_GENERATOR} MATCHES "Visual") 17 | target_compile_options(lsm INTERFACE -W3 -EHsc) 18 | else() 19 | target_compile_options(lsm INTERFACE -Wall -Wno-missing-braces -std=c++11 -fPIC) 20 | endif() 21 | 22 | option(LSM_BUILD_TEST "build test" ON) 23 | if(${LSM_BUILD_TEST} AND (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)) 24 | enable_testing() 25 | add_subdirectory(test/) 26 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2021 Aron Homberg, https://arons.site 4 | Copyright (c) 2017 itch corp., https://itch.io 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `libsharedmemory` 2 | 3 | `libsharedmemory` is a small C++11 header-only library for using shared memory on Windows, Linux and macOS. `libsharedmemory` makes it easy to transfer data between isolated host OS processes. It also helps inter-connecting modules of applications that are implemented in different programming languages. It allows for simple read/write data transfer using single, indexed memory address access and also using array-types like `std::string`, `float*`, `double*`. 4 | 5 | 6 | 7 | ## Example 8 | 9 | ```cpp 10 | 11 | std::string dataToTransfer = "{ foo: 'coolest IPC ever! 🧑‍💻' }"; 12 | 13 | // the name of the shared memory is os-wide announced 14 | // the size is in bytes and must be big enough to handle the data (up to 4GiB) 15 | // if persistency is disabled, the shared memory segment will 16 | // be garbage collected when the process that wrote it is killed 17 | SharedMemoryWriteStream write$ {/*name*/ "jsonPipe", /*size*/ 65535, /*persistent*/ true}; 18 | SharedMemoryReadStream read$ {/*name*/ "jsonPipe", /*size*/ 65535, /*persistent*/ true}; 19 | 20 | // writing the string to the shared memory 21 | write$.write(dataToTransfer); 22 | 23 | // reading the string from shared memory 24 | // you can run this in another process, thread, 25 | // even in another app written in another programming language 26 | std::string dataString = read$.readString(); 27 | 28 | std::cout << "UTF8 string written and read" << dataString << std::endl; 29 | ``` 30 | 31 | ## Source code package management via `npm` 32 | 33 | In case you want to use this library in your codebase, 34 | you could just copy & paste the `include/libsharedmemory/libsharedmemory.hpp` into your `include` or `deps` directory. But then you'd have to manually manage the code base. 35 | 36 | However, you could also use `npm` for a smarter dependency management approach. 37 | Therefore, install [Node.js](https://www.nodejs.org) which comes bundled with `npm`, the Node package manager. 38 | 39 | Now run `npm init` in your project root directoy. 40 | After initial setup, run `npm install cpp_libsharedmemory` and add `node_modules/cpp_libsharedmemory/include/libsharedmemory` to your include path. 41 | 42 | Whenever this library updates, you can also update your dependencies via 43 | `npm upgrade`. Futhermore, people who audit the code can announce security 44 | reports that are announced when running `npm audit`. Finally, it's also much 45 | easier for you to install all project dependencies by just running `npm install` 46 | in your projects root directory. Managing third party code becomes obsolete at all. 47 | 48 | ## Limits 49 | 50 | `libsharedmemory` does only support the following datatypes (array-like): 51 | - `std::string` 52 | - `float*` 53 | - `double*` 54 | 55 | Single value access via `.data()[index]` API: 56 | - all scalar datatypes supported in C/C++ 57 | 58 | - This library doesn't care for endinanness. This should be naturally fine 59 | because shared memory shouldn't be shared between different machine 60 | architectures. However, if you plan to copy the shared buffer onto a 61 | network layer prototcol, make sure to add an endianess indication bit. 62 | 63 | - Although the binary memory layout should give you no headache 64 | when compiling/linking using different compilers, 65 | the behavior is undefined. 66 | 67 | - At the time of writing, there is no support for shared memory persistency 68 | on Windows. Shared memory is lost after the writing process is killed. 69 | 70 | ## Memory layout 71 | 72 | When writing data into a named shared memory segment, `libsharedmemory` 73 | does write 5 bytes of meta information: 74 | 75 | - `flags` (`char`) is a bitmask that indicates data change (via an odd/even bit flip) as well as the data type transferred (1 byte) 76 | - `size` (`int`) indicates the buffer size in bytes (4 bytes) 77 | 78 | Therefore the binary memory layout is: 79 | `|flags|size|data|` 80 | 81 | The following datatype flags are defined: 82 | ```c 83 | enum DataType { 84 | kMemoryChanged = 1, 85 | kMemoryTypeString = 2, 86 | kMemoryTypeFloat = 4, 87 | kMemoryTypeDouble = 8, 88 | }; 89 | ``` 90 | 91 | `kMemoryChanged` is the change indicator bit. It will flip odd/evenly 92 | to indicate data change. Continuous data reader will thus be able 93 | to catch every data change. 94 | 95 | ## Build 96 | 97 | This project is meant to be built with `cmake` and `clang`. 98 | However, it _should_ also build with MSVC and GCC. 99 | 100 | ```sh 101 | ./build.sh 102 | ``` 103 | 104 | ## Test 105 | 106 | Test executables are built automatically and can be executed 107 | to verify the correct function of the implementation on your machine: 108 | 109 | ```sh 110 | ./test.sh 111 | ``` 112 | 113 | ## License 114 | 115 | `libsharedmemory` is released under the MIT license, see the `LICENSE` file. 116 | 117 | ## Roadmap 118 | 119 | 1) Windows shared memory persistency support 120 | 2) Multi-threaded non-blocking `onChange( lambda fn )` data change handler on the read stream -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cmake -B build 3 | cmake --build build -------------------------------------------------------------------------------- /include/libsharedmemory/libsharedmemory.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef INCLUDE_LIBSHAREDMEMORY_HPP_ 3 | #define INCLUDE_LIBSHAREDMEMORY_HPP_ 4 | 5 | #include 6 | #define LIBSHAREDMEMORY_VERSION_MAJOR 0 7 | #define LIBSHAREDMEMORY_VERSION_MINOR 0 8 | #define LIBSHAREDMEMORY_VERSION_PATCH 9 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include // nullptr_t, ptrdiff_t, std::size_t 16 | 17 | #if defined(_WIN32) 18 | #define WIN32_LEAN_AND_MEAN 19 | #include 20 | #undef WIN32_LEAN_AND_MEAN 21 | #endif 22 | 23 | namespace lsm { 24 | 25 | enum Error { 26 | kOK = 0, 27 | kErrorCreationFailed = 100, 28 | kErrorMappingFailed = 110, 29 | kErrorOpeningFailed = 120, 30 | }; 31 | 32 | enum DataType { 33 | kMemoryChanged = 1, 34 | kMemoryTypeString = 2, 35 | kMemoryTypeFloat = 4, 36 | kMemoryTypeDouble = 8, 37 | }; 38 | 39 | // byte sizes of memory layout 40 | const size_t bufferSizeSize = 4; // size_t takes 4 bytes 41 | const size_t sizeOfOneFloat = 4; // float takes 4 bytes 42 | const size_t sizeOfOneChar = 1; // char takes 1 byte 43 | const size_t sizeOfOneDouble = 8; // double takes 8 bytes 44 | const size_t flagSize = 1; // char takes 1 byte 45 | 46 | class Memory { 47 | public: 48 | // path should only contain alpha-numeric characters, and is normalized 49 | // on linux/macOS. 50 | explicit Memory(std::string path, std::size_t size, bool persist); 51 | 52 | // create a shared memory area and open it for writing 53 | inline Error create() { return createOrOpen(true); }; 54 | 55 | // open an existing shared memory for reading 56 | inline Error open() { return createOrOpen(false); }; 57 | 58 | inline std::size_t size() { return _size; }; 59 | 60 | inline const std::string &path() { return _path; } 61 | 62 | inline void *data() { return _data; } 63 | 64 | void destroy(); 65 | 66 | void close(); 67 | 68 | ~Memory(); 69 | 70 | private: 71 | Error createOrOpen(bool create); 72 | 73 | std::string _path; 74 | void *_data = nullptr; 75 | std::size_t _size = 0; 76 | bool _persist = true; 77 | #if defined(_WIN32) 78 | HANDLE _handle; 79 | #else 80 | int _fd = -1; 81 | #endif 82 | }; 83 | 84 | // Windows shared memory implementation 85 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) 86 | 87 | #include // CreateFileMappingA, OpenFileMappingA, etc. 88 | 89 | Memory::Memory(const std::string path, const std::size_t size, const bool persist) : _path(path), _size(size), _persist(persist) {}; 90 | 91 | Error Memory::createOrOpen(const bool create) { 92 | if (create) { 93 | DWORD size_high_order = 0; 94 | DWORD size_low_order = static_cast(size_); 95 | 96 | _handle = CreateFileMappingA(INVALID_HANDLE_VALUE, // use paging file 97 | NULL, // default security 98 | PAGE_READWRITE, // read/write access 99 | size_high_order, size_low_order, 100 | _path.c_str() // name of mapping object 101 | ); 102 | 103 | if (!_handle) { 104 | return kErrorCreationFailed; 105 | } 106 | } else { 107 | _handle = OpenFileMappingA(FILE_MAP_READ, // read access 108 | FALSE, // do not inherit the name 109 | _path.c_str() // name of mapping object 110 | ); 111 | 112 | // TODO: Windows has no default support for shared memory persistence 113 | // see: destroy() to implement that 114 | 115 | if (!_handle) { 116 | return kErrorOpeningFailed; 117 | } 118 | } 119 | 120 | // TODO: might want to use GetWriteWatch to get called whenever 121 | // the memory section changes 122 | // https://docs.microsoft.com/de-de/windows/win32/api/memoryapi/nf-memoryapi-getwritewatch?redirectedfrom=MSDN 123 | 124 | const DWORD access = create ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ; 125 | _data = MapViewOfFile(_handle, access, 0, 0, _size); 126 | 127 | if (!_data) { 128 | return kErrorMappingFailed; 129 | } 130 | return kOK; 131 | } 132 | 133 | void Memory::destroy() { 134 | 135 | // TODO: Windows needs priviledges to define a shared memory (file mapping) 136 | // OBJ_PERMANENT; furthermore, ZwCreateSection would need to be used. 137 | // Instead of doing this; saving a file here (by name, temp dir) 138 | // and reading memory from file in createOrOpen seems more suitable. 139 | // Especially, because files can be removed on reboot using: 140 | // MoveFileEx() with the MOVEFILE_DELAY_UNTIL_REBOOT flag and lpNewFileName 141 | // set to NULL. 142 | } 143 | 144 | void Memory::close() { 145 | if (_data) { 146 | UnmapViewOfFile(_data); 147 | _data = nullptr; 148 | } 149 | CloseHandle(_handle); 150 | } 151 | 152 | Memory::~Memory() { 153 | close(); 154 | if (!_persist) { 155 | destroy(); 156 | } 157 | } 158 | #endif // defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) 159 | 160 | #if defined(__APPLE__) || defined(__linux__) || defined(__unix__) || defined(_POSIX_VERSION) || defined(__ANDROID__) 161 | 162 | #include // for O_* constants 163 | #include // mmap, munmap 164 | #include // for mode constants 165 | #include // unlink 166 | 167 | #if defined(__APPLE__) 168 | 169 | #include 170 | 171 | #endif // __APPLE__ 172 | 173 | #include 174 | 175 | inline Memory::Memory(const std::string path, const std::size_t size, const bool persist) : _size(size), _persist(persist) { 176 | _path = "/" + path; 177 | }; 178 | 179 | inline Error Memory::createOrOpen(const bool create) { 180 | if (create) { 181 | // shm segments persist across runs, and macOS will refuse 182 | // to ftruncate an existing shm segment, so to be on the safe 183 | // side, we unlink it beforehand. 184 | const int ret = shm_unlink(_path.c_str()); 185 | if (ret < 0) { 186 | if (errno != ENOENT) { 187 | return kErrorCreationFailed; 188 | } 189 | } 190 | } 191 | 192 | const int flags = create ? (O_CREAT | O_RDWR) : O_RDONLY; 193 | 194 | _fd = shm_open(_path.c_str(), flags, 0755); 195 | if (_fd < 0) { 196 | if (create) { 197 | return kErrorCreationFailed; 198 | } else { 199 | return kErrorOpeningFailed; 200 | } 201 | } 202 | 203 | if (create) { 204 | // this is the only way to specify the size of a 205 | // newly-created POSIX shared memory object 206 | int ret = ftruncate(_fd, _size); 207 | if (ret != 0) { 208 | return kErrorCreationFailed; 209 | } 210 | } 211 | 212 | const int prot = create ? (PROT_READ | PROT_WRITE) : PROT_READ; 213 | 214 | _data = mmap(nullptr, // addr 215 | _size, // length 216 | prot, // prot 217 | MAP_SHARED, // flags 218 | _fd, // fd 219 | 0 // offset 220 | ); 221 | 222 | if (_data == MAP_FAILED) { 223 | return kErrorMappingFailed; 224 | } 225 | 226 | if (!_data) { 227 | return kErrorMappingFailed; 228 | } 229 | return kOK; 230 | } 231 | 232 | inline void Memory::destroy() { shm_unlink(_path.c_str()); } 233 | 234 | inline void Memory::close() { 235 | munmap(_data, _size); 236 | ::close(_fd); 237 | } 238 | 239 | inline Memory::~Memory() { 240 | close(); 241 | if (!_persist) { 242 | destroy(); 243 | } 244 | } 245 | 246 | #endif // defined(__APPLE__) || defined(__linux__) || defined(__unix__) || defined(_POSIX_VERSION) || defined(__ANDROID__) 247 | 248 | class SharedMemoryReadStream { 249 | public: 250 | 251 | SharedMemoryReadStream(const std::string name, const std::size_t bufferSize, const bool isPersistent): 252 | _memory(name, bufferSize, isPersistent) { 253 | 254 | if (_memory.open() != kOK) { 255 | throw "Shared memory segment could not be opened."; 256 | } 257 | } 258 | 259 | inline char readFlags() { 260 | char* memory = (char*) _memory.data(); 261 | return memory[0]; 262 | } 263 | 264 | inline void close() { _memory.close(); } 265 | 266 | 267 | inline size_t readSize(char dataType) { 268 | void *memory = _memory.data(); 269 | std::size_t size = 0; 270 | 271 | // TODO(kyr0): should be clarified why we need to use size_t there 272 | // for the size to be received correctly, but in float, we need int 273 | // Might be prone to undefined behaviour; should be tested 274 | // with various compilers; otherwise use memcpy() for the size 275 | // and align the memory with one cast. 276 | 277 | if (dataType & kMemoryTypeDouble) { 278 | size_t *intMemory = (size_t *)memory; 279 | // copy size data to size variable 280 | std::memcpy(&size, &intMemory[flagSize], bufferSizeSize); 281 | } 282 | 283 | if (dataType & kMemoryTypeFloat) { 284 | int* intMemory = (int*) memory; 285 | // copy size data to size variable 286 | std::memcpy(&size, &intMemory[flagSize], bufferSizeSize); 287 | } 288 | 289 | if (dataType & kMemoryTypeString) { 290 | char* charMemory = (char*) memory; 291 | // copy size data to size variable 292 | std::memcpy(&size, &charMemory[flagSize], bufferSizeSize); 293 | } 294 | return size; 295 | } 296 | 297 | inline size_t readLength(char dataType) { 298 | size_t size = readSize(dataType); 299 | 300 | if (dataType & kMemoryTypeString) { 301 | return size / sizeOfOneChar; 302 | } 303 | 304 | if (dataType & kMemoryTypeFloat) { 305 | return size / sizeOfOneFloat; 306 | } 307 | 308 | if (dataType & kMemoryTypeDouble) { 309 | return size / sizeOfOneDouble; 310 | } 311 | return 0; 312 | } 313 | 314 | /** 315 | * @brief Returns a doible* read from shared memory 316 | * Caller has the obligation to call delete [] on the returning float*. 317 | * 318 | * @return float* 319 | */ 320 | // TODO: might wanna use templated functions here like: readNumericArray() 321 | inline double* readDoubleArray() { 322 | void *memory = _memory.data(); 323 | std::size_t size = readSize(kMemoryTypeDouble); 324 | double* typedMemory = (double*) memory; 325 | 326 | // allocating memory on heap (this might leak) 327 | double *data = new double[size / sizeOfOneDouble](); 328 | 329 | // copy to data buffer 330 | std::memcpy(data, &typedMemory[flagSize + bufferSizeSize], size); 331 | 332 | return data; 333 | } 334 | 335 | /** 336 | * @brief Returns a float* read from shared memory 337 | * Caller has the obligation to call delete [] on the returning float*. 338 | * 339 | * @return float* 340 | */ 341 | inline float* readFloatArray() { 342 | void *memory = _memory.data(); 343 | float *typedMemory = (float *)memory; 344 | 345 | std::size_t size = readSize(kMemoryTypeFloat); 346 | 347 | // allocating memory on heap (this might leak) 348 | float *data = new float[size / sizeOfOneFloat](); 349 | 350 | // copy to data buffer 351 | std::memcpy(data, &typedMemory[flagSize + bufferSizeSize], size); 352 | 353 | return data; 354 | } 355 | 356 | inline std::string readString() { 357 | char* memory = (char*) _memory.data(); 358 | 359 | std::size_t size = readSize(kMemoryTypeString); 360 | 361 | // create a string that copies the data from memory 362 | std::string data = 363 | std::string(&memory[flagSize + bufferSizeSize], size); 364 | 365 | return data; 366 | } 367 | 368 | private: 369 | Memory _memory; 370 | }; 371 | 372 | class SharedMemoryWriteStream { 373 | public: 374 | 375 | SharedMemoryWriteStream(const std::string name, const std::size_t bufferSize, const bool isPersistent): 376 | _memory(name, bufferSize, isPersistent) { 377 | 378 | if (_memory.create() != kOK) { 379 | throw "Shared memory segment could not be created."; 380 | } 381 | } 382 | 383 | inline void close() { 384 | _memory.close(); 385 | } 386 | 387 | // https://stackoverflow.com/questions/18591924/how-to-use-bitmask 388 | inline char getWriteFlags(const char type, 389 | const char currentFlags) { 390 | char flags = type; 391 | 392 | if ((currentFlags & (kMemoryChanged)) == kMemoryChanged) { 393 | // disable flag, leave rest untouched 394 | flags &= ~kMemoryChanged; 395 | } else { 396 | // enable flag, leave rest untouched 397 | flags ^= kMemoryChanged; 398 | } 399 | return flags; 400 | } 401 | 402 | inline void write(const std::string& string) { 403 | char* memory = (char*) _memory.data(); 404 | 405 | // 1) copy change flag into buffer for change detection 406 | char flags = getWriteFlags(kMemoryTypeString, ((char*) _memory.data())[0]); 407 | std::memcpy(&memory[0], &flags, flagSize); 408 | 409 | // 2) copy buffer size into buffer (meta data for deserializing) 410 | const char *stringData = string.data(); 411 | const std::size_t bufferSize = string.size(); 412 | 413 | // write data 414 | std::memcpy(&memory[flagSize], &bufferSize, bufferSizeSize); 415 | 416 | // 3) copy stringData into memory buffer 417 | std::memcpy(&memory[flagSize + bufferSizeSize], stringData, bufferSize); 418 | } 419 | 420 | // TODO: might wanna use template function here for numeric arrays, 421 | // like void writeNumericArray( data, std::size_t length) 422 | inline void write(float* data, std::size_t length) { 423 | float* memory = (float*) _memory.data(); 424 | 425 | char flags = getWriteFlags(kMemoryTypeFloat, ((char*) _memory.data())[0]); 426 | std::memcpy(&memory[0], &flags, flagSize); 427 | 428 | // 2) copy buffer size into buffer (meta data for deserializing) 429 | const std::size_t bufferSize = length * sizeOfOneFloat; 430 | std::memcpy(&memory[flagSize], &bufferSize, bufferSizeSize); 431 | 432 | // 3) copy float* into memory buffer 433 | std::memcpy(&memory[flagSize + bufferSizeSize], data, bufferSize); 434 | } 435 | 436 | inline void write(double* data, std::size_t length) { 437 | double* memory = (double*) _memory.data(); 438 | 439 | char flags = getWriteFlags(kMemoryTypeDouble, ((char*) _memory.data())[0]); 440 | std::memcpy(&memory[0], &flags, flagSize); 441 | 442 | // 2) copy buffer size into buffer (meta data for deserializing) 443 | const std::size_t bufferSize = length * sizeOfOneDouble; 444 | 445 | std::memcpy(&memory[flagSize], &bufferSize, bufferSizeSize); 446 | 447 | // 3) copy double* into memory buffer 448 | std::memcpy(&memory[flagSize + bufferSizeSize], data, bufferSize); 449 | } 450 | 451 | inline void destroy() { 452 | _memory.destroy(); 453 | } 454 | 455 | private: 456 | Memory _memory; 457 | }; 458 | 459 | 460 | }; // namespace lsm 461 | 462 | #endif // INCLUDE_LIBSHAREDMEMORY_HPP_ -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpp_libsharedmemory", 3 | "version": "0.0.5", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpp_libsharedmemory", 3 | "version": "0.0.9", 4 | "description": "C++11 header-only library for using shared memory on Windows, Linux and macOS", 5 | "main": "include/libsharedmemory/libsharedmemory.hpp", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "files": [ 10 | "include" 11 | ], 12 | "scripts": { 13 | "build": "./build.sh", 14 | "pretest": "npm run build", 15 | "test": "./test.sh" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/kyr0/libsharedmemory.git" 20 | }, 21 | "keywords": [ 22 | "cpp", 23 | "shared", 24 | "memory", 25 | "cross-platform", 26 | "library" 27 | ], 28 | "author": "Aron Homberg ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/kyr0/libsharedmemory/issues" 32 | }, 33 | "homepage": "https://github.com/kyr0/libsharedmemory#readme" 34 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyr0/libsharedmemory/c2d5877a984dc75489aeedd490a6a5561fbcdbf9/screenshot.png -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./build/test/lsm_test && echo "ALL TESTS SUCCESSFUL!" || echo "TEST(S) FAILED!" -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(source_files test.cc) 2 | add_executable(lsm_test ${source_files}) 3 | target_link_libraries(lsm_test PUBLIC lsm) 4 | target_include_directories(lsm_test PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 5 | set_property(TARGET lsm_test PROPERTY CXX_STANDARD 11) 6 | 7 | if(MSVC) 8 | target_compile_definitions(lsm_test PRIVATE TYPE_SAFE_TEST_NO_STATIC_ASSERT) 9 | elseif(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) 10 | target_compile_definitions(lsm_test PRIVATE TYPE_SAFE_TEST_NO_STATIC_ASSERT) 11 | endif() 12 | 13 | add_test(NAME test COMMAND lsm_test) -------------------------------------------------------------------------------- /test/lest.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014, 2015, 2016 by Martin Moene 2 | // 3 | // lest is based on ideas by Kevlin Henney, see video at 4 | // http://skillsmatter.com/podcast/agile-testing/kevlin-henney-rethinking-unit-testing-in-c-plus-plus 5 | // 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 7 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef LEST_LEST_HPP_INCLUDED 10 | #define LEST_LEST_HPP_INCLUDED 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #ifdef __clang__ 35 | # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" 36 | # pragma clang diagnostic ignored "-Woverloaded-shift-op-parentheses" 37 | # pragma clang diagnostic ignored "-Wunused-comparison" 38 | # pragma clang diagnostic ignored "-Wunused-value" 39 | #elif defined __GNUC__ 40 | # pragma GCC diagnostic ignored "-Wunused-value" 41 | #endif 42 | 43 | #define lest_VERSION "1.29.0" 44 | 45 | #ifndef lest_FEATURE_AUTO_REGISTER 46 | # define lest_FEATURE_AUTO_REGISTER 0 47 | #endif 48 | 49 | #ifndef lest_FEATURE_COLOURISE 50 | # define lest_FEATURE_COLOURISE 0 51 | #endif 52 | 53 | #ifndef lest_FEATURE_LITERAL_SUFFIX 54 | # define lest_FEATURE_LITERAL_SUFFIX 0 55 | #endif 56 | 57 | #ifndef lest_FEATURE_REGEX_SEARCH 58 | # define lest_FEATURE_REGEX_SEARCH 0 59 | #endif 60 | 61 | #ifndef lest_FEATURE_TIME_PRECISION 62 | #define lest_FEATURE_TIME_PRECISION 0 63 | #endif 64 | 65 | #if lest_FEATURE_REGEX_SEARCH 66 | # include 67 | #endif 68 | 69 | #if ! defined( lest_NO_SHORT_MACRO_NAMES ) && ! defined( lest_NO_SHORT_ASSERTION_NAMES ) 70 | # define MODULE lest_MODULE 71 | 72 | # if ! lest_FEATURE_AUTO_REGISTER 73 | # define CASE lest_CASE 74 | # endif 75 | 76 | # define SETUP lest_SETUP 77 | # define SECTION lest_SECTION 78 | 79 | # define EXPECT lest_EXPECT 80 | # define EXPECT_NOT lest_EXPECT_NOT 81 | # define EXPECT_NO_THROW lest_EXPECT_NO_THROW 82 | # define EXPECT_THROWS lest_EXPECT_THROWS 83 | # define EXPECT_THROWS_AS lest_EXPECT_THROWS_AS 84 | 85 | # define SCENARIO lest_SCENARIO 86 | # define GIVEN lest_GIVEN 87 | # define WHEN lest_WHEN 88 | # define THEN lest_THEN 89 | # define AND_WHEN lest_AND_WHEN 90 | # define AND_THEN lest_AND_THEN 91 | #endif 92 | 93 | #define lest_SCENARIO( sketch ) lest_CASE( lest::text("Scenario: ") + sketch ) 94 | #define lest_GIVEN( context ) lest_SETUP( lest::text( "Given: ") + context ) 95 | #define lest_WHEN( story ) lest_SECTION( lest::text( " When: ") + story ) 96 | #define lest_THEN( story ) lest_SECTION( lest::text( " Then: ") + story ) 97 | #define lest_AND_WHEN( story ) lest_SECTION( lest::text( " And: ") + story ) 98 | #define lest_AND_THEN( story ) lest_SECTION( lest::text( " And: ") + story ) 99 | 100 | #if lest_FEATURE_AUTO_REGISTER 101 | 102 | # define lest_CASE( specification, proposition ) \ 103 | static void lest_FUNCTION( lest::env & ); \ 104 | namespace { lest::add_test lest_REGISTRAR( specification, lest::test( proposition, lest_FUNCTION ) ); } \ 105 | static void lest_FUNCTION( lest::env & lest_env ) 106 | 107 | #else // lest_FEATURE_AUTO_REGISTER 108 | 109 | # define lest_CASE( proposition, ... ) \ 110 | proposition, [__VA_ARGS__]( lest::env & lest_env ) 111 | 112 | # define lest_MODULE( specification, module ) \ 113 | namespace { lest::add_module _( specification, module ); } 114 | 115 | #endif //lest_FEATURE_AUTO_REGISTER 116 | 117 | #define lest_SETUP( context ) \ 118 | for ( int lest__section = 0, lest__count = 1; lest__section < lest__count; lest__count -= 0==lest__section++ ) 119 | 120 | #define lest_SECTION( proposition ) \ 121 | static int lest_UNIQUE( id ) = 0; \ 122 | if ( lest::guard( lest_UNIQUE( id ), lest__section, lest__count ) ) \ 123 | for ( int lest__section = 0, lest__count = 1; lest__section < lest__count; lest__count -= 0==lest__section++ ) 124 | 125 | #define lest_EXPECT( expr ) \ 126 | do { \ 127 | try \ 128 | { \ 129 | if ( lest::result score = lest_DECOMPOSE( expr ) ) \ 130 | throw lest::failure{ lest_LOCATION, #expr, score.decomposition }; \ 131 | else if ( lest_env.pass ) \ 132 | lest::report( lest_env.os, lest::passing{ lest_LOCATION, #expr, score.decomposition }, lest_env.testing ); \ 133 | } \ 134 | catch(...) \ 135 | { \ 136 | lest::inform( lest_LOCATION, #expr ); \ 137 | } \ 138 | } while ( lest::is_false() ) 139 | 140 | #define lest_EXPECT_NOT( expr ) \ 141 | do { \ 142 | try \ 143 | { \ 144 | if ( lest::result score = lest_DECOMPOSE( expr ) ) \ 145 | { \ 146 | if ( lest_env.pass ) \ 147 | lest::report( lest_env.os, lest::passing{ lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ) }, lest_env.testing ); \ 148 | } \ 149 | else \ 150 | throw lest::failure{ lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ) }; \ 151 | } \ 152 | catch(...) \ 153 | { \ 154 | lest::inform( lest_LOCATION, lest::not_expr( #expr ) ); \ 155 | } \ 156 | } while ( lest::is_false() ) 157 | 158 | #define lest_EXPECT_NO_THROW( expr ) \ 159 | do \ 160 | { \ 161 | try \ 162 | { \ 163 | expr; \ 164 | } \ 165 | catch (...) \ 166 | { \ 167 | lest::inform( lest_LOCATION, #expr ); \ 168 | } \ 169 | if ( lest_env.pass ) \ 170 | lest::report( lest_env.os, lest::got_none( lest_LOCATION, #expr ), lest_env.testing ); \ 171 | } while ( lest::is_false() ) 172 | 173 | #define lest_EXPECT_THROWS( expr ) \ 174 | do \ 175 | { \ 176 | try \ 177 | { \ 178 | expr; \ 179 | } \ 180 | catch (...) \ 181 | { \ 182 | if ( lest_env.pass ) \ 183 | lest::report( lest_env.os, lest::got{ lest_LOCATION, #expr }, lest_env.testing ); \ 184 | break; \ 185 | } \ 186 | throw lest::expected{ lest_LOCATION, #expr }; \ 187 | } \ 188 | while ( lest::is_false() ) 189 | 190 | #define lest_EXPECT_THROWS_AS( expr, excpt ) \ 191 | do \ 192 | { \ 193 | try \ 194 | { \ 195 | expr; \ 196 | } \ 197 | catch ( excpt & ) \ 198 | { \ 199 | if ( lest_env.pass ) \ 200 | lest::report( lest_env.os, lest::got{ lest_LOCATION, #expr, lest::of_type( #excpt ) }, lest_env.testing ); \ 201 | break; \ 202 | } \ 203 | catch (...) {} \ 204 | throw lest::expected{ lest_LOCATION, #expr, lest::of_type( #excpt ) }; \ 205 | } \ 206 | while ( lest::is_false() ) 207 | 208 | #define lest_UNIQUE( name ) lest_UNIQUE2( name, __LINE__ ) 209 | #define lest_UNIQUE2( name, line ) lest_UNIQUE3( name, line ) 210 | #define lest_UNIQUE3( name, line ) name ## line 211 | 212 | #define lest_DECOMPOSE( expr ) ( lest::expression_decomposer() << expr ) 213 | 214 | #define lest_FUNCTION lest_UNIQUE(__lest_function__ ) 215 | #define lest_REGISTRAR lest_UNIQUE(__lest_registrar__ ) 216 | 217 | #define lest_LOCATION lest::location{__FILE__, __LINE__} 218 | 219 | namespace lest { 220 | 221 | using text = std::string; 222 | using texts = std::vector; 223 | 224 | struct env; 225 | 226 | struct test 227 | { 228 | text name; 229 | std::function behaviour; 230 | 231 | #if lest_FEATURE_AUTO_REGISTER 232 | test( text name, std::function behaviour ) 233 | : name( name ), behaviour( behaviour ) {} 234 | #endif 235 | }; 236 | 237 | using tests = std::vector; 238 | 239 | #if lest_FEATURE_AUTO_REGISTER 240 | 241 | struct add_test 242 | { 243 | add_test( tests & specification, test const & test_case ) 244 | { 245 | specification.push_back( test_case ); 246 | } 247 | }; 248 | 249 | #else 250 | 251 | struct add_module 252 | { 253 | template 254 | add_module( tests & specification, test const (&module)[N] ) 255 | { 256 | specification.insert( specification.end(), std::begin( module ), std::end( module ) ); 257 | } 258 | }; 259 | 260 | #endif 261 | 262 | struct result 263 | { 264 | const bool passed; 265 | const text decomposition; 266 | 267 | explicit operator bool() { return ! passed; } 268 | }; 269 | 270 | struct location 271 | { 272 | const text file; 273 | const int line; 274 | 275 | location( text file, int line ) 276 | : file( file ), line( line ) {} 277 | }; 278 | 279 | struct comment 280 | { 281 | const text info; 282 | 283 | comment( text info ) : info( info ) {} 284 | explicit operator bool() { return ! info.empty(); } 285 | }; 286 | 287 | struct message : std::runtime_error 288 | { 289 | const text kind; 290 | const location where; 291 | const comment note; 292 | 293 | ~message() throw() {} // GCC 4.6 294 | 295 | message( text kind, location where, text expr, text note = "" ) 296 | : std::runtime_error( expr ), kind( kind ), where( where ), note( note ) {} 297 | }; 298 | 299 | struct failure : message 300 | { 301 | failure( location where, text expr, text decomposition ) 302 | : message{ "failed", where, expr + " for " + decomposition } {} 303 | }; 304 | 305 | struct success : message 306 | { 307 | // using message::message; // VC is lagging here 308 | 309 | success( text kind, location where, text expr, text note = "" ) 310 | : message( kind, where, expr, note ) {} 311 | }; 312 | 313 | struct passing : success 314 | { 315 | passing( location where, text expr, text decomposition ) 316 | : success( "passed", where, expr + " for " + decomposition ) {} 317 | }; 318 | 319 | struct got_none : success 320 | { 321 | got_none( location where, text expr ) 322 | : success( "passed: got no exception", where, expr ) {} 323 | }; 324 | 325 | struct got : success 326 | { 327 | got( location where, text expr ) 328 | : success( "passed: got exception", where, expr ) {} 329 | 330 | got( location where, text expr, text excpt ) 331 | : success( "passed: got exception " + excpt, where, expr ) {} 332 | }; 333 | 334 | struct expected : message 335 | { 336 | expected( location where, text expr, text excpt = "" ) 337 | : message{ "failed: didn't get exception", where, expr, excpt } {} 338 | }; 339 | 340 | struct unexpected : message 341 | { 342 | unexpected( location where, text expr, text note = "" ) 343 | : message{ "failed: got unexpected exception", where, expr, note } {} 344 | }; 345 | 346 | struct guard 347 | { 348 | int & id; 349 | int const & section; 350 | 351 | guard( int & id, int const & section, int & count ) 352 | : id( id ), section( section ) 353 | { 354 | if ( section == 0 ) 355 | id = count++ - 1; 356 | } 357 | operator bool() { return id == section; } 358 | }; 359 | 360 | class approx 361 | { 362 | public: 363 | explicit approx ( double magnitude ) 364 | : epsilon_ { std::numeric_limits::epsilon() * 100 } 365 | , scale_ { 1.0 } 366 | , magnitude_{ magnitude } {} 367 | 368 | approx( approx const & other ) = default; 369 | 370 | static approx custom() { return approx( 0 ); } 371 | 372 | approx operator()( double magnitude ) 373 | { 374 | approx approx ( magnitude ); 375 | approx.epsilon( epsilon_ ); 376 | approx.scale ( scale_ ); 377 | return approx; 378 | } 379 | 380 | double magnitude() const { return magnitude_; } 381 | 382 | approx & epsilon( double epsilon ) { epsilon_ = epsilon; return *this; } 383 | approx & scale ( double scale ) { scale_ = scale; return *this; } 384 | 385 | friend bool operator == ( double lhs, approx const & rhs ) 386 | { 387 | // Thanks to Richard Harris for his help refining this formula. 388 | return std::abs( lhs - rhs.magnitude_ ) < rhs.epsilon_ * ( rhs.scale_ + (std::min)( std::abs( lhs ), std::abs( rhs.magnitude_ ) ) ); 389 | } 390 | 391 | friend bool operator == ( approx const & lhs, double rhs ) { return operator==( rhs, lhs ); } 392 | friend bool operator != ( double lhs, approx const & rhs ) { return !operator==( lhs, rhs ); } 393 | friend bool operator != ( approx const & lhs, double rhs ) { return !operator==( rhs, lhs ); } 394 | 395 | friend bool operator <= ( double lhs, approx const & rhs ) { return lhs < rhs.magnitude_ || lhs == rhs; } 396 | friend bool operator <= ( approx const & lhs, double rhs ) { return lhs.magnitude_ < rhs || lhs == rhs; } 397 | friend bool operator >= ( double lhs, approx const & rhs ) { return lhs > rhs.magnitude_ || lhs == rhs; } 398 | friend bool operator >= ( approx const & lhs, double rhs ) { return lhs.magnitude_ > rhs || lhs == rhs; } 399 | 400 | private: 401 | double epsilon_; 402 | double scale_; 403 | double magnitude_; 404 | }; 405 | 406 | inline bool is_false( ) { return false; } 407 | inline bool is_true ( bool flag ) { return flag; } 408 | 409 | inline text not_expr( text message ) 410 | { 411 | return "! ( " + message + " )"; 412 | } 413 | 414 | inline text with_message( text message ) 415 | { 416 | return "with message \"" + message + "\""; 417 | } 418 | 419 | inline text of_type( text type ) 420 | { 421 | return "of type " + type; 422 | } 423 | 424 | inline void inform( location where, text expr ) 425 | { 426 | try 427 | { 428 | throw; 429 | } 430 | catch( message const & ) 431 | { 432 | throw; 433 | } 434 | catch( std::exception const & e ) 435 | { 436 | throw unexpected{ where, expr, with_message( e.what() ) }; \ 437 | } 438 | catch(...) 439 | { 440 | throw unexpected{ where, expr, "of unknown type" }; \ 441 | } 442 | } 443 | 444 | // Expression decomposition: 445 | 446 | template 447 | auto make_value_string( T const & value ) -> std::string; 448 | 449 | template 450 | auto make_memory_string( T const & item ) -> std::string; 451 | 452 | #if lest_FEATURE_LITERAL_SUFFIX 453 | inline char const * sfx( char const * text ) { return text; } 454 | #else 455 | inline char const * sfx( char const * ) { return ""; } 456 | #endif 457 | 458 | inline std::string to_string( std::nullptr_t ) { return "nullptr"; } 459 | inline std::string to_string( std::string const & text ) { return "\"" + text + "\"" ; } 460 | inline std::string to_string( std::wstring const & text ) ; 461 | 462 | inline std::string to_string( char const * const text ) { return text ? to_string( std::string ( text ) ) : "{null string}"; } 463 | inline std::string to_string( char * const text ) { return text ? to_string( std::string ( text ) ) : "{null string}"; } 464 | inline std::string to_string( wchar_t const * const text ) { return text ? to_string( std::wstring( text ) ) : "{null string}"; } 465 | inline std::string to_string( wchar_t * const text ) { return text ? to_string( std::wstring( text ) ) : "{null string}"; } 466 | 467 | inline std::string to_string( char text ) { return "\'" + std::string( 1, text ) + "\'" ; } 468 | inline std::string to_string( signed char text ) { return "\'" + std::string( 1, text ) + "\'" ; } 469 | inline std::string to_string( unsigned char text ) { return "\'" + std::string( 1, text ) + "\'" ; } 470 | 471 | inline std::string to_string( bool flag ) { return flag ? "true" : "false"; } 472 | 473 | inline std::string to_string( signed short value ) { return make_value_string( value ) ; } 474 | inline std::string to_string( unsigned short value ) { return make_value_string( value ) + sfx("u" ); } 475 | inline std::string to_string( signed int value ) { return make_value_string( value ) ; } 476 | inline std::string to_string( unsigned int value ) { return make_value_string( value ) + sfx("u" ); } 477 | inline std::string to_string( signed long value ) { return make_value_string( value ) + sfx("l" ); } 478 | inline std::string to_string( unsigned long value ) { return make_value_string( value ) + sfx("ul" ); } 479 | inline std::string to_string( signed long long value ) { return make_value_string( value ) + sfx("ll" ); } 480 | inline std::string to_string( unsigned long long value ) { return make_value_string( value ) + sfx("ull"); } 481 | inline std::string to_string( double value ) { return make_value_string( value ) ; } 482 | inline std::string to_string( float value ) { return make_value_string( value ) + sfx("f" ); } 483 | 484 | template 485 | struct is_streamable 486 | { 487 | template 488 | static auto test( int ) -> decltype( std::declval() << std::declval(), std::true_type() ); 489 | 490 | template 491 | static auto test( ... ) -> std::false_type; 492 | 493 | #ifdef _MSC_VER 494 | enum { value = std::is_same< decltype( test(0) ), std::true_type >::value }; 495 | #else 496 | static constexpr bool value = std::is_same< decltype( test(0) ), std::true_type >::value; 497 | #endif 498 | }; 499 | 500 | template 501 | struct is_container 502 | { 503 | template 504 | static auto test( int ) -> decltype( std::declval().begin() == std::declval().end(), std::true_type() ); 505 | 506 | template 507 | static auto test( ... ) -> std::false_type; 508 | 509 | #ifdef _MSC_VER 510 | enum { value = std::is_same< decltype( test(0) ), std::true_type >::value }; 511 | #else 512 | static constexpr bool value = std::is_same< decltype( test(0) ), std::true_type >::value; 513 | #endif 514 | }; 515 | 516 | template 517 | using ForEnum = typename std::enable_if< std::is_enum::value, R>::type; 518 | 519 | template 520 | using ForNonEnum = typename std::enable_if< ! std::is_enum::value, R>::type; 521 | 522 | template 523 | using ForStreamable = typename std::enable_if< is_streamable::value, R>::type; 524 | 525 | template 526 | using ForNonStreamable = typename std::enable_if< ! is_streamable::value, R>::type; 527 | 528 | template 529 | using ForContainer = typename std::enable_if< is_container::value, R>::type; 530 | 531 | template 532 | using ForNonContainer = typename std::enable_if< ! is_container::value, R>::type; 533 | 534 | template 535 | auto make_enum_string( T const & ) -> ForNonEnum 536 | { 537 | return text("[type: ") + typeid(T).name() + "]"; 538 | } 539 | 540 | template 541 | auto make_enum_string( T const & item ) -> ForEnum 542 | { 543 | return to_string( static_cast::type>( item ) ); 544 | } 545 | 546 | template 547 | auto make_string( T const & item ) -> ForNonStreamable 548 | { 549 | return make_enum_string( item ); 550 | } 551 | 552 | template 553 | auto make_string( T const & item ) -> ForStreamable 554 | { 555 | std::ostringstream os; os << item; return os.str(); 556 | } 557 | 558 | template 559 | auto make_string( T * p )-> std::string 560 | { 561 | if ( p ) return make_memory_string( p ); 562 | else return "NULL"; 563 | } 564 | 565 | template 566 | auto make_string( R C::* p ) -> std::string 567 | { 568 | if ( p ) return make_memory_string( p ); 569 | else return "NULL"; 570 | } 571 | 572 | template 573 | auto make_string( std::pair const & pair ) -> std::string 574 | { 575 | std::ostringstream oss; 576 | oss << "{ " << to_string( pair.first ) << ", " << to_string( pair.second ) << " }"; 577 | return oss.str(); 578 | } 579 | 580 | template 581 | struct make_tuple_string 582 | { 583 | static std::string make( TU const & tuple ) 584 | { 585 | std::ostringstream os; 586 | os << to_string( std::get( tuple ) ) << ( N < std::tuple_size::value ? ", ": " "); 587 | return make_tuple_string::make( tuple ) + os.str(); 588 | } 589 | }; 590 | 591 | template 592 | struct make_tuple_string 593 | { 594 | static std::string make( TU const & ) { return ""; } 595 | }; 596 | 597 | template 598 | auto make_string( std::tuple const & tuple ) -> std::string 599 | { 600 | return "{ " + make_tuple_string, sizeof...(TS)>::make( tuple ) + "}"; 601 | } 602 | 603 | template 604 | auto to_string( T const & item ) -> ForNonContainer 605 | { 606 | return make_string( item ); 607 | } 608 | 609 | template 610 | auto to_string( C const & cont ) -> ForContainer 611 | { 612 | std::ostringstream os; 613 | os << "{ "; 614 | for ( auto & x : cont ) 615 | { 616 | os << to_string( x ) << ", "; 617 | } 618 | os << "}"; 619 | return os.str(); 620 | } 621 | 622 | inline 623 | auto to_string( std::wstring const & text ) -> std::string 624 | { 625 | std::string result; result.reserve( text.size() ); 626 | 627 | for( auto & chr : text ) 628 | { 629 | result += chr <= 0xff ? static_cast( chr ) : '?'; 630 | } 631 | return to_string( result ); 632 | } 633 | 634 | template 635 | auto make_value_string( T const & value ) -> std::string 636 | { 637 | std::ostringstream os; os << value; return os.str(); 638 | } 639 | 640 | inline 641 | auto make_memory_string( void const * item, std::size_t size ) -> std::string 642 | { 643 | // reverse order for little endian architectures: 644 | 645 | auto is_little_endian = [] 646 | { 647 | union U { int i = 1; char c[ sizeof(int) ]; }; 648 | 649 | return 1 != U{}.c[ sizeof(int) - 1 ]; 650 | }; 651 | 652 | int i = 0, end = static_cast( size ), inc = 1; 653 | 654 | if ( is_little_endian() ) { i = end - 1; end = inc = -1; } 655 | 656 | unsigned char const * bytes = static_cast( item ); 657 | 658 | std::ostringstream os; 659 | os << "0x" << std::setfill( '0' ) << std::hex; 660 | for ( ; i != end; i += inc ) 661 | { 662 | os << std::setw(2) << static_cast( bytes[i] ) << " "; 663 | } 664 | return os.str(); 665 | } 666 | 667 | template 668 | auto make_memory_string( T const & item ) -> std::string 669 | { 670 | return make_memory_string( &item, sizeof item ); 671 | } 672 | 673 | inline 674 | auto to_string( approx const & appr ) -> std::string 675 | { 676 | return to_string( appr.magnitude() ); 677 | } 678 | 679 | template 680 | auto to_string( L const & lhs, std::string op, R const & rhs ) -> std::string 681 | { 682 | std::ostringstream os; os << to_string( lhs ) << " " << op << " " << to_string( rhs ); return os.str(); 683 | } 684 | 685 | template 686 | struct expression_lhs 687 | { 688 | const L lhs; 689 | 690 | expression_lhs( L lhs ) : lhs( lhs ) {} 691 | 692 | operator result() { return result{ !!lhs, to_string( lhs ) }; } 693 | 694 | template result operator==( R const & rhs ) { return result{ lhs == rhs, to_string( lhs, "==", rhs ) }; } 695 | template result operator!=( R const & rhs ) { return result{ lhs != rhs, to_string( lhs, "!=", rhs ) }; } 696 | template result operator< ( R const & rhs ) { return result{ lhs < rhs, to_string( lhs, "<" , rhs ) }; } 697 | template result operator<=( R const & rhs ) { return result{ lhs <= rhs, to_string( lhs, "<=", rhs ) }; } 698 | template result operator> ( R const & rhs ) { return result{ lhs > rhs, to_string( lhs, ">" , rhs ) }; } 699 | template result operator>=( R const & rhs ) { return result{ lhs >= rhs, to_string( lhs, ">=", rhs ) }; } 700 | }; 701 | 702 | struct expression_decomposer 703 | { 704 | template 705 | expression_lhs operator<< ( L const & operand ) 706 | { 707 | return expression_lhs( operand ); 708 | } 709 | }; 710 | 711 | // Reporter: 712 | 713 | #if lest_FEATURE_COLOURISE 714 | 715 | inline text red ( text words ) { return "\033[1;31m" + words + "\033[0m"; } 716 | inline text green( text words ) { return "\033[1;32m" + words + "\033[0m"; } 717 | inline text gray ( text words ) { return "\033[1;30m" + words + "\033[0m"; } 718 | 719 | inline bool starts_with( text words, text with ) 720 | { 721 | return 0 == words.find( with ); 722 | } 723 | 724 | inline text replace( text words, text from, text to ) 725 | { 726 | size_t pos = words.find( from ); 727 | return pos == std::string::npos ? words : words.replace( pos, from.length(), to ); 728 | } 729 | 730 | inline text colour( text words ) 731 | { 732 | if ( starts_with( words, "failed" ) ) return replace( words, "failed", red ( "failed" ) ); 733 | else if ( starts_with( words, "passed" ) ) return replace( words, "passed", green( "passed" ) ); 734 | 735 | return replace( words, "for", gray( "for" ) ); 736 | } 737 | 738 | inline bool is_cout( std::ostream & os ) { return &os == &std::cout; } 739 | 740 | struct colourise 741 | { 742 | const text words; 743 | 744 | colourise( text words ) 745 | : words( words ) {} 746 | 747 | // only colourise for std::cout, not for a stringstream as used in tests: 748 | 749 | std::ostream & operator()( std::ostream & os ) const 750 | { 751 | return is_cout( os ) ? os << colour( words ) : os << words; 752 | } 753 | }; 754 | 755 | inline std::ostream & operator<<( std::ostream & os, colourise words ) { return words( os ); } 756 | #else 757 | inline text colourise( text words ) { return words; } 758 | #endif 759 | 760 | inline text pluralise( text word, int n ) 761 | { 762 | return n == 1 ? word : word + "s"; 763 | } 764 | 765 | inline std::ostream & operator<<( std::ostream & os, comment note ) 766 | { 767 | return os << (note ? " " + note.info : "" ); 768 | } 769 | 770 | inline std::ostream & operator<<( std::ostream & os, location where ) 771 | { 772 | #ifdef __GNUG__ 773 | return os << where.file << ":" << where.line; 774 | #else 775 | return os << where.file << "(" << where.line << ")"; 776 | #endif 777 | } 778 | 779 | inline void report( std::ostream & os, message const & e, text test ) 780 | { 781 | os << e.where << ": " << colourise( e.kind ) << e.note << ": " << test << ": " << colourise( e.what() ) << std::endl; 782 | } 783 | 784 | // Test runner: 785 | 786 | #if lest_FEATURE_REGEX_SEARCH 787 | inline bool search( text re, text line ) 788 | { 789 | return std::regex_search( line, std::regex( re ) ); 790 | } 791 | #else 792 | inline bool search( text part, text line ) 793 | { 794 | auto case_insensitive_equal = []( char a, char b ) 795 | { 796 | return tolower( a ) == tolower( b ); 797 | }; 798 | 799 | return std::search( 800 | line.begin(), line.end(), 801 | part.begin(), part.end(), case_insensitive_equal ) != line.end(); 802 | } 803 | #endif 804 | 805 | inline bool match( texts whats, text line ) 806 | { 807 | for ( auto & what : whats ) 808 | { 809 | if ( search( what, line ) ) 810 | return true; 811 | } 812 | return false; 813 | } 814 | 815 | inline bool select( text name, texts include ) 816 | { 817 | auto none = []( texts args ) { return args.size() == 0; }; 818 | 819 | #if lest_FEATURE_REGEX_SEARCH 820 | auto hidden = []( text name ){ return match( { "\\[\\..*", "\\[hide\\]" }, name ); }; 821 | #else 822 | auto hidden = []( text name ){ return match( { "[.", "[hide]" }, name ); }; 823 | #endif 824 | 825 | if ( none( include ) ) 826 | { 827 | return ! hidden( name ); 828 | } 829 | 830 | bool any = false; 831 | for ( auto pos = include.rbegin(); pos != include.rend(); ++pos ) 832 | { 833 | auto & part = *pos; 834 | 835 | if ( part == "@" || part == "*" ) 836 | return true; 837 | 838 | if ( search( part, name ) ) 839 | return true; 840 | 841 | if ( '!' == part[0] ) 842 | { 843 | any = true; 844 | if ( search( part.substr(1), name ) ) 845 | return false; 846 | } 847 | else 848 | { 849 | any = false; 850 | } 851 | } 852 | return any && ! hidden( name ); 853 | } 854 | 855 | inline int indefinite( int repeat ) { return repeat == -1; } 856 | 857 | using seed_t = unsigned long; 858 | 859 | struct options 860 | { 861 | bool help = false; 862 | bool abort = false; 863 | bool count = false; 864 | bool list = false; 865 | bool tags = false; 866 | bool time = false; 867 | bool pass = false; 868 | bool lexical = false; 869 | bool random = false; 870 | bool version = false; 871 | int repeat = 1; 872 | seed_t seed = 0; 873 | }; 874 | 875 | struct env 876 | { 877 | std::ostream & os; 878 | bool pass; 879 | text testing; 880 | 881 | env( std::ostream & os, bool pass ) 882 | : os( os ), pass( pass ), testing() {} 883 | 884 | env & operator()( text test ) 885 | { 886 | testing = test; return *this; 887 | } 888 | }; 889 | 890 | struct action 891 | { 892 | std::ostream & os; 893 | 894 | action( std::ostream & os ) : os( os ) {} 895 | 896 | action( action const & ) = delete; 897 | void operator=( action const & ) = delete; 898 | 899 | operator int() { return 0; } 900 | bool abort() { return false; } 901 | action & operator()( test ) { return *this; } 902 | }; 903 | 904 | struct print : action 905 | { 906 | print( std::ostream & os ) : action( os ) {} 907 | 908 | print & operator()( test testing ) 909 | { 910 | os << testing.name << "\n"; return *this; 911 | } 912 | }; 913 | 914 | inline texts tags( text name, texts result = {} ) 915 | { 916 | auto none = std::string::npos; 917 | auto lb = name.find_first_of( "[" ); 918 | auto rb = name.find_first_of( "]" ); 919 | 920 | if ( lb == none || rb == none ) 921 | return result; 922 | 923 | result.emplace_back( name.substr( lb, rb - lb + 1 ) ); 924 | 925 | return tags( name.substr( rb + 1 ), result ); 926 | } 927 | 928 | struct ptags : action 929 | { 930 | std::set result; 931 | 932 | ptags( std::ostream & os ) : action( os ), result() {} 933 | 934 | ptags & operator()( test testing ) 935 | { 936 | for ( auto & tag : tags( testing.name ) ) 937 | result.insert( tag ); 938 | 939 | return *this; 940 | } 941 | 942 | ~ptags() 943 | { 944 | std::copy( result.begin(), result.end(), std::ostream_iterator( os, "\n" ) ); 945 | } 946 | }; 947 | 948 | struct count : action 949 | { 950 | int n = 0; 951 | 952 | count( std::ostream & os ) : action( os ) {} 953 | 954 | count & operator()( test ) { ++n; return *this; } 955 | 956 | ~count() 957 | { 958 | os << n << " selected " << pluralise("test", n) << "\n"; 959 | } 960 | }; 961 | 962 | struct timer 963 | { 964 | using time = std::chrono::high_resolution_clock; 965 | 966 | time::time_point start = time::now(); 967 | 968 | double elapsed_seconds() const 969 | { 970 | return 1e-6 * std::chrono::duration_cast< std::chrono::microseconds >( time::now() - start ).count(); 971 | } 972 | }; 973 | 974 | struct times : action 975 | { 976 | env output; 977 | options option; 978 | int selected = 0; 979 | int failures = 0; 980 | 981 | timer total; 982 | 983 | times( std::ostream & os, options option ) 984 | : action( os ), output( os, option.pass ), option( option ), total() 985 | { 986 | os << std::setfill(' ') << std::fixed << std::setprecision( lest_FEATURE_TIME_PRECISION ); 987 | } 988 | 989 | operator int() { return failures; } 990 | 991 | bool abort() { return option.abort && failures > 0; } 992 | 993 | times & operator()( test testing ) 994 | { 995 | timer t; 996 | 997 | try 998 | { 999 | testing.behaviour( output( testing.name ) ); 1000 | } 1001 | catch( message const & ) 1002 | { 1003 | ++failures; 1004 | } 1005 | 1006 | os << std::setw(3) << ( 1000 * t.elapsed_seconds() ) << " ms: " << testing.name << "\n"; 1007 | 1008 | return *this; 1009 | } 1010 | 1011 | ~times() 1012 | { 1013 | os << "Elapsed time: " << std::setprecision(1) << total.elapsed_seconds() << " s\n"; 1014 | } 1015 | }; 1016 | 1017 | struct confirm : action 1018 | { 1019 | env output; 1020 | options option; 1021 | int selected = 0; 1022 | int failures = 0; 1023 | 1024 | confirm( std::ostream & os, options option ) 1025 | : action( os ), output( os, option.pass ), option( option ) {} 1026 | 1027 | operator int() { return failures; } 1028 | 1029 | bool abort() { return option.abort && failures > 0; } 1030 | 1031 | confirm & operator()( test testing ) 1032 | { 1033 | try 1034 | { 1035 | ++selected; testing.behaviour( output( testing.name ) ); 1036 | } 1037 | catch( message const & e ) 1038 | { 1039 | ++failures; report( os, e, testing.name ); 1040 | } 1041 | return *this; 1042 | } 1043 | 1044 | ~confirm() 1045 | { 1046 | if ( failures > 0 ) 1047 | { 1048 | os << failures << " out of " << selected << " selected " << pluralise("test", selected) << " " << colourise( "failed.\n" ); 1049 | } 1050 | else if ( option.pass ) 1051 | { 1052 | os << "All " << selected << " selected " << pluralise("test", selected) << " " << colourise( "passed.\n" ); 1053 | } 1054 | } 1055 | }; 1056 | 1057 | template 1058 | bool abort( Action & perform ) 1059 | { 1060 | return perform.abort(); 1061 | } 1062 | 1063 | template< typename Action > 1064 | Action && for_test( tests specification, texts in, Action && perform, int n = 1 ) 1065 | { 1066 | for ( int i = 0; indefinite( n ) || i < n; ++i ) 1067 | { 1068 | for ( auto & testing : specification ) 1069 | { 1070 | if ( select( testing.name, in ) ) 1071 | if ( abort( perform( testing ) ) ) 1072 | return std::move( perform ); 1073 | } 1074 | } 1075 | return std::move( perform ); 1076 | } 1077 | 1078 | inline void sort( tests & specification ) 1079 | { 1080 | auto test_less = []( test const & a, test const & b ) { return a.name < b.name; }; 1081 | std::sort( specification.begin(), specification.end(), test_less ); 1082 | } 1083 | 1084 | inline void shuffle( tests & specification, options option ) 1085 | { 1086 | std::shuffle( specification.begin(), specification.end(), std::mt19937( option.seed ) ); 1087 | } 1088 | 1089 | // workaround MinGW bug, http://stackoverflow.com/a/16132279: 1090 | 1091 | inline int stoi( text num ) 1092 | { 1093 | return std::strtol( num.c_str(), NULL, 10 ); 1094 | } 1095 | 1096 | inline bool is_number( text arg ) 1097 | { 1098 | return std::all_of( arg.begin(), arg.end(), ::isdigit ); 1099 | } 1100 | 1101 | inline seed_t seed( text opt, text arg ) 1102 | { 1103 | if ( is_number( arg ) ) 1104 | return static_cast( lest::stoi( arg ) ); 1105 | 1106 | if ( arg == "time" ) 1107 | return static_cast( std::chrono::high_resolution_clock::now().time_since_epoch().count() ); 1108 | 1109 | throw std::runtime_error( "expecting 'time' or positive number with option '" + opt + "', got '" + arg + "' (try option --help)" ); 1110 | } 1111 | 1112 | inline int repeat( text opt, text arg ) 1113 | { 1114 | const int num = lest::stoi( arg ); 1115 | 1116 | if ( indefinite( num ) || num >= 0 ) 1117 | return num; 1118 | 1119 | throw std::runtime_error( "expecting '-1' or positive number with option '" + opt + "', got '" + arg + "' (try option --help)" ); 1120 | } 1121 | 1122 | inline auto split_option( text arg ) -> std::tuple 1123 | { 1124 | auto pos = arg.rfind( '=' ); 1125 | 1126 | return pos == text::npos 1127 | ? std::make_tuple( arg, "" ) 1128 | : std::make_tuple( arg.substr( 0, pos ), arg.substr( pos + 1 ) ); 1129 | } 1130 | 1131 | inline auto split_arguments( texts args ) -> std::tuple 1132 | { 1133 | options option; texts in; 1134 | 1135 | bool in_options = true; 1136 | 1137 | for ( auto & arg : args ) 1138 | { 1139 | if ( in_options ) 1140 | { 1141 | text opt, val; 1142 | std::tie( opt, val ) = split_option( arg ); 1143 | 1144 | if ( opt[0] != '-' ) { in_options = false; } 1145 | else if ( opt == "--" ) { in_options = false; continue; } 1146 | else if ( opt == "-h" || "--help" == opt ) { option.help = true; continue; } 1147 | else if ( opt == "-a" || "--abort" == opt ) { option.abort = true; continue; } 1148 | else if ( opt == "-c" || "--count" == opt ) { option.count = true; continue; } 1149 | else if ( opt == "-g" || "--list-tags" == opt ) { option.tags = true; continue; } 1150 | else if ( opt == "-l" || "--list-tests" == opt ) { option.list = true; continue; } 1151 | else if ( opt == "-t" || "--time" == opt ) { option.time = true; continue; } 1152 | else if ( opt == "-p" || "--pass" == opt ) { option.pass = true; continue; } 1153 | else if ( "--version" == opt ) { option.version = true; continue; } 1154 | else if ( opt == "--order" && "declared" == val ) { /* by definition */ ; continue; } 1155 | else if ( opt == "--order" && "lexical" == val ) { option.lexical = true; continue; } 1156 | else if ( opt == "--order" && "random" == val ) { option.random = true; continue; } 1157 | else if ( opt == "--random-seed" ) { option.seed = seed ( "--random-seed", val ); continue; } 1158 | else if ( opt == "--repeat" ) { option.repeat = repeat( "--repeat" , val ); continue; } 1159 | else throw std::runtime_error( "unrecognised option '" + arg + "' (try option --help)" ); 1160 | } 1161 | in.push_back( arg ); 1162 | } 1163 | return std::make_tuple( option, in ); 1164 | } 1165 | 1166 | inline int usage( std::ostream & os ) 1167 | { 1168 | os << 1169 | "\nUsage: test [options] [test-spec ...]\n" 1170 | "\n" 1171 | "Options:\n" 1172 | " -h, --help this help message\n" 1173 | " -a, --abort abort at first failure\n" 1174 | " -c, --count count selected tests\n" 1175 | " -g, --list-tags list tags of selected tests\n" 1176 | " -l, --list-tests list selected tests\n" 1177 | " -p, --pass also report passing tests\n" 1178 | " -t, --time list duration of selected tests\n" 1179 | " --order=declared use source code test order (default)\n" 1180 | " --order=lexical use lexical sort test order\n" 1181 | " --order=random use random test order\n" 1182 | " --random-seed=n use n for random generator seed\n" 1183 | " --random-seed=time use time for random generator seed\n" 1184 | " --repeat=n repeat selected tests n times (-1: indefinite)\n" 1185 | " --version report lest version and compiler used\n" 1186 | " -- end options\n" 1187 | "\n" 1188 | "Test specification:\n" 1189 | " \"@\", \"*\" all tests, unless excluded\n" 1190 | " empty all tests, unless tagged [hide] or [.optional-name]\n" 1191 | #if lest_FEATURE_REGEX_SEARCH 1192 | " \"re\" select tests that match regular expression\n" 1193 | " \"!re\" omit tests that match regular expression\n" 1194 | #else 1195 | " \"text\" select tests that contain text (case insensitive)\n" 1196 | " \"!text\" omit tests that contain text (case insensitive)\n" 1197 | #endif 1198 | ; 1199 | return 0; 1200 | } 1201 | 1202 | inline text compiler() 1203 | { 1204 | std::ostringstream os; 1205 | #if defined (__clang__ ) 1206 | os << "clang " << __clang_version__; 1207 | #elif defined (__GNUC__ ) 1208 | os << "gcc " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__; 1209 | #elif defined ( _MSC_VER ) 1210 | os << "MSVC " << (_MSC_VER / 100 - 5 - (_MSC_VER < 1900)) << " (" << _MSC_VER << ")"; 1211 | #else 1212 | os << "[compiler]"; 1213 | #endif 1214 | return os.str(); 1215 | } 1216 | 1217 | inline int version( std::ostream & os ) 1218 | { 1219 | os << "lest version " << lest_VERSION << "\n" 1220 | << "Compiled with " << compiler() << " on " << __DATE__ << " at " << __TIME__ << ".\n" 1221 | << "For more information, see https://github.com/martinmoene/lest.\n"; 1222 | return 0; 1223 | } 1224 | 1225 | inline int run( tests specification, texts arguments, std::ostream & os = std::cout ) 1226 | { 1227 | try 1228 | { 1229 | options option; texts in; 1230 | std::tie( option, in ) = split_arguments( arguments ); 1231 | 1232 | if ( option.lexical ) { sort( specification ); } 1233 | if ( option.random ) { shuffle( specification, option ); } 1234 | 1235 | if ( option.help ) { return usage ( os ); } 1236 | if ( option.version ) { return version ( os ); } 1237 | if ( option.count ) { return for_test( specification, in, count( os ) ); } 1238 | if ( option.list ) { return for_test( specification, in, print( os ) ); } 1239 | if ( option.tags ) { return for_test( specification, in, ptags( os ) ); } 1240 | if ( option.time ) { return for_test( specification, in, times( os, option ) ); } 1241 | 1242 | return for_test( specification, in, confirm( os, option ), option.repeat ); 1243 | } 1244 | catch ( std::exception const & e ) 1245 | { 1246 | os << "Error: " << e.what() << "\n"; 1247 | return 1; 1248 | } 1249 | } 1250 | 1251 | inline int run( tests specification, int argc, char * argv[], std::ostream & os = std::cout ) 1252 | { 1253 | return run( specification, texts( argv + 1, argv + argc ), os ); 1254 | } 1255 | 1256 | template 1257 | int run( test const (&specification)[N], texts arguments, std::ostream & os = std::cout ) 1258 | { 1259 | return run( tests( specification, specification + N ), arguments, os ); 1260 | } 1261 | 1262 | template 1263 | int run( test const (&specification)[N], std::ostream & os = std::cout ) 1264 | { 1265 | return run( tests( specification, specification + N ), {}, os ); 1266 | } 1267 | 1268 | template 1269 | int run( test const (&specification)[N], int argc, char * argv[], std::ostream & os = std::cout ) 1270 | { 1271 | return run( tests( specification, specification + N ), texts( argv + 1, argv + argc ), os ); 1272 | } 1273 | 1274 | } // namespace lest 1275 | 1276 | #endif // LEST_LEST_HPP_INCLUDED -------------------------------------------------------------------------------- /test/test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lest.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace lsm; 10 | 11 | const lest::test specification[] = { 12 | CASE("shared memory can be created and opened and transfer uint8_t") { 13 | Memory memoryWriter {"lsmtest", 64, true}; 14 | EXPECT(kOK == memoryWriter.create()); 15 | 16 | ((uint8_t*)memoryWriter.data())[0] = 0x11; 17 | ((uint8_t*)memoryWriter.data())[1] = 0x34; 18 | 19 | Memory memoryReader{"lsmtest", 64, true}; 20 | 21 | EXPECT(kOK == memoryReader.open()); 22 | 23 | std::cout << "1. single uint8_t: SUCCESS" << std::endl; 24 | 25 | EXPECT(0x11 == ((uint8_t*)memoryReader.data())[0]); 26 | EXPECT(0x34 == ((uint8_t *)memoryReader.data())[1]); 27 | 28 | memoryWriter.close(); 29 | memoryReader.close(); 30 | }, 31 | 32 | CASE("non-existing shared memory objects err") { 33 | Memory memoryReader{"lsmtest2", 64, true}; 34 | EXPECT(kErrorOpeningFailed == memoryReader.open()); 35 | std::cout << "2. error when opening non-existing segment: SUCCESS" << std::endl; 36 | }, 37 | 38 | CASE("using MemoryStreamWriter and MemoryStreamReader to transfer std::string") { 39 | 40 | std::string dataToTransfer = "{ foo: 'coolest IPC ever! 🧑‍💻' }"; 41 | 42 | SharedMemoryWriteStream write${"jsonPipe", 65535, true}; 43 | SharedMemoryReadStream read${"jsonPipe", 65535, true}; 44 | 45 | write$.write(dataToTransfer); 46 | 47 | std::string dataString = read$.readString(); 48 | 49 | std::cout << "3. std::string (UTF8): SUCCESS | " << dataString << std::endl; 50 | 51 | EXPECT(dataToTransfer == dataString); 52 | 53 | write$.close(); 54 | read$.close(); 55 | } 56 | , 57 | 58 | CASE("Write more then less, then read") { 59 | 60 | for (int i=0; i<1000; i++) { 61 | SharedMemoryWriteStream write${"varyingDataSizePipe", 65535, true}; 62 | SharedMemoryReadStream read${"varyingDataSizePipe", 65535, true}; 63 | 64 | std::string t1 = "abccde" + std::to_string(i); 65 | write$.write(t1); 66 | 67 | std::string t2 = "abc" + std::to_string(i); 68 | write$.write(t2); 69 | 70 | std::string dataString = read$.readString(); 71 | 72 | EXPECT(t2 == dataString); 73 | 74 | write$.close(); 75 | read$.close(); 76 | } 77 | std::cout << "4. std::string more/less: SUCCESS; 1000 runs" 78 | << std::endl; 79 | 80 | }, 81 | 82 | CASE("Write a lot") { 83 | std::string blob = 84 | "ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab" 85 | "😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃a" 86 | "b😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃" 87 | "ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab😃ab" 88 | "😃ab😃ab😃ab😃ab😃ab"; 89 | 90 | SharedMemoryWriteStream write${"blobDataSizePipe", 65535, true}; 91 | SharedMemoryReadStream read${"blobDataSizePipe", 65535, true}; 92 | 93 | write$.write(blob); 94 | 95 | std::string dataString = read$.readString(); 96 | 97 | EXPECT(blob == dataString); 98 | 99 | std::cout << "5. std::string blob: SUCCESS" << std::endl; 100 | }, 101 | 102 | CASE("Can read flags, sets the right datatype and data change bit flips") { 103 | 104 | SharedMemoryWriteStream write${"blobDataSizePipe2", 65535, true}; 105 | SharedMemoryReadStream read${"blobDataSizePipe2", 65535, true}; 106 | 107 | write$.write("foo!"); 108 | 109 | char flagsData = read$.readFlags(); 110 | 111 | EXPECT(read$.readLength(kMemoryTypeString) == 4); 112 | 113 | std::bitset<8> flags(flagsData); 114 | 115 | EXPECT(!!(flagsData & kMemoryTypeString)); 116 | 117 | std::cout << "6. status flag shows string data type flag: SUCCESS: 0b" 118 | << flags << std::endl; 119 | 120 | EXPECT(!!(flagsData & kMemoryChanged)); 121 | 122 | std::cout << "6.1 status flag has the change bit set: SUCCESS: 0b" 123 | << flags << std::endl; 124 | 125 | write$.write("foo!"); 126 | 127 | char flagsData2 = read$.readFlags(); 128 | std::bitset<8> flags2(flagsData2); 129 | 130 | EXPECT(!!(flagsData2 & ~kMemoryChanged)); 131 | 132 | write$.write("foo!1"); 133 | 134 | char flagsData3 = read$.readFlags(); 135 | std::bitset<8> flags3(flagsData3); 136 | EXPECT(!!(flagsData3 & kMemoryChanged)); 137 | 138 | std::cout 139 | << "6.2 status bit flips to zero when writing again: SUCCESS: 0b" 140 | << flags2 << std::endl; 141 | 142 | std::cout 143 | << "6.3 status bit flips to one when writing again: SUCCESS: 0b" 144 | << flags3 << std::endl; 145 | 146 | write$.close(); 147 | read$.close(); 148 | }, 149 | 150 | CASE("Can write and read a float* array") { 151 | 152 | float numbers[72] = { 153 | 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 154 | 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 155 | 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 156 | 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 157 | 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 158 | 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 3.14f, 1.3f, 3.4f, 6.14f, 159 | }; 160 | 161 | SharedMemoryWriteStream write${"numberPipe", 65535, true}; 162 | SharedMemoryReadStream read${"numberPipe", 65535, true}; 163 | 164 | write$.write(numbers, 72); 165 | 166 | EXPECT(read$.readLength(kMemoryTypeFloat) == 72); 167 | 168 | char flagsData = read$.readFlags(); 169 | std::bitset<8> flags(flagsData); 170 | 171 | std::cout 172 | << "Flags for float* read: 0b" 173 | << flags << std::endl; 174 | EXPECT(!!(flagsData & kMemoryTypeFloat)); 175 | EXPECT(!!(flagsData & kMemoryChanged)); 176 | 177 | float* numbersReadPtr = read$.readFloatArray(); 178 | 179 | EXPECT(numbers[0] == numbersReadPtr[0]); 180 | EXPECT(numbers[1] == numbersReadPtr[1]); 181 | EXPECT(numbers[2] == numbersReadPtr[2]); 182 | EXPECT(numbers[3] == numbersReadPtr[3]); 183 | EXPECT(numbers[71] == numbersReadPtr[71]); 184 | 185 | std::cout << "7. float[72]: SUCCESS" << std::endl; 186 | 187 | write$.write(numbers, 72); 188 | 189 | char flagsData2 = read$.readFlags(); 190 | std::bitset<8> flags2(flagsData2); 191 | 192 | EXPECT(!!(flagsData2 & ~kMemoryChanged)); 193 | 194 | write$.write(numbers, 72); 195 | 196 | char flagsData3 = read$.readFlags(); 197 | std::bitset<8> flags3(flagsData3); 198 | EXPECT(!!(flagsData3 & kMemoryChanged)); 199 | 200 | std::cout 201 | << "7.1 status bit flips to zero when writing again: SUCCESS: 0b" 202 | << flags2 << std::endl; 203 | 204 | std::cout 205 | << "7.2 status bit flips to one when writing again: SUCCESS: 0b" 206 | << flags3 << std::endl; 207 | 208 | 209 | delete[] numbersReadPtr; 210 | write$.close(); 211 | read$.close(); 212 | }, 213 | 214 | CASE("Can write and read a double* array") { 215 | 216 | double numbers[72] = { 217 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 218 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 219 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 220 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 221 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 222 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 223 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 224 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 225 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 226 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 227 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 228 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 229 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 230 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 231 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 232 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 233 | 1.38038450934, 3.43723642783, 3.1438540345, 331.390696969, 234 | 3.483045044, 6.14848338383, 7.3293840293, 8.4234234, 235 | }; 236 | 237 | SharedMemoryWriteStream write${"numberPipe", 65535, true}; 238 | SharedMemoryReadStream read${"numberPipe", 65535, true}; 239 | 240 | write$.write(numbers, 72); 241 | 242 | EXPECT(read$.readLength(kMemoryTypeDouble) == 72); 243 | 244 | char flagsData = read$.readFlags(); 245 | std::bitset<8> flags(flagsData); 246 | 247 | std::cout 248 | << "Flags for double* read: 0b" 249 | << flags << std::endl; 250 | EXPECT(!!(flagsData & kMemoryTypeDouble)); 251 | EXPECT(!!(flagsData & kMemoryChanged)); 252 | 253 | double* numbersReadPtr = read$.readDoubleArray(); 254 | 255 | EXPECT(numbers[0] == numbersReadPtr[0]); 256 | EXPECT(numbers[1] == numbersReadPtr[1]); 257 | EXPECT(numbers[2] == numbersReadPtr[2]); 258 | EXPECT(numbers[3] == numbersReadPtr[3]); 259 | EXPECT(numbers[71] == numbersReadPtr[71]); 260 | 261 | std::cout << "8. double[72]: SUCCESS" << std::endl; 262 | 263 | write$.write(numbers, 72); 264 | 265 | char flagsData2 = read$.readFlags(); 266 | std::bitset<8> flags2(flagsData2); 267 | 268 | EXPECT(!!(flagsData2 & ~kMemoryChanged)); 269 | 270 | write$.write(numbers, 72); 271 | 272 | char flagsData3 = read$.readFlags(); 273 | std::bitset<8> flags3(flagsData3); 274 | EXPECT(!!(flagsData3 & kMemoryChanged)); 275 | 276 | std::cout 277 | << "8.1 status bit flips to zero when writing again: SUCCESS: 0b" 278 | << flags2 << std::endl; 279 | 280 | std::cout 281 | << "8.2 status bit flips to one when writing again: SUCCESS: 0b" 282 | << flags3 << std::endl; 283 | delete[] numbersReadPtr; 284 | write$.close(); 285 | read$.close(); 286 | }, 287 | }; 288 | 289 | int main (int argc, char *argv[]) { 290 | return lest::run(specification, argc, argv); 291 | } 292 | --------------------------------------------------------------------------------