Merge lp:~pete-woods/unity-api/gobject-pointer-functions into lp:unity-api

Proposed by Pete Woods on 2017-01-12
Status: Merged
Approved by: Michi Henning on 2017-01-19
Approved revision: 265
Merged at revision: 265
Proposed branch: lp:~pete-woods/unity-api/gobject-pointer-functions
Merge into: lp:unity-api
Diff against target: 714 lines (+623/-4)
8 files modified
debian/changelog (+7/-0)
include/unity/util/GObjectMemory.h (+128/-0)
test/gtest/unity/util/CMakeLists.txt (+1/-0)
test/gtest/unity/util/GObjectMemory/CMakeLists.txt (+14/-0)
test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp (+461/-0)
test/headers/CMakeLists.txt (+6/-2)
test/headers/compile_headers.py (+4/-1)
test/headers/includechecker.py (+2/-1)
To merge this branch: bzr merge lp:~pete-woods/unity-api/gobject-pointer-functions
Reviewer Review Type Date Requested Status
Michi Henning (community) 2017-01-17 Approve on 2017-01-19
Charles Kerr (community) 2017-01-12 Approve on 2017-01-17
Unity8 CI Bot continuous-integration Approve on 2017-01-17
James Henstridge 2017-01-18 Pending
Review via email: mp+314614@code.launchpad.net

Commit Message

unity::shell::util - add GObject shared pointer utility functions

Description of the Change

In a bunch of different projects, we have C++ code wrapping up GObject-based C APIs (for GDBus, GMenu, etc). When this code is done well, it uses shared_ptr wrappers to manage the lifecycle of the GObjects automatically. However we have a bunch of the same wrappers spread across these codebases. This is an attempt at unifying some of those helpers.

This code is intentionally header-only, so that there can be no excuses about using this functionality in terms of linking to libunity-apiX.so.

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

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

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

review: Approve (continuous-integration)
Charles Kerr (charlesk) wrote :

I suggested a couple of improvements inline but basically the code is sound -- the premise, the gobject wrangling, and the tests are all sound.

However, IIUC michi wrote the compile_headers.py test and came up with the policy that it's testing. I'd prefer we ran the exception by him rather than me Approving it without giving him a heads-up.

Refraining from "Approve" for that reason only

Charles Kerr (charlesk) :
review: Needs Information
Charles Kerr (charlesk) :
Michi Henning (michihenning) wrote :

I'm fine with the exception to the policy.

I'm wondering though whether the approach Jussi and James came up with quite some time ago wouldn't be preferable:

http://bazaar.launchpad.net/~unity-team/thumbnailer/trunk/view/head:/include/internal/gobj_memory.h

In particular, the proposal here forces use of a shared_ptr, but that is not always what I want, because the instance may not be meant to be shared. The gobj_ptr approach doesn't have this problem because I can trivially convert it to a shared_ptr if I want sharing.

Would it make sense to either use the already-existing approach or, alternatively, convert the one here to use a unique_ptr?

Pete Woods (pete-woods) wrote :

Hi guys. Thanks for your input! I've re-done this branch using the code Jussi wrote, with some additions such as adding make_* style helper methods.

I've also imported all the unit tests for this from Jussi, too. I've kept around some of my old tests for the helper functions, though.

Pete Woods (pete-woods) wrote :

I also added a standard hash function for the gobj_ptr class, so that it behaves inside unordered containers.

The make_gobject function now makes use of std::forward (thanks to michi for showing me the existence of this), so it is quite a bit simpler!

Unity8 CI Bot (unity8-ci-bot) wrote :

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

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

