Merge lp:~pete-woods/unity-api/add-signal-managers into lp:unity-api

Proposed by Pete Woods on 2017-03-24
Status: Merged
Approved by: Pete Woods on 2017-04-04
Approved revision: 299
Merged at revision: 282
Proposed branch: lp:~pete-woods/unity-api/add-signal-managers
Merge into: lp:unity-api
Prerequisite: lp:~pete-woods/unity-api/add-glib-assigner-class
Diff against target: 491 lines (+325/-0)
11 files modified
debian/control (+1/-0)
include/unity/util/GObjectMemory.h (+33/-0)
include/unity/util/GioMemory.h (+72/-0)
include/unity/util/GlibMemory.h (+28/-0)
include/unity/util/ResourcePtr.h (+10/-0)
test/gtest/unity/util/CMakeLists.txt (+1/-0)
test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp (+34/-0)
test/gtest/unity/util/GioMemory/CMakeLists.txt (+30/-0)
test/gtest/unity/util/GioMemory/GioMemory_test.cpp (+114/-0)
test/headers/CMakeLists.txt (+1/-0)
test/headers/includechecker.py (+1/-0)
To merge this branch: bzr merge lp:~pete-woods/unity-api/add-signal-managers
Reviewer Review Type Date Requested Status
Unity8 CI Bot continuous-integration Needs Fixing on 2017-04-04
Michi Henning (community) 2017-03-24 Approve on 2017-03-27
Review via email: mp+320961@code.launchpad.net

Commit message

unity::util - Add glib signal managers

Description of the change

unity::util - Add glib signal managers

To post a comment you must log in.
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:295
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/165/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4698
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4726
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4549
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4549/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4549
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4549/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4549
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4549/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4549
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4549/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4549
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4549/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4549
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4549/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/165/rebuild

review: Approve (continuous-integration)
Michi Henning (michihenning) wrote :

This looks sensible to me. I'd prefer if someone with more glib mojo than me could top-approve though.

review: Approve
294. By Pete Woods on 2017-04-04

Fix null assignment behaviour in assigner classes

295. By Pete Woods on 2017-04-04

Tweak docs

296. By Pete Woods on 2017-04-04

Fix whitespace error

298. By Pete Woods on 2017-03-25

Fix up tests, use libqtdbustest for temporary test bus

299. By Pete Woods on 2017-04-04

