├── .gitignore ├── .travis.yml ├── 3rdParty └── cute │ ├── .gitignore │ ├── .travis.yml │ ├── CMakeConfigCompilers.cmake │ ├── CMakeLists.txt │ ├── LICENSE.txt │ ├── README.md │ ├── config-win64.bat │ ├── cute │ ├── CMakeLists.txt │ ├── capture.hpp │ ├── cleanup_guard.hpp │ ├── context.hpp │ ├── cute.hpp │ ├── detail │ │ ├── decomposer.hpp │ │ ├── eval_context.hpp │ │ ├── macros_impl.hpp │ │ ├── test_registry.hpp │ │ ├── utils.hpp │ │ └── utils_file.hpp │ ├── exception.hpp │ ├── macros.hpp │ ├── reporters │ │ ├── reporter_ide.hpp │ │ └── reporter_junit.hpp │ ├── test.hpp │ ├── test_result.hpp │ ├── test_suite_result.hpp │ ├── thread.hpp │ ├── tick.hpp │ └── to_string.hpp │ ├── docs │ ├── multi_threaded_tests.md │ ├── temp_folders.md │ ├── test_runners_and_reporters.md │ └── writing_tests.md │ └── test │ ├── CMakeLists.txt │ ├── cute_cleanup_guard_unittests.cpp │ ├── cute_core_unittests.cpp │ ├── cute_file_unittests.cpp │ ├── cute_thread_unittests.cpp │ ├── cute_xml_unittests.cpp │ └── main.cpp ├── CMakeConfigCompilers.cmake ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── config-VS2012-win64.bat ├── config-VS2015-win64.bat ├── signals-cpp ├── CMakeLists.txt ├── config.hpp ├── connection.hpp ├── connections.hpp ├── signal.hpp └── signals.hpp └── test ├── CMakeLists.txt ├── main.cpp └── signals_unittests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | Build/ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | compiler: 4 | - gcc 5 | # - clang 6 | 7 | env: 8 | - BUILD_CONFIG=Debug 9 | - BUILD_CONFIG=Release 10 | 11 | before_install: 12 | # pull in some common scripts for setting up Travis CI build environment 13 | - git clone --depth=1 https://github.com/Kosta-Github/travis-setup.git 14 | - sudo travis-setup/update-environment.sh 15 | - sudo travis-setup/update-gcc-latest.sh 16 | 17 | install: 18 | - cmake -DCMAKE_BUILD_TYPE=$BUILD_CONFIG 19 | 20 | script: 21 | - make -k && ctest -V --timeout 60 22 | -------------------------------------------------------------------------------- /3rdParty/cute/.gitignore: -------------------------------------------------------------------------------- 1 | Build 2 | -------------------------------------------------------------------------------- /3rdParty/cute/.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | compiler: 4 | - gcc 5 | # - clang 6 | 7 | env: 8 | - BUILD_CONFIG=Debug 9 | - BUILD_CONFIG=Release 10 | 11 | before_install: 12 | # pull in some common scripts for setting up Travis CI build environment 13 | - git clone --depth=1 https://github.com/Kosta-Github/travis-setup.git 14 | - sudo travis-setup/update-environment.sh 15 | - sudo travis-setup/update-gcc-latest.sh 16 | 17 | install: 18 | - cmake -DCMAKE_BUILD_TYPE=$BUILD_CONFIG 19 | 20 | script: 21 | - make -k && ctest -V --timeout 60 22 | 23 | -------------------------------------------------------------------------------- /3rdParty/cute/CMakeConfigCompilers.cmake: -------------------------------------------------------------------------------- 1 | # Check for cxx0x, cxx11, and libcxx11 options 2 | include(CheckCXXCompilerFlag) 3 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" HAS_CXX0x) 4 | CHECK_CXX_COMPILER_FLAG("-std=c++11" HAS_CXX11) 5 | CHECK_CXX_COMPILER_FLAG("-std=c++11 -stdlib=libc++" HAS_LIBCXX11) 6 | 7 | # switch to the right compiler 8 | if(HAS_LIBCXX11) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++") 10 | elseif(HAS_CXX11) 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 12 | elseif(HAS_CXX0x) 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 14 | elseif(MSVC) 15 | add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) 16 | endif() 17 | 18 | # check for adding a reference to the pthread lib 19 | CHECK_CXX_COMPILER_FLAG("-pthread" HAS_PTHREAD) 20 | if(HAS_PTHREAD) 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") 22 | endif(HAS_PTHREAD) 23 | -------------------------------------------------------------------------------- /3rdParty/cute/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.8) 2 | 3 | project(cute CXX) 4 | message("configure: cute") 5 | 6 | include(CMakeConfigCompilers.cmake) 7 | 8 | include_directories(.) 9 | 10 | # configure unit tests 11 | enable_testing() 12 | 13 | add_subdirectory(cute) 14 | add_subdirectory(test) 15 | -------------------------------------------------------------------------------- /3rdParty/cute/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Konstantin (Kosta) Baumann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /3rdParty/cute/README.md: -------------------------------------------------------------------------------- 1 | cute: C++ unit test executor [![Build Status](https://travis-ci.org/Kosta-Github/cute.png)](https://travis-ci.org/Kosta-Github/cute) 2 | ============================ 3 | 4 | Feature set: 5 | - uses C++11 features 6 | - no external dependencies 7 | - header-only 8 | - supports multi-threaded test cases and is thread-safe 9 | - supports tags for test case filtering 10 | - can use custom reporters (provided: simple ide/console reporter, JUnit XML reporter) 11 | - supports per test case temporary folder with automatic cleanup 12 | - planned: provides capabilities for mocking classes and interfaces 13 | 14 | 15 | status: beta 16 | ============ 17 | 18 | The development has just started and is not considered to be production-ready at the moment. 19 | 20 | 21 | usage 22 | ===== 23 | 24 | - [Writing tests](docs/writing_tests.md) 25 | - [Test runners and reporters](docs/test_runners_and_reporters.md) 26 | - [Using temporary folders](docs/temp_folders.md) 27 | - [Multi-threaded tests](docs/multi_threaded_tests.md) 28 | 29 | 30 | related work 31 | ============ 32 | 33 | `cute` is based on the following ideas and frameworks: 34 | - [lest](https://github.com/martinmoene/lest) 35 | - [catch](https://github.com/philsquared/Catch) 36 | - [turtle](http://turtle.sourceforge.net/) 37 | 38 | 39 | dependencies 40 | ============ 41 | 42 | - [cmake](http://cmake.org/): only required for building and executing the self-test unit test suite 43 | -------------------------------------------------------------------------------- /3rdParty/cute/config-win64.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmake -H. -BBuild -G "Visual Studio 11 Win64" 3 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(cute CXX) 4 | message("configure: cute lib") 5 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/capture.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | namespace cute { 12 | 13 | struct capture { 14 | std::string const name; 15 | std::string const value; 16 | 17 | inline capture(std::string name_ = "", std::string value_ = "") : name(std::move(name_)), value(std::move(value_)) { } 18 | }; 19 | 20 | struct captures { 21 | inline captures() { } 22 | 23 | inline captures( 24 | capture cap0_, 25 | capture cap1_ = capture(), 26 | capture cap2_ = capture(), 27 | capture cap3_ = capture(), 28 | capture cap4_ = capture(), 29 | capture cap5_ = capture(), 30 | capture cap6_ = capture(), 31 | capture cap7_ = capture(), 32 | capture cap8_ = capture(), 33 | capture cap9_ = capture() 34 | ) { 35 | add(std::move(cap0_)); 36 | add(std::move(cap1_)); 37 | add(std::move(cap2_)); 38 | add(std::move(cap3_)); 39 | add(std::move(cap4_)); 40 | add(std::move(cap5_)); 41 | add(std::move(cap6_)); 42 | add(std::move(cap7_)); 43 | add(std::move(cap8_)); 44 | add(std::move(cap9_)); 45 | } 46 | 47 | inline captures( 48 | captures caps0_, 49 | captures caps1_ 50 | ) { 51 | for(auto&& c : caps0_.list) { add(std::move(c)); } 52 | for(auto&& c : caps1_.list) { add(std::move(c)); } 53 | } 54 | 55 | inline void add(capture cap) { 56 | if(!cap.name.empty()) { list.emplace_back(std::move(cap)); } 57 | } 58 | 59 | std::vector list; 60 | }; 61 | 62 | } // namespace cute 63 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/cleanup_guard.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #if defined(CUTE_HAS_NOEXCEPT) 13 | # define CUTE_NOEXCEPT noexcept 14 | #else // defined(CUTE_HAS_NOEXCEPT) 15 | # define CUTE_NOEXCEPT throw() 16 | #endif // defined(CUTE_HAS_NOEXCEPT) 17 | 18 | namespace cute { 19 | 20 | /// The cleanup_guard helper class (also known as scope_guard) uses the RAII principle for 21 | /// ensuring that custom cleanup code is run even in cases of exceptions and early returns, 22 | /// while it reduces the coding complexity for doing so at the same time. This is achieved 23 | /// by creating a cleanup_guard object and passing in a lambda function to the constructor 24 | /// defining the custom cleanupaction that is required for cleaning up a certain resource 25 | /// gracefully in case of failures. As soon as the cleanup_guard object gets destructed 26 | /// (at the end of its scope) the cleanup function gets called and the specified action is 27 | /// executed. In cases where you want to dismiss the cleanup action (e.g., because all 28 | /// operations on the resource were successful and you want to keep it) you can call 29 | /// dismiss() on the cleanup_guard object and the cleanup action will not be triggered 30 | /// anymore. This comes in especially useful if you need to deal with several dependent 31 | /// resources and you want to maintain some "transactional" behavior such as keep them all 32 | /// or none of them. 33 | /// 34 | /// Example: 35 | /// void func() { 36 | /// const char* const filename = "my file.txt"; 37 | /// std::ofstream file(filename); 38 | /// if(!file.is_open()) { return false; } 39 | /// 40 | /// cleanup_guard cleanup([&] { 41 | /// file.close(); remove(filename); 42 | /// }); 43 | /// 44 | /// if(!write_to_file(file)) { return false; } // cleanup called 45 | /// 46 | /// if(!write_to_file(file)) { throw ...; } // cleanup called 47 | /// 48 | /// if(!file.good()) { return false; } // cleanup called 49 | /// 50 | /// // all ops were successful => keep the file 51 | /// cleanup.dismiss(); 52 | /// return true; 53 | /// } 54 | /// 55 | class cleanup_guard { 56 | public: 57 | /// Constructs a new cleanup_guard object by passing in a cleanup 58 | /// callback function. The passed in callback can be a nullptr or 59 | /// it needs to be convertible to std::function and be 60 | /// callable for the whole lifetime of the constructed cleanup_guard 61 | /// object or at least up to the point where dismiss() or cleanup() 62 | /// is called. 63 | template 64 | inline explicit cleanup_guard(FUNC&& func) : m_func(std::forward(func)) { } 65 | 66 | // support move semantics 67 | inline cleanup_guard(cleanup_guard&& other) CUTE_NOEXCEPT { operator=(std::move(other)); } 68 | inline cleanup_guard& operator=(cleanup_guard&& other) CUTE_NOEXCEPT { 69 | assert(!m_func); std::swap(m_func, other.m_func); return *this; 70 | } 71 | 72 | /// Destructs the cleanup_guard and executes the cleanup callback 73 | /// if it hasn't been dismissed before. 74 | inline ~cleanup_guard() { cleanup(); } 75 | 76 | /// Dismisses the cleanup callback function so that it gets not called 77 | /// in the destructor. 78 | inline void dismiss() { m_func = nullptr; } 79 | 80 | /// Executes the cleanup callback function explicitly and earlier than 81 | /// on scope exit. 82 | inline void cleanup() { if(m_func) { m_func(); m_func = nullptr; } } 83 | 84 | private: 85 | // disable some auto-generated members 86 | cleanup_guard(); // = delete; 87 | cleanup_guard(const cleanup_guard& other); // = delete; 88 | cleanup_guard& operator=(const cleanup_guard& other); // = delete; 89 | 90 | private: 91 | std::function m_func; 92 | }; 93 | 94 | } // cute 95 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/context.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "cleanup_guard.hpp" 9 | #include "exception.hpp" 10 | #include "macros.hpp" 11 | #include "test.hpp" 12 | #include "test_result.hpp" 13 | #include "test_suite_result.hpp" 14 | #include "detail/eval_context.hpp" 15 | #include "detail/test_registry.hpp" 16 | 17 | #include 18 | 19 | namespace cute { 20 | 21 | inline std::string temp_folder() { 22 | return cute::detail::eval_context::current().create_temp_folder(); 23 | } 24 | 25 | struct context { 26 | std::vector> reporters; 27 | std::string include_tags; 28 | std::string exclude_tags; 29 | 30 | inline test_suite_result run() const { 31 | return run(detail::test_registry::instance().tests); 32 | } 33 | 34 | inline test_suite_result run( 35 | std::vector const& tests 36 | ) const { 37 | auto const time_start_all = detail::time_now(); 38 | 39 | // set a new termination handler which prints out the current test case and aborts the application 40 | auto prev_termination_handler = std::set_terminate(terminate_handler); 41 | auto restore_termination_handler = cute::cleanup_guard([&]() { std::set_terminate(prev_termination_handler); }); 42 | 43 | detail::eval_context eval; 44 | eval.reporters = &reporters; 45 | 46 | auto const incl_tags = detail::parse_tags(include_tags); 47 | auto const excl_tags = detail::parse_tags(exclude_tags); 48 | 49 | for(auto&& test : tests) { 50 | eval.current_test = &test; 51 | 52 | auto skip = detail::skip_test(test.tags, incl_tags, excl_tags); 53 | 54 | auto rep = test_result(test); 55 | rep.result = (skip ? result_type::skip : result_type::pass); 56 | 57 | if(!skip) { 58 | auto const time_start = detail::time_now(); 59 | 60 | try { 61 | auto const count_start = eval.checks_performed.load(); 62 | 63 | --eval.checks_performed; // decr by one since CUTE_DETAIL_ASSERT() below will increment it again 64 | CUTE_DETAIL_ASSERT((static_cast(test.test_case()), true), test.file, test.line, "", cute::captures(), cute::captures()); 65 | 66 | auto const count_end = eval.checks_performed.load(); 67 | if(count_start == count_end) { 68 | eval.register_exception(cute::exception(test.file, test.line, "no check performed in test case")); 69 | } 70 | 71 | // ensure that the temp folder can be cleared and that no file locks exists after the test case 72 | if(!eval.delete_temp_folder()) { 73 | eval.register_exception(cute::exception(test.file, test.line, "could not cleanup temp folder")); 74 | } 75 | } catch(...) { 76 | // nothing to do 77 | } 78 | 79 | // ensure that the temp folder gets also cleared even in case of a test failure 80 | eval.delete_temp_folder(); 81 | 82 | auto const time_end = detail::time_now(); 83 | rep.duration_ms = detail::time_diff_ms(time_start, time_end); 84 | 85 | if(eval.excp) { 86 | rep.result = result_type::fail; 87 | rep.excp = std::move(eval.excp); 88 | } 89 | } 90 | 91 | for(auto&& reporter : reporters) { 92 | if(reporter) { reporter(rep, eval.test_results.size(), tests.size()); } 93 | } 94 | 95 | eval.test_results.emplace_back(rep); 96 | } 97 | 98 | eval.current_test = nullptr; 99 | 100 | auto const time_end_all = detail::time_now(); 101 | eval.duration_ms = detail::time_diff_ms(time_start_all, time_end_all); 102 | 103 | return eval.result(); 104 | } 105 | 106 | private: 107 | static void terminate_handler() { 108 | auto&& ctx = cute::detail::eval_context::current(); 109 | 110 | if(auto test = ctx.current_test) { 111 | auto rep = test_result(*test, result_type::fatal); 112 | rep.excp = std::make_shared(test->file, test->line, "std::terminate() called"); 113 | 114 | for(auto&& reporter : *ctx.reporters) { 115 | if(reporter) { reporter(rep, 0, 1); } 116 | } 117 | } 118 | 119 | std::abort(); 120 | } 121 | 122 | }; 123 | 124 | } // namespace cute 125 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/cute.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "context.hpp" 9 | #include "exception.hpp" 10 | #include "macros.hpp" 11 | #include "test.hpp" 12 | #include "test_result.hpp" 13 | #include "test_suite_result.hpp" 14 | #include "tick.hpp" 15 | #include "thread.hpp" 16 | #include "detail/decomposer.hpp" 17 | #include "detail/test_registry.hpp" 18 | #include "detail/utils.hpp" 19 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/detail/decomposer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "../to_string.hpp" 9 | 10 | namespace cute { 11 | namespace detail { 12 | 13 | struct decomposer { 14 | 15 | template 16 | struct expression { 17 | T const& obj; 18 | 19 | inline expression(T const& obj_) : obj(obj_) { } 20 | 21 | inline operator std::string() const { return to_string(obj); } 22 | 23 | template inline std::string operator!() const { std::ostringstream os; os << '!' << to_string(obj); return os.str(); } 24 | 25 | template inline std::string operator==(R const& rhs) const { return bin_op_to_string("==", rhs); } 26 | template inline std::string operator!=(R const& rhs) const { return bin_op_to_string("!=", rhs); } 27 | template inline std::string operator< (R const& rhs) const { return bin_op_to_string("<" , rhs); } 28 | template inline std::string operator<=(R const& rhs) const { return bin_op_to_string("<=", rhs); } 29 | template inline std::string operator> (R const& rhs) const { return bin_op_to_string(">" , rhs); } 30 | template inline std::string operator>=(R const& rhs) const { return bin_op_to_string(">=", rhs); } 31 | 32 | template inline std::string operator&&(R const& rhs) const { return bin_op_to_string("&&", rhs); } 33 | template inline std::string operator||(R const& rhs) const { return bin_op_to_string("||", rhs); } 34 | 35 | template 36 | inline std::string bin_op_to_string(char const* op, R const& rhs) const { 37 | std::ostringstream os; os << to_string(obj) << ' ' << op << ' ' << to_string(rhs); return os.str(); 38 | } 39 | }; 40 | 41 | template 42 | inline expression operator->*(T const& op) const { return expression(op); } 43 | }; 44 | 45 | } // namespace detail 46 | } // namespace cute 47 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/detail/eval_context.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "../exception.hpp" 9 | #include "../test.hpp" 10 | #include "../test_result.hpp" 11 | #include "../test_suite_result.hpp" 12 | #include "utils_file.hpp" 13 | 14 | #include 15 | 16 | namespace cute { 17 | namespace detail { 18 | 19 | struct eval_context { 20 | eval_context() : 21 | reporters(nullptr), current_test(nullptr), 22 | checks_performed(0), duration_ms(0), 23 | m_previous(g_current) 24 | { 25 | g_current = this; 26 | } 27 | 28 | ~eval_context() { 29 | assert(g_current == this); 30 | g_current = m_previous; 31 | } 32 | 33 | std::vector> const* reporters; 34 | 35 | test const* current_test; 36 | 37 | std::atomic checks_performed; 38 | std::size_t duration_ms; 39 | 40 | std::mutex exceptions_mutex; 41 | std::shared_ptr excp; 42 | inline void register_exception(exception ex, bool throw_exception = true) { 43 | std::lock_guard lock(exceptions_mutex); 44 | if(excp) { 45 | if(throw_exception) { throw ex; } 46 | return; 47 | } 48 | excp = std::make_shared(std::move(ex)); 49 | if(throw_exception) { 50 | throw *excp; 51 | } 52 | } 53 | 54 | std::mutex temp_folder_mutex; 55 | std::string temp_folder; 56 | inline std::string create_temp_folder() { 57 | std::lock_guard lock(temp_folder_mutex); 58 | if(!temp_folder.empty()) { return temp_folder; } 59 | temp_folder = cute::detail::create_temp_folder(); 60 | return temp_folder; 61 | } 62 | inline bool delete_temp_folder() { 63 | std::lock_guard lock(temp_folder_mutex); 64 | if(temp_folder.empty()) { return true; } 65 | auto res = cute::detail::delete_folder(temp_folder); 66 | temp_folder.clear(); 67 | return res; 68 | } 69 | 70 | std::vector test_results; 71 | 72 | inline test_suite_result result() { 73 | return test_suite_result( 74 | std::move(test_results), checks_performed, duration_ms 75 | ); 76 | } 77 | 78 | static eval_context* g_current; 79 | eval_context* const m_previous; 80 | inline static eval_context& current() { assert(g_current); return *g_current; } 81 | }; 82 | 83 | } // namespace detail 84 | } // namespace cute 85 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/detail/macros_impl.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #define CUTE_DETAIL_UNIQUE_NAME_LINE_2(NAME, LINE) NAME##LINE 9 | #define CUTE_DETAIL_UNIQUE_NAME_LINE(NAME, LINE) CUTE_DETAIL_UNIQUE_NAME_LINE_2(NAME, LINE) 10 | #define CUTE_DETAIL_UNIQUE_NAME(NAME) CUTE_DETAIL_UNIQUE_NAME_LINE(NAME, __LINE__) 11 | 12 | #define CUTE_DETAIL_ASSERT(EXPR_EVAL, FILE, LINE, EXPR_TEXT, CAPTURES1, CAPTURES2) \ 13 | try { \ 14 | ++cute::detail::eval_context::current().checks_performed; \ 15 | if(!static_cast(EXPR_EVAL)) { \ 16 | cute::detail::eval_context::current().register_exception( \ 17 | cute::exception(FILE, LINE, "validation failed: " EXPR_TEXT, "", CAPTURES1, CAPTURES2) \ 18 | ); \ 19 | } \ 20 | } catch(cute::exception const& ex) { \ 21 | cute::detail::eval_context::current().register_exception(ex); \ 22 | } catch(std::exception const &ex) { \ 23 | cute::detail::eval_context::current().register_exception( \ 24 | cute::exception(FILE, LINE, "got an unexpected exception with message \"" + std::string(ex.what()) + "\"", EXPR_TEXT, CAPTURES1, CAPTURES2) \ 25 | ); \ 26 | } catch(...) { \ 27 | cute::detail::eval_context::current().register_exception( \ 28 | cute::exception(FILE, LINE, "got an unexpected exception of unknown type", EXPR_TEXT, CAPTURES1, CAPTURES2) \ 29 | ); \ 30 | } 31 | 32 | #define CUTE_DETAIL_ASSERT_THROWS_AS(EXPR, EXCEPT, CAPTURES) \ 33 | { \ 34 | auto CUTE_DETAIL_UNIQUE_NAME(exception_ok) = false; \ 35 | try { \ 36 | ++cute::detail::eval_context::current().checks_performed; \ 37 | static_cast(EXPR); \ 38 | } catch(EXCEPT const&) { \ 39 | CUTE_DETAIL_UNIQUE_NAME(exception_ok) = true; \ 40 | } catch(...) { \ 41 | } \ 42 | if(!CUTE_DETAIL_UNIQUE_NAME(exception_ok)) { \ 43 | cute::detail::eval_context::current().register_exception( \ 44 | cute::exception(__FILE__, __LINE__, "didn't get an expected exception of type \"" #EXCEPT "\"", #EXPR, CAPTURES) \ 45 | ); \ 46 | } \ 47 | } 48 | 49 | #define CUTE_DETAIL_INIT() \ 50 | cute::detail::test_registry& cute::detail::test_registry::instance() { static test_registry reg; return reg; } \ 51 | cute::detail::eval_context* cute::detail::eval_context::g_current = nullptr; 52 | 53 | 54 | #define CUTE_DETAIL_TEST(...) \ 55 | static void CUTE_DETAIL_UNIQUE_NAME(cute_detail_test)(); \ 56 | static bool CUTE_DETAIL_UNIQUE_NAME(cute_detail_test_reg) = \ 57 | cute::detail::test_registry::instance().add(cute::test( \ 58 | __FILE__, __LINE__, CUTE_DETAIL_UNIQUE_NAME(cute_detail_test), __VA_ARGS__ \ 59 | )); \ 60 | static void CUTE_DETAIL_UNIQUE_NAME(cute_detail_test)() 61 | 62 | #define CUTE_DETAIL_DECOMPOSE(EXPR) static_cast(cute::detail::decomposer()->* EXPR) 63 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/detail/test_registry.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "../test.hpp" 9 | 10 | #include 11 | 12 | namespace cute { 13 | namespace detail { 14 | 15 | struct test_registry { 16 | std::vector tests; 17 | inline bool add(test t) { 18 | tests.emplace_back(std::move(t)); return true; 19 | } 20 | 21 | static test_registry& instance(); 22 | }; 23 | 24 | } // namespace detail 25 | } // namespace cute 26 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/detail/utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "utils_file.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace cute { 15 | namespace detail { 16 | 17 | inline auto trim(std::string const& s) -> std::string { 18 | auto start = 0; 19 | while(s[start] == ' ') { ++start; } 20 | if(start >= s.size()) { return ""; } 21 | auto end = s.size() - 1; 22 | while(s[end] == ' ') { --end; } 23 | return s.substr(start, end - start + 1); 24 | } 25 | 26 | inline auto parse_tags(std::string const& tags) -> std::set { 27 | std::set res; 28 | 29 | auto start = std::string::size_type(0); 30 | while(start < tags.size()) { 31 | auto end = tags.find(',', start); 32 | auto tag = trim(tags.substr(start, end - start)); 33 | if(!tag.empty()) { res.insert(std::move(tag)); } 34 | start = ((end < tags.npos) ? end + 1 : tags.npos); 35 | } 36 | 37 | return res; 38 | } 39 | 40 | inline bool skip_test( 41 | std::set const& test_tags, 42 | std::set const& include_tags, 43 | std::set const& exclude_tags 44 | ) { 45 | if(test_tags.empty()) { return false; } 46 | 47 | for(auto&& include : include_tags) { 48 | if(test_tags.find(include) == test_tags.end()) { return true; } 49 | } 50 | 51 | for(auto&& exclude : exclude_tags) { 52 | if(test_tags.find(exclude) != test_tags.end()) { return true; } 53 | } 54 | 55 | return false; 56 | } 57 | 58 | inline std::string xml_encode( 59 | std::string const& str 60 | ) { 61 | std::string res; 62 | res.reserve(2 * str.size()); 63 | for(auto c : str) { 64 | switch(c) { 65 | case '&': res += "&"; break; 66 | case '\"': res += """; break; 67 | case '\'': res += "'"; break; 68 | case '<': res += "<"; break; 69 | case '>': res += ">"; break; 70 | default: res += c; break; 71 | } 72 | } 73 | return res; 74 | } 75 | 76 | inline auto time_now() -> decltype(std::chrono::high_resolution_clock::now()) { 77 | return std::chrono::high_resolution_clock::now(); 78 | } 79 | 80 | template 81 | inline std::size_t time_diff_ms(T&& start, T&& end) { 82 | auto diff = std::chrono::duration_cast(end - start).count(); 83 | return static_cast(diff); 84 | } 85 | 86 | } // namespace detail 87 | } // namespace cute 88 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/detail/utils_file.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #if defined(WIN32) 12 | # include 13 | # include 14 | # include 15 | #else // defined(WIN32) 16 | # include 17 | # include 18 | # include 19 | #endif // defined(WIN32) 20 | 21 | namespace cute { 22 | namespace detail { 23 | 24 | #if defined(WIN32) 25 | 26 | inline std::string create_temp_folder() { 27 | char buf[MAX_PATH+1] = { 0x00 }; 28 | if(!GetTempPath(MAX_PATH, buf)) { return ""; } 29 | 30 | auto folder = std::string(buf); 31 | while(!folder.empty() && (folder.back() == '\\')) { folder.pop_back(); } 32 | 33 | folder += std::string("\\cute_") + std::to_string(std::time(nullptr)) + '\\'; 34 | if(_mkdir(folder.c_str())) { return ""; } 35 | 36 | return folder; 37 | } 38 | 39 | inline bool delete_folder(std::string dir) { 40 | std::replace(dir.begin(), dir.end(), '/', '\\'); 41 | while(!dir.empty() && (dir.back() == '\\')) { dir.pop_back(); } // remove trailing backslashes 42 | 43 | WIN32_FIND_DATA ffd; 44 | auto hFind = FindFirstFile((dir + "\\*").c_str(), &ffd); // list all files in that folder 45 | if (hFind == INVALID_HANDLE_VALUE) { return false; } 46 | 47 | bool success = true; 48 | do { 49 | auto sub = std::string(ffd.cFileName); 50 | if((sub != ".") && (sub != "..")) { // skip "." and ".." folders 51 | auto sub_path = dir + "\\" + sub; 52 | if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 53 | success = delete_folder(sub_path) && success; 54 | } else { 55 | // remove read-only flag 56 | success = (SetFileAttributes(sub_path.c_str(), FILE_ATTRIBUTE_NORMAL) != 0) && success; 57 | success = (remove(sub_path.c_str()) == 0) && success; 58 | } 59 | } 60 | } while(FindNextFile(hFind, &ffd) != 0); 61 | 62 | success = (FindClose(hFind) != FALSE) && success; 63 | success = (_rmdir(dir.c_str()) == 0) && success; 64 | 65 | return success; 66 | } 67 | 68 | #else // defined(WIN32) 69 | 70 | inline std::string create_temp_folder() { 71 | auto dir = std::string("/tmp/cute_XXXXXX"); 72 | auto res = mkdtemp(&dir[0]); 73 | return (res ? (dir + '/') : ""); 74 | } 75 | 76 | inline bool delete_folder(std::string dir) { 77 | while(!dir.empty() && (dir.back() == '/')) { dir.pop_back(); } // remove trailing slashes 78 | if(dir.empty()) { return false; } 79 | 80 | auto dp = opendir(dir.c_str()); 81 | if(!dp) { return false; } 82 | 83 | bool success = true; 84 | while(auto ep = readdir(dp)) { 85 | auto sub = std::string(ep->d_name); 86 | if((sub != ".") && (sub != "..")) { // skip "." and ".." folders 87 | auto sub_path = dir + "/" + sub; 88 | 89 | struct stat stat_buf; 90 | if(stat(sub_path.c_str(), &stat_buf) == 0) { 91 | if(S_ISREG(stat_buf.st_mode) || S_ISLNK(stat_buf.st_mode)) { 92 | success = (unlink(sub_path.c_str()) == 0) && success; 93 | } else if(S_ISDIR(stat_buf.st_mode)) { 94 | success = delete_folder(sub_path) && success; 95 | } else { 96 | success = false; 97 | } 98 | } else { 99 | success = false; 100 | } 101 | } 102 | } 103 | 104 | success = (closedir(dp) == 0) && success; 105 | success = (rmdir(dir.c_str()) == 0) && success; 106 | 107 | return success; 108 | } 109 | 110 | #endif // defined(WIN32) 111 | 112 | } // namespace detail 113 | } // namespace cute 114 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/exception.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "capture.hpp" 9 | 10 | #include 11 | #include 12 | 13 | namespace cute { 14 | 15 | // The cute exception is not derived from std::exception by intend, 16 | // otherwise we could not differentiate within CUTE_THROWS_AS() 17 | // between expected std::exceptions and cute::exceptions. 18 | struct exception { 19 | std::string const file; 20 | int const line; 21 | std::string const message; 22 | std::string const expression; 23 | cute::captures const captures; 24 | 25 | inline exception( 26 | std::string file_, 27 | int line_, 28 | std::string message_, 29 | std::string expression_ = "", 30 | cute::captures caps1_ = cute::captures(), 31 | cute::captures caps2_ = cute::captures() 32 | ) : 33 | file(std::move(file_)), 34 | line(std::move(line_)), 35 | message(std::move(message_)), 36 | expression(std::move(expression_)), 37 | captures(std::move(caps1_), std::move(caps2_)) 38 | { 39 | assert(!message.empty()); 40 | } 41 | }; 42 | 43 | } // namespace cute 44 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/macros.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "detail/macros_impl.hpp" 9 | 10 | #define CUTE_INIT() CUTE_DETAIL_INIT() 11 | #define CUTE_TEST(...) CUTE_DETAIL_TEST(__VA_ARGS__) 12 | #define CUTE_CAPTURE(EXPR) cute::capture(#EXPR, CUTE_DETAIL_DECOMPOSE(EXPR)) 13 | 14 | #define CUTE_ASSERT(EXPR, ...) CUTE_DETAIL_ASSERT(EXPR, __FILE__, __LINE__, #EXPR, CUTE_CAPTURE(EXPR), cute::captures(__VA_ARGS__)) 15 | #define CUTE_ASSERT_THROWS_NOT(EXPR, ...) CUTE_DETAIL_ASSERT((static_cast(EXPR), true), __FILE__, __LINE__, #EXPR, cute::captures(__VA_ARGS__), cute::captures()) 16 | #define CUTE_ASSERT_THROWS(EXPR, ...) CUTE_DETAIL_ASSERT_THROWS_AS(EXPR, std::exception, cute::captures(__VA_ARGS__)) 17 | #define CUTE_ASSERT_THROWS_AS(EXPR, EXCEPT, ...) CUTE_DETAIL_ASSERT_THROWS_AS(EXPR, EXCEPT, cute::captures(__VA_ARGS__)) 18 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/reporters/reporter_ide.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "../test_result.hpp" 9 | 10 | #include 11 | 12 | namespace cute { 13 | 14 | namespace detail { 15 | 16 | inline std::string ide_make_file_line_string( 17 | std::string file, 18 | int line 19 | ) { 20 | #if defined(__GNUG__) 21 | file += ":" + std::to_string(line) + ": "; 22 | #else // defined(__GNUG__) 23 | file += "(" + std::to_string(line) + "): "; 24 | #endif // defined(__GNUG__) 25 | return file; 26 | } 27 | 28 | } // namespace detail 29 | 30 | inline void reporter_ide_to_stream( 31 | std::ostream& out, 32 | test_result const& res, 33 | std::size_t test_index_cur, 34 | std::size_t test_index_max 35 | ) { 36 | auto type = std::string(); 37 | switch(res.result) { 38 | case result_type::pass: type = "pass: "; break; 39 | case result_type::skip: type = "skip: "; break; 40 | case result_type::fail: type = "error: "; break; 41 | case result_type::fatal: type = "fatal: "; break; 42 | default: assert(false); 43 | } 44 | 45 | auto test_header = detail::ide_make_file_line_string(res.test.file, res.test.line) + type; 46 | out << test_header << res.test.name; 47 | if(test_index_max > 1) { out << " [" << (test_index_cur + 1) << "/" << test_index_max << "]"; } 48 | if(res.result != result_type::skip) { 49 | out << "; duration: " << res.duration_ms << " ms"; 50 | } 51 | out << std::endl; 52 | 53 | if(auto ex = res.excp.get()) { 54 | auto ex_header = detail::ide_make_file_line_string(ex->file, ex->line) + type; 55 | 56 | out << ex_header << " reason: " << ex->message << std::endl; 57 | if(!ex->expression.empty()) { out << ex_header << " expression: " << ex->expression << std::endl; } 58 | 59 | for(auto&& c : ex->captures.list) { 60 | out << ex_header << " with: " << c.name; 61 | if(c.name != c.value) { out << " => " << c.value; } 62 | out << std::endl; 63 | } 64 | } 65 | } 66 | 67 | inline void reporter_ide( 68 | test_result const& res, 69 | std::size_t test_index_cur, 70 | std::size_t test_index_max 71 | ) { 72 | reporter_ide_to_stream((res.result == result_type::pass) ? std::cout : std::cerr, res, test_index_cur, test_index_max); 73 | } 74 | 75 | inline void reporter_ide_summary_to_stream( 76 | std::ostream& out, 77 | test_suite_result const& results 78 | ) { 79 | out << std::endl; 80 | out << "test suite summary:" << std::endl; 81 | out << results.test_cases_passed << "/" << results.test_results.size() << " tests passed." << std::endl; 82 | out << results.test_cases_skipped << "/" << results.test_results.size() << " tests skipped." << std::endl; 83 | out << results.test_cases_failed << "/" << results.test_results.size() << " tests failed." << std::endl; 84 | out << results.checks_performed << " checks performed." << std::endl; 85 | out << results.duration_ms << " ms used for the whole test suite." << std::endl; 86 | } 87 | 88 | inline void reporter_ide_summary( 89 | test_suite_result const& results 90 | ) { 91 | reporter_ide_summary_to_stream((results.test_cases_failed > 0) ? std::cout : std::cerr, results); 92 | } 93 | 94 | } // namespace cute 95 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/reporters/reporter_junit.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "reporter_ide.hpp" 9 | #include "../test_result.hpp" 10 | #include "../test_suite_result.hpp" 11 | 12 | #include 13 | 14 | namespace cute { 15 | namespace detail { 16 | 17 | inline void junit_write_lead_in( 18 | std::ostream& out, 19 | test_suite_result const& suite 20 | ) { 21 | out << "" << std::endl; 22 | out << " " << std::endl; 27 | } 28 | 29 | inline void junit_write_test( 30 | std::ostream& out, 31 | test_result const& test 32 | ) { 33 | // extract bse filename part from full source filename 34 | auto name = test.test.file; 35 | auto pos1 = name.find_last_of("/\\"); 36 | if(pos1 != name.npos) { name = name.substr(pos1 + 1); } 37 | auto pos2 = name.find_last_of("."); 38 | name = name.substr(0, pos2); 39 | 40 | out << " " << std::endl; 45 | 46 | if(test.result == result_type::skip) { 47 | out << " " << std::endl; 48 | } 49 | 50 | if(auto ex = test.excp.get()) { 51 | std::string type; 52 | switch(test.result) { 53 | case result_type::pass: type = "pass"; break; 54 | case result_type::skip: type = "skip"; break; 55 | case result_type::fail: type = "fail"; break; 56 | case result_type::fatal: type = "fatal"; break; 57 | default: assert(false); 58 | } 59 | 60 | std::ostringstream text; 61 | reporter_ide_to_stream(text, test, 0, 1); 62 | 63 | out << " message) << "\" "; 66 | out << ">" << std::endl; 67 | out << xml_encode(text.str()); 68 | out << " " << std::endl; 69 | } 70 | 71 | out << " " << std::endl; 72 | } 73 | 74 | inline void junit_write_lead_out( 75 | std::ostream& out 76 | ) { 77 | out << " " << std::endl; 78 | out << "/" << std::endl; 79 | } 80 | 81 | } // namespace detail 82 | 83 | inline std::ostream& reporter_junit_summary( 84 | std::ostream& out, 85 | test_suite_result const& suite 86 | ) { 87 | detail::junit_write_lead_in(out, suite); 88 | 89 | // write resut for each test 90 | for(auto&& test : suite.test_results) { 91 | detail::junit_write_test(out, test); 92 | } 93 | 94 | detail::junit_write_lead_out(out); 95 | 96 | return out; 97 | } 98 | 99 | } // namespace cute 100 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/test.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "detail/utils.hpp" 9 | 10 | #include 11 | 12 | namespace cute { 13 | 14 | struct test { 15 | std::string const name; 16 | std::set const tags; 17 | std::function const test_case; 18 | std::string const file; 19 | int const line; 20 | 21 | inline test(std::string file_, int line_, std::function test_case_, std::string name_, std::string const& tags_ = "") : 22 | name(std::move(name_)), tags(detail::parse_tags(tags_)), test_case(std::move(test_case_)), file(std::move(file_)), line(std::move(line_)) 23 | { } 24 | }; 25 | 26 | } // namespace cute 27 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/test_result.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "capture.hpp" 9 | 10 | #include 11 | 12 | namespace cute { 13 | 14 | enum class result_type { 15 | pass, 16 | skip, 17 | fail, 18 | fatal 19 | }; 20 | 21 | struct test_result { 22 | cute::test const test; 23 | 24 | result_type result; 25 | std::size_t duration_ms; 26 | std::shared_ptr excp; 27 | 28 | inline test_result( 29 | cute::test test_, 30 | result_type result_ = result_type::pass, 31 | std::size_t duration_ms_ = 0 32 | ) : 33 | test(std::move(test_)), 34 | result(std::move(result_)), 35 | duration_ms(std::move(duration_ms_)) 36 | { } 37 | }; 38 | 39 | } // namespace cute 40 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/test_suite_result.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "detail/utils_file.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace cute { 15 | 16 | struct test_suite_result { 17 | std::vector const test_results; 18 | std::size_t const test_cases_passed; 19 | std::size_t const test_cases_skipped; 20 | std::size_t const test_cases_failed; 21 | std::size_t const checks_performed; 22 | std::size_t const duration_ms; // milliseconds 23 | 24 | inline test_suite_result( 25 | std::vector test_results_, 26 | std::size_t checks_performed_, 27 | std::size_t duration_ms_ 28 | ) : 29 | test_results(std::move(test_results_)), 30 | test_cases_passed(0), test_cases_skipped(0), test_cases_failed(0), 31 | checks_performed(checks_performed_), 32 | duration_ms(duration_ms_) 33 | { 34 | auto& passed = const_cast(test_cases_passed); 35 | auto& skipped = const_cast(test_cases_skipped); 36 | auto& failed = const_cast(test_cases_failed); 37 | for(auto&& r : test_results) { 38 | switch(r.result) { 39 | case result_type::pass: ++passed; break; 40 | case result_type::skip: ++skipped; break; 41 | case result_type::fail: ++failed; break; 42 | case result_type::fatal: ++failed; break; 43 | default: assert(false); 44 | } 45 | } 46 | } 47 | }; 48 | 49 | } // namespace cute 50 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/thread.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | namespace cute { 12 | 13 | struct thread { 14 | template 15 | inline thread(FUNC&& func) : m_thread(wrap_func(std::forward(func))) { } 16 | inline thread(thread&& o) CUTE_NOEXCEPT : m_thread(std::move(o.m_thread)) { } 17 | inline thread& operator=(thread&& o) CUTE_NOEXCEPT { m_thread = std::move(o.m_thread); return *this; } 18 | inline ~thread() { if(m_thread.joinable()) { m_thread.join(); } } 19 | 20 | inline void join() { m_thread.join(); } 21 | 22 | private: 23 | template 24 | inline static std::function wrap_func(FUNC func) { 25 | auto test = cute::detail::eval_context::current().current_test; 26 | assert(test); 27 | 28 | auto wrapped = [=]() { 29 | try { 30 | CUTE_DETAIL_ASSERT( 31 | (static_cast(func()), true), test->file, test->line, 32 | "unhandled exception in separate thread detected", 33 | cute::captures(), cute::captures() 34 | ); 35 | } catch(...) { 36 | } 37 | }; 38 | 39 | return wrapped; 40 | } 41 | 42 | std::thread m_thread; 43 | }; 44 | 45 | } // namespace cute 46 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/tick.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include "macros.hpp" 9 | #include "detail/decomposer.hpp" 10 | 11 | #include 12 | #include 13 | 14 | namespace cute { 15 | 16 | struct tick { 17 | inline tick(int start_tick_ = 0) : m_tick(start_tick_) { } 18 | 19 | template 20 | inline void at_tick(int tick_, FUNC&& func_) { 21 | { // limit the scope of the lock to not include 22 | // the notify_all() call below; otherwise the 23 | // notified threads would still be blocked by 24 | // the locked mutex and go for a nap again 25 | 26 | // wait until the scheduled tick 27 | std::unique_lock lock(m_mutex); 28 | m_wakeup.wait(lock, [&]() { return (m_tick >= tick_); }); 29 | 30 | // verify that the tick value is correct (e.g., no 31 | // two actions have been registered for the same tick) 32 | CUTE_ASSERT(m_tick == tick_, CUTE_CAPTURE("at_tick(): several actions registered for the same tick")); 33 | 34 | // update the current tick 35 | ++m_tick; 36 | 37 | // perform the action 38 | func_(); 39 | } 40 | 41 | // wakeup other waiting threads again to let them check 42 | // if they are due based on their scheduled tick 43 | m_wakeup.notify_all(); 44 | } 45 | 46 | inline void reached_tick(int tick_) { 47 | // just a no-op in order to increase the current tick value from 48 | // a specific thread 49 | at_tick(tick_, []() { }); 50 | } 51 | 52 | template 53 | inline void delay_tick_for(int tick_, DELAY&& delay_) { 54 | // just schedule a sleep operation for the given tick 55 | at_tick(tick_, [=]() { std::this_thread::sleep_for(delay_); }); 56 | } 57 | 58 | template 59 | inline void blocks_until_tick(int tick_, FUNC&& func_) { 60 | // perform the action and check afterwards that 61 | // the action didn't return too early 62 | func_(); 63 | 64 | CUTE_ASSERT(m_tick >= tick_, CUTE_CAPTURE("blocks_until_tick(): function returned too early")); 65 | } 66 | 67 | private: 68 | std::mutex m_mutex; 69 | std::condition_variable m_wakeup; 70 | std::atomic m_tick; 71 | }; 72 | 73 | } // namespace cute 74 | -------------------------------------------------------------------------------- /3rdParty/cute/cute/to_string.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cute { 13 | 14 | inline std::string to_string(std::nullptr_t const&) { return "nullptr"; } 15 | inline std::string to_string(std::string const& str) { return ('\"' + str + '\"'); } 16 | inline std::string to_string(char const* const& str) { return (str ? ('\"' + std::string(str) + '\"') : "nullptr"); } 17 | inline std::string to_string(char const& c) { return ('\'' + std::string(1, c) + '\''); } 18 | inline std::string to_string(bool const& b) { return (b ? "true" : "false"); } 19 | 20 | template 21 | inline auto to_string(T const& value) -> std::string { std::ostringstream os; os << value; return os.str(); } 22 | 23 | template 24 | inline std::string to_string(std::shared_ptr const& p) { std::ostringstream os; os << p.get(); return os.str(); } 25 | 26 | template 27 | inline std::string to_string(std::unique_ptr const& p) { std::ostringstream os; os << p.get(); return os.str(); } 28 | 29 | template 30 | inline std::string to_string(std::weak_ptr const& p) { std::ostringstream os; os << p.get(); return os.str(); } 31 | 32 | inline std::string to_string(std::type_info const& ti) { return ti.name(); } 33 | 34 | } // namespace cute 35 | -------------------------------------------------------------------------------- /3rdParty/cute/docs/multi_threaded_tests.md: -------------------------------------------------------------------------------- 1 | Multi-threaded tests 2 | ==================== 3 | 4 | When writing multi-threaded test cases the following pitfalls needs to be avoided: 5 | 6 | 1. Uncaught assertions (thrown by `cute` or from your code) in separate threads are leading to call `std::terminate()`: it must be ensured that all exceptions are caught on the separate thread. `cute::thread` can be used for this purpose. It is just a simple wrapper around `std::thread` and wraps the execution with a `catch-all-exceptions` handler signalling an unhandled exception as a test failure. 7 | 8 | 2. `std::thread`s need to be joined before their lifetime ends otherwise it leads to calling `std::terminate()`: `cute::thread` can be used for this purpose as well, since it ensures proper joining of the wrapped `std::thread` prior to it's destruction. 9 | 10 | 3. Test validation calls are not thread safe: `cute` has been designed right from it's beginning with multi-threading in mind and all test validation calls can be safely used in arbitrary threads. 11 | 12 | ```C++ 13 | CUTE_TEST("A passed check in a separate thread is detected") { 14 | auto t = cute::thread([&]() { CUTE_ASSERT(true); }); 15 | } 16 | 17 | // this test will fail (as expected) 18 | CUTE_TEST("A failed check in a separate thread is detected") { 19 | auto t = cute::thread([&]() { CUTE_ASSERT(false); }); 20 | } 21 | 22 | // this test will fail (as expected) 23 | CUTE_TEST("An unhandled exception in a cute::thread is detected") { 24 | auto t = cute::thread([&]() { throw std::runtime_error("forced exception"); }); 25 | } 26 | ``` 27 | 28 | 29 | TODO 30 | ==== 31 | 32 | - describe usage of `cute::tick` 33 | -------------------------------------------------------------------------------- /3rdParty/cute/docs/temp_folders.md: -------------------------------------------------------------------------------- 1 | Temporary output folder 2 | ======================= 3 | 4 | Although it is best to avoid writing tests which creates files in the local filesystem, but nevertheless there are certain cases in which it could come in handy to be able to dump out some temp files into the file system during testing. 5 | 6 | `cute` supports this usage scenario by providing the function `cute::temp_folder()` which can be called from within a test case implementation. The function creates a new and empty folder and returns the path to it. This ensures that each test case have its own temp folder and does not write into a shared location, which could lead to all kinds of trouble during testing. After finishing the test case execution the created temp folder will be cleaned up automatically again. 7 | 8 | On the `Windows` platform the temp folder cleanup step will additionally verify that all files created in the test temp folder are really deleted again (i.e., that there are no pending file locks on them anymore) and report a test failure otherwise. Due to some differences in file handling and file locking this extra validation step does not work on non-`Windows` platforms. 9 | 10 | ```C++ 11 | CUTE_TEST("The temp folder should be deleted after a test case") { 12 | auto folder = cute::temp_folder(); 13 | 14 | // create some files in the temp folder 15 | std::ofstream(folder + "file_1"); 16 | std::ofstream(folder + "file_2"); 17 | std::ofstream(folder + "file_3"); 18 | 19 | // we cannot have a test case without an actual check/assert in it => add a dummy check 20 | CUTE_ASSERT(true); 21 | } 22 | ``` 23 | All created files get deleted automatically again. 24 | 25 | The following test will fail under `Windows` since it holds on the file lock even beyond the end of the test case. 26 | ```C++ 27 | CUTE_TEST("A test should fail if the temp folder could not be deleted afterwards (Windows only)") { 28 | auto folder = cute::temp_folder(); 29 | 30 | // static => keep a file lock around even after the test 31 | // in order to verify that the test fails if the temp folder 32 | // could not completely cleaned up again 33 | static auto keep_alive = std::make_shared(folder + "file_1"); 34 | 35 | // we cannot have a test case without an actual check/assert in it => add a dummy check 36 | CUTE_ASSERT(true); 37 | } 38 | ``` 39 | The output will look like this: 40 | ``` 41 | .../cute_tests.cpp:10: error: test should fail if the temp folder could not be deleted afterwards (Windows only) 42 | .../cute_tests.cpp:10: error: duration: 0 ms 43 | .../cute_tests.cpp:10: error: reason: could not cleanup temp folder 44 | ``` 45 | -------------------------------------------------------------------------------- /3rdParty/cute/docs/test_runners_and_reporters.md: -------------------------------------------------------------------------------- 1 | Simple IDE reporter 2 | =================== 3 | 4 | A test runner using the provided simple IDE reporter would like like this: 5 | ```C++ 6 | #include 7 | #include 8 | 9 | CUTE_INIT(); // initialize the cute framework 10 | 11 | int main_with_ide_reporter(int argc, char* argv[]) { 12 | // create the test suite execution context 13 | auto context = cute::context(); 14 | 15 | // print out the status of each test during the execution of the test suite 16 | context.reporters.emplace_back(cute::reporter_ide); 17 | 18 | // run the test suite 19 | auto results = context.run(); 20 | 21 | // print out the test suite result summary 22 | cute::reporter_ide_summary(results); 23 | 24 | // exit with an exit code indicating success or failure for the test suite 25 | return ((results.test_cases_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS); 26 | } 27 | ``` 28 | 29 | 30 | Simple JUnit reporter 31 | ===================== 32 | 33 | A test runner using the provided `JUnit` reporter would like like this: 34 | ```C++ 35 | #include 36 | #include 37 | 38 | CUTE_INIT(); // initialize the cute framework 39 | 40 | int main_with_junit_reporter(int argc, char* argv[]) { 41 | // create the test suite execution context 42 | auto context = cute::context(); 43 | 44 | // run the test suite 45 | auto results = context.run(); 46 | 47 | // print out the test suite result summary in JUnit XML format 48 | cute::reporter_junit_summary(std::cout, results); 49 | 50 | // exit with an exit code indicating success or failure for the test suite 51 | return ((results.test_cases_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS); 52 | } 53 | ``` 54 | 55 | Both reporters could also be combined, e.g., by writing the test results into a separate `JUnit` XML results file. 56 | -------------------------------------------------------------------------------- /3rdParty/cute/docs/writing_tests.md: -------------------------------------------------------------------------------- 1 | A simple test case 2 | ================== 3 | 4 | A very simple failing test case would look like this: 5 | ```C++ 6 | CUTE_TEST("Test two strings for equality") { 7 | auto str1 = std::string("hello"); 8 | auto str2 = std::string("world"); 9 | CUTE_ASSERT(str1 == str2); 10 | } 11 | ``` 12 | This would produce the following output with the provided IDE reporter: 13 | ``` 14 | .../cute_tests.cpp:64: error: Test two strings for equality 15 | .../cute_tests.cpp:64: error: duration: 0 ms 16 | .../cute_tests.cpp:67: error: reason: validation failed: str1 == str2 17 | .../cute_tests.cpp:67: error: with: str1 == str2 => "hello" == "world" 18 | ``` 19 | 20 | 21 | Capturing additional values 22 | =========================== 23 | 24 | Capturing additional values and reporting them in case of an error can be achieved by adding some `CUTE_CAPTURE()` calls to `CUTE_ASSERT()`: 25 | ```C++ 26 | CUTE_TEST("Capture additional values") { 27 | auto str1 = std::string("hello"); 28 | auto str2 = std::string("world"); 29 | auto value = 42; 30 | CUTE_ASSERT(str1 == str2, 31 | CUTE_CAPTURE(str1.length()), 32 | CUTE_CAPTURE(str2.capacity()), 33 | CUTE_CAPTURE(value) 34 | ); 35 | } 36 | ``` 37 | This would produce the following output with the provided IDE reporter: 38 | ``` 39 | .../cute_tests.cpp:70: error: Capture additional values 40 | .../cute_tests.cpp:70: error: duration: 0 ms 41 | .../cute_tests.cpp:79: error: reason: validation failed: str1 == str2 42 | .../cute_tests.cpp:79: error: with: str1 == str2 => "hello" == "world" 43 | .../cute_tests.cpp:79: error: with: str1.size() => 5 44 | .../cute_tests.cpp:79: error: with: str2.capacity() => 22 45 | .../cute_tests.cpp:79: error: with: value => 42 46 | ``` 47 | 48 | 49 | Supported validation macros 50 | =========================== 51 | 52 | The following macros can be used for validation within a test case: 53 | 54 | macro | validation 55 | --------------------------------- | ---------- 56 | `CUTE_ASSERT(ex)` | Verifies that the given boolean expression `ex` evaluates to `true`. 57 | `CUTE_ASSERT_THROWS_NOT(ex)` | Verifies that evaluating the given expression `ex` will not throw an `exception`. 58 | `CUTE_ASSERT_THROWS(ex)` | Verifies that evaluating the given expression `ex` will throw an `exception`. 59 | `CUTE_ASSERT_THROWS_AS(ex, excp)` | Verifies that evaluating the given expression `ex` will throw an `exception` of type `excp` or derived from it. 60 | 61 | Each validation macro can be extended by several `CUTE_CAPTURE()` occurrences in order to capture additional values and provide more context information in case of a failure. 62 | 63 | 64 | Misc 65 | ==== 66 | Note that `cute` stops a test case by throwing an `exception` as soon as a violation of a `CUTE_ASSERT*()` is detected. 67 | 68 | If a `CUTE_TEST` does not trigger any validation during it's execution the test is reported as failed as well, since this usually indicates an error in test implementation. 69 | -------------------------------------------------------------------------------- /3rdParty/cute/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(cute CXX) 4 | message("configure: cute unit tests") 5 | 6 | include(CheckCXXSourceCompiles) 7 | CHECK_CXX_SOURCE_COMPILES( 8 | "struct A { A() noexcept { } }; int main(int argc, const char* argv[]) { return 0; }" 9 | CUTE_HAS_NOEXCEPT 10 | ) 11 | if(CUTE_HAS_NOEXCEPT) 12 | add_definitions(-DCUTE_HAS_NOEXCEPT) 13 | endif(CUTE_HAS_NOEXCEPT) 14 | 15 | # check for adding a reference to the pthread lib 16 | CHECK_CXX_COMPILER_FLAG("-pthread" CUTE_HAS_PTHREAD) 17 | if(CUTE_HAS_PTHREAD) 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") 19 | endif(CUTE_HAS_PTHREAD) 20 | 21 | add_executable( 22 | cute_unittests 23 | main.cpp 24 | cute_cleanup_guard_unittests.cpp 25 | cute_core_unittests.cpp 26 | cute_file_unittests.cpp 27 | cute_thread_unittests.cpp 28 | cute_xml_unittests.cpp 29 | ../cute/capture.hpp 30 | ../cute/cleanup_guard.hpp 31 | ../cute/context.hpp 32 | ../cute/cute.hpp 33 | ../cute/exception.hpp 34 | ../cute/macros.hpp 35 | ../cute/test.hpp 36 | ../cute/test_result.hpp 37 | ../cute/test_suite_result.hpp 38 | ../cute/tick.hpp 39 | ../cute/thread.hpp 40 | ../cute/to_string.hpp 41 | ../cute/detail/decomposer.hpp 42 | ../cute/detail/eval_context.hpp 43 | ../cute/detail/macros_impl.hpp 44 | ../cute/detail/test_registry.hpp 45 | ../cute/detail/utils.hpp 46 | ../cute/detail/utils_file.hpp 47 | ../cute/reporters/reporter_ide.hpp 48 | ../cute/reporters/reporter_junit.hpp 49 | ) 50 | 51 | add_test( 52 | NAME cute_unittests 53 | COMMAND cute_unittests 54 | ) 55 | -------------------------------------------------------------------------------- /3rdParty/cute/test/cute_cleanup_guard_unittests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #include "../cute/cute.hpp" 7 | #include "../cute/cleanup_guard.hpp" 8 | 9 | CUTE_TEST("create a cleanup_guard without a callback/cleanup action", "pass") { 10 | auto cleanup = cute::cleanup_guard(nullptr); 11 | CUTE_ASSERT(true); 12 | } 13 | 14 | CUTE_TEST("create a cleanup_guard and execute the action at scope exit", "pass") { 15 | auto value = int(0); 16 | 17 | { 18 | auto cleanup = cute::cleanup_guard([&] { value = 42; }); 19 | } 20 | 21 | CUTE_ASSERT(value == 42); 22 | } 23 | 24 | CUTE_TEST("create a cleanup_guard and execute the action in case of an exception at scope exit", "pass") { 25 | auto value = int(0); 26 | auto const exception_message = std::string("forced exception"); 27 | 28 | try { 29 | auto cleanup = cute::cleanup_guard([&] { value = 42; }); 30 | throw std::runtime_error(exception_message); 31 | cleanup.dismiss(); 32 | } catch(std::runtime_error const& ex) { 33 | // ensure that only the expected exception has been thrown 34 | CUTE_ASSERT(ex.what() == exception_message); 35 | } 36 | 37 | CUTE_ASSERT(value == 42); 38 | } 39 | 40 | CUTE_TEST("create a cleanup_guard and do not execute after calling dismiss()", "pass") { 41 | auto value = int(0); 42 | 43 | { 44 | auto cleanup = cute::cleanup_guard([&] { value = 42; }); 45 | cleanup.dismiss(); 46 | } 47 | 48 | CUTE_ASSERT(value == 0); 49 | } 50 | 51 | CUTE_TEST("create a cleanup_guard and execute the cleanup action explicitly", "pass") { 52 | auto value = int(0); 53 | 54 | { 55 | auto cleanup = cute::cleanup_guard([&] { value = 42; }); 56 | CUTE_ASSERT(value == 0); 57 | cleanup.cleanup(); 58 | CUTE_ASSERT(value == 42); 59 | value = 0; 60 | } 61 | 62 | // check that the cleanup action was not triggered a second time 63 | CUTE_ASSERT(value == 0); 64 | } 65 | -------------------------------------------------------------------------------- /3rdParty/cute/test/cute_core_unittests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #include "../cute/cute.hpp" 7 | 8 | CUTE_TEST("CUTE_ASSERT(true) should pass", "pass") { 9 | CUTE_ASSERT(true); 10 | } 11 | 12 | CUTE_TEST("CUTE_ASSERT(false) should fail", "fail") { 13 | CUTE_ASSERT(false); 14 | } 15 | 16 | CUTE_TEST("CUTE_ASSERT_THROWS() should pass if an exception is thrown", "pass") { 17 | auto throw_an_exception = []() { throw std::runtime_error("forced exception"); }; 18 | CUTE_ASSERT_THROWS(throw_an_exception()); 19 | } 20 | 21 | CUTE_TEST("CUTE_ASSERT_THROWS() should fail if no exception is thrown", "fail") { 22 | auto throw_no_exception = []() { }; 23 | CUTE_ASSERT_THROWS(throw_no_exception()); 24 | } 25 | 26 | CUTE_TEST("CUTE_ASSERT_THROWS_AS() should pass if the correct exception is thrown", "pass") { 27 | auto throw_an_exception = []() { throw std::runtime_error("forced exception"); }; 28 | CUTE_ASSERT_THROWS_AS(throw_an_exception(), std::runtime_error); 29 | } 30 | 31 | CUTE_TEST("CUTE_ASSERT_THROWS_AS() should pass if a derived excpetion is thrown", "pass") { 32 | auto throw_an_exception = []() { throw std::runtime_error("forced exception"); }; 33 | CUTE_ASSERT_THROWS_AS(throw_an_exception(), std::exception); 34 | } 35 | 36 | CUTE_TEST("CUTE_ASSERT_THROWS_AS() should fail if an excpetion of an unexpected type is thrown", "fail") { 37 | auto throw_an_exception = []() { throw std::runtime_error("forced exception"); }; 38 | CUTE_ASSERT_THROWS_AS(throw_an_exception(), std::logic_error); 39 | } 40 | 41 | CUTE_TEST("CUTE_ASSERT_THROWS_AS() should fail if no excpetion is thrown", "fail") { 42 | auto throw_no_exception = []() { }; 43 | CUTE_ASSERT_THROWS_AS(throw_no_exception(), std::runtime_error); 44 | } 45 | 46 | CUTE_TEST("CUTE_ASSERT_THROWS_NOT() should pass if no exception is thrown", "pass") { 47 | auto throw_no_exception = []() { }; 48 | CUTE_ASSERT_THROWS_NOT(throw_no_exception()); 49 | } 50 | 51 | CUTE_TEST("CUTE_ASSERT_THROWS_NOT() should fail if an exception is thrown", "fail") { 52 | auto throw_an_exception = []() { throw std::runtime_error("forced exception"); }; 53 | CUTE_ASSERT_THROWS_NOT(throw_an_exception()); 54 | } 55 | 56 | CUTE_TEST("a test case should fail if an uncaught exception is thrown", "fail") { 57 | throw std::runtime_error("forced exception"); 58 | } 59 | 60 | CUTE_TEST("a test case should fail if no check is performed in it", "fail") { 61 | // nothing to do here 62 | } 63 | 64 | CUTE_TEST("CUTE_ASSERT() can capture more than one variable with the help of CUTE_CAPTURE()", "fail") { 65 | auto str1 = std::string("hello"); 66 | auto str2 = std::string("world"); 67 | CUTE_ASSERT(str1 == str2, CUTE_CAPTURE(str1), CUTE_CAPTURE(str2)); 68 | } 69 | -------------------------------------------------------------------------------- /3rdParty/cute/test/cute_file_unittests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #include "../cute/cute.hpp" 7 | 8 | #include 9 | 10 | CUTE_TEST("detail::create_temp_folder() should create a new folder", "pass") { 11 | auto folder = cute::temp_folder(); 12 | CUTE_ASSERT(!folder.empty(), CUTE_CAPTURE(folder)); 13 | } 14 | 15 | CUTE_TEST("the temp folder should be deleted after a test case", "pass") { 16 | auto folder = cute::temp_folder(); 17 | 18 | // create some files in the temp folder 19 | std::ofstream(folder + "file_1"); 20 | std::ofstream(folder + "file_2"); 21 | std::ofstream(folder + "file_3"); 22 | 23 | // we cannot have a test case without an actual check/assert in it => add a dummy check 24 | CUTE_ASSERT(true); 25 | } 26 | 27 | 28 | // since deleting a still open file behaves differently on Windows (deletion fails) than on 29 | // Linux/OSX (deletion succeeds, the file will not be visible anymore and gets fully deleted 30 | // once the last file pointer on it gets destroyed) this test is only performed on Windows systems 31 | #if defined(WIN32) 32 | CUTE_TEST("a test should fail if the temp folder could not be deleted afterwards", "fail") { 33 | auto folder = cute::temp_folder(); 34 | 35 | // static => keep a file lock around even after the test 36 | // in order to verify that the test fails if the temp folder 37 | // could not completely cleaned up again 38 | static auto keep_alive = std::make_shared(folder + "file_1"); 39 | 40 | // we cannot have a test case without an actual check/assert in it => add a dummy check 41 | CUTE_ASSERT(true); 42 | } 43 | #endif // defined(WIN32) 44 | -------------------------------------------------------------------------------- /3rdParty/cute/test/cute_thread_unittests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #include "../cute/cute.hpp" 7 | 8 | CUTE_TEST("a passed check in a separate thread is detected", "pass") { 9 | auto t = cute::thread([&]() { CUTE_ASSERT(true); }); 10 | } 11 | 12 | CUTE_TEST("a failed check in a separate thread is detected", "fail") { 13 | auto t = cute::thread([&]() { CUTE_ASSERT(false); }); 14 | } 15 | 16 | CUTE_TEST("an unhandled exception in a cute::thread is detected", "fail") { 17 | auto t = cute::thread([&]() { throw std::runtime_error("forced exception"); }); 18 | } 19 | 20 | CUTE_TEST("actions registered with 'at_tick()' are executed in the right order", "pass") { 21 | cute::tick ticker; 22 | std::atomic value(0); 23 | 24 | auto t2 = cute::thread([&]() { ticker.at_tick(2, [&]() { CUTE_ASSERT(value == 2); ++value; CUTE_ASSERT(value == 3); }); }); 25 | auto t1 = cute::thread([&]() { ticker.at_tick(1, [&]() { CUTE_ASSERT(value == 1); ++value; CUTE_ASSERT(value == 2); }); }); 26 | auto t0 = cute::thread([&]() { ticker.at_tick(0, [&]() { CUTE_ASSERT(value == 0); ++value; CUTE_ASSERT(value == 1); }); }); 27 | 28 | ticker.at_tick(3, [&]() { CUTE_ASSERT(value == 3); }); 29 | } 30 | 31 | CUTE_TEST("actions registered with 'at_tick()' for the same tick are failing", "fail") { 32 | cute::tick ticker; 33 | 34 | ticker.at_tick(0, [&]() { }); 35 | ticker.at_tick(0, [&]() { }); 36 | } 37 | 38 | CUTE_TEST("actions registered with 'blocks_until_tick()' are passing if they are returning as scheduled", "pass") { 39 | cute::tick ticker; 40 | std::atomic proceed(false); 41 | 42 | auto t = cute::thread([&]() { 43 | ticker.reached_tick(0); 44 | ticker.at_tick(2, [&]() { proceed = true; }); 45 | }); 46 | 47 | ticker.reached_tick(1); 48 | ticker.blocks_until_tick(3, [&]() { while(!proceed) { } }); 49 | } 50 | 51 | CUTE_TEST("actions registered with 'blocks_until_tick()' are failing if they are returning earlier", "fail") { 52 | cute::tick ticker; 53 | 54 | ticker.blocks_until_tick(1, [&]() { }); 55 | } 56 | -------------------------------------------------------------------------------- /3rdParty/cute/test/cute_xml_unittests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #include "../cute/cute.hpp" 7 | 8 | CUTE_TEST("'xml_encode()' escapes all required chars", "pass") { 9 | CUTE_ASSERT(cute::detail::xml_encode("&") == "&"); 10 | CUTE_ASSERT(cute::detail::xml_encode("\"") == """); 11 | CUTE_ASSERT(cute::detail::xml_encode("\'") == "'"); 12 | CUTE_ASSERT(cute::detail::xml_encode("<") == "<"); 13 | CUTE_ASSERT(cute::detail::xml_encode(">") == ">"); 14 | } 15 | 16 | CUTE_TEST("'xml_encode()' escapes all required chars within a string", "pass") { 17 | CUTE_ASSERT(cute::detail::xml_encode("a&\"\'<>z") == "a&"'<>z"); 18 | } 19 | -------------------------------------------------------------------------------- /3rdParty/cute/test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 by Konstantin (Kosta) Baumann 2 | // 3 | // Distributed under the MIT license (See accompanying file LICENSE.txt) 4 | // 5 | 6 | #include "../cute/cute.hpp" 7 | #include "../cute/reporters/reporter_ide.hpp" 8 | #include "../cute/reporters/reporter_junit.hpp" 9 | 10 | #include 11 | 12 | CUTE_INIT(); // initialize the cute framework 13 | 14 | int main_with_ide_reporter(int argc, char* argv[]) { 15 | // create the test suite execution context 16 | auto context = cute::context(); 17 | 18 | // print out the status of each test during the execution of the test suite 19 | context.reporters.emplace_back(cute::reporter_ide); 20 | 21 | // run the test suite 22 | auto results = context.run(); 23 | 24 | // print out the test suite result summary 25 | cute::reporter_ide_summary(results); 26 | 27 | // exit with an exit code indicating success or failure for the test suite 28 | return ((results.test_cases_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS); 29 | } 30 | 31 | int main_with_junit_reporter(int argc, char* argv[]) { 32 | // create the test suite execution context 33 | auto context = cute::context(); 34 | 35 | // run the test suite 36 | auto results = context.run(); 37 | 38 | // print out the test suite result summary in JUnit XML format 39 | cute::reporter_junit_summary(std::cout, results); 40 | 41 | // exit with an exit code indicating success or failure for the test suite 42 | return ((results.test_cases_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS); 43 | } 44 | 45 | int main(int argc, char* argv[]) { 46 | auto pass_ctx = cute::context(); 47 | pass_ctx.include_tags = "pass"; 48 | pass_ctx.reporters.emplace_back(cute::reporter_ide); 49 | auto pass_res = pass_ctx.run(); 50 | 51 | auto fail_ctx = cute::context(); 52 | fail_ctx.include_tags = "fail"; 53 | fail_ctx.reporters.emplace_back([&](cute::test_result const& res, std::size_t test_index_cur, std::size_t test_index_max) { 54 | auto r = res; 55 | switch(r.result) { 56 | case cute::result_type::pass: r.result = cute::result_type::fail; break; 57 | case cute::result_type::skip: break; 58 | case cute::result_type::fail: r.result = cute::result_type::pass; break; 59 | case cute::result_type::fatal: break; 60 | default: assert(false); 61 | } 62 | cute::reporter_ide(r, test_index_cur, test_index_max); 63 | }); 64 | auto fail_res = fail_ctx.run(); 65 | 66 | // for this self-test in which we check the correct behavior for 67 | // passing and failing test cases we need to combine both results 68 | // by "inverting" the "failed" cases... 69 | auto tests_all = pass_res.test_results.size(); 70 | auto tests_passed = pass_res.test_cases_passed + fail_res.test_cases_failed; 71 | auto tests_skipped = pass_res.test_cases_skipped + fail_res.test_cases_skipped - tests_all; 72 | auto tests_failed = pass_res.test_cases_failed + fail_res.test_cases_passed; 73 | auto checks_performed = pass_res.checks_performed + fail_res.checks_performed; 74 | auto duration_ms = pass_res.duration_ms + fail_res.duration_ms; 75 | 76 | auto self_test_failed = ((tests_all == 0) || (tests_failed > 0) || (tests_skipped > 0)); 77 | 78 | cute::reporter_junit_summary(std::cout, pass_res); 79 | cute::reporter_junit_summary(std::cout, fail_res); 80 | 81 | auto& out = (self_test_failed ? std::cerr : std::cout); 82 | 83 | out << std::endl; 84 | out << "test summary:" << std::endl; 85 | out << tests_passed << "/" << tests_all << " tests passed." << std::endl; 86 | out << tests_skipped << "/" << tests_all << " tests skipped." << std::endl; 87 | out << tests_failed << "/" << tests_all << " tests failed." << std::endl; 88 | out << checks_performed << " checks performed." << std::endl; 89 | out << duration_ms << " ms used for the whole test suite." << std::endl; 90 | 91 | return (self_test_failed ? EXIT_FAILURE : EXIT_SUCCESS); 92 | } 93 | -------------------------------------------------------------------------------- /CMakeConfigCompilers.cmake: -------------------------------------------------------------------------------- 1 | # Check for cxx0x, cxx11, and libcxx11 options 2 | include(CheckCXXCompilerFlag) 3 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" HAS_CXX0x) 4 | CHECK_CXX_COMPILER_FLAG("-std=c++11" HAS_CXX11) 5 | CHECK_CXX_COMPILER_FLAG("-std=c++11 -stdlib=libc++" HAS_LIBCXX11) 6 | CHECK_CXX_COMPILER_FLAG("-pthread" HAS_PTHREAD) 7 | 8 | # switch to the right compiler 9 | if(HAS_LIBCXX11) 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++") 11 | elseif(HAS_CXX11) 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 13 | elseif(HAS_CXX0x) 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 15 | elseif(MSVC) 16 | # add_definitions() 17 | endif() 18 | 19 | # add reference to the pthread lib 20 | if(HAS_PTHREAD) 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") 22 | endif(HAS_PTHREAD) 23 | 24 | # Add a workaround for the g++ bug 25 | # c.f. http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52680 26 | # http://gcc.gnu.org/ml/libstdc++-cvs/2012-q4/msg00045.html 27 | if (${CMAKE_SYSTEM} MATCHES "Linux") 28 | # Try to use std::this_thread::yield() 29 | include(CheckCXXSourceCompiles) 30 | CHECK_CXX_SOURCE_COMPILES( 31 | "#include int main(int argc, const char** argv) { std::this_thread::yield(); return 0; }" 32 | STD_THREAD_YIELD_CAN_BE_USED 33 | ) 34 | if (NOT STD_THREAD_YIELD_CAN_BE_USED) 35 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_USE_NANOSLEEP -D_GLIBCXX_USE_SCHED_YIELD") 36 | endif() 37 | endif() 38 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(signals-cpp CXX) 4 | message("configure: signals-cpp") 5 | 6 | include(CMakeConfigCompilers.cmake) 7 | 8 | set(SIGNALS_CPP_3RD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdParty) 9 | 10 | SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) 11 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 12 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 13 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 14 | 15 | include_directories(.) 16 | 17 | # configure unit tests 18 | enable_testing() 19 | 20 | add_subdirectory(signals-cpp) 21 | add_subdirectory(test) 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | signals-cpp [![Build Status](https://travis-ci.org/Kosta-Github/signals-cpp.png)](https://travis-ci.org/Kosta-Github/signals-cpp) [![Build status AppVeyor](https://ci.appveyor.com/api/projects/status/e7s580d8k277b8fn/branch/master?svg=true)](https://ci.appveyor.com/project/Kosta-Github/signals-cpp/branch/master) 2 | =========== 3 | Provide a very simple header-only C++ interface for creating and managing signal-slot-connections in an easy and thread-safe manner. 4 | 5 | misc 6 | ==== 7 | This library is header-only and uses some new C++11 features (such as `std::mutex`, `std::atomic`, lambda-expressions, and ...) to provide an easy-to-use interface and being thread-safe at the same time. There are several important advantages over the approach used by the Qt signal-and-slot implementation: 8 | - no need for an additional build step (i.e., `MOC` compiler), 9 | - no special macros and language extensions required (i.e., `Q_EMIT`, `Q_SLOTS`, `Q_OBJECT`, ...), 10 | - not intrusive, since no inheritance from a certain base class is required (i.e., `QObject`), 11 | - checking whether the signal interface matches that of the target slot happens during compile-time, 12 | - no need for a meta-type system, and 13 | - free-standing functions and lambda-expressions can also be used as a target slots. 14 | 15 | 16 | sample usage 17 | ============ 18 | Expose a `signal` `valueChanged` with a single `int` parameter in a class `A` as a public member: 19 | ``` 20 | class A { 21 | public: 22 | ... 23 | void setValue(int v); 24 | ... 25 | public: 26 | sigs::signal valueChanged; 27 | ... 28 | }; 29 | ``` 30 | The `signal` can be emitted or fired by calling its `fire()` method passing the actual `int` value as argument: 31 | ``` 32 | void A::setValue(int v) { 33 | ... 34 | valueChanged.fire(v); 35 | } 36 | ``` 37 | To connect this `signal` to a method `onValueChanged()` of another class `B` add a `connections` object to class `B` (ideally as the *last* member): 38 | ``` 39 | class B { 40 | public: 41 | B(A* a); 42 | ... 43 | void onValueChanged(int v); 44 | ... 45 | private: 46 | ... 47 | sigs::connections m_conns; // should be the last member 48 | }; 49 | ``` 50 | To establish the actual `connection` (e.g., in the constructor of class `B`), call the `connect()` method of the `m_conns` member: 51 | ``` 52 | B::B(A* a) { 53 | ... 54 | m_conns.connect(a->valueChanged, this, &onValueChanged); 55 | ... 56 | } 57 | ``` 58 | You can also `connect` easily to a C++11 lambda expression like this: 59 | ``` 60 | B::B(A* a) { 61 | ... 62 | m_conns.connect(a->valueChanged, [](int v) { 63 | std::cout << "signal 'valueChanged' triggered for: " << v << std::endl; 64 | }); 65 | ... 66 | } 67 | ``` 68 | The `connections` get automatically disconnected on destruction of either object `a` or `b`, which ensures that no *dangling connections* exist. 69 | 70 | external dependencies 71 | ===================== 72 | - [cute](https://github.com/Kosta-Github/cute): only for unit tests 73 | - [cmake](http://cmake.org): build system; only for the unit tests 74 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | configuration: 4 | - Debug 5 | - Release 6 | 7 | platform: 8 | - x64 9 | 10 | before_build: 11 | - config-VS2012-win64.bat 12 | 13 | build: 14 | project: Build\signals-cpp.sln 15 | parallel: true 16 | verbosity: minimal 17 | 18 | test_script: 19 | - cd Build && ctest --verbose --timeout 120 --build-config %CONFIGURATION% 20 | -------------------------------------------------------------------------------- /config-VS2012-win64.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmake -H. -BBuild -G "Visual Studio 11 2012" -A x64 3 | -------------------------------------------------------------------------------- /config-VS2015-win64.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmake -H. -BBuild -G "Visual Studio 14 2015" -A x64 3 | -------------------------------------------------------------------------------- /signals-cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(signals-cpp CXX) 4 | message("configure: signals-cpp library") 5 | -------------------------------------------------------------------------------- /signals-cpp/config.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #pragma once 25 | 26 | #if defined(_MSC_VER) && (_MSC_VER < 1900) 27 | # define SIGNALS_CPP_NOEXCEPT throw() 28 | #else // defined(_MSC_VER) && (_MSC_VER < 1900) 29 | # define SIGNALS_CPP_NOEXCEPT noexcept 30 | #endif // defined(_MSC_VER) && (_MSC_VER < 1900) 31 | 32 | #if defined(_MSC_VER) && (_MSC_VER < 1900) 33 | # define SIGNALS_CPP_NEED_EXPLICIT_MOVE 34 | #endif // defined(_MSC_VER) && (_MSC_VER < 1900) 35 | 36 | #if defined(__clang__) || (defined(_MSC_VER) && (_MSC_VER >= 1900)) 37 | # define SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES 38 | #endif 39 | 40 | namespace signals { } 41 | namespace sigs = signals; 42 | -------------------------------------------------------------------------------- /signals-cpp/connection.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #pragma once 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "config.hpp" 31 | 32 | namespace signals { 33 | 34 | /// The `connection` class is just an abstract handle or representation for 35 | /// a connection between a signal and the corresponding target callback or slot. 36 | /// It can be checked if the connection is still connected and it could be 37 | /// disconnected. 38 | struct connection { 39 | struct data { 40 | inline data() : connected(true), running(0) { } 41 | 42 | std::atomic connected; // connection still active? 43 | std::atomic running; // number of currently active calls routed through this connection 44 | }; 45 | 46 | public: 47 | inline connection() { } 48 | inline connection(std::shared_ptr d) : m_data(std::move(d)) { } 49 | 50 | /// Checks if the `connection` represented by this object is (still) connected. 51 | inline bool connected() const { return (m_data && m_data->connected); } 52 | 53 | /// Disconnects this `connection`. After the `disconnect` call the corresponding 54 | /// target callback will not be triggered anymore. If there are currently some 55 | /// active calls running via this `connection` the `disconnect` call blocks until 56 | /// all calls have finished. 57 | inline bool disconnect(bool wait_if_running = false) { 58 | auto d = m_data; 59 | if(!d) { return false; } 60 | 61 | const bool was_connected = d->connected.exchange(false); 62 | 63 | if(wait_if_running) { 64 | while(d->running > 0) { 65 | std::this_thread::yield(); 66 | } 67 | } 68 | 69 | return was_connected; 70 | } 71 | 72 | public: 73 | // only for internal use 74 | template 75 | inline void call(CB&& cb) { 76 | auto d = m_data; 77 | if(!d || !d->connected) { return; } 78 | 79 | ++d->running; 80 | if(d->connected) { cb(); } 81 | --d->running; 82 | } 83 | 84 | // only for internal use 85 | inline static connection make_connection() { 86 | return connection(std::make_shared()); 87 | } 88 | 89 | private: 90 | std::shared_ptr m_data; 91 | }; 92 | 93 | } // namespace signals 94 | -------------------------------------------------------------------------------- /signals-cpp/connections.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #pragma once 25 | 26 | #include 27 | #include 28 | 29 | #include "connection.hpp" 30 | 31 | namespace signals { 32 | 33 | /// The `connections` class helps in tracking and disconnecting a group of connections. 34 | /// On object destruction it disconnects all tracked connections automatically. In order 35 | /// to track a connection the `connect` method can be used with the actual `signal` as 36 | /// the first argument; all following arguments will be passed on to the `signal::connect` 37 | /// method. Or an existing `connection` object can be explicitly added to the list of 38 | /// tracked connections via the `add` method. 39 | /// 40 | /// E.g., a `connections` object can be used as a member variable in a class that 41 | /// connects itself to other signals. The `connections` member should therefore be best 42 | /// placed at the end of the class members of that class, so it gets destructed before 43 | /// all other members which ensures that all other data members are still available and 44 | /// valid to finish potential callback running in parallel on other threads. The 45 | struct connections { 46 | inline connections() { } 47 | inline ~connections() { disconnect_all(true); } 48 | 49 | #if defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 50 | 51 | /// Connects to the given `signal` `s` and adds the created `connection` to 52 | /// the list of tracked connections. 53 | template 54 | inline connection connect(SIGNAL& s, ARGS&&... args) { 55 | auto conn = s.connect(std::forward(args)...); 56 | add(conn); 57 | return conn; 58 | } 59 | 60 | #else // defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 61 | 62 | template 63 | inline connection connect(SIGNAL& s, ARG1&& arg1) { 64 | auto conn = s.connect(std::forward(arg1)); 65 | add(conn); 66 | return conn; 67 | } 68 | 69 | template 70 | inline connection connect(SIGNAL& s, ARG1&& arg1, ARG2&& arg2) { 71 | auto conn = s.connect(std::forward(arg1), std::forward(arg2)); 72 | add(conn); 73 | return conn; 74 | } 75 | 76 | template 77 | inline connection connect(SIGNAL& s, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3) { 78 | auto conn = s.connect(std::forward(arg1), std::forward(arg2), std::forward(arg3)); 79 | add(conn); 80 | return conn; 81 | } 82 | 83 | template 84 | inline connection connect(SIGNAL& s, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4) { 85 | auto conn = s.connect(std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4)); 86 | add(conn); 87 | return conn; 88 | } 89 | 90 | template 91 | inline connection connect(SIGNAL& s, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4, ARG5&& arg5) { 92 | auto conn = s.connect(std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4), std::forward(arg5)); 93 | add(conn); 94 | return conn; 95 | } 96 | 97 | template 98 | inline connection connect(SIGNAL& s, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4, ARG5&& arg5, ARG6&& arg6) { 99 | auto conn = s.connect(std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4), std::forward(arg5), std::forward(arg6)); 100 | add(conn); 101 | return conn; 102 | } 103 | 104 | #endif // defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 105 | 106 | /// If the given `connection` `conn` is connected it gets added to the list of 107 | /// tracked connections. 108 | inline void add(connection conn) { 109 | if(conn.connected()) { 110 | m_conns.emplace_back(std::move(conn)); 111 | } 112 | } 113 | 114 | /// Disconnects all tracked `connections`. 115 | inline void disconnect_all(bool wait_if_running = false) { 116 | for(auto&& i : m_conns) { i.disconnect(false); } // first disconnect all connections without waiting 117 | if(wait_if_running) { for(auto&& i : m_conns) { i.disconnect(true); } } // then wait for them (if requested) 118 | m_conns.clear(); 119 | } 120 | 121 | #if defined(SIGNALS_CPP_NEED_EXPLICIT_MOVE) 122 | public: 123 | inline connections(connections&& o) : m_conns(std::move(o.m_conns)) { } 124 | inline connections& operator=(connections&& o) { m_conns = std::move(o.m_conns); return *this; } 125 | #endif // defined(SIGNALS_CPP_NEED_EXPLICIT_MOVE) 126 | 127 | private: 128 | connections(connections const& o); // = delete; 129 | connections& operator=(connections const& o); // = delete; 130 | 131 | private: 132 | std::vector m_conns; 133 | }; 134 | 135 | } // namespace signals 136 | -------------------------------------------------------------------------------- /signals-cpp/signal.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #pragma once 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "connections.hpp" 34 | 35 | namespace signals { 36 | 37 | template 38 | struct signal { 39 | 40 | inline signal() { } 41 | inline ~signal() { disconnect_all(true); } 42 | 43 | inline connection connect(std::function target) { 44 | assert(target); 45 | 46 | // create the new conection handle 47 | auto conn = connection::make_connection(); 48 | 49 | // create a new targets vector (will be filled in with 50 | // the existing and still active targets within the lock below) 51 | auto new_targets = std::make_shared>(); 52 | 53 | // lock the mutex for writing 54 | std::lock_guard lock(m_write_targets_mutex); 55 | 56 | // copy existing targets 57 | if(auto t = m_targets) { 58 | new_targets->reserve(t->size() + 1); 59 | for(const auto& i : *t) { 60 | if(i.conn.connected()) { 61 | new_targets->push_back(i); 62 | } 63 | } 64 | } 65 | 66 | // add the new connection to the new vector 67 | new_targets->emplace_back(conn, std::move(target)); 68 | 69 | // replace the pointer to the targets (in a thread safe manner) 70 | m_targets = new_targets; 71 | 72 | return conn; 73 | } 74 | 75 | #if defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 76 | 77 | template 78 | inline connection connect(OBJ* obj, void (OBJ::*method)(ARGS... args)) { 79 | assert(obj); 80 | assert(method); 81 | return connect([=](ARGS... args) { (obj->*method)(args...); }); 82 | } 83 | 84 | #else // defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 85 | 86 | template 87 | inline connection connect(OBJ* obj, void (OBJ::*method)()) { 88 | assert(obj); 89 | assert(method); 90 | return connect([=]() { (obj->*method)(); }); 91 | } 92 | 93 | template 94 | inline connection connect(OBJ* obj, void (OBJ::*method)(ARG1 arg1)) { 95 | assert(obj); 96 | assert(method); 97 | return connect([=](ARG1 arg1) { (obj->*method)(arg1); }); 98 | } 99 | 100 | template 101 | inline connection connect(OBJ* obj, void (OBJ::*method)(ARG1 arg1, ARG2 arg2)) { 102 | assert(obj); 103 | assert(method); 104 | return connect([=](ARG1 arg1, ARG2 arg2) { (obj->*method)(arg1, arg2); }); 105 | } 106 | 107 | template 108 | inline connection connect(OBJ* obj, void (OBJ::*method)(ARG1 arg1, ARG2 arg2, ARG3 arg3)) { 109 | assert(obj); 110 | assert(method); 111 | return connect([=](ARG1 arg1, ARG2 arg2, ARG3 arg3) { (obj->*method)(arg1, arg2, arg3); }); 112 | } 113 | 114 | template 115 | inline connection connect(OBJ* obj, void (OBJ::*method)(ARG1 arg1, ARG2 arg2, ARG3 arg3, ARG4 arg4)) { 116 | assert(obj); 117 | assert(method); 118 | return connect([=](ARG1 arg1, ARG2 arg2, ARG3 arg3, ARG4 arg4) { (obj->*method)(arg1, arg2, arg3, arg4); }); 119 | } 120 | 121 | template 122 | inline connection connect(OBJ* obj, void (OBJ::*method)(ARG1 arg1, ARG2 arg2, ARG3 arg3, ARG4 arg4, ARG5 arg5)) { 123 | assert(obj); 124 | assert(method); 125 | return connect([=](ARG1 arg1, ARG2 arg2, ARG3 arg3, ARG4 arg4, ARG5 arg5) { (obj->*method)(arg1, arg2, arg3, arg4, arg5); }); 126 | } 127 | 128 | #endif // defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 129 | 130 | inline void disconnect_all(bool wait_if_running) { 131 | auto t = decltype(m_targets)(nullptr); 132 | 133 | { // clean out the targets pointer so no other thread 134 | // will fire this signal anymore (already running fired 135 | // calls might still reference the targets) 136 | std::lock_guard lock(m_write_targets_mutex); 137 | std::swap(m_targets, t); // replace m_targets pointer with a nullptr 138 | } 139 | 140 | // disconnect all targets 141 | if(t) { 142 | for(auto&& i : *t) { i.conn.disconnect(wait_if_running); } 143 | } 144 | } 145 | 146 | #if defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 147 | 148 | template 149 | inline void fire_if(bool condition, ARGS&&... args) const { 150 | if(condition) { 151 | if(auto t = get_targets()) { 152 | for(auto& i : *t) { i.conn.call([&]() { i.target(std::forward(args)...); }); } 153 | } 154 | } 155 | } 156 | template 157 | inline void fire(ARGS&&... args) const { fire_if(true, std::forward(args)...); } 158 | 159 | #else // defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 160 | 161 | inline void fire_if(bool condition) const { 162 | if(condition) { 163 | if(auto t = get_targets()) { 164 | for(auto& i : *t) { 165 | i.conn.call([&]() { i.target(); }); 166 | } 167 | } 168 | } 169 | } 170 | inline void fire() const { 171 | fire_if(true); 172 | } 173 | 174 | template 175 | inline void fire_if(bool condition, ARG1&& arg1) const { 176 | if(condition) { 177 | if(auto t = get_targets()) { 178 | for(auto& i : *t) { 179 | i.conn.call([&]() { i.target(std::forward(arg1)); }); 180 | } 181 | } 182 | } 183 | } 184 | template 185 | inline void fire(ARG1&& arg1) const { 186 | fire_if(true, std::forward(arg1)); 187 | } 188 | 189 | template 190 | inline void fire_if(bool condition, ARG1&& arg1, ARG2&& arg2) const { 191 | if(condition) { 192 | if(auto t = get_targets()) { 193 | for(auto& i : *t) { 194 | i.conn.call([&]() { i.target(std::forward(arg1), std::forward(arg2)); }); 195 | } 196 | } 197 | } 198 | } 199 | template 200 | inline void fire(ARG1&& arg1, ARG2&& arg2) const { 201 | fire_if(true, std::forward(arg1), std::forward(arg2)); 202 | } 203 | 204 | template 205 | inline void fire_if(bool condition, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3) const { 206 | if(condition) { 207 | if(auto t = get_targets()) { 208 | for(auto& i : *t) { 209 | i.conn.call([&]() { i.target(std::forward(arg1), std::forward(arg2), std::forward(arg3)); }); 210 | } 211 | } 212 | } 213 | } 214 | template 215 | inline void fire(ARG1&& arg1, ARG2&& arg2, ARG3&& arg3) const { 216 | fire_if(true, std::forward(arg1), std::forward(arg2), std::forward(arg3)); 217 | } 218 | 219 | template 220 | inline void fire_if(bool condition, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4) const { 221 | if(condition) { 222 | if(auto t = get_targets()) { 223 | for(auto& i : *t) { 224 | i.conn.call([&]() { i.target(std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4)); }); 225 | } 226 | } 227 | } 228 | } 229 | template 230 | inline void fire(ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4) const { 231 | fire_if(true, std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4)); 232 | } 233 | 234 | template 235 | inline void fire_if(bool condition, ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4, ARG5&& arg5) const { 236 | if(condition) { 237 | if(auto t = get_targets()) { 238 | for(auto& i : *t) { 239 | i.conn.call([&]() { i.target(std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4), std::forward(arg5)); }); 240 | } 241 | } 242 | } 243 | } 244 | template 245 | inline void fire(ARG1&& arg1, ARG2&& arg2, ARG3&& arg3, ARG4&& arg4, ARG5&& arg5) const { 246 | fire_if(true, std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4), std::forward(arg5)); 247 | } 248 | 249 | #endif // defined(SIGNALS_CPP_HAVE_VARIADIC_TEMPLATES) 250 | 251 | public: 252 | inline signal(signal&& o) SIGNALS_CPP_NOEXCEPT { 253 | std::lock_guard lock(o.m_write_targets_mutex); 254 | m_targets = std::move(o.m_targets); 255 | } 256 | 257 | inline signal& operator=(signal&& o) SIGNALS_CPP_NOEXCEPT { 258 | // use std::lock(...) in combination with std::defer_lock to acquire two locks 259 | // without worrying about potential deadlocks (see: http://en.cppreference.com/w/cpp/thread/lock) 260 | std::unique_lock lock1(m_write_targets_mutex, std::defer_lock); 261 | std::unique_lock lock2(o.m_write_targets_mutex, std::defer_lock); 262 | std::lock(lock1, lock2); 263 | 264 | m_targets = std::move(o.m_targets); 265 | return *this; 266 | } 267 | 268 | private: 269 | signal(signal const& o); // = delete; 270 | signal& operator=(signal const& o); // = delete; 271 | 272 | private: 273 | struct connection_target { 274 | inline connection_target(connection c, std::function t) : 275 | conn(std::move(c)), target(std::move(t)) 276 | { } 277 | 278 | connection conn; 279 | std::function target; 280 | }; 281 | 282 | private: 283 | std::shared_ptr> get_targets() const { 284 | std::lock_guard lock(m_write_targets_mutex); 285 | return m_targets; 286 | } 287 | 288 | mutable std::mutex m_write_targets_mutex; 289 | std::shared_ptr> m_targets; 290 | }; 291 | 292 | } // namespace signals 293 | -------------------------------------------------------------------------------- /signals-cpp/signals.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #pragma once 25 | 26 | #include "config.hpp" 27 | #include "connection.hpp" 28 | #include "connections.hpp" 29 | #include "signal.hpp" 30 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(signals-cpp CXX) 4 | message("configure: signals-cpp unittests") 5 | 6 | # configure cute 7 | include_directories("${SIGNALS_CPP_3RD_PARTY_DIR}/cute/") 8 | 9 | add_executable( 10 | signals_unittests 11 | main.cpp 12 | signals_unittests.cpp 13 | ../signals-cpp/config.hpp 14 | ../signals-cpp/connection.hpp 15 | ../signals-cpp/connections.hpp 16 | ../signals-cpp/signal.hpp 17 | ../signals-cpp/signals.hpp 18 | ) 19 | 20 | add_test( 21 | NAME signals_unittests 22 | COMMAND signals_unittests 23 | ) 24 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #include 25 | #include 26 | 27 | CUTE_INIT(); // initialize the cute framework 28 | 29 | int main(int argc, char* argv[]) { 30 | // create the test suite execution context 31 | auto context = cute::context(); 32 | 33 | // print out the status of each test during the execution of the test suite 34 | context.reporters.emplace_back(cute::reporter_ide); 35 | 36 | // run the test suite 37 | auto results = context.run(); 38 | 39 | // print out the test suite result summary 40 | cute::reporter_ide_summary(results); 41 | 42 | // exit with an exit code indicating success or failure for the test suite 43 | return ((results.test_cases_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS); 44 | } 45 | -------------------------------------------------------------------------------- /test/signals_unittests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2013 by Konstantin (Kosta) Baumann & Autodesk Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | 24 | #include 25 | 26 | #include 27 | 28 | CUTE_TEST( 29 | "test a single simple connection", 30 | "[signals],[signals_01],[single-threaded]" 31 | ) { 32 | signals::signal sig; 33 | 34 | int value = 0; 35 | sig.connect([&](int v) { value = v; }); 36 | 37 | sig.fire(42); 38 | CUTE_ASSERT(value == 42); 39 | } 40 | 41 | CUTE_TEST( 42 | "test a single simple connection with fire_if()", 43 | "[signals],[signals_01],[fire_if],[single-threaded]" 44 | ) { 45 | signals::signal sig; 46 | 47 | int value = 0; 48 | sig.connect([&](int v) { value = v; }); 49 | 50 | sig.fire_if(false, 42); 51 | CUTE_ASSERT(value == 0); 52 | 53 | sig.fire_if(true, 42); 54 | CUTE_ASSERT(value == 42); 55 | } 56 | 57 | CUTE_TEST( 58 | "test to disconnect a single simple connection", 59 | "[signals],[signals_02],[single-threaded]" 60 | ) { 61 | signals::signal sig; 62 | 63 | int value = 0; 64 | auto conn = sig.connect([&](int v) { value = v; }); 65 | 66 | sig.fire(42); 67 | CUTE_ASSERT(value == 42); 68 | 69 | CUTE_ASSERT(conn.disconnect()); 70 | CUTE_ASSERT(!conn.disconnect()); // cannot be disconnected a second time 71 | sig.fire(84); 72 | CUTE_ASSERT(value == 42); // still needs to be 42, since we disconnected 73 | } 74 | 75 | CUTE_TEST( 76 | "test to check the existance of a connection", 77 | "[signals],[signals_03],[single-threaded]" 78 | ) { 79 | signals::signal sig; 80 | 81 | auto connTrue = sig.connect([&](int) { }); 82 | auto connFalse = signals::connection(); 83 | 84 | CUTE_ASSERT(connTrue.connected()); 85 | CUTE_ASSERT(!connFalse.connected()); 86 | 87 | CUTE_ASSERT(connTrue.disconnect()); 88 | CUTE_ASSERT(!connTrue.connected()); 89 | } 90 | 91 | CUTE_TEST( 92 | "test multiple simple connections", 93 | "[signals],[signals_04],[single-threaded]" 94 | ) { 95 | signals::signal sig; 96 | 97 | int value1 = 0, value2 = 0; 98 | sig.connect([&](int v) { value1 = v; }); 99 | sig.connect([&](int v) { value2 = v * 2; }); 100 | 101 | sig.fire(42); 102 | CUTE_ASSERT(value1 == 42); 103 | CUTE_ASSERT(value2 == 84); 104 | } 105 | 106 | CUTE_TEST( 107 | "test the connections mediator", 108 | "[signals],[signals_05],[single-threaded]" 109 | ) { 110 | signals::signal sig; 111 | signals::connections conns; 112 | 113 | int value1 = 0, value2 = 0; 114 | conns.connect(sig, [&](int v) { value1 = v; }); 115 | conns.connect(sig, [&](int v) { value2 = v * 2; }); 116 | 117 | sig.fire(42); 118 | CUTE_ASSERT(value1 == 42); 119 | CUTE_ASSERT(value2 == 84); 120 | 121 | conns.disconnect_all(); 122 | sig.fire(21); 123 | CUTE_ASSERT(value1 == 42); // should be the same as above 124 | CUTE_ASSERT(value2 == 84); // should be the same as above 125 | } 126 | 127 | CUTE_TEST( 128 | "test to connect to a method", 129 | "[signals],[signals_06],[single-threaded]" 130 | ) { 131 | struct Test { 132 | Test() : v(0) { 133 | conns.connect(sigVoid, this, &Test::onVoidValue); 134 | conns.connect(sigInt, this, &Test::onIntValue); 135 | } 136 | 137 | void onVoidValue() { v = 1510; } 138 | void onIntValue(int v_) { v = v_; } 139 | 140 | int v; 141 | signals::signal sigVoid; 142 | signals::signal sigInt; 143 | signals::connections conns; 144 | }; 145 | 146 | Test t; 147 | CUTE_ASSERT(t.v == 0); 148 | 149 | t.sigVoid.fire(); 150 | CUTE_ASSERT(t.v == 1510); 151 | 152 | t.sigInt.fire(42); 153 | CUTE_ASSERT(t.v == 42); 154 | } 155 | 156 | CUTE_TEST( 157 | "test to connect to a method with inheriting from signals::connections", 158 | "[signals],[signals_07],[single-threaded]" 159 | ) { 160 | struct Test : signals::connections { 161 | Test() : v(0) { 162 | connect(sigVoid, this, &Test::onVoidValue); 163 | connect(sigInt, this, &Test::onIntValue); 164 | } 165 | 166 | void onVoidValue() { v = 1510; } 167 | void onIntValue(int v_) { v = v_; } 168 | 169 | int v; 170 | signals::signal sigVoid; 171 | signals::signal sigInt; 172 | }; 173 | 174 | Test t; 175 | CUTE_ASSERT(t.v == 0); 176 | 177 | t.sigVoid.fire(); 178 | CUTE_ASSERT(t.v == 1510); 179 | 180 | t.sigInt.fire(42); 181 | CUTE_ASSERT(t.v == 42); 182 | } 183 | 184 | CUTE_TEST( 185 | "test to disconnect a connection from within the callback triggered by that connection", 186 | "[signals],[signals_08],[single-threaded]" 187 | ) { 188 | signals::signal sig; 189 | signals::connection conn; 190 | 191 | conn = sig.connect([&]() { conn.disconnect(false); }); 192 | CUTE_ASSERT(conn.connected()); 193 | sig.fire(); 194 | CUTE_ASSERT(!conn.connected()); 195 | } 196 | 197 | CUTE_TEST( 198 | "test to disconnect a connection from within the callback triggered by that connection on another thread", 199 | "[signals],[signals_09],[multi-threaded]" 200 | ) { 201 | signals::signal sig; 202 | signals::connection conn; 203 | 204 | cute::tick ticker; 205 | 206 | conn = sig.connect([&]() { 207 | ticker.at_tick(2, [&]() { conn.disconnect(false); }); 208 | ticker.delay_tick_for(4, std::chrono::milliseconds(10)); 209 | }); 210 | auto t = cute::thread([&]() { 211 | ticker.reached_tick(1); 212 | sig.fire(); 213 | ticker.reached_tick(5); 214 | }); 215 | 216 | ticker.at_tick(0, [&]() { CUTE_ASSERT(conn.connected()); }); 217 | ticker.at_tick(3, [&]() { CUTE_ASSERT(!conn.connected()); }); 218 | ticker.blocks_until_tick(4, [&]() { conn.disconnect(true); }); 219 | ticker.reached_tick(6); 220 | } 221 | 222 | CUTE_TEST( 223 | "test to connect a new connection from within the callback triggered by a signal", 224 | "[signals],[signals_10],[single-threaded]" 225 | ) { 226 | signals::signal sig; 227 | signals::connection conn1, conn2; 228 | int value = 0; 229 | 230 | conn1 = sig.connect([&]() { 231 | conn1.disconnect(); 232 | conn2 = sig.connect([&]() { value = 42; }); 233 | }); 234 | 235 | CUTE_ASSERT(conn1.connected()); 236 | CUTE_ASSERT(!conn2.connected()); 237 | CUTE_ASSERT(value == 0); 238 | 239 | sig.fire(); 240 | CUTE_ASSERT(!conn1.connected()); 241 | CUTE_ASSERT(conn2.connected()); 242 | CUTE_ASSERT(value == 0); 243 | 244 | sig.fire(); 245 | CUTE_ASSERT(!conn1.connected()); 246 | CUTE_ASSERT(conn2.connected()); 247 | CUTE_ASSERT(value ==42); 248 | } 249 | 250 | CUTE_TEST( 251 | "test to connect a new connection from within the callback triggered by a signal and immediately trigger " 252 | "this new connection from another thread without the need to finish the first trigger before that", 253 | "[signals],[signals_11],[multi-threaded]" 254 | ) { 255 | signals::signal sig; 256 | signals::connection conn1, conn2; 257 | int value = 0; 258 | 259 | cute::tick ticker; 260 | 261 | conn1 = sig.connect([&]() { 262 | ticker.at_tick(2, [&]() { conn1.disconnect(); }); 263 | ticker.at_tick(4, [&]() { conn2 = sig.connect([&]() { value = 42; }); }); 264 | ticker.reached_tick(9); 265 | }); 266 | auto t1 = cute::thread([&]() { 267 | ticker.reached_tick(1); 268 | sig.fire(); 269 | ticker.reached_tick(10); 270 | }); 271 | 272 | auto t2 = cute::thread([&]() { 273 | ticker.reached_tick(6); 274 | sig.fire(); 275 | ticker.reached_tick(7); 276 | }); 277 | 278 | ticker.at_tick(0, [&]() { 279 | CUTE_ASSERT(conn1.connected()); 280 | CUTE_ASSERT(!conn2.connected()); 281 | CUTE_ASSERT(value == 0); 282 | }); 283 | 284 | ticker.at_tick(3, [&]() { 285 | CUTE_ASSERT(!conn1.connected()); 286 | CUTE_ASSERT(!conn2.connected()); 287 | CUTE_ASSERT(value == 0); 288 | }); 289 | 290 | ticker.at_tick(5, [&]() { 291 | CUTE_ASSERT(!conn1.connected()); 292 | CUTE_ASSERT(conn2.connected()); 293 | CUTE_ASSERT(value == 0); 294 | }); 295 | 296 | ticker.at_tick(8, [&]() { 297 | CUTE_ASSERT(!conn1.connected()); 298 | CUTE_ASSERT(conn2.connected()); 299 | CUTE_ASSERT(value == 42); 300 | }); 301 | 302 | ticker.reached_tick(11); 303 | } 304 | --------------------------------------------------------------------------------