From 6eecb02c5f0b23a07bf4446a3b6f4f7d27bd8586 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Aug 2023 19:44:20 +0300 Subject: [PATCH] Disposable tests (#409) --- .../rpp/disposables/refcount_disposable.hpp | 8 ++-- src/rpp/rpp/operators/details/utils.hpp | 9 +++- src/rpp/rpp/operators/group_by.hpp | 3 +- src/rpp/rpp/operators/merge.hpp | 2 +- src/rpp/rpp/operators/with_latest_from.hpp | 2 +- src/tests/rpp/test_concat.cpp | 7 +++ src/tests/rpp/test_distinct_until_changed.cpp | 7 +++ src/tests/rpp/test_filter.cpp | 6 +++ src/tests/rpp/test_first.cpp | 7 +++ src/tests/rpp/test_flat_map.cpp | 7 +++ src/tests/rpp/test_group_by.cpp | 2 +- src/tests/rpp/test_last.cpp | 7 +++ src/tests/rpp/test_map.cpp | 7 +++ src/tests/rpp/test_merge.cpp | 12 +++++ src/tests/rpp/test_repeat.cpp | 6 +++ src/tests/rpp/test_scan.cpp | 7 +++ src/tests/rpp/test_skip.cpp | 7 +++ src/tests/rpp/test_subscribe_on.cpp | 8 ++++ src/tests/rpp/test_take.cpp | 7 +++ src/tests/rpp/test_take_while.cpp | 7 +++ src/tests/rpp/test_with_lastest_from.cpp | 12 +++++ src/tests/utils/disposable_observable.hpp | 44 +++++++++++++++++++ 22 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 src/tests/utils/disposable_observable.hpp diff --git a/src/rpp/rpp/disposables/refcount_disposable.hpp b/src/rpp/rpp/disposables/refcount_disposable.hpp index 3672a1fbc..30308ffbd 100644 --- a/src/rpp/rpp/disposables/refcount_disposable.hpp +++ b/src/rpp/rpp/disposables/refcount_disposable.hpp @@ -26,7 +26,7 @@ namespace rpp * * @ingroup disposables */ -class refcount_disposable final : public interface_composite_disposable, public std::enable_shared_from_this +class refcount_disposable : public interface_composite_disposable, public std::enable_shared_from_this { public: refcount_disposable() = default; @@ -35,9 +35,9 @@ class refcount_disposable final : public interface_composite_disposable, public refcount_disposable(const refcount_disposable&) = delete; refcount_disposable(refcount_disposable&&) noexcept = delete; - bool is_disposed() const noexcept override { return m_refcount.load(std::memory_order_acquire) == 0; } + bool is_disposed() const noexcept final { return m_refcount.load(std::memory_order_acquire) == 0; } - void dispose() override + void dispose() final { while (auto current_value = m_refcount.load(std::memory_order_acquire)) { @@ -66,7 +66,7 @@ class refcount_disposable final : public interface_composite_disposable, public using interface_composite_disposable::add; - void add(disposable_wrapper disposable) override + void add(disposable_wrapper disposable) final { m_underlying.add(std::move(disposable)); } diff --git a/src/rpp/rpp/operators/details/utils.hpp b/src/rpp/rpp/operators/details/utils.hpp index 3e6084793..968b76a66 100644 --- a/src/rpp/rpp/operators/details/utils.hpp +++ b/src/rpp/rpp/operators/details/utils.hpp @@ -43,5 +43,12 @@ class pointer_under_lock std::scoped_lock m_lock; }; - +template +struct with_auto_dispose : public T +{ + ~with_auto_dispose() override + { + T::dispose(); + } +}; } // namespace rpp::operators::details \ No newline at end of file diff --git a/src/rpp/rpp/operators/group_by.hpp b/src/rpp/rpp/operators/group_by.hpp index d943b4ef8..d91105a2f 100644 --- a/src/rpp/rpp/operators/group_by.hpp +++ b/src/rpp/rpp/operators/group_by.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -67,7 +68,7 @@ struct group_by_observer_strategy RPP_NO_UNIQUE_ADDRESS KeyComparator comparator; mutable std::map, KeyComparator> key_to_subject{}; - std::shared_ptr disposable = std::make_shared(); + std::shared_ptr> disposable = std::make_shared>(); void on_subscribe(rpp::constraint::observer auto& obs) const { diff --git a/src/rpp/rpp/operators/merge.hpp b/src/rpp/rpp/operators/merge.hpp index 2d6fdabbd..4c3c4d807 100644 --- a/src/rpp/rpp/operators/merge.hpp +++ b/src/rpp/rpp/operators/merge.hpp @@ -26,7 +26,7 @@ namespace rpp::operators::details { template -class merge_disposable final : public composite_disposable +class merge_disposable final : public with_auto_dispose { public: merge_disposable(TObserver&& observer) : m_observer(std::move(observer)) {} diff --git a/src/rpp/rpp/operators/with_latest_from.hpp b/src/rpp/rpp/operators/with_latest_from.hpp index f06776373..01d2f90e3 100644 --- a/src/rpp/rpp/operators/with_latest_from.hpp +++ b/src/rpp/rpp/operators/with_latest_from.hpp @@ -25,7 +25,7 @@ namespace rpp::operators::details { template -class with_latest_from_disposable final : public composite_disposable +class with_latest_from_disposable final : public with_auto_dispose { public: explicit with_latest_from_disposable(Observer&& observer, const TSelector& selector) diff --git a/src/tests/rpp/test_concat.cpp b/src/tests/rpp/test_concat.cpp index c303f0cec..dca35c0b2 100644 --- a/src/tests/rpp/test_concat.cpp +++ b/src/tests/rpp/test_concat.cpp @@ -17,6 +17,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + #include #include @@ -237,4 +239,9 @@ TEST_CASE("concat of iterable doesn't produce extra copies") CHECK(tracker.get_copy_count() - initial_copy == 2); // 1 copy to observable + 1 copy to observer CHECK(tracker.get_move_count() - initial_move == 0); } +} + +TEST_CASE("concat disposes original disposable on disposing") +{ + test_operator_over_observable_with_disposable([](auto&& observable){return rpp::source::concat(observable);}); } \ No newline at end of file diff --git a/src/tests/rpp/test_distinct_until_changed.cpp b/src/tests/rpp/test_distinct_until_changed.cpp index e2f38e395..389a1841a 100644 --- a/src/tests/rpp/test_distinct_until_changed.cpp +++ b/src/tests/rpp/test_distinct_until_changed.cpp @@ -15,6 +15,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + TEMPLATE_TEST_CASE("distinct_until_changed filters out consecutive duplicates and send first value from duplicates", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) { @@ -46,4 +48,9 @@ TEST_CASE("distinct_until_changed doesn't produce extra copies") .send_by_move = {.copy_count = 1, // 1 copy to internal state .move_count = 1} // 1 move to final subscriber }); +} + +TEST_CASE("distinct_until_changed disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::distinct_until_changed()); } \ No newline at end of file diff --git a/src/tests/rpp/test_filter.cpp b/src/tests/rpp/test_filter.cpp index e7f8aaa51..e3ea43b1e 100644 --- a/src/tests/rpp/test_filter.cpp +++ b/src/tests/rpp/test_filter.cpp @@ -15,6 +15,7 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" #include #include @@ -69,4 +70,9 @@ TEST_CASE("filter doesn't produce extra copies") .move_count = 0} }); } +} + +TEST_CASE("filter disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::filter([](const int&){return false;})); } \ No newline at end of file diff --git a/src/tests/rpp/test_first.cpp b/src/tests/rpp/test_first.cpp index b5747dc4d..47d6021d1 100644 --- a/src/tests/rpp/test_first.cpp +++ b/src/tests/rpp/test_first.cpp @@ -18,6 +18,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + TEST_CASE("first only emits once") { @@ -88,4 +90,9 @@ TEST_CASE("first doesn't produce extra copies") .move_count = 1} // 1 move to final subscriber }, 2); } +} + +TEST_CASE("first disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::first()); } \ No newline at end of file diff --git a/src/tests/rpp/test_flat_map.cpp b/src/tests/rpp/test_flat_map.cpp index b9c601e3c..e8f7f1120 100644 --- a/src/tests/rpp/test_flat_map.cpp +++ b/src/tests/rpp/test_flat_map.cpp @@ -21,6 +21,8 @@ #include "copy_count_tracker.hpp" #include "mock_observer.hpp" +#include "disposable_observable.hpp" + #include #include @@ -134,4 +136,9 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod } } } +} + +TEST_CASE("flat_map disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::flat_map([](const auto& v){return rpp::source::just(v); })); } \ No newline at end of file diff --git a/src/tests/rpp/test_group_by.cpp b/src/tests/rpp/test_group_by.cpp index 2be4dd32c..8d39b7c17 100644 --- a/src/tests/rpp/test_group_by.cpp +++ b/src/tests/rpp/test_group_by.cpp @@ -261,4 +261,4 @@ TEST_CASE("group_by selectors affects types", "[group_by]") REQUIRE(mock.get_on_completed_count() == 0); } } -} \ No newline at end of file +} diff --git a/src/tests/rpp/test_last.cpp b/src/tests/rpp/test_last.cpp index 1cc0edddf..7e6aad9b9 100644 --- a/src/tests/rpp/test_last.cpp +++ b/src/tests/rpp/test_last.cpp @@ -18,6 +18,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + TEST_CASE("last only emits once") @@ -92,4 +94,9 @@ TEST_CASE("last doesn't produce extra copies") .move_count = 3} // 2 move to std::optional + 1 move to final subscriber }, 2); } +} + +TEST_CASE("last disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::last()); } \ No newline at end of file diff --git a/src/tests/rpp/test_map.cpp b/src/tests/rpp/test_map.cpp index 9bbd31fa6..477b23762 100644 --- a/src/tests/rpp/test_map.cpp +++ b/src/tests/rpp/test_map.cpp @@ -15,6 +15,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + #include #include @@ -63,4 +65,9 @@ TEST_CASE("map doesn't produce extra copies") .move_count = 2} // 1 move on return from map + 1 move to final subscriber }); } +} + +TEST_CASE("map disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::map([](auto&& v){return std::forward(v);})); } \ No newline at end of file diff --git a/src/tests/rpp/test_merge.cpp b/src/tests/rpp/test_merge.cpp index 98a5a6e91..ddc655ff1 100644 --- a/src/tests/rpp/test_merge.cpp +++ b/src/tests/rpp/test_merge.cpp @@ -22,6 +22,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + #include #include @@ -285,4 +287,14 @@ TEST_CASE("merge doesn't produce extra copies") REQUIRE(verifier.get_copy_count() == 0); REQUIRE(verifier.get_move_count() == 1); // 1 move to final subscriber } +} + +TEST_CASE("merge disposes original disposable on disposing") +{ + auto observable_disposable = std::make_shared(); + auto observable = observable_with_disposable(observable_disposable); + + test_operator_with_disposable(rpp::ops::merge_with(observable)); + + CHECK(observable_disposable->is_disposed()); } \ No newline at end of file diff --git a/src/tests/rpp/test_repeat.cpp b/src/tests/rpp/test_repeat.cpp index 8b35c44c4..a16073422 100644 --- a/src/tests/rpp/test_repeat.cpp +++ b/src/tests/rpp/test_repeat.cpp @@ -16,6 +16,7 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" TEST_CASE("repeat resubscribes") @@ -130,4 +131,9 @@ TEST_CASE("repeat doesn't produce extra copies") .move_count = 2} // 2 times 1 move to final subscriber }); } +} + +TEST_CASE("repeat disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::repeat()); } \ No newline at end of file diff --git a/src/tests/rpp/test_scan.cpp b/src/tests/rpp/test_scan.cpp index c1e73e7bd..b1c67f79b 100644 --- a/src/tests/rpp/test_scan.cpp +++ b/src/tests/rpp/test_scan.cpp @@ -15,6 +15,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + #include "rpp/operators/subscribe.hpp" @@ -166,4 +168,9 @@ TEST_CASE("scan doesn't produce extra copies") CHECK(tracker.get_move_count() == 3); // 2 times 1 move to internal state } } +} + +TEST_CASE("scan disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::scan([](auto&& s, auto&&){return s; })); } \ No newline at end of file diff --git a/src/tests/rpp/test_skip.cpp b/src/tests/rpp/test_skip.cpp index 9caa9fb2a..5abb56dc1 100644 --- a/src/tests/rpp/test_skip.cpp +++ b/src/tests/rpp/test_skip.cpp @@ -17,6 +17,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + TEMPLATE_TEST_CASE("skip ignores first `count` of items", "", @@ -79,4 +81,9 @@ TEST_CASE("skip doesn't produce extra copies") .move_count = 1} // 1 move to final subscriber }, 2); } +} + +TEST_CASE("skip disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::skip(1)); } \ No newline at end of file diff --git a/src/tests/rpp/test_subscribe_on.cpp b/src/tests/rpp/test_subscribe_on.cpp index 0c28378ef..63c2cdbd8 100644 --- a/src/tests/rpp/test_subscribe_on.cpp +++ b/src/tests/rpp/test_subscribe_on.cpp @@ -22,6 +22,8 @@ #include #include "mock_observer.hpp" +#include "disposable_observable.hpp" + #include "rpp/disposables/composite_disposable.hpp" #include "rpp/disposables/fwd.hpp" #include "rpp/operators/fwd.hpp" @@ -110,3 +112,9 @@ TEST_CASE("subscribe_on schedules job in another scheduler") CHECK(second->is_disposed()); } } + + +TEST_CASE("group_by disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::subscribe_on(rpp::schedulers::current_thread{})); +} \ No newline at end of file diff --git a/src/tests/rpp/test_take.cpp b/src/tests/rpp/test_take.cpp index 3db0c730f..5cfefbcd1 100644 --- a/src/tests/rpp/test_take.cpp +++ b/src/tests/rpp/test_take.cpp @@ -15,6 +15,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + TEST_CASE("take operator limits emissions") { @@ -106,4 +108,9 @@ TEST_CASE("take doesn't produce extra copies") .move_count = 1} // 1 move to final subscriber }, 2); } +} + +TEST_CASE("take disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::take(1)); } \ No newline at end of file diff --git a/src/tests/rpp/test_take_while.cpp b/src/tests/rpp/test_take_while.cpp index 415b0f40e..99fac6cd9 100644 --- a/src/tests/rpp/test_take_while.cpp +++ b/src/tests/rpp/test_take_while.cpp @@ -10,6 +10,8 @@ #include "mock_observer.hpp" #include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" + #include #include @@ -57,4 +59,9 @@ TEST_CASE("take_while doesn't produce extra copies") .move_count = 1} // 1 move to final subscriber }); } +} + +TEST_CASE("take_while disposes original disposable on disposing") +{ + test_operator_with_disposable(rpp::ops::take_while([](auto){return true; })); } \ No newline at end of file diff --git a/src/tests/rpp/test_with_lastest_from.cpp b/src/tests/rpp/test_with_lastest_from.cpp index 83088cfa6..25655c8f6 100644 --- a/src/tests/rpp/test_with_lastest_from.cpp +++ b/src/tests/rpp/test_with_lastest_from.cpp @@ -16,6 +16,7 @@ #include #include "mock_observer.hpp" +#include "disposable_observable.hpp" TEST_CASE("with_latest_from combines observables") @@ -179,4 +180,15 @@ TEST_CASE("with_latest_from handles race condition", "[with_latest_from]") } } } +} + +TEST_CASE("with_latest_from disposes original disposable on disposing") +{ + auto observable_disposable = std::make_shared(); + auto observable = observable_with_disposable(observable_disposable); + + test_operator_with_disposable(rpp::ops::with_latest_from(observable)); + + + CHECK(observable_disposable->is_disposed()); } \ No newline at end of file diff --git a/src/tests/utils/disposable_observable.hpp b/src/tests/utils/disposable_observable.hpp new file mode 100644 index 000000000..b5a48eef4 --- /dev/null +++ b/src/tests/utils/disposable_observable.hpp @@ -0,0 +1,44 @@ +// ReactivePlusPlus library +// +// Copyright Aleksey Loginov 2023 - present. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Project home: https://github.com/victimsnino/ReactivePlusPlus +// + +#pragma once + +#include +#include + +#include + +template +auto observable_with_disposable(rpp::disposable_wrapper d) +{ + return rpp::source::create([d](auto&& obs) + { + obs.set_upstream(d); + }); +} + +template +void test_operator_over_observable_with_disposable(auto&& op) +{ + auto observable_disposable = std::make_shared(); + auto observable = observable_with_disposable(observable_disposable); + + auto observer_disposable = std::make_shared(); + op(observable) | rpp::ops::subscribe(rpp::composite_disposable_wrapper{observer_disposable}, [](const auto&){}); + + observer_disposable->dispose(); + CHECK(observable_disposable->is_disposed()); +} + +template +void test_operator_with_disposable(auto&& op) +{ + test_operator_over_observable_with_disposable([op](auto&& observable){return observable | op; }); +}