Add docs

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-12-23 09:14:43 +0000
3+++ debian/control 2017-04-04 09:34:42 +0000
4@@ -14,6 +14,7 @@
5 graphviz,
6 libglib2.0-dev,
7 libgtest-dev,
8+ libqtdbustest1-dev,
9 pkg-config,
10 python3:any,
11 qt5-default,
12
13=== modified file 'include/unity/util/GObjectMemory.h'
14--- include/unity/util/GObjectMemory.h 2017-04-04 09:34:42 +0000
15+++ include/unity/util/GObjectMemory.h 2017-04-04 09:34:42 +0000
16@@ -24,6 +24,8 @@
17 #include <stdexcept>
18 #include <glib-object.h>
19
20+#include <unity/util/ResourcePtr.h>
21+
22 namespace unity
23 {
24
25@@ -109,6 +111,20 @@
26 SP& smart_ptr_;
27 };
28
29+template <typename T>
30+struct GObjectSignalUnsubscriber
31+{
32+ void operator()(gulong handle) noexcept
33+ {
34+ if (handle != 0 && G_IS_OBJECT(obj_.get()))
35+ {
36+ g_signal_handler_disconnect(obj_.get(), handle);
37+ }
38+ }
39+
40+ GObjectSPtr<T> obj_;
41+};
42+
43 }
44
45 /**
46@@ -187,6 +203,23 @@
47 return internal::GObjectAssigner<SP>(smart_ptr);
48 }
49
50+template<typename T>
51+using GObjectSignalConnection = ResourcePtr<gulong, internal::GObjectSignalUnsubscriber<T>>;
52+
53+/**
54+ \brief Simple wrapper to manage the lifecycle of GObject signal connections.
55+
56+ When 'nameConnection_' goes out of scope or is dealloc'ed, the source will be removed:
57+ \code{.cpp}
58+ GObjectSignalConnection<FooBar> nameConnection_;
59+ nameConnection_ = gobject_signal_connection(g_signal_connect(o.get(), "notify::name", G_CALLBACK(on_notify_name), this), o);
60+ \endcode
61+ */
62+template <typename T>
63+inline GObjectSignalConnection<T> gobject_signal_connection(gulong id, const GObjectSPtr<T>& obj)
64+{
65+ return GObjectSignalConnection<T>(id, internal::GObjectSignalUnsubscriber<T>{obj});
66+}
67
68 } // namespace until
69
70
71=== added file 'include/unity/util/GioMemory.h'
72--- include/unity/util/GioMemory.h 1970-01-01 00:00:00 +0000
73+++ include/unity/util/GioMemory.h 2017-04-04 09:34:42 +0000
74@@ -0,0 +1,72 @@
75+/*
76+ * Copyright (C) 2013-2017 Canonical Ltd.
77+ *
78+ * This program is free software: you can redistribute it and/or modify
79+ * it under the terms of the GNU Lesser General Public License version 3 as
80+ * published by the Free Software Foundation.
81+ *
82+ * This program is distributed in the hope that it will be useful,
83+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
84+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
85+ * GNU Lesser General Public License for more details.
86+ *
87+ * You should have received a copy of the GNU Lesser General Public License
88+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
89+ *
90+ * Authored by: Pete Woods <pete.woods@canonical.com>
91+ */
92+
93+#ifndef UNITY_UTIL_GIOMEMORY_H
94+#define UNITY_UTIL_GIOMEMORY_H
95+
96+#include <gio/gio.h>
97+
98+#include <unity/util/GObjectMemory.h>
99+
100+namespace unity
101+{
102+
103+namespace util
104+{
105+
106+namespace internal
107+{
108+struct GDBusSignalUnsubscriber
109+{
110+public:
111+ void operator()(guint handle) noexcept
112+ {
113+ if (handle != 0 && G_IS_OBJECT(bus_.get()))
114+ {
115+ g_dbus_connection_signal_unsubscribe(bus_.get(), handle);
116+ }
117+ }
118+
119+ GObjectSPtr<GDBusConnection> bus_;
120+};
121+
122+}
123+
124+typedef ResourcePtr<guint, internal::GDBusSignalUnsubscriber> GDBusSignalConnection;
125+
126+/**
127+ \brief Simple wrapper to manage the lifecycle of manual GDBus signal connections.
128+
129+ When 'signalConnection_' goes out of scope or is dealloc'ed, the connection will be removed:
130+ \code{.cpp}
131+ GDBusSignalConnection signalConnection_;
132+
133+ signalConnection_ = gdbus_signal_connection(
134+ g_dbus_connection_signal_subscribe(bus.get(), nullptr, "org.does.not.exist", nullptr, "/does/not/exist", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, on_dbus_signal, this, nullptr), bus);
135+ \endcode
136+ */
137+inline GDBusSignalConnection gdbus_signal_connection(guint id, GObjectSPtr<GDBusConnection> bus) noexcept
138+{
139+ return GDBusSignalConnection(id, internal::GDBusSignalUnsubscriber{bus});
140+}
141+
142+} // namespace until
143+
144+} // namespace unity
145+
146+#endif
147
148=== modified file 'include/unity/util/GlibMemory.h'
149--- include/unity/util/GlibMemory.h 2017-04-04 09:34:42 +0000
150+++ include/unity/util/GlibMemory.h 2017-04-04 09:34:42 +0000
151@@ -24,6 +24,8 @@
152 #include <memory>
153 #include <glib.h>
154
155+#include <unity/util/ResourcePtr.h>
156+
157 namespace unity
158 {
159
160@@ -83,6 +85,17 @@
161 SP& smart_ptr_;
162 };
163
164+struct GSourceUnsubscriber
165+{
166+ void operator()(guint tag) noexcept
167+ {
168+ if (tag != 0)
169+ {
170+ g_source_remove(tag);
171+ }
172+ }
173+};
174+
175 }
176
177 #define UNITY_UTIL_DEFINE_GLIB_SMART_POINTERS(TypeName, func) \
178@@ -156,6 +169,21 @@
179 return internal::GlibAssigner<SP>(smart_ptr);
180 }
181
182+using GSourceManager = ResourcePtr<guint, internal::GSourceUnsubscriber>;
183+
184+/**
185+ \brief Simple wrapper to manage the lifecycle of sources.
186+
187+ When 'timer' goes out of scope or is dealloc'ed, the source will be removed:
188+ \code{.cpp}
189+ auto timer = g_source_manager(g_timeout_add(5000, on_timeout, nullptr));
190+ \endcode
191+ */
192+inline GSourceManager g_source_manager(guint id)
193+{
194+ return GSourceManager(id, internal::GSourceUnsubscriber());
195+}
196+
197 /**
198 * Below here is some hackery to extract the matching deleters for all built in glib types.
199 */
200
201=== modified file 'include/unity/util/ResourcePtr.h'
202--- include/unity/util/ResourcePtr.h 2014-06-25 23:46:22 +0000
203+++ include/unity/util/ResourcePtr.h 2017-04-04 09:34:42 +0000
204@@ -20,6 +20,7 @@
205 #define UNITY_UTIL_RESOURCEPTR_H
206
207 #include <mutex>
208+#include <type_traits>
209
210 namespace unity
211 {
212@@ -135,6 +136,7 @@
213 */
214 typedef D deleter_type;
215
216+ ResourcePtr();
217 explicit ResourcePtr(D d);
218 ResourcePtr(R r, D d);
219 ResourcePtr(ResourcePtr&& r);
220@@ -175,6 +177,14 @@
221 typedef LockAdopter<decltype(m_)> AdoptLock;
222 };
223
224+template<typename R, typename D>
225+ResourcePtr<R, D>::ResourcePtr()
226+ : initialized_(false)
227+{
228+ static_assert(!std::is_pointer<deleter_type>::value,
229+ "constructed with null function pointer deleter");
230+}
231+
232 /**
233 Constructs a ResourcePtr with the specified deleter. No resource is held, so a call to has_resource()
234 after constructing a ResourcePtr this way returns <code>false</code>.
235
236=== modified file 'test/gtest/unity/util/CMakeLists.txt'
237--- test/gtest/unity/util/CMakeLists.txt 2017-01-19 13:52:32 +0000
238+++ test/gtest/unity/util/CMakeLists.txt 2017-04-04 09:34:42 +0000
239@@ -1,6 +1,7 @@
240 add_subdirectory(Daemon)
241 add_subdirectory(DefinesPtrs)
242 add_subdirectory(FileIO)
243+add_subdirectory(GioMemory)
244 add_subdirectory(GlibMemory)
245 add_subdirectory(GObjectMemory)
246 add_subdirectory(IniParser)
247
248=== modified file 'test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp'
249--- test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp 2017-04-04 09:34:42 +0000
250+++ test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp 2017-04-04 09:34:42 +0000
251@@ -54,6 +54,8 @@
252
253 void foo_bar_assigner_null(FooBar** in);
254
255+void foo_bar_set_name(FooBar* fooBar, const gchar* const name);
256+
257 G_END_DECLS
258
259 // private implementation
260@@ -197,6 +199,11 @@
261 }
262 }
263
264+void foo_bar_set_name(FooBar* fooBar, const gchar* const name)
265+{
266+ g_object_set(fooBar, "name", name, nullptr);
267+}
268+
269 //
270 // Test cases
271 //
272@@ -213,6 +220,22 @@
273 {
274 DELETED_OBJECTS.clear();
275 }
276+
277+ static void on_notify_name(GObject *object, GParamSpec *, gpointer user_data)
278+ {
279+ static_cast<GObjectMemoryTest*>(user_data)->onNotifyName(object);
280+ }
281+
282+ void onNotifyName(GObject *object)
283+ {
284+ gcharUPtr name;
285+ g_object_get(object, "name", assign_glib(name), nullptr);
286+ nameChanges_.emplace_back(name.get());
287+ }
288+
289+ list<string> nameChanges_;
290+
291+ GObjectSignalConnection<FooBar> nameConnection_;
292 };
293
294 TEST_F(GObjectMemoryTest, trivial)
295@@ -496,6 +519,17 @@
296 EXPECT_EQ(list<Deleted>({{"hi", 6}, {"bye", 7}}), DELETED_OBJECTS);
297 }
298
299+TEST_F(GObjectMemoryTest, signals)
300+{
301+ auto o(share_gobject(foo_bar_new_full("hi", 1)));
302+ nameConnection_ = gobject_signal_connection(g_signal_connect(o.get(), "notify::name", G_CALLBACK(on_notify_name), this), o);
303+ foo_bar_set_name(o.get(), "change1");
304+ nameConnection_.dealloc();
305+ foo_bar_set_name(o.get(), "change2");
306+
307+ EXPECT_EQ(list<string>{"change1"}, nameChanges_);
308+}
309+
310 typedef pair<const char*, guint> GObjectMemoryMakeSharedTestParam;
311
312 class GObjectMemoryMakeHelperMethodsTest: public testing::TestWithParam<GObjectMemoryMakeSharedTestParam>
313
314=== added directory 'test/gtest/unity/util/GioMemory'
315=== added file 'test/gtest/unity/util/GioMemory/CMakeLists.txt'
316--- test/gtest/unity/util/GioMemory/CMakeLists.txt 1970-01-01 00:00:00 +0000
317+++ test/gtest/unity/util/GioMemory/CMakeLists.txt 2017-04-04 09:34:42 +0000
318@@ -0,0 +1,30 @@
319+pkg_check_modules(GIO REQUIRED gio-2.0)
320+pkg_check_modules(QDBUSTEST REQUIRED libqtdbustest-1)
321+
322+find_package(Qt5Core REQUIRED)
323+find_package(Qt5DBus REQUIRED)
324+
325+include_directories(
326+ ${Qt5Core_INCLUDE_DIRS}
327+ ${Qt5DBus_INCLUDE_DIRS}
328+ ${GIO_INCLUDE_DIRS}
329+ ${QDBUSTEST_INCLUDE_DIRS}
330+ )
331+
332+add_definitions(
333+ -DQT_NO_KEYWORDS=1
334+ )
335+
336+add_executable(GioMemory_test
337+ GioMemory_test.cpp
338+ )
339+
340+target_link_libraries(GioMemory_test
341+ ${TESTLIBS}
342+ ${GIO_LDFLAGS}
343+ ${QDBUSTEST_LDFLAGS}
344+ Qt5::Core
345+ Qt5::DBus
346+ )
347+
348+add_test(GioMemory_test GioMemory_test)
349
350=== added file 'test/gtest/unity/util/GioMemory/GioMemory_test.cpp'
351--- test/gtest/unity/util/GioMemory/GioMemory_test.cpp 1970-01-01 00:00:00 +0000
352+++ test/gtest/unity/util/GioMemory/GioMemory_test.cpp 2017-04-04 09:34:42 +0000
353@@ -0,0 +1,114 @@
354+/*
355+ * Copyright (C) 2017 Canonical Ltd.
356+ *
357+ * This program is free software: you can redistribute it and/or modify
358+ * it under the terms of the GNU Lesser General Public License version 3 as
359+ * published by the Free Software Foundation.
360+ *
361+ * This program is distributed in the hope that it will be useful,
362+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
363+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
364+ * GNU Lesser General Public License for more details.
365+ *
366+ * You should have received a copy of the GNU Lesser General Public License
367+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
368+ *
369+ * Authored by: Pete Woods <pete.woods@canonical.com>
370+ */
371+
372+#include <unity/util/GioMemory.h>
373+#include <unity/util/GlibMemory.h>
374+#include <libqtdbustest/DBusTestRunner.h>
375+#include <gtest/gtest.h>
376+#include <list>
377+#include <string>
378+
379+using namespace std;
380+using namespace unity::util;
381+using namespace QtDBusTest;
382+
383+namespace
384+{
385+
386+class GioMemoryTest: public testing::Test
387+{
388+protected:
389+ static void SetUpTestCase()
390+ {
391+ g_log_set_always_fatal((GLogLevelFlags) (G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL));
392+ }
393+
394+ static void on_dbus_signal(GDBusConnection *, const gchar *, const gchar *, const gchar *, const gchar *signal_name, GVariant *, gpointer user_data)
395+ {
396+ static_cast<GioMemoryTest*>(user_data)->onDbusSignal(signal_name);
397+ }
398+
399+ void onDbusSignal(const gchar *signal_name)
400+ {
401+ signals_.emplace_back(signal_name);
402+ g_main_loop_quit(mainloop_.get());
403+ }
404+
405+ static gboolean on_timeout(gpointer user_data)
406+ {
407+ return static_cast<GioMemoryTest*>(user_data)->onTimeout();
408+ }
409+
410+ gboolean onTimeout()
411+ {
412+ g_main_loop_quit(mainloop_.get());
413+ return G_SOURCE_CONTINUE;
414+ }
415+
416+ static GObjectSPtr<GDBusConnection> getSessionBus()
417+ {
418+ auto address = unique_glib(g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, nullptr));
419+
420+ auto bus = unique_gobject(
421+ g_dbus_connection_new_for_address_sync(address.get(), (GDBusConnectionFlags) (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), nullptr,
422+ nullptr, nullptr));
423+
424+ g_dbus_connection_set_exit_on_close(bus.get(), FALSE);
425+
426+ return bus;
427+ }
428+
429+ DBusTestRunner dbusTestRunner;
430+
431+ GDBusSignalConnection signalConnection_;
432+
433+ GMainLoopSPtr mainloop_;
434+
435+ list<string> signals_;
436+};
437+
438+
439+TEST_F(GioMemoryTest, signals)
440+{
441+ mainloop_ = share_glib(g_main_loop_new(nullptr, false));
442+
443+ auto bus = getSessionBus();
444+ ASSERT_TRUE(bool(bus));
445+
446+ signalConnection_ = gdbus_signal_connection(
447+ g_dbus_connection_signal_subscribe(bus.get(), nullptr, "org.does.not.exist", nullptr, "/does/not/exist", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, on_dbus_signal, this, nullptr), bus);
448+
449+ g_dbus_connection_emit_signal(bus.get(), nullptr, "/does/not/exist", "org.does.not.exist", "hello", nullptr, nullptr);
450+ {
451+ auto timer = g_source_manager(g_timeout_add(5000, on_timeout, this));
452+ g_main_loop_run(mainloop_.get());
453+ EXPECT_EQ(list<string>{"hello"}, signals_);
454+ }
455+ signals_.clear();
456+
457+ signalConnection_.dealloc();
458+
459+ g_dbus_connection_emit_signal(bus.get(), nullptr, "/does/not/exist", "org.does.not.exist", "hello", nullptr, nullptr);
460+ {
461+ auto timer = g_source_manager(g_timeout_add(5000, on_timeout, this));
462+ g_main_loop_run(mainloop_.get());
463+ EXPECT_TRUE(signals_.empty());
464+ }
465+}
466+
467+}
468
469=== modified file 'test/headers/CMakeLists.txt'
470--- test/headers/CMakeLists.txt 2017-01-19 13:52:32 +0000
471+++ test/headers/CMakeLists.txt 2017-04-04 09:34:42 +0000
472@@ -11,6 +11,7 @@
473 )
474
475 set(exclusions
476+ "GioMemory.h"
477 "GlibMemory.h"
478 "GObjectMemory.h"
479 )
480
481=== modified file 'test/headers/includechecker.py'
482--- test/headers/includechecker.py 2017-01-19 13:52:32 +0000
483+++ test/headers/includechecker.py 2017-04-04 09:34:42 +0000
484@@ -38,6 +38,7 @@
485 'unity/shell': { 'Qt' }, # Anything under unity/shell can include anything starting with Qt
486 'unity/util/GObjectMemory': { 'glib' }, # The unity/util/GObjectMemory header can include anything starting with glib
487 'unity/util/GlibMemory': { 'glib' }, # The unity/util/GlibMemory header can include anything starting with glib
488+ 'unity/util/GioMemory': { 'glib' }, # The unity/util/GioMemory header can include anything starting with glib
489 }
490
491 def check_file(filename, permitted_includes):

Subscribers

People subscribed via source and target branches

to all changes: