diff --git a/HACKING.md b/HACKING.md index cce820222..e85482528 100644 --- a/HACKING.md +++ b/HACKING.md @@ -133,8 +133,4 @@ variable. --> ### conan -If you are going to build/run grpc/sfml related parts of code, then you can use conan to install all required dependecies. Install conan and just call cmd -```cmd -conan install . --output-folder=build --build=missing -s compiler.cppstd=gnu20 -c tools.system.package_manager:mode=install -c tools.system.package_manager:sudo=True --settings=build_type=Release -``` -or if your cmake version is >= 3.24, then cmake would do it by itself if you set variable `RPP_USE_CONAN`. +if your cmake version is >= 3.24, you can use conan to install RPP's dependencies. To use it your Cmake preset should be inherited from `use-conan`. CMake would configure conan properly. diff --git a/Readme.md b/Readme.md index 832d625d3..f1a3866df 100644 --- a/Readme.md +++ b/Readme.md @@ -183,6 +183,8 @@ DEALINGS IN THE SOFTWARE. ReactivePlusPlus library uses: - [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. - [snitch](https://github.com/cschreib/snitch) for unit testing only, fetched automatically in case of `RPP_BUILD_TESTS` enabled +- [trompeloeil](https://github.com/rollbear/trompeloeil) for mocking in unit testing only, fetched automatically in case of `RPP_BUILD_TESTS` enabled +- [nanobench](https://github.com/martinus/nanobench) for benchmarking only, fetched automatically in case of `RPP_BUILD_BENCHMARKS` enabled - [RxCpp](https://github.com/ReactiveX/RxCpp) only for comparison of performance between RPP and RxCpp in CI benchmarks. Used as cmake dependency under option - [reactivex.io](https://reactivex.io) as source for insipration and definition of entities used in RPP. Some comments used in RPP source code taken from [reactivex.io](https://reactivex.io) - [rxmarbles python](https://pypi.org/project/rxmarbles/) as generator of marbles graphs in doxygen documentation diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 59d265051..7a9bdfa03 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -58,19 +58,23 @@ macro(rpp_fetch_library_extended NAME URL TAG TARGET_NAME) endmacro() macro(rpp_fetch_library NAME URL TAG) - rpp_fetch_library_extended(${NAME} ${URL} ${TAG} ${NAME}) + find_package(${NAME} QUIET) + if (NOT ${NAME}_FOUND) + message("-- RPP: Fetching ${NAME}...") + rpp_fetch_library_extended(${NAME} ${URL} ${TAG} ${NAME}) + endif() endmacro() # ==================== RXCPP ======================= if (RPP_BUILD_RXCPP AND RPP_BUILD_BENCHMARKS) set(RXCPP_DISABLE_TESTS_AND_EXAMPLES 1) - rpp_fetch_library(rxcpp https://github.com/ReactiveX/RxCpp.git origin/main) endif() # ===================== Snitch =================== if (RPP_BUILD_TESTS) rpp_fetch_library(snitch https://github.com/cschreib/snitch.git main) + rpp_fetch_library(trompeloeil https://github.com/rollbear/trompeloeil.git main) endif() diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake index 740f1631c..cb6cc6d92 100644 --- a/cmake/dev-mode.cmake +++ b/cmake/dev-mode.cmake @@ -9,6 +9,7 @@ if(POLICY CMP0091) cmake_policy(SET CMP0091 NEW) endif() + if(POLICY CMP0144) cmake_policy(SET CMP0144 NEW) endif() diff --git a/cmake/variables.cmake b/cmake/variables.cmake index a3c403f6b..8d91fdb67 100644 --- a/cmake/variables.cmake +++ b/cmake/variables.cmake @@ -90,6 +90,18 @@ if (RPP_DEVELOPER_MODE) option(RPP_ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF) option(RPP_BUILD_RXCPP "Build RxCpp to compare results with it." OFF) + if (DEFINED CONAN_ARGS) + if (RPP_BUILD_TESTS) + set(CONAN_ARGS "${CONAN_ARGS};-o rpp/*:with_tests=True") + endif() + if (RPP_BUILD_SFML_CODE) + set(CONAN_ARGS "${CONAN_ARGS};-o rpp/*:with_sfml=True") + endif() + if (RPP_BUILD_BENCHMARKS) + set(CONAN_ARGS "${CONAN_ARGS};-o rpp/*:with_benchmarks=True") + endif() + endif() + if(RPP_ENABLE_COVERAGE) include(cmake/coverage.cmake) endif() diff --git a/conanfile.py b/conanfile.py index 60cce2bd7..bc7892cd9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,11 +1,40 @@ from conan import ConanFile -class Config(ConanFile): +class RppConan(ConanFile): + name = "rpp" settings = "os", "compiler", "build_type", "arch" generators = "CMakeDeps", "CMakeToolchain" + options = { + "with_grpc" : [False, True], + "with_sfml" : [False, True], + "with_tests" : [False, True], + "with_cmake" : [False, True], + "with_benchmarks" : [False, True] + } + default_options = { + "with_grpc" : False, + "with_sfml" : False, + "with_tests": False, + "with_cmake": False, + "with_benchmarks" : False + } + def requirements(self): - self.requires("sfml/2.6.1") - # self.requires("grpc/1.54.3", transitive_libs=True, transitive_headers=True) - # self.requires("protobuf/3.21.12") - # self.requires("libmount/2.39", override=True) + if self.options.with_tests: + self.requires("trompeloeil/47") + self.requires("snitch/1.2.3") + + if self.options.with_benchmarks: + self.requires("nanobench/4.3.11") + + if self.options.with_sfml: + self.requires("sfml/2.6.1", options={"audio": False}) + + # if self.options.with_grpc: + # self.requires("grpc/1.54.3", transitive_libs=True, transitive_headers=True) + # self.requires("protobuf/3.21.12") + # self.requires("libmount/2.39", override=True) + + if self.options.with_cmake: + self.tool_requires("cmake/3.29.3") diff --git a/src/benchmarks/CMakeLists.txt b/src/benchmarks/CMakeLists.txt index 77c272e4d..38ffd201f 100644 --- a/src/benchmarks/CMakeLists.txt +++ b/src/benchmarks/CMakeLists.txt @@ -12,7 +12,7 @@ set(TARGET benchmarks) add_executable(${TARGET} benchmarks.cpp) -target_link_libraries(${TARGET} PRIVATE rpp nanobench) +target_link_libraries(${TARGET} PRIVATE rpp nanobench::nanobench) if (RPP_BUILD_RXCPP) target_link_libraries(${TARGET} PRIVATE rxcpp) target_compile_definitions(${TARGET} PRIVATE RPP_BUILD_RXCPP) diff --git a/src/benchmarks/benchmarks.cpp b/src/benchmarks/benchmarks.cpp index 23024edab..796d019d5 100644 --- a/src/benchmarks/benchmarks.cpp +++ b/src/benchmarks/benchmarks.cpp @@ -1,3 +1,4 @@ +#define ANKERL_NANOBENCH_IMPLEMENT #include #include diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 45c99fabe..71ae46e80 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -14,7 +14,7 @@ macro(add_test_target target_name module files) set(TARGET ${target_name}) add_executable(${TARGET} ${files}) - target_link_libraries(${TARGET} PRIVATE snitch::snitch rpp_tests_utils ${module}) + target_link_libraries(${TARGET} PRIVATE snitch::snitch trompeloeil::trompeloeil rpp_tests_utils ${module}) set_target_properties(${TARGET} PROPERTIES FOLDER Tests/Suites/${module}) add_test_with_coverage(${TARGET}) diff --git a/src/tests/rpp/test_buffer.cpp b/src/tests/rpp/test_buffer.cpp index 3a5e50342..629388a49 100644 --- a/src/tests/rpp/test_buffer.cpp +++ b/src/tests/rpp/test_buffer.cpp @@ -11,81 +11,65 @@ #include #include -#include #include #include #include #include #include "disposable_observable.hpp" +#include "rpp_trompeloil.hpp" TEST_CASE("buffer bundles items") { + trompeloeil::sequence s{}; + auto mock = mock_observer>{}; + SECTION("observable of -1-2-3-|") { - auto mock = mock_observer_strategy>{}; - auto obs = rpp::source::just(1, 2, 3); - SECTION("subscribe on it via buffer(0)") + auto obs = rpp::source::just(1, 2, 3); + SECTION("buffer(0) - shall see -{1}-{2}-{3}-|") { - obs | rpp::ops::buffer(0) - | rpp::ops::subscribe(mock); - SECTION("shall see -{1}-{2}-{3}-|") - { - CHECK(mock.get_received_values() == std::vector{std::vector{1}, std::vector{2}, std::vector{3}}); - CHECK(mock.get_on_completed_count() == 1); - CHECK(mock.get_on_error_count() == 0); - } + REQUIRE_CALL(*mock, on_next(std::vector{1})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_next(std::vector{2})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_next(std::vector{3})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(s); + + obs | rpp::ops::buffer(0) | rpp::ops::subscribe(mock); } - SECTION("subscribe on it via buffer(1)") + SECTION("buffer(1) - shall see -{1}-{2}-{3}-|") { + REQUIRE_CALL(*mock, on_next(std::vector{1})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_next(std::vector{2})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_next(std::vector{3})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(s); + obs | rpp::ops::buffer(1) | rpp::ops::subscribe(mock); - SECTION("shall see -{1}-{2}-{3}-|") - { - CHECK(mock.get_received_values() == std::vector{std::vector{1}, std::vector{2}, std::vector{3}}); - CHECK(mock.get_on_completed_count() == 1); - CHECK(mock.get_on_error_count() == 0); - } } - SECTION("subscribe on it via buffer(2)") + SECTION("buffer(2) - shall see -{1,2}-{3}|") { + REQUIRE_CALL(*mock, on_next(std::vector{1, 2})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_next(std::vector{3})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(s); + obs | rpp::ops::buffer(2) | rpp::ops::subscribe(mock); - SECTION("shall see -{1,2}-{3}|") - { - CHECK(mock.get_received_values() == std::vector>{ - std::vector{1, 2}, - std::vector{3}, - }); - CHECK(mock.get_on_completed_count() == 1); - CHECK(mock.get_on_error_count() == 0); - } } - SECTION("subscribe on it via buffer(3)") + SECTION("buffer(3) - shall see -{1,2,3}-|") { + REQUIRE_CALL(*mock, on_next(std::vector{1, 2, 3})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(s); + obs | rpp::ops::buffer(3) | rpp::ops::subscribe(mock); - SECTION("shall see -{1,2,3}-|") - { - CHECK(mock.get_received_values() == std::vector>{ - std::vector{1, 2, 3}, - }); - CHECK(mock.get_on_completed_count() == 1); - CHECK(mock.get_on_error_count() == 0); - } } - SECTION("subscribe on it via buffer(4)") + SECTION("buffer(4) - shall see -{1,2,3}-|") { + REQUIRE_CALL(*mock, on_next(std::vector{1, 2, 3})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(s); + obs | rpp::ops::buffer(4) | rpp::ops::subscribe(mock); - SECTION("shall see -{1,2,3}-|") - { - CHECK(mock.get_received_values() == std::vector>{ - std::vector{1, 2, 3}, - }); - CHECK(mock.get_on_completed_count() == 1); - CHECK(mock.get_on_error_count() == 0); - } } } @@ -95,43 +79,28 @@ TEST_CASE("buffer bundles items") rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic(), rpp::source::just(2).as_dynamic()) | rpp::ops::merge(); - auto mock = mock_observer_strategy>{}; - SECTION("subscribe on it via buffer(0)") + SECTION("buffer(0) - shall see -{1}-x, which means error event is through") { + REQUIRE_CALL(*mock, on_next(std::vector{1})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_error(trompeloeil::_)).IN_SEQUENCE(s); + obs | rpp::ops::buffer(0) | rpp::ops::subscribe(mock); - SECTION("shall see -{1}-x, which means error event is through") - { - CHECK(mock.get_received_values() == std::vector>{ - std::vector{1}, - }); - CHECK(mock.get_on_completed_count() == 0); - CHECK(mock.get_on_error_count() == 1); - } } - SECTION("subscribe on it via buffer(1)") + SECTION("buffer(1) - shall see -{1}-x, which means error event is through") { + REQUIRE_CALL(*mock, on_next(std::vector{1})).IN_SEQUENCE(s); + REQUIRE_CALL(*mock, on_error(trompeloeil::_)).IN_SEQUENCE(s); + obs | rpp::ops::buffer(1) | rpp::ops::subscribe(mock); - SECTION("shall see -{1}-x, which means error event is through") - { - CHECK(mock.get_received_values() == std::vector>{ - std::vector{1}, - }); - CHECK(mock.get_on_completed_count() == 0); - CHECK(mock.get_on_error_count() == 1); - } } - SECTION("subscribe on it via buffer(2)") + SECTION("buffer(2) - shall see --x, which means error event is through") { + REQUIRE_CALL(*mock, on_error(trompeloeil::_)).IN_SEQUENCE(s); + obs | rpp::ops::buffer(2) | rpp::ops::subscribe(mock); - SECTION("shall see --x, which means error event is through") - { - CHECK(mock.get_received_values().empty()); - CHECK(mock.get_on_completed_count() == 0); - CHECK(mock.get_on_error_count() == 1); - } } } } diff --git a/src/tests/rpp/test_map.cpp b/src/tests/rpp/test_map.cpp index 0058b3bac..12a469a48 100644 --- a/src/tests/rpp/test_map.cpp +++ b/src/tests/rpp/test_map.cpp @@ -10,12 +10,12 @@ #include -#include #include #include #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "rpp_trompeloil.hpp" #include #include @@ -26,27 +26,27 @@ TEMPLATE_TEST_CASE("map modifies values and forward errors/completions", "", rpp SECTION("map changes value") { - mock_observer_strategy mock{}; + mock_observer mock{}; + trompeloeil::sequence seq; - obs | rpp::operators::map([](auto v) { return std::string("TEST ") + std::to_string(v); }) | rpp::operators::subscribe(mock); + REQUIRE_CALL(*mock, on_next("TEST 1")).IN_SEQUENCE(seq); + REQUIRE_CALL(*mock, on_next("TEST 2")).IN_SEQUENCE(seq); + REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(seq); - CHECK(mock.get_received_values() == std::vector{"TEST 1", "TEST 2"}); - CHECK(mock.get_on_error_count() == 0); - CHECK(mock.get_on_completed_count() == 1); + obs | rpp::operators::map([](auto v) { return std::string("TEST ") + std::to_string(v); }) | rpp::operators::subscribe(std::move(mock)); } SECTION("map with exception value") { - mock_observer_strategy mock{}; + mock_observer mock{}; + trompeloeil::sequence seq; - auto map = rpp::operators::map([](int) -> int { throw std::runtime_error{""}; }); + REQUIRE_CALL(*mock, on_error(trompeloeil::_)).IN_SEQUENCE(seq); - obs | map | rpp::operators::subscribe(mock); // NOLINT + auto map = rpp::operators::map([](int) -> int { throw std::runtime_error{"map failed"}; }); - CHECK(mock.get_received_values() == std::vector{}); - CHECK(mock.get_on_error_count() == 1); - CHECK(mock.get_on_completed_count() == 0); + obs | map | rpp::operators::subscribe(std::move(mock)); // NOLINT } } diff --git a/src/tests/utils/rpp_trompeloil.hpp b/src/tests/utils/rpp_trompeloil.hpp new file mode 100644 index 000000000..6018c97c6 --- /dev/null +++ b/src/tests/utils/rpp_trompeloil.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include + +#include + +#include + + +namespace trompeloeil +{ + template<> + inline void reporter::send( + severity s, + const char*, + unsigned long, + const char* msg) + { + FAIL_CHECK(msg); + if (s == severity::fatal) + { + std::terminate(); // terminate due to rpp could catch exceptions but we dont want it + } + } + + template<> + inline void reporter::sendOk( + const char* trompeloeil_mock_calls_done_correctly) + { + REQUIRE(trompeloeil_mock_calls_done_correctly != 0); + } +} // namespace trompeloeil + +template +class mock_observer +{ +public: + struct impl_t + { + impl_t() = default; + + MAKE_MOCK1(on_next, void(const T&), const); + MAKE_MOCK1(on_next, void(T&&), const); + MAKE_MOCK1(on_error, void(const std::exception_ptr& err), const); + MAKE_MOCK0(on_completed, void(), const); + }; + + impl_t& operator*() const noexcept { return *impl; } + + void on_next(const T& v) const noexcept + { + impl->on_next(v); + } + + void on_next(T&& v) const noexcept + { + impl->on_next(std::move(v)); + } + + void on_error(const std::exception_ptr& err) const noexcept { impl->on_error(err); } + void on_completed() const noexcept { impl->on_completed(); } + + static bool is_disposed() noexcept { return false; } + static void set_upstream(const rpp::disposable_wrapper&) noexcept {} + +private: + std::shared_ptr impl = std::make_shared(); +};