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 
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 
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 
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.
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 }
TEST(Signal, emission_works)
std::function< void(const std::function< void()> &)> Dispatcher
Definition: connection.h:34
Scoped helper class to map signal-slot connection mgmt. to RAII.
Definition: connection.h:141
Connection connect(const Slot &slot) const
Connects the provided slot to this signal instance.
Definition: signal.h:86
A signal class that observers can subscribe to.
Definition: signal.h:36