1 /* rcbox.c: Reference counted data
2 *
3 * Copyright 2018 Emmanuele Bassi
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <glib.h>
22
23 typedef struct {
24 float x, y;
25 } Point;
26
27 static Point *global_point;
28
29 /* test_rcbox_new: Test g_rc_box_new() */
30 static void
31 test_rcbox_new (void)
32 {
33 Point *a = g_rc_box_new (Point);
34
35 g_assert_nonnull (a);
36 g_assert_cmpuint (g_rc_box_get_size (a), ==, sizeof (Point));
37
38 g_rc_box_release (a);
39
40 a = g_rc_box_new0 (Point);
41 g_assert_nonnull (a);
42 g_assert_cmpfloat (a->x, ==, 0.f);
43 g_assert_cmpfloat (a->y, ==, 0.f);
44
45 g_rc_box_release (a);
46 }
47
48 /* test_atomic_rcbox_new: Test g_atomic_rc_box_new() */
49 static void
50 test_atomic_rcbox_new (void)
51 {
52 Point *a = g_atomic_rc_box_new (Point);
53
54 g_assert_nonnull (a);
55 g_assert_cmpuint (g_atomic_rc_box_get_size (a), ==, sizeof (Point));
56
57 g_atomic_rc_box_release (a);
58
59 a = g_atomic_rc_box_new0 (Point);
60 g_assert_nonnull (a);
61 g_assert_cmpfloat (a->x, ==, 0.f);
62 g_assert_cmpfloat (a->y, ==, 0.f);
63
64 g_atomic_rc_box_release (a);
65 }
66
67 static void
68 point_clear (Point *p)
69 {
70 g_assert_nonnull (p);
71 g_assert_true (global_point == p);
72
73 g_assert_cmpfloat (p->x, ==, 42.0f);
74 g_assert_cmpfloat (p->y, ==, 47.0f);
75
76 g_test_message ("global_point = %p", p);
77 global_point = NULL;
78 }
79
80 /* test_rcbox_release_full: Verify that g_rc_box_release_full() calls
81 * the clear function only when the last reference is released
82 */
83 static void
84 test_rcbox_release_full (void)
85 {
86 Point *p = g_rc_box_new (Point);
87
88 g_assert_nonnull (p);
89 global_point = p;
90
91 p->x = 42.0f;
92 p->y = 47.0f;
93
94 g_assert_true (g_rc_box_acquire (p) == p);
95
96 g_rc_box_release_full (p, (GDestroyNotify) point_clear);
97 g_assert_nonnull (global_point);
98 g_assert_true (p == global_point);
99
100 g_rc_box_release_full (p, (GDestroyNotify) point_clear);
101 g_assert_null (global_point);
102 }
103
104 /* test_atomic_rcbox_release_full: Verify that g_atomic_rc_box_release_full()
105 * calls the clear function only when the last reference is released
106 */
107 static void
108 test_atomic_rcbox_release_full (void)
109 {
110 Point *p = g_atomic_rc_box_new (Point);
111
112 g_assert_nonnull (p);
113 global_point = p;
114
115 p->x = 42.0f;
116 p->y = 47.0f;
117
118 g_assert_true (g_atomic_rc_box_acquire (p) == p);
119
120 g_atomic_rc_box_release_full (p, (GDestroyNotify) point_clear);
121 g_assert_nonnull (global_point);
122 g_assert_true (p == global_point);
123
124 g_atomic_rc_box_release_full (p, (GDestroyNotify) point_clear);
125 g_assert_null (global_point);
126 }
127
128 static Point *global_point_a;
129 static Point *global_point_b;
130
131 static void
132 point_clear_dup_a (Point *a)
133 {
134 g_assert_true (a == global_point_a);
135
136 g_test_message ("global_point_a = %p", a);
137 global_point_a = NULL;
138 }
139
140 static void
141 point_clear_dup_b (Point *b)
142 {
143 g_assert_true (b == global_point_b);
144
145 g_test_message ("global_point_b = %p", b);
146 global_point_b = NULL;
147 }
148
149 /* test_rcbox_dup: Verify that g_rc_box_dup() copies only the
150 * data and does not change the reference count of the original
151 */
152 static void
153 test_rcbox_dup (void)
154 {
155 Point *a, *b;
156
157 a = g_rc_box_new (Point);
158 a->x = 10.f;
159 a->y = 5.f;
160
161 b = g_rc_box_dup (sizeof (Point), a);
162 g_assert_true (a != b);
163 g_assert_cmpfloat (a->x, ==, b->x);
164 g_assert_cmpfloat (a->y, ==, b->y);
165
166 global_point_a = a;
167 global_point_b = b;
168
169 a->x = 1.f;
170 a->y = 1.f;
171 g_assert_cmpfloat (a->x, !=, b->x);
172 g_assert_cmpfloat (a->y, !=, b->y);
173
174 b->x = 5.f;
175 b->y = 10.f;
176 g_assert_cmpfloat (a->x, !=, b->x);
177 g_assert_cmpfloat (a->y, !=, b->y);
178
179 g_rc_box_release_full (a, (GDestroyNotify) point_clear_dup_a);
180 g_assert_null (global_point_a);
181 g_assert_nonnull (global_point_b);
182
183 g_rc_box_release_full (b, (GDestroyNotify) point_clear_dup_b);
184 g_assert_null (global_point_b);
185 }
186
187 /* test_atomic_rcbox_dup: Verify that g_atomic_rc_box_dup() copies
188 * only the data and does not change the reference count of the original
189 */
190 static void
191 test_atomic_rcbox_dup (void)
192 {
193 Point *a, *b;
194
195 a = g_atomic_rc_box_new (Point);
196 a->x = 10.f;
197 a->y = 5.f;
198
199 b = g_atomic_rc_box_dup (sizeof (Point), a);
200 g_assert_true (a != b);
201 g_assert_cmpfloat (a->x, ==, b->x);
202 g_assert_cmpfloat (a->y, ==, b->y);
203
204 global_point_a = a;
205 global_point_b = b;
206
207 a->x = 1.f;
208 a->y = 1.f;
209 g_assert_cmpfloat (a->x, !=, b->x);
210 g_assert_cmpfloat (a->y, !=, b->y);
211
212 b->x = 5.f;
213 b->y = 10.f;
214 g_assert_cmpfloat (a->x, !=, b->x);
215 g_assert_cmpfloat (a->y, !=, b->y);
216
217 g_atomic_rc_box_release_full (a, (GDestroyNotify) point_clear_dup_a);
218 g_assert_null (global_point_a);
219 g_assert_nonnull (global_point_b);
220
221 g_atomic_rc_box_release_full (b, (GDestroyNotify) point_clear_dup_b);
222 g_assert_null (global_point_b);
223 }
224
225 /* The expected alignment of the refcounted data, absent any other
226 * alignment requirement, is `2 * sizeof(void*)`; GLib only really
227 * supports void* sized 8 or 4 (see the comment in gatomic.h)
228 */
229 #if GLIB_SIZEOF_VOID_P == 8
230 static const gsize rcbox_alignment = 16;
231 #else
232 static const gsize rcbox_alignment = 8;
233 #endif
234
235 /* verify that the refcounted allocation is properly aligned */
236 static void
237 test_rcbox_alignment (void)
238 {
239 const gsize block_sizes[] = {
240 1,
241 2,
242 4,
243 sizeof (gint32) * 3,
244 };
245
246 gsize i;
247
248 for (i = 0; i < G_N_ELEMENTS (block_sizes); i++)
249 {
250 gpointer p = g_rc_box_alloc0 (block_sizes[i]);
251
252 g_assert_nonnull (p);
253 g_assert_true (((guintptr) p & (rcbox_alignment - 1)) == 0);
254
255 g_rc_box_release (p);
256 }
257 }
258
259 /* verify that the atomically refcounted allocation is properly aligned */
260 static void
261 test_atomic_rcbox_alignment (void)
262 {
263 const gsize block_sizes[] = {
264 1,
265 2,
266 4,
267 sizeof (gint32) * 3,
268 };
269
270 gsize i;
271
272 for (i = 0; i < G_N_ELEMENTS (block_sizes); i++)
273 {
274 gpointer p = g_atomic_rc_box_alloc0 (block_sizes[i]);
275
276 g_assert_nonnull (p);
277 g_assert_true (((guintptr) p & (rcbox_alignment - 1)) == 0);
278
279 g_atomic_rc_box_release (p);
280 }
281 }
282
283 int
284 main (int argc,
285 char *argv[])
286 {
287 g_test_init (&argc, &argv, NULL);
288
289 g_test_add_func ("/rcbox/new", test_rcbox_new);
290 g_test_add_func ("/rcbox/release-full", test_rcbox_release_full);
291 g_test_add_func ("/rcbox/dup", test_rcbox_dup);
292 g_test_add_func ("/rcbox/alignment", test_rcbox_alignment);
293
294 g_test_add_func ("/atomic-rcbox/new", test_atomic_rcbox_new);
295 g_test_add_func ("/atomic-rcbox/release-full", test_atomic_rcbox_release_full);
296 g_test_add_func ("/atomic-rcbox/dup", test_atomic_rcbox_dup);
297 g_test_add_func ("/atomic-rcbox/alignment", test_atomic_rcbox_alignment);
298
299 return g_test_run ();
300 }