properties-cpp 0.0.1 A very simple convenience library for handling properties and signals in C++11.

signals_test.cpp
Go to the documentation of this file.
1/*
2 * Copyright © 2013 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 */
18
19#include <core/signal.h>
20
21#include <gtest/gtest.h>
22
23#include <condition_variable>
24#include <functional>
25#include <mutex>
26#include <thread>
27
28 namespace
29{
30 template<typename T>
31 struct Expectation
32{
33 Expectation(const T& expected_value) : expected_value(expected_value)
34 {
35 }
36
37 bool satisfied() const
38 {
39 return triggered && current_value == expected_value;
40 }
41
42 bool triggered = false;
43 T expected_value;
44 T current_value;
45};
46}
47
48 TEST(Signal, emission_works)
49{
50 Expectation<int> expectation{42};
51
52 core::Signal<int> s;
53 s.connect([&expectation](int value) { expectation.triggered = true; expectation.current_value = value; });
54
55 s(42);
56
57 EXPECT_TRUE(expectation.satisfied());
58}
59
60 TEST(Signal, disconnect_results_in_slots_not_invoked_anymore)
61{
62 Expectation<int> expectation{42};
63
64 core::Signal<int> s;
65 auto connection = s.connect(
66 [&expectation](int value)
67 {
68 expectation.triggered = true;
69 expectation.current_value = value;
70 });
71 connection.disconnect();
72 s(42);
73
74 EXPECT_FALSE(expectation.satisfied());
75}
76
77 TEST(Signal, disconnect_via_scoped_connection_results_in_slots_not_invoked_anymore)
78{
79 Expectation<int> expectation{42};
80
81 core::Signal<int> s;
82 auto connection = s.connect(
83 [&expectation](int value)
84 {
85 expectation.triggered = true;
86 expectation.current_value = value;
87 });
88 {
89 core::ScopedConnection sc{connection};
90 }
91 s(42);
92
93 EXPECT_FALSE(expectation.satisfied());
94}
95
96 TEST(Signal, a_signal_going_out_of_scope_disconnects_from_slots)
97{
98 auto signal = std::make_shared<core::Signal<int>>();
99
100 auto connection = signal->connect([](int value) { std::cout << value << std::endl; });
101
102 signal.reset();
103
104 core::Connection::Dispatcher dispatcher{};
105
106 EXPECT_NO_THROW(connection.disconnect());
107 EXPECT_NO_THROW(connection.dispatch_via(dispatcher));
108}
109
110#include <queue>
111
112 namespace
113{
114 struct EventLoop
115{
116 typedef std::function<void()> Handler;
117
118 void stop()
119 {
120 stop_requested = true;
121 }
122
123 void run()
124 {
125 while (!stop_requested)
126 {
127 std::unique_lock<std::mutex> ul(guard);
128 wait_condition.wait_for(
129 ul,
130 std::chrono::milliseconds{500},
131 [this]() { return handlers.size() > 0; });
132
133 while (handlers.size() > 0)
134 {
135 handlers.front()();
136 handlers.pop();
137 }
138 }
139 }
140
141 void dispatch(const Handler& h)
142 {
143 std::lock_guard<std::mutex> lg(guard);
144 handlers.push(h);
145 }
146
147 bool stop_requested = false;
148 std::queue<Handler> handlers;
149 std::mutex guard;
150 std::condition_variable wait_condition;
151};
152}
153
154 TEST(Signal, installing_a_custom_dispatcher_ensures_invocation_on_correct_thread)
155{
156 // We instantiate an event loop and run it on a different thread than the main one.
157 EventLoop dispatcher;
158 std::thread dispatcher_thread{[&dispatcher]() { dispatcher.run(); }};
159 std::thread::id dispatcher_thread_id = dispatcher_thread.get_id();
160
161 // The signal that we want to dispatch via the event loop.
162 core::Signal<int, double> s;
163
164 static const int expected_invocation_count = 10000;
165
166 // Setup the connection. For each invocation we check that the id of the
167 // thread the handler is being called upon equals the thread that the
168 // event loop is running upon.
169 auto connection = s.connect(
170 [&dispatcher, dispatcher_thread_id](int value, double)
171 {
172 EXPECT_EQ(dispatcher_thread_id,
173 std::this_thread::get_id());
174
175 if (value == expected_invocation_count)
176 dispatcher.stop();
177 });
178
179 // Route the connection via the dispatcher
180 connection.dispatch_via(
181 std::bind(
182 &EventLoop::dispatch,
183 std::ref(dispatcher),
184 std::placeholders::_1));
185
186 // Invoke the signal from the main thread.
187 for (unsigned int i = 1; i <= expected_invocation_count; i++)
188 s(i, 42.);
189
190 if (dispatcher_thread.joinable())
191 dispatcher_thread.join();
192}
core::Connection::dispatch_via
void dispatch_via(const Dispatcher &dispatcher)
Installs a dispatcher for this signal-slot connection.
Definition: connection.h:61
core::Connection::disconnect
void disconnect()
End a signal-slot connection.
Definition: connection.h:51
core::Connection::Dispatcher
std::function< void(const std::function< void()> &)> Dispatcher
Definition: connection.h:34
core::ScopedConnection
Scoped helper class to map signal-slot connection mgmt. to RAII.
Definition: connection.h:142
core::Signal
A signal class that observers can subscribe to.
Definition: signal.h:37
core::Signal::connect
Connection connect(const Slot &slot) const
Connects the provided slot to this signal instance.
Definition: signal.h:86
signal.h
TEST
TEST(Signal, emission_works)
Definition: signals_test.cpp:48