Branch data Line data Source code
1 : : /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2021 Marco Trevisan <marco.trevisan@canonical.com>
4 : :
5 : : #include "installed-tests/js/libgjstesttools/gjs-test-tools.h"
6 : :
7 : : #include <mutex>
8 : : #include <unordered_set>
9 : :
10 : : #include "gjs/jsapi-util.h"
11 : :
12 : : #ifdef G_OS_UNIX
13 : : # include <errno.h>
14 : : # include <fcntl.h> /* for FD_CLOEXEC */
15 : : # include <stdarg.h>
16 : : # include <unistd.h> /* for close, write */
17 : :
18 : : # include <glib-unix.h> /* for g_unix_open_pipe */
19 : : #endif
20 : :
21 : : static std::atomic<GObject*> s_tmp_object = nullptr;
22 : : static GWeakRef s_tmp_weak;
23 : : static std::unordered_set<GObject*> s_finalized_objects;
24 : : static std::mutex s_finalized_objects_lock;
25 : :
26 : : struct FinalizedObjectsLocked {
27 : 180 : FinalizedObjectsLocked() : hold(s_finalized_objects_lock) {}
28 : :
29 : 180 : std::unordered_set<GObject*>* operator->() { return &s_finalized_objects; }
30 : : std::lock_guard<std::mutex> hold;
31 : : };
32 : :
33 : 26 : void gjs_test_tools_init() {}
34 : :
35 : 48 : void gjs_test_tools_reset() {
36 : 48 : gjs_test_tools_clear_saved();
37 : 48 : g_weak_ref_set(&s_tmp_weak, nullptr);
38 : :
39 : 48 : FinalizedObjectsLocked()->clear();
40 : 48 : }
41 : :
42 : : // clang-format off
43 [ + + ]: 61 : static G_DEFINE_QUARK(gjs-test-utils::finalize, finalize);
44 : : // clang-format on
45 : :
46 : 23 : static void monitor_object_finalization(GObject* object) {
47 : 23 : g_object_steal_qdata(object, finalize_quark());
48 : 23 : g_object_set_qdata_full(object, finalize_quark(), object, [](void* data) {
49 : 4 : FinalizedObjectsLocked()->insert(static_cast<GObject*>(data));
50 : 4 : });
51 : 23 : }
52 : :
53 : 0 : void gjs_test_tools_delayed_ref(GObject* object, int interval) {
54 : 0 : g_timeout_add(
55 : : interval,
56 : 0 : [](void *data) {
57 : 0 : g_object_ref(G_OBJECT(data));
58 : 0 : return G_SOURCE_REMOVE;
59 : : },
60 : : object);
61 : 0 : }
62 : :
63 : 2 : void gjs_test_tools_delayed_unref(GObject* object, int interval) {
64 : 2 : g_timeout_add(
65 : : interval,
66 : 4 : [](void *data) {
67 : 2 : g_object_unref(G_OBJECT(data));
68 : 2 : return G_SOURCE_REMOVE;
69 : : },
70 : : object);
71 : 2 : }
72 : :
73 : 0 : void gjs_test_tools_delayed_dispose(GObject* object, int interval) {
74 : 0 : g_timeout_add(
75 : : interval,
76 : 0 : [](void *data) {
77 : 0 : g_object_run_dispose(G_OBJECT(data));
78 : 0 : return G_SOURCE_REMOVE;
79 : : },
80 : : object);
81 : 0 : }
82 : :
83 : 6 : void gjs_test_tools_save_object(GObject* object) {
84 : 6 : g_object_ref(object);
85 : 6 : gjs_test_tools_save_object_unreffed(object);
86 : 6 : }
87 : :
88 : 7 : void gjs_test_tools_save_object_unreffed(GObject* object) {
89 : 7 : GObject* expected = nullptr;
90 : 7 : g_assert_true(s_tmp_object.compare_exchange_strong(expected, object));
91 : 7 : }
92 : :
93 : 50 : void gjs_test_tools_clear_saved() {
94 [ + - ]: 50 : if (!FinalizedObjectsLocked()->count(s_tmp_object)) {
95 : 50 : auto* object = s_tmp_object.exchange(nullptr);
96 [ + + ]: 50 : g_clear_object(&object);
97 : : } else {
98 : 0 : s_tmp_object = nullptr;
99 : : }
100 : 50 : }
101 : :
102 : 12 : void gjs_test_tools_ref_other_thread(GObject* object, GError** error) {
103 : 12 : auto* thread = g_thread_try_new("ref_object", g_object_ref, object, error);
104 [ + - ]: 12 : if (thread)
105 : 12 : g_thread_join(thread);
106 : : // cppcheck-suppress memleak
107 : 12 : }
108 : :
109 : : typedef enum {
110 : : REF = 1 << 0,
111 : : UNREF = 1 << 1,
112 : : } RefType;
113 : :
114 : : typedef struct {
115 : : GObject* object;
116 : : RefType ref_type;
117 : : int delay;
118 : : } RefThreadData;
119 : :
120 : 23 : static RefThreadData* ref_thread_data_new(GObject* object, int interval,
121 : : RefType ref_type) {
122 : 23 : auto* ref_data = g_new(RefThreadData, 1);
123 : :
124 : 23 : ref_data->object = object;
125 : 23 : ref_data->delay = interval;
126 : 23 : ref_data->ref_type = ref_type;
127 : :
128 : 23 : monitor_object_finalization(object);
129 : :
130 : 23 : return ref_data;
131 : : }
132 : :
133 : 23 : static void* ref_thread_func(void* data) {
134 : : GjsAutoPointer<RefThreadData, void, g_free> ref_data =
135 : 23 : static_cast<RefThreadData*>(data);
136 : :
137 [ - + ]: 23 : if (FinalizedObjectsLocked()->count(ref_data->object))
138 : 0 : return nullptr;
139 : :
140 [ + + ]: 23 : if (ref_data->delay > 0)
141 : 9 : g_usleep(ref_data->delay);
142 : :
143 [ - + ]: 23 : if (FinalizedObjectsLocked()->count(ref_data->object))
144 : 0 : return nullptr;
145 : :
146 [ + + ]: 23 : if (ref_data->ref_type & REF)
147 : 10 : g_object_ref(ref_data->object);
148 : :
149 [ + + ]: 23 : if (!(ref_data->ref_type & UNREF)) {
150 : 1 : return ref_data->object;
151 [ + + ]: 22 : } else if (ref_data->ref_type & REF) {
152 : 9 : g_usleep(ref_data->delay);
153 : :
154 [ - + ]: 9 : if (FinalizedObjectsLocked()->count(ref_data->object))
155 : 0 : return nullptr;
156 : : }
157 : :
158 [ + + ]: 22 : if (ref_data->object != s_tmp_object)
159 : 15 : g_object_steal_qdata(ref_data->object, finalize_quark());
160 : 22 : g_object_unref(ref_data->object);
161 : 22 : return nullptr;
162 : 23 : }
163 : :
164 : 12 : void gjs_test_tools_unref_other_thread(GObject* object, GError** error) {
165 : : auto* thread =
166 : 12 : g_thread_try_new("unref_object", ref_thread_func,
167 : 12 : ref_thread_data_new(object, -1, UNREF), error);
168 [ + - ]: 12 : if (thread)
169 : 12 : g_thread_join(thread);
170 : : // cppcheck-suppress memleak
171 : 12 : }
172 : :
173 : : /**
174 : : * gjs_test_tools_delayed_ref_other_thread:
175 : : * Returns: (transfer full)
176 : : */
177 : 1 : GThread* gjs_test_tools_delayed_ref_other_thread(GObject* object, int interval,
178 : : GError** error) {
179 : 1 : return g_thread_try_new("ref_object", ref_thread_func,
180 : 2 : ref_thread_data_new(object, interval, REF), error);
181 : : }
182 : :
183 : : /**
184 : : * gjs_test_tools_delayed_unref_other_thread:
185 : : * Returns: (transfer full)
186 : : */
187 : 1 : GThread* gjs_test_tools_delayed_unref_other_thread(GObject* object,
188 : : int interval,
189 : : GError** error) {
190 : 1 : return g_thread_try_new("unref_object", ref_thread_func,
191 : 1 : ref_thread_data_new(object, interval, UNREF),
192 : 1 : error);
193 : : }
194 : :
195 : : /**
196 : : * gjs_test_tools_delayed_ref_unref_other_thread:
197 : : * Returns: (transfer full)
198 : : */
199 : 9 : GThread* gjs_test_tools_delayed_ref_unref_other_thread(GObject* object,
200 : : int interval,
201 : : GError** error) {
202 : 9 : return g_thread_try_new(
203 : : "ref_unref_object", ref_thread_func,
204 : 9 : ref_thread_data_new(object, interval,
205 : : static_cast<RefType>(REF | UNREF)),
206 : 9 : error);
207 : : }
208 : :
209 : 2 : void gjs_test_tools_run_dispose_other_thread(GObject* object, GError** error) {
210 : 2 : auto* thread = g_thread_try_new(
211 : : "run_dispose",
212 : 4 : [](void* object) -> void* {
213 : 2 : g_object_run_dispose(G_OBJECT(object));
214 : 2 : return nullptr;
215 : : },
216 : : object, error);
217 : : // cppcheck-suppress leakNoVarFunctionCall
218 : 2 : g_thread_join(thread);
219 : 2 : }
220 : :
221 : : /**
222 : : * gjs_test_tools_get_saved:
223 : : * Returns: (transfer full)
224 : : */
225 : 3 : GObject* gjs_test_tools_get_saved() {
226 [ - + ]: 3 : if (FinalizedObjectsLocked()->count(s_tmp_object))
227 : 0 : s_tmp_object = nullptr;
228 : :
229 : 3 : return s_tmp_object.exchange(nullptr);
230 : : }
231 : :
232 : : /**
233 : : * gjs_test_tools_steal_saved:
234 : : * Returns: (transfer none)
235 : : */
236 : 1 : GObject* gjs_test_tools_steal_saved() { return gjs_test_tools_get_saved(); }
237 : :
238 : 6 : void gjs_test_tools_save_weak(GObject* object) {
239 : 6 : g_weak_ref_set(&s_tmp_weak, object);
240 : 6 : }
241 : :
242 : : /**
243 : : * gjs_test_tools_peek_saved:
244 : : * Returns: (transfer none)
245 : : */
246 : 20 : GObject* gjs_test_tools_peek_saved() {
247 [ - + ]: 20 : if (FinalizedObjectsLocked()->count(s_tmp_object))
248 : 0 : return nullptr;
249 : :
250 : 20 : return s_tmp_object;
251 : : }
252 : :
253 : 0 : int gjs_test_tools_get_saved_ref_count() {
254 : 0 : GObject* saved = gjs_test_tools_peek_saved();
255 [ # # ]: 0 : return saved ? saved->ref_count : 0;
256 : : }
257 : :
258 : : /**
259 : : * gjs_test_tools_get_weak:
260 : : * Returns: (transfer full)
261 : : */
262 : 6 : GObject* gjs_test_tools_get_weak() {
263 : 6 : return static_cast<GObject*>(g_weak_ref_get(&s_tmp_weak));
264 : : }
265 : :
266 : : /**
267 : : * gjs_test_tools_get_weak_other_thread:
268 : : * Returns: (transfer full)
269 : : */
270 : 3 : GObject* gjs_test_tools_get_weak_other_thread(GError** error) {
271 : 3 : auto* thread = g_thread_try_new(
272 : 3 : "weak_get", [](void*) -> void* { return gjs_test_tools_get_weak(); },
273 : : NULL, error);
274 [ - + ]: 3 : if (!thread)
275 : 0 : return nullptr;
276 : :
277 : : return static_cast<GObject*>(
278 : : // cppcheck-suppress leakNoVarFunctionCall
279 : 3 : g_thread_join(thread));
280 : : }
281 : :
282 : : /**
283 : : * gjs_test_tools_get_disposed:
284 : : * Returns: (transfer none)
285 : : */
286 : 1 : GObject* gjs_test_tools_get_disposed(GObject* object) {
287 : 1 : g_object_run_dispose(G_OBJECT(object));
288 : 1 : return object;
289 : : }
290 : :
291 : : #ifdef G_OS_UNIX
292 : :
293 : : // Adapted from glnx_throw_errno_prefix()
294 : 0 : static gboolean throw_errno_prefix(GError** error, const char* prefix) {
295 : 0 : int errsv = errno;
296 : :
297 : 0 : g_set_error_literal(error, G_IO_ERROR, g_io_error_from_errno(errsv),
298 : : g_strerror(errsv));
299 : 0 : g_prefix_error(error, "%s: ", prefix);
300 : :
301 : 0 : errno = errsv;
302 : 0 : return FALSE;
303 : : }
304 : :
305 : : #endif /* G_OS_UNIX */
306 : :
307 : : /**
308 : : * gjs_open_bytes:
309 : : * @bytes: bytes to send to the pipe
310 : : * @error: Return location for a #GError, or %NULL
311 : : *
312 : : * Creates a pipe and sends @bytes to it, such that it is suitable for passing
313 : : * to g_subprocess_launcher_take_fd().
314 : : *
315 : : * Returns: file descriptor, or -1 on error
316 : : */
317 : 8 : int gjs_test_tools_open_bytes(GBytes* bytes, GError** error) {
318 : : int pipefd[2], result;
319 : : size_t count;
320 : : const void* buf;
321 : : ssize_t bytes_written;
322 : :
323 : 8 : g_return_val_if_fail(bytes, -1);
324 : 8 : g_return_val_if_fail(error == NULL || *error == NULL, -1);
325 : :
326 : : #ifdef G_OS_UNIX
327 [ - + ]: 8 : if (!g_unix_open_pipe(pipefd, FD_CLOEXEC, error))
328 : 0 : return -1;
329 : :
330 : 8 : buf = g_bytes_get_data(bytes, &count);
331 : :
332 : 8 : bytes_written = write(pipefd[1], buf, count);
333 [ - + ]: 8 : if (bytes_written < 0) {
334 : 0 : throw_errno_prefix(error, "write");
335 : 0 : return -1;
336 : : }
337 : :
338 [ - + ]: 8 : if ((size_t)bytes_written != count)
339 : 0 : g_warning("%s: %zu bytes sent, only %zd bytes written", __func__, count,
340 : : bytes_written);
341 : :
342 : 8 : result = close(pipefd[1]);
343 [ - + ]: 8 : if (result == -1) {
344 : 0 : throw_errno_prefix(error, "close");
345 : 0 : return -1;
346 : : }
347 : :
348 : 8 : return pipefd[0];
349 : : #else
350 : : g_error("%s is currently supported on UNIX only", __func__);
351 : : #endif
352 : : }
|