Dispatch view.event_added over the added half of the changed range

fb75510 pointed it at the removed half, so it fired on departing downloads instead.
This commit is contained in:
xirvik
2026-09-19 01:11:49 +00:00
committed by Jari Sundell
parent 0d3404576a
commit d9439d7a95
4 changed files with 182 additions and 1 deletions
+1 -1
View File
@@ -301,7 +301,7 @@ View::filter() {
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_removed, d.get()); });
if (!m_event_added.is_empty())
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
std::for_each(splitChanged, changed.end(), [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
emit_changed();
}
+8
View File
@@ -1,4 +1,5 @@
TESTS = \
rtorrent_Test_Core \
rtorrent_Test_Rpc \
rtorrent_Test_Src
@@ -7,6 +8,7 @@ check_PROGRAMS = $(TESTS)
rtorrent_Test_LDADD = \
../src/libsub_root.a
rtorrent_Test_Core_LDADD = $(rtorrent_Test_LDADD)
rtorrent_Test_Rpc_LDADD = $(rtorrent_Test_LDADD)
rtorrent_Test_Src_LDADD = $(rtorrent_Test_LDADD)
@@ -30,6 +32,10 @@ rtorrent_Test_Common = \
helpers/test_utils.h \
helpers/utils.h
rtorrent_Test_Core_SOURCES = $(rtorrent_Test_Common) \
core/test_view.cc \
core/test_view.h
rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_command.cc \
rpc/test_command.h \
@@ -62,6 +68,8 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_watch_ready_queue.cc \
src/test_watch_ready_queue.h
rtorrent_Test_Core_CXXFLAGS = $(CPPUNIT_CFLAGS)
rtorrent_Test_Core_LDFLAGS = $(CPPUNIT_LIBS) -ldl
rtorrent_Test_Rpc_CXXFLAGS = $(CPPUNIT_CFLAGS)
rtorrent_Test_Rpc_LDFLAGS = $(CPPUNIT_LIBS) -ldl
rtorrent_Test_Src_CXXFLAGS = $(CPPUNIT_CFLAGS)
+158
View File
@@ -0,0 +1,158 @@
#include "config.h"
#include "test/core/test_view.h"
#include <algorithm>
#include <cstddef>
#include <deque>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "command_helpers.h"
#include "control.h"
#include "core/download.h"
#include "core/view.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestView);
namespace {
const char* filter_command = "test.view.filter";
const char* added_command = "test.view.event_added";
const char* removed_command = "test.view.event_removed";
// View only stores these and hands them to the command layer as RPC targets,
// which casts the pointer without reading through it.
struct download_stub {
alignas(core::Download) std::byte storage[sizeof(core::Download)];
};
std::deque<download_stub> stub_storage;
std::vector<core::Download*> stub_downloads;
std::set<core::Download*> matches_filter;
std::vector<core::Download*> added_events;
std::vector<core::Download*> removed_events;
std::shared_ptr<core::Download>
make_stub_download() {
auto download = reinterpret_cast<core::Download*>(&stub_storage.emplace_back());
stub_downloads.push_back(download);
return std::shared_ptr<core::Download>(download, [](core::Download*) {});
}
// Renders a dispatch list as "d0,d2" so a failure names the downloads the
// handler saw, not just how many there were.
std::string
describe(const std::vector<core::Download*>& downloads) {
std::string result;
for (const auto& download : downloads) {
auto itr = std::find(stub_downloads.begin(), stub_downloads.end(), download);
if (!result.empty())
result += ',';
result += 'd' + std::to_string(std::distance(stub_downloads.begin(), itr));
}
return result;
}
torrent::Object
cmd_filter(core::Download* download, const torrent::Object&) {
return torrent::Object(static_cast<int64_t>(matches_filter.count(download)));
}
torrent::Object
cmd_event_added(core::Download* download, const torrent::Object&) {
added_events.push_back(download);
return torrent::Object();
}
torrent::Object
cmd_event_removed(core::Download* download, const torrent::Object&) {
removed_events.push_back(download);
return torrent::Object();
}
torrent::Object
command_object(const char* command) {
return torrent::Object(std::string(command) + "=");
}
} // namespace
void
TestView::setUp() {
TestFixtureWithMainThread::setUp();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has(filter_command)) {
CMD2_DL(filter_command, &cmd_filter);
CMD2_DL(added_command, &cmd_event_added);
CMD2_DL(removed_command, &cmd_event_removed);
}
stub_storage.clear();
stub_downloads.clear();
matches_filter.clear();
added_events.clear();
removed_events.clear();
}
void
TestView::tearDown() {
TestFixtureWithMainThread::tearDown();
}
// d0 and d1 start visible and stop matching, d2 and d3 start filtered out and
// start matching. The two halves are the same size on purpose: a test that
// only counted the dispatches would pass even if both events were sent over
// the same half of the changed range.
void
TestView::test_filter_dispatches_events_to_the_right_downloads() {
core::View view;
view.initialize("test_view");
auto d0 = make_stub_download();
auto d1 = make_stub_download();
auto d2 = make_stub_download();
auto d3 = make_stub_download();
view.insert(d0);
view.insert(d1);
view.insert(d2);
view.insert(d3);
view.set_visible(d0.get());
view.set_visible(d1.get());
CPPUNIT_ASSERT_EQUAL(core::View::size_type(2), view.size_visible());
matches_filter = {d2.get(), d3.get()};
view.set_filter(command_object(filter_command));
view.set_event_added(command_object(added_command));
view.set_event_removed(command_object(removed_command));
added_events.clear();
removed_events.clear();
view.filter();
CPPUNIT_ASSERT_EQUAL(std::string("d2,d3"), describe(added_events));
CPPUNIT_ASSERT_EQUAL(std::string("d0,d1"), describe(removed_events));
CPPUNIT_ASSERT_EQUAL(core::View::size_type(2), view.size_visible());
CPPUNIT_ASSERT(*view.begin_visible() == d2);
CPPUNIT_ASSERT(*(view.begin_visible() + 1) == d3);
}
+15
View File
@@ -0,0 +1,15 @@
#include "test/helpers/test_main_thread.h"
class TestView : public TestFixtureWithMainThread {
CPPUNIT_TEST_SUITE(TestView);
CPPUNIT_TEST(test_filter_dispatches_events_to_the_right_downloads);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_filter_dispatches_events_to_the_right_downloads();
};