review: Approve (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

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

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

review: Approve (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

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

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

review: Approve (continuous-integration)
Pete Woods (pete-woods) wrote :

Added a follow-on MR here: https://code.launchpad.net/~pete-woods/unity-api/glib-pointer-functions/+merge/314967

This one adds utility methods for all glib types (a bit more tricky here, as they all have different deleters).

Charles Kerr (charlesk) :
review: Approve
Pete Woods (pete-woods) wrote :

Thinking about this a bit more. I want to be absolutely sure that using a new class gobj_ptr has a tangible advantage over a unique_ptr with a custom deleter. I'm struggling to find the advantage of gobj_ptr.

The advantage of unique_ptr I can see is that it's automatically convertible to a shared_ptr, whilst preserving the custom deleter. You can also keep the floating ref validation in the factory method.

Michi Henning (michihenning) wrote :

> Thinking about this a bit more. I want to be absolutely sure that using a new
> class gobj_ptr has a tangible advantage over a unique_ptr with a custom
> deleter. I'm struggling to find the advantage of gobj_ptr.

Using a unique_ptr with a custom deleter would break the unique_ptr contract. That's because, in the standard, the constructor from pointer is noexcept, as are the copy constructor, copy assignment operator, and reset(). In gobj_ptr, they are not noexcept because of the floating reference issue.
(I'm honestly wondering though whether it might not be better to just declare the floating reference issue a precondition and abort if such a pointer is passed. It would avoid all the stuffing around with the not-noexcept methods.)

> The advantage of unique_ptr I can see is that it's automatically convertible
> to a shared_ptr, whilst preserving the custom deleter. You can also keep the
> floating ref validation in the factory method.

Except for the noexcept problem. But it does beg the question... Why not just use this?

struct Deleter
{
    void operator()(GObject* p) { g_object_unref(p); }
};

Deleter d;
std::unique_ptr<GObject, Deleter> p(g_object_new(...), d);

For the hash function, I don't like the derivation from __hash_base. All that __hash_base provides are the typedefs for result_type and argument_type, which we could define ourselves here. That would avoid relying on a libstdc++ implementation detail that, strictly speaking, we don't even know exists. (Using __hash_base is inherently non-portable and implementation dependent.)

I suspect that you won't like what I'm about to say, but I'll say it anyway...

We have used camel case for type names everywhere so far, and gobj_ptr is what we otherwise use for method names. In the interest of the consistency of the overall API, it would be preferable to rename gobj_ptr to GObjectPtr. That would also be consistent with the quite similar ResourcePtr.

But, before making any more changes, we should figure out why we have gobj_ptr when a unique_ptr with a custom deleter appears to do the same job. Is the floating reference the only reason for having created a separate class? If so, it might be better to just declare the floating reference thing an SEP and use unique_ptr with a custom deleter instead? James, do you remember?

Pete Woods (pete-woods) wrote :

FYI, the deleter we're using here when it comes to shared_ptr (as opposed to gobj_ptr) is below:

> void g_object_deleter(void *ptr) {
> g_return_if_fail(G_IS_OBJECT(ptr));
> g_object_unref(ptr);
> }

I would also propose making the methods as follows for the unique_ptr construction:
> template<typename T, typename... Args>
> inline std::unique_ptr<T, > make_gobject(GType object_type,
> const gchar *first_property_name, Args&&... args) {
> return std::unique_ptr<T, decltype(&g_object_deleter)>
> (G_TYPE_CHECK_INSTANCE_CAST(
> g_object_new(object_type, first_property_name, std::forward<Args>(args)...),
> object_type, T), &g_object_deleter);
> }

and:

> template<typename T>
> inline std::unique_ptr<T> unique_gobject(T* ptr) {
> if (ptr != nullptr && g_object_is_floating(G_OBJECT(ptr))) {
> g_object_unref(ptr); // maybe?
> throw std::invalid_argument("Tried to add a floating gobject into a gobj_ptr.");
> }
> return std::unique_ptr<T, decltype(&g_object_deleter)>(ptr, &g_object_deleter);
> }

This way the factory method can throw the exception safely, also unref the floating object, and we can use unique_ptr with its advantages I mentioned earlier.

Pete Woods (pete-woods) wrote :

Hmm, LP has munged the code formatting.

Pete Woods (pete-woods) wrote :

I've updated the branch with what I proposed

Pete Woods (pete-woods) wrote :
Pete Woods (pete-woods) wrote :

The more I think about it, the more I don't like the behaviour of the old gobj_ptr class. It was assignable from another gobj_ptr, which is definitely not behaving like a unique_ptr at all. It was behaving like a shared_ptr there.

Michi Henning (michihenning) wrote :

> The more I think about it, the more I don't like the behaviour of the old
> gobj_ptr class. It was assignable from another gobj_ptr, which is definitely
> not behaving like a unique_ptr at all. It was behaving like a shared_ptr
> there.

I just had a look. It works; after calling release(), you have to explicitly unref the pointer that is returned (but that may not deallocate the instance if the refcount is still non-zero). The reference counts remain sane. It's sort of a hybrid of unique_ptr and shared_ptr, which will be surprising to some people, I guess.

I like your factory/helper method approach. It's clean and re-uses as much from the std library as possible, and there is no need to learn the semantics of a new class.

I left some minor comments inline.

In the doc, you could mention that the template parameter is optional due to template type deduction. For example, this works:

auto u = unique_gobject(foo_bar_new());
auto s = share_gobject(foo_bar_new());

I would also suggest to add an overload for unique_ptr:

template<typename T, typename D>
inline std::shared_ptr<T>
share_gobject(std::unique_ptr<T, D>&& p)
{
    return std::shared_ptr<T>(std::move(p));
}

With this, you can write:

auto u = unique_gobject(foo_bar_new());
auto s = share_gobject(std::move(u));

The shared_ptr constructor remembers the deleter used by the unique_ptr, so GObjectDeleter will still be called.

Anal-retentive comment: the code should use Allman style, not K&R style for the curlies; that's what is used elsewhere in unity-api.

Pete Woods (pete-woods) wrote :

Thanks for the thorough review and comments! Will get on that now :)

Pete Woods (pete-woods) wrote :

And nothing wrong with the comment RE braces. I don't know why I got it into my head we were doing K&R here.

Pete Woods (pete-woods) wrote :

Okay, I've reformatted the code to BSD/Allman and fixed the inline comments. We already discussed the share_gobject override thing in IRC.

265. By Pete Woods on 2017-01-19

unity::util - add GObject shared memory utility classes and helper methods.

Michi Henning (michihenning) wrote :

Thank you, let's do it! :-)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2017-01-10 06:42:46 +0000
3+++ debian/changelog 2017-01-19 13:53:10 +0000
4@@ -1,3 +1,10 @@
5+unity-api (8.1-0ubuntu1) UNRELEASED; urgency=medium
6+
7+ * unity::util - add GObject shared memory utility classes
8+ and helper methods.
9+
10+ -- Pete <pete.woods@canonical.com> Wed, 11 Jan 2017 16:07:23 +0000
11+
12 unity-api (8.0+17.04.20170110.1-0ubuntu1) zesty; urgency=medium
13
14 [ Albert Astals Cid ]
15
16=== added file 'include/unity/util/GObjectMemory.h'
17--- include/unity/util/GObjectMemory.h 1970-01-01 00:00:00 +0000
18+++ include/unity/util/GObjectMemory.h 2017-01-19 13:53:10 +0000
19@@ -0,0 +1,128 @@
20+/*
21+ * Copyright (C) 2013-2017 Canonical Ltd.
22+ *
23+ * This program is free software: you can redistribute it and/or modify
24+ * it under the terms of the GNU Lesser General Public License version 3 as
25+ * published by the Free Software Foundation.
26+ *
27+ * This program is distributed in the hope that it will be useful,
28+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
29+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30+ * GNU Lesser General Public License for more details.
31+ *
32+ * You should have received a copy of the GNU Lesser General Public License
33+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
34+ *
35+ * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
36+ * Pete Woods <pete.woods@canonical.com>
37+ */
38+
39+#ifndef UNITY_UTIL_GOBJECTMEMORY_H
40+#define UNITY_UTIL_GOBJECTMEMORY_H
41+
42+#include <memory>
43+#include <stdexcept>
44+#include <glib-object.h>
45+
46+namespace unity
47+{
48+
49+namespace util
50+{
51+
52+namespace
53+{
54+
55+inline static void sink_floating_gobject(gpointer t)
56+{
57+ if (t != nullptr && g_object_is_floating(G_OBJECT(t)))
58+ {
59+ g_object_ref_sink(t);
60+ }
61+}
62+
63+}
64+
65+/**
66+ \brief Used by the make_gobject, unique_gobject and share_gobject as the deleter.
67+
68+ Useful if for some reason the normal helper methods are not suitable for your needs.
69+
70+ Example:
71+ \code{.cpp}
72+ GObjectDeleter d;
73+ auto shared = shared_ptr<FooBar>(foo_bar_new("name"), d);
74+ auto unique = unique_ptr<FooBar, GObjectDeleter>(foo_bar_new("name"), d);
75+ \endcode
76+ */
77+struct GObjectDeleter
78+{
79+ void operator()(gpointer ptr)
80+ {
81+ g_return_if_fail(G_IS_OBJECT(ptr));
82+ g_object_unref(ptr);
83+ }
84+};
85+
86+/**
87+ \brief Helper method to wrap a unique_ptr around an existing GObject.
88+
89+ Useful if the GObject class you are constructing already has a
90+ dedicated factory from the C library it comes from, and you
91+ intend to have a unique instance of it.
92+
93+ Example:
94+ \code{.cpp}
95+ auto obj = unique_gobject(foo_bar_new("name"));
96+ \endcode
97+ */
98+template<typename T>
99+inline std::unique_ptr<T, GObjectDeleter> unique_gobject(T* ptr)
100+{
101+ sink_floating_gobject(ptr);
102+ GObjectDeleter d;
103+ return std::unique_ptr<T, GObjectDeleter>(ptr, d);
104+}
105+
106+/**
107+ \brief Helper method to wrap a shared_ptr around an existing GObject.
108+
109+ Useful if the GObject class you are constructing already has a
110+ dedicated factory from the C library it comes from, and you
111+ intend to share it.
112+
113+ Example:
114+ \code{.cpp}
115+ auto obj = share_gobject(foo_bar_new("name"));
116+ \endcode
117+ */
118+template<typename T>
119+inline std::shared_ptr<T> share_gobject(T* ptr)
120+{
121+ sink_floating_gobject(ptr);
122+ GObjectDeleter d;
123+ return std::shared_ptr<T>(ptr, d);
124+}
125+
126+/**
127+ \brief Helper method to construct a gobj_ptr-wrapped GObject class.
128+
129+ Uses the same signature as the g_object_new() method.
130+
131+ Example:
132+ \code{.cpp}
133+ auto obj = make_gobject<FooBar>(FOO_TYPE_BAR, "name", "banana", nullptr);
134+ \endcode
135+ */
136+template<typename T, typename ... Args>
137+inline std::unique_ptr<T, GObjectDeleter> make_gobject(GType object_type, const gchar *first_property_name, Args&&... args)
138+{
139+ gpointer ptr = g_object_new(object_type, first_property_name, std::forward<Args>(args)...);
140+ return unique_gobject(G_TYPE_CHECK_INSTANCE_CAST(ptr, object_type, T));
141+}
142+
143+} // namespace until
144+
145+} // namespace unity
146+
147+#endif
148
149=== modified file 'test/gtest/unity/util/CMakeLists.txt'
150--- test/gtest/unity/util/CMakeLists.txt 2013-06-06 12:02:05 +0000
151+++ test/gtest/unity/util/CMakeLists.txt 2017-01-19 13:53:10 +0000
152@@ -1,6 +1,7 @@
153 add_subdirectory(Daemon)
154 add_subdirectory(DefinesPtrs)
155 add_subdirectory(FileIO)
156+add_subdirectory(GObjectMemory)
157 add_subdirectory(IniParser)
158 add_subdirectory(ResourcePtr)
159 add_subdirectory(internal)
160
161=== added directory 'test/gtest/unity/util/GObjectMemory'
162=== added file 'test/gtest/unity/util/GObjectMemory/CMakeLists.txt'
163--- test/gtest/unity/util/GObjectMemory/CMakeLists.txt 1970-01-01 00:00:00 +0000
164+++ test/gtest/unity/util/GObjectMemory/CMakeLists.txt 2017-01-19 13:53:10 +0000
165@@ -0,0 +1,14 @@
166+pkg_check_modules(GOBJECT REQUIRED gobject-2.0)
167+
168+include_directories(${GOBJECT_INCLUDE_DIRS})
169+
170+add_executable(GObjectMemory_test
171+ GObjectMemory_test.cpp
172+ )
173+
174+target_link_libraries(GObjectMemory_test
175+ ${TESTLIBS}
176+ ${GOBJECT_LDFLAGS}
177+ )
178+
179+add_test(GObjectMemory_test GObjectMemory_test)
180
181=== added file 'test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp'
182--- test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp 1970-01-01 00:00:00 +0000
183+++ test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp 2017-01-19 13:53:10 +0000
184@@ -0,0 +1,461 @@
185+/*
186+ * Copyright (C) 2013-2017 Canonical Ltd.
187+ *
188+ * This program is free software: you can redistribute it and/or modify
189+ * it under the terms of the GNU Lesser General Public License version 3 as
190+ * published by the Free Software Foundation.
191+ *
192+ * This program is distributed in the hope that it will be useful,
193+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
194+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
195+ * GNU Lesser General Public License for more details.
196+ *
197+ * You should have received a copy of the GNU Lesser General Public License
198+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
199+ *
200+ * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
201+ * Pete Woods <pete.woods@canonical.com>
202+ */
203+
204+#include <unity/util/GObjectMemory.h>
205+#include <glib-object.h>
206+#include <gtest/gtest.h>
207+#include <list>
208+#include <string>
209+#include <unordered_set>
210+
211+using namespace std;
212+using namespace unity::util;
213+
214+namespace
215+{
216+
217+typedef pair<string, int> Deleted;
218+static list<Deleted> DELETED_OBJECTS;
219+
220+//
221+// Below here is a basic implementation of a GObject type
222+//
223+
224+// "public" header
225+
226+G_BEGIN_DECLS
227+
228+#define FOO_TYPE_BAR foo_bar_get_type()
229+G_DECLARE_FINAL_TYPE (FooBar, foo_bar, FOO, BAR, GObject)
230+
231+FooBar *foo_bar_new();
232+
233+FooBar *foo_bar_new_full(const gchar* const name, guint id);
234+
235+G_END_DECLS
236+
237+// private implementation
238+
239+struct _FooBar
240+{
241+ GObject parent_instance;
242+
243+ gchar *name;
244+
245+ guint id;
246+};
247+
248+G_DEFINE_TYPE (FooBar, foo_bar, G_TYPE_OBJECT)
249+
250+enum
251+{
252+ PROP_NAME = 1,
253+ PROP_ID,
254+ N_PROPERTIES
255+};
256+
257+static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
258+
259+static void foo_bar_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
260+{
261+ FooBar *self = FOO_BAR(object);
262+
263+ switch (property_id)
264+ {
265+ case PROP_NAME:
266+ g_free(self->name);
267+ self->name = g_value_dup_string(value);
268+ break;
269+
270+ case PROP_ID:
271+ self->id = g_value_get_uint(value);
272+ break;
273+
274+ default:
275+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
276+ break;
277+ }
278+}
279+
280+static void foo_bar_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
281+{
282+ FooBar *self = FOO_BAR(object);
283+
284+ switch (property_id)
285+ {
286+ case PROP_NAME:
287+ g_value_set_string(value, self->name);
288+ break;
289+
290+ case PROP_ID:
291+ g_value_set_uint(value, self->id);
292+ break;
293+
294+ default:
295+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
296+ break;
297+ }
298+}
299+
300+static void foo_bar_finalize(GObject *gobject)
301+{
302+ FooBar* self = FOO_BAR(gobject);
303+
304+ DELETED_OBJECTS.emplace_back(Deleted(string(self->name == NULL ? "" : self->name), self->id));
305+
306+ g_free(self->name);
307+
308+ G_OBJECT_CLASS (foo_bar_parent_class)->finalize(gobject);
309+}
310+
311+static void foo_bar_class_init(FooBarClass *klass)
312+{
313+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
314+
315+ object_class->set_property = foo_bar_set_property;
316+ object_class->get_property = foo_bar_get_property;
317+ object_class->finalize = foo_bar_finalize;
318+
319+ obj_properties[PROP_NAME] =
320+ g_param_spec_string ("name",
321+ "Name",
322+ "Name of the file to load and display from.",
323+ "default-name" /* default value */,
324+ (GParamFlags) (G_PARAM_READWRITE));
325+
326+ obj_properties[PROP_ID] =
327+ g_param_spec_uint ("id",
328+ "ID",
329+ "ID of this dummy class",
330+ 0 /* minimum value */,
331+ 10 /* maximum value */,
332+ 2 /* default value */,
333+ (GParamFlags) G_PARAM_READWRITE);
334+
335+ g_object_class_install_properties (object_class,
336+ N_PROPERTIES,
337+ obj_properties);
338+}
339+
340+static void foo_bar_init(FooBar *)
341+{
342+}
343+
344+FooBar *foo_bar_new()
345+{
346+ return FOO_BAR(g_object_new(FOO_TYPE_BAR, NULL));
347+}
348+
349+FooBar *foo_bar_new_full(const gchar* const name, guint id)
350+{
351+ return FOO_BAR(g_object_new(FOO_TYPE_BAR, "name", name, "id", id, NULL));
352+}
353+
354+//
355+// Test cases
356+//
357+
358+class GObjectMemoryTest: public testing::Test
359+{
360+protected:
361+ void SetUp() override
362+ {
363+ DELETED_OBJECTS.clear();
364+ }
365+};
366+
367+TEST_F(GObjectMemoryTest, trivial)
368+{
369+ auto basic = unique_gobject(foo_bar_new());
370+ EXPECT_TRUE(bool(basic));
371+ EXPECT_TRUE(G_IS_OBJECT(basic.get()));
372+}
373+
374+TEST_F(GObjectMemoryTest, compare)
375+{
376+ FooBar* o1 = foo_bar_new();
377+ FooBar* o2 = foo_bar_new();
378+ if (o1 > o2) {
379+ std::swap(o1, o2);
380+ }
381+ ASSERT_TRUE(o1 < o2);
382+ auto u1 = unique_gobject(o1);
383+ auto u2 = unique_gobject(o2);
384+
385+ EXPECT_TRUE(!(u1 == nullptr));
386+ EXPECT_TRUE(u1 != nullptr);
387+ EXPECT_TRUE(u1 != u2);
388+ EXPECT_TRUE(!(u1 == u2));
389+ EXPECT_TRUE(u1 < u2);
390+ EXPECT_TRUE(!(u2 < u1));
391+ EXPECT_TRUE(!(u1 == u2));
392+ EXPECT_TRUE(!(u2 == u1));
393+ EXPECT_TRUE(u1 <= u2);
394+ EXPECT_TRUE(!(u2 <= u1));
395+}
396+
397+// This is its own thing due to need to avoid double release.
398+
399+TEST_F(GObjectMemoryTest, equality)
400+{
401+ FooBar* o = foo_bar_new();
402+ auto u1 = unique_gobject(o);
403+ g_object_ref(o);
404+ auto u2 = unique_gobject(o);
405+ EXPECT_TRUE(u1 == u2);
406+ EXPECT_TRUE(u2 == u1);
407+ EXPECT_TRUE(!(u1 != u2));
408+ EXPECT_TRUE(!(u2 != u1));
409+}
410+
411+TEST_F(GObjectMemoryTest, release)
412+{
413+ FooBar* o = foo_bar_new();
414+ auto u = unique_gobject(o);
415+ EXPECT_TRUE(u != nullptr);
416+ EXPECT_TRUE(u.get() != nullptr);
417+ EXPECT_TRUE(o == u.release());
418+ EXPECT_TRUE(!u);
419+ EXPECT_TRUE(u.get() == nullptr);
420+ g_object_unref(o);
421+}
422+
423+TEST_F(GObjectMemoryTest, refcount)
424+{
425+ GObject* o = G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr));
426+ EXPECT_EQ(1, o->ref_count);
427+ g_object_ref(o);
428+
429+ {
430+ EXPECT_EQ(2, o->ref_count);
431+ auto u = unique_gobject<GObject>(o);
432+ EXPECT_EQ(2, o->ref_count);
433+ // Now it dies and refcount is reduced.
434+ }
435+
436+ EXPECT_EQ(1, o->ref_count);
437+ g_object_unref(o);
438+}
439+
440+TEST_F(GObjectMemoryTest, swap)
441+{
442+ FooBar* o1 = foo_bar_new();
443+ FooBar* o2 = foo_bar_new();
444+ auto u1 = unique_gobject(o1);
445+ auto u2 = unique_gobject(o2);
446+
447+ u1.swap(u2);
448+ EXPECT_EQ(o2, u1.get());
449+ EXPECT_EQ(o1, u2.get());
450+
451+ std::swap(u1, u2);
452+ EXPECT_EQ(o1, u1.get());
453+ EXPECT_EQ(o2, u2.get());
454+}
455+
456+TEST_F(GObjectMemoryTest, floating)
457+{
458+ GObject* o = G_OBJECT(g_object_new(G_TYPE_INITIALLY_UNOWNED, nullptr));
459+ auto u = unique_gobject<GObject>(o);
460+ // it should have sunk the reference
461+ EXPECT_TRUE(g_object_is_floating(u.get()) == FALSE);
462+}
463+
464+TEST_F(GObjectMemoryTest, move)
465+{
466+ GObject* o1 = G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr));
467+ GObject* o2 = G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr));
468+ g_object_ref(o1);
469+ auto u1 = unique_gobject<GObject>(o1);
470+ auto u2 = unique_gobject<GObject>(o2);
471+ u1 = std::move(u2);
472+ EXPECT_TRUE(u1.get() == o2);
473+ EXPECT_TRUE(!u2);
474+ EXPECT_TRUE(o1->ref_count == 1);
475+ g_object_unref(o1);
476+}
477+
478+TEST_F(GObjectMemoryTest, null)
479+{
480+ FooBar* o1 = NULL;
481+ FooBar* o3 = foo_bar_new();
482+ auto u1 = unique_gobject(o1);
483+ auto u2 = unique_gobject<FooBar>(nullptr);
484+ auto u3 = unique_gobject(o3);
485+
486+ EXPECT_TRUE(!u1);
487+ EXPECT_TRUE(!u2);
488+ u3 = nullptr;
489+ EXPECT_TRUE(!u3);
490+}
491+
492+TEST_F(GObjectMemoryTest, reset)
493+{
494+ FooBar* o1 = foo_bar_new();
495+ FooBar* o2 = foo_bar_new();
496+ auto u = unique_gobject(o1);
497+
498+ u.reset(o2);
499+ EXPECT_EQ(o2, u.get());
500+ u.reset(nullptr);
501+ EXPECT_TRUE(!u);
502+}
503+
504+TEST_F(GObjectMemoryTest, sizeoftest) {
505+ EXPECT_EQ(sizeof(FooBar*), sizeof(unique_ptr<FooBar>));
506+}
507+
508+TEST_F(GObjectMemoryTest, hash)
509+{
510+ unordered_set<shared_ptr<FooBar>> s;
511+ auto a = share_gobject(foo_bar_new());
512+ auto b = share_gobject(foo_bar_new());
513+ auto c = share_gobject(foo_bar_new());
514+
515+ s.emplace(a);
516+ EXPECT_EQ(1, s.size());
517+ EXPECT_FALSE(s.end() == s.find(a));
518+
519+ s.emplace(b);
520+ EXPECT_EQ(2, s.size());
521+ EXPECT_FALSE(s.end() == s.find(b));
522+
523+ s.emplace(c);
524+ EXPECT_EQ(3, s.size());
525+ EXPECT_FALSE(s.end() == s.find(c));
526+
527+ // Shouldn't add the duplicates
528+ s.emplace(a);
529+ s.emplace(b);
530+ s.emplace(c);
531+ EXPECT_EQ(3, s.size());
532+
533+ s.erase(a);
534+ EXPECT_EQ(2, s.size());
535+
536+ s.erase(b);
537+ EXPECT_EQ(1, s.size());
538+
539+ s.erase(c);
540+ EXPECT_TRUE(s.empty());
541+}
542+
543+TEST_F(GObjectMemoryTest, uniquePtrDeletesGObjects)
544+{
545+ {
546+ auto a = unique_gobject(foo_bar_new_full("a", 1));
547+ auto b = unique_gobject(foo_bar_new_full("b", 2));
548+ }
549+ EXPECT_EQ(list<Deleted>({{"b", 2}, {"a", 1}}), DELETED_OBJECTS);
550+}
551+
552+TEST_F(GObjectMemoryTest, sharedPtrDeletesGObjects)
553+{
554+ {
555+ auto a = share_gobject(foo_bar_new_full("a", 1));
556+ auto b = share_gobject(foo_bar_new_full("b", 2));
557+ }
558+ EXPECT_EQ(list<Deleted>({{"b", 2}, {"a", 1}}), DELETED_OBJECTS);
559+}
560+
561+TEST_F(GObjectMemoryTest, makeGObjectDeletesGObjects)
562+{
563+ {
564+ auto a = make_gobject<FooBar>(FOO_TYPE_BAR, "name", "a", "id", 1, nullptr);
565+ auto b = make_gobject<FooBar>(FOO_TYPE_BAR, "name", "b", "id", 2, nullptr);
566+ auto c = make_gobject<FooBar>(FOO_TYPE_BAR, "name", "c", "id", 3, nullptr);
567+ }
568+ EXPECT_EQ(list<Deleted>({{"c", 3}, {"b", 2}, {"a", 1}}), DELETED_OBJECTS);
569+}
570+
571+TEST_F(GObjectMemoryTest, foo)
572+{
573+ {
574+ shared_ptr<FooBar> s;
575+ auto u = unique_gobject(foo_bar_new_full("hi", 6));
576+ s = move(u);
577+ // unique instance has been stolen from
578+ EXPECT_FALSE(u);
579+ }
580+ EXPECT_EQ(list<Deleted>({{"hi", 6}}), DELETED_OBJECTS);
581+ {
582+ shared_ptr<FooBar> s(unique_gobject(foo_bar_new_full("bye", 7)));
583+ }
584+ EXPECT_EQ(list<Deleted>({{"hi", 6}, {"bye", 7}}), DELETED_OBJECTS);
585+}
586+
587+typedef pair<const char*, guint> GObjectMemoryMakeSharedTestParam;
588+
589+class GObjectMemoryMakeHelperMethodsTest: public testing::TestWithParam<GObjectMemoryMakeSharedTestParam>
590+{
591+protected:
592+ /**
593+ * We test for multiple properties so that we can be sure the various
594+ * helper methods correctly pass through different data types and
595+ * multiple combinations of arguments correctly.
596+ */
597+ static void checkProperties(gpointer obj, const char* expectedName, guint expectedId)
598+ {
599+ char* name = NULL;
600+ guint id = 0;
601+
602+ g_object_get(obj, "name", &name, "id", &id, NULL);
603+ EXPECT_STREQ(expectedName, name);
604+ EXPECT_EQ(expectedId, id);
605+
606+ g_free(name);
607+ }
608+};
609+
610+TEST_P(GObjectMemoryMakeHelperMethodsTest, make_gobject_passes_arguments)
611+{
612+ auto p = GetParam();
613+ auto obj = make_gobject<FooBar>(FOO_TYPE_BAR, "name", p.first, "id", p.second, nullptr);
614+ checkProperties(obj.get(), p.first, p.second);
615+}
616+
617+TEST_P(GObjectMemoryMakeHelperMethodsTest, make_gobject_no_arguments)
618+{
619+ auto p = GetParam();
620+ auto obj = make_gobject<FooBar>(FOO_TYPE_BAR, nullptr);
621+ g_object_set(obj.get(), "name", p.first, "id", p.second, nullptr);
622+ checkProperties(obj.get(), p.first, p.second);
623+}
624+
625+TEST_P(GObjectMemoryMakeHelperMethodsTest, unique_foo_bar_new)
626+{
627+ auto p = GetParam();
628+ auto obj = unique_gobject(foo_bar_new_full(p.first,p.second));
629+ checkProperties(obj.get(), p.first, p.second);
630+}
631+
632+TEST_P(GObjectMemoryMakeHelperMethodsTest, share_foo_bar_new)
633+{
634+ auto p = GetParam();
635+ auto obj = share_gobject(foo_bar_new_full(p.first, p.second));
636+ checkProperties(obj.get(), p.first, p.second);
637+}
638+
639+INSTANTIATE_TEST_CASE_P(BunchOfNames,
640+ GObjectMemoryMakeHelperMethodsTest,
641+ ::testing::Values(GObjectMemoryMakeSharedTestParam{"meeny", 1},
642+ GObjectMemoryMakeSharedTestParam{"miny", 2},
643+ GObjectMemoryMakeSharedTestParam{"moe", 3}));
644+
645+}
646
647=== modified file 'test/headers/CMakeLists.txt'
648--- test/headers/CMakeLists.txt 2013-05-16 06:53:27 +0000
649+++ test/headers/CMakeLists.txt 2017-01-19 13:53:10 +0000
650@@ -10,6 +10,10 @@
651 unity/util
652 )
653
654+set(exclusions
655+ "GObjectMemory.h"
656+)
657+
658 foreach(dir ${subdirs})
659
660 string(REPLACE "/" "-" location ${dir})
661@@ -20,12 +24,12 @@
662 # Test that each public header compiles stand-alone.
663 add_test(stand-alone-${location}-headers
664 ${CMAKE_CURRENT_SOURCE_DIR}/compile_headers.py
665- ${public_inc_dir} ${CMAKE_CXX_COMPILER} "-I${root_inc_dir} -I${public_inc_dir} ${CMAKE_CXX_FLAGS}")
666+ ${public_inc_dir} ${CMAKE_CXX_COMPILER} "-I${root_inc_dir} -I${public_inc_dir} ${CMAKE_CXX_FLAGS}" "--exclusions" ${exclusions})
667
668 # Test that each internal header compiles stand-alone.
669 add_test(stand-alone-${location}-internal-headers
670 ${CMAKE_CURRENT_SOURCE_DIR}/compile_headers.py
671- ${internal_inc_dir} ${CMAKE_CXX_COMPILER} "-I${root_inc_dir} -I${internal_inc_dir} ${CMAKE_CXX_FLAGS}")
672+ ${internal_inc_dir} ${CMAKE_CXX_COMPILER} "-I${root_inc_dir} -I${internal_inc_dir} ${CMAKE_CXX_FLAGS}" "--exclusions" ${exclusions})
673
674 # Test that no public header includes an internal header
675 add_test(clean-public-${location}-headers ${CMAKE_CURRENT_SOURCE_DIR}/check_public_headers.py ${public_inc_dir})
676
677=== modified file 'test/headers/compile_headers.py'
678--- test/headers/compile_headers.py 2013-06-26 23:47:34 +0000
679+++ test/headers/compile_headers.py 2017-01-19 13:53:10 +0000
680@@ -135,8 +135,11 @@
681 parser.add_argument('compiler', nargs = 1, help = 'The compiler executable, such as "gcc"')
682 parser.add_argument('copts', nargs = '?', default="",
683 help = 'The compiler options (excluding -c), such as "-g -Wall -I." as a single string.')
684+ parser.add_argument('--exclusions', nargs = '*', help = 'Header files to ignore, e.g. "foo.h bar.h"')
685 args = parser.parse_args()
686
687+ exclusions = args.exclusions
688+
689 #
690 # Find all the .h files in specified directory and do the compilation for each one.
691 #
692@@ -147,7 +150,7 @@
693 msg = "cannot open \"" + hdr_dir + "\": " + e.strerror
694 error(msg)
695 sys.exit(1)
696- hdrs = [hdr for hdr in files if hdr.endswith('.h')]
697+ hdrs = [hdr for hdr in files if (hdr.endswith('.h') and hdr not in exclusions)]
698
699 try:
700 test_files(hdrs, args.compiler[0], args.copts, args.verbose)
701
702=== modified file 'test/headers/includechecker.py'
703--- test/headers/includechecker.py 2013-06-26 23:47:34 +0000
704+++ test/headers/includechecker.py 2017-01-19 13:53:10 +0000
705@@ -35,7 +35,8 @@
706 # with one of the specified prefixes.
707 #
708 allowed = {
709- 'unity/shell': { 'Qt' } # Anything under unity/shell can include anything starting with Qt
710+ 'unity/shell': { 'Qt' }, # Anything under unity/shell can include anything starting with Qt
711+ 'unity/util/GObjectMemory': { 'glib' }, # The unity/util/GObjectMemory header can include anything starting with glib
712 }
713
714 def check_file(filename, permitted_includes):

Subscribers

People subscribed via source and target branches

to all changes: