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 : : #ifdef G_OS_UNIX
11 : : # include <errno.h>
12 : : # include <fcntl.h> /* for FD_CLOEXEC */
13 : : # include <stdarg.h>
14 : : # include <unistd.h> /* for close, write */
15 : :
16 : : # include <glib-unix.h> /* for g_unix_open_pipe */
17 : : #endif
18 : :
19 : : #include "gjs/auto.h"
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 : 181 : FinalizedObjectsLocked() : hold(s_finalized_objects_lock) {}
28 : :
29 : 181 : 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 : 9 : void gjs_test_tools_ref(GObject* object) { g_object_ref(object); }
43 : :
44 : 22 : void gjs_test_tools_unref(GObject* object) { g_object_unref(object); }
45 : :
46 : : // clang-format off
47 [ + + ]: 61 : static G_DEFINE_QUARK(gjs-test-utils::finalize, finalize);
48 : : // clang-format on
49 : :
50 : 23 : static void monitor_object_finalization(GObject* object) {
51 : 23 : g_object_steal_qdata(object, finalize_quark());
52 : 23 : g_object_set_qdata_full(object, finalize_quark(), object, [](void* data) {
53 : 4 : FinalizedObjectsLocked()->insert(static_cast<GObject*>(data));
54 : 4 : });
55 : 23 : }
56 : :
57 : 0 : void gjs_test_tools_delayed_ref(GObject* object, int interval) {
58 : 0 : g_timeout_add(
59 : : interval,
60 : 0 : [](void *data) {
61 : 0 : g_object_ref(G_OBJECT(data));
62 : 0 : return G_SOURCE_REMOVE;
63 : : },
64 : : object);
65 : 0 : }
66 : :
67 : 2 : void gjs_test_tools_delayed_unref(GObject* object, int interval) {
68 : 2 : g_timeout_add(
69 : : interval,
70 : 4 : [](void *data) {
71 : 2 : g_object_unref(G_OBJECT(data));
72 : 2 : return G_SOURCE_REMOVE;
73 : : },
74 : : object);
75 : 2 : }
76 : :
77 : 0 : void gjs_test_tools_delayed_dispose(GObject* object, int interval) {
78 : 0 : g_timeout_add(
79 : : interval,
80 : 0 : [](void *data) {
81 : 0 : g_object_run_dispose(G_OBJECT(data));
82 : 0 : return G_SOURCE_REMOVE;
83 : : },
84 : : object);
85 : 0 : }
86 : :
87 : 7 : void gjs_test_tools_save_object(GObject* object) {
88 : 7 : g_object_ref(object);
89 : 7 : gjs_test_tools_save_object_unreffed(object);
90 : 7 : }
91 : :
92 : 8 : void gjs_test_tools_save_object_unreffed(GObject* object) {
93 : 8 : GObject* expected = nullptr;
94 : 8 : g_assert_true(s_tmp_object.compare_exchange_strong(expected, object));
95 : 8 : }
96 : :
97 : 50 : void gjs_test_tools_clear_saved() {
98 [ + - ]: 50 : if (!FinalizedObjectsLocked()->count(s_tmp_object)) {
99 : 50 : auto* object = s_tmp_object.exchange(nullptr);
100 [ + + ]: 50 : g_clear_object(&object);
101 : : } else {
102 : 0 : s_tmp_object = nullptr;
103 : : }
104 : 50 : }
105 : :
106 : 12 : void gjs_test_tools_ref_other_thread(GObject* object, GError** error) {
107 : 12 : auto* thread = g_thread_try_new("ref_object", g_object_ref, object, error);
108 [ + - ]: 12 : if (thread)
109 : 12 : g_thread_join(thread);
110 : : // cppcheck-suppress memleak
111 : 12 : }
112 : :
113 : 1 : static gpointer emit_test_signal_other_thread_func(gpointer data) {
114 : 1 : g_signal_emit_by_name(data, "test");
115 : 1 : return nullptr;
116 : : }
117 : :
118 : 1 : void gjs_test_tools_emit_test_signal_other_thread(GObject* object,
119 : : GError** error) {
120 : : auto* thread =
121 : 1 : g_thread_try_new("emit_signal_object",
122 : : emit_test_signal_other_thread_func, object, error);
123 [ + - ]: 1 : if (thread)
124 : 1 : g_thread_join(thread);
125 : : // cppcheck-suppress memleak
126 : 1 : }
127 : :
128 : : typedef enum {
129 : : REF = 1 << 0,
130 : : UNREF = 1 << 1,
131 : : } RefType;
132 : :
133 : : typedef struct {
134 : : GObject* object;
135 : : RefType ref_type;
136 : : int delay;
137 : : } RefThreadData;
138 : :
139 : 23 : static RefThreadData* ref_thread_data_new(GObject* object, int interval,
140 : : RefType ref_type) {
141 : 23 : auto* ref_data = g_new(RefThreadData, 1);
142 : :
143 : 23 : ref_data->object = object;
144 : 23 : ref_data->delay = interval;
145 : 23 : ref_data->ref_type = ref_type;
146 : :
147 : 23 : monitor_object_finalization(object);
148 : :
149 : 23 : return ref_data;
150 : : }
151 : :
152 : 23 : static void* ref_thread_func(void* data) {
153 : 23 : Gjs::AutoPointer<RefThreadData, void, g_free> ref_data{
154 : 23 : static_cast<RefThreadData*>(data)};
155 : :
156 [ - + ]: 23 : if (FinalizedObjectsLocked()->count(ref_data->object))
157 : 0 : return nullptr;
158 : :
159 [ + + ]: 23 : if (ref_data->delay > 0)
160 : 9 : g_usleep(ref_data->delay);
161 : :
162 [ - + ]: 23 : if (FinalizedObjectsLocked()->count(ref_data->object))
163 : 0 : return nullptr;
164 : :
165 [ + + ]: 23 : if (ref_data->ref_type & REF)
166 : 10 : g_object_ref(ref_data->object);
167 : :
168 [ + + ]: 23 : if (!(ref_data->ref_type & UNREF)) {
169 : 1 : return ref_data->object;
170 [ + + ]: 22 : } else if (ref_data->ref_type & REF) {
171 : 9 : g_usleep(ref_data->delay);
172 : :
173 [ - + ]: 9 : if (FinalizedObjectsLocked()->count(ref_data->object))
174 : 0 : return nullptr;
175 : : }
176 : :
177 [ + + ]: 22 : if (ref_data->object != s_tmp_object)
178 : 15 : g_object_steal_qdata(ref_data->object, finalize_quark());
179 : 22 : g_object_unref(ref_data->object);
180 : 22 : return nullptr;
181 : 23 : }
182 : :
183 : 12 : void gjs_test_tools_unref_other_thread(GObject* object, GError** error) {
184 : : auto* thread =
185 : 12 : g_thread_try_new("unref_object", ref_thread_func,
186 : 12 : ref_thread_data_new(object, -1, UNREF), error);
187 [ + - ]: 12 : if (thread)
188 : 12 : g_thread_join(thread);
189 : : // cppcheck-suppress memleak
190 : 12 : }
191 : :
192 : : /**
193 : : * gjs_test_tools_delayed_ref_other_thread:
194 : : * Returns: (transfer full)
195 : : */
196 : 1 : GThread* gjs_test_tools_delayed_ref_other_thread(GObject* object, int interval,
197 : : GError** error) {
198 : 1 : return g_thread_try_new("ref_object", ref_thread_func,
199 : 2 : ref_thread_data_new(object, interval, REF), error);
200 : : }
201 : :
202 : : /**
203 : : * gjs_test_tools_delayed_unref_other_thread:
204 : : * Returns: (transfer full)
205 : : */
206 : 1 : GThread* gjs_test_tools_delayed_unref_other_thread(GObject* object,
207 : : int interval,
208 : : GError** error) {
209 : 1 : return g_thread_try_new("unref_object", ref_thread_func,
210 : 1 : ref_thread_data_new(object, interval, UNREF),
211 : 1 : error);
212 : : }
213 : :
214 : : /**
215 : : * gjs_test_tools_delayed_ref_unref_other_thread:
216 : : * Returns: (transfer full)
217 : : */
218 : 9 : GThread* gjs_test_tools_delayed_ref_unref_other_thread(GObject* object,
219 : : int interval,
220 : : GError** error) {
221 : 9 : return g_thread_try_new(
222 : : "ref_unref_object", ref_thread_func,
223 : 9 : ref_thread_data_new(object, interval,
224 : : static_cast<RefType>(REF | UNREF)),
225 : 9 : error);
226 : : }
227 : :
228 : 2 : void gjs_test_tools_run_dispose_other_thread(GObject* object, GError** error) {
229 : 2 : auto* thread = g_thread_try_new(
230 : : "run_dispose",
231 : 4 : [](void* object) -> void* {
232 : 2 : g_object_run_dispose(G_OBJECT(object));
233 : 2 : return nullptr;
234 : : },
235 : : object, error);
236 : : // cppcheck-suppress leakNoVarFunctionCall
237 : 2 : g_thread_join(thread);
238 : : // cppcheck-suppress memleak
239 : 2 : }
240 : :
241 : : /**
242 : : * gjs_test_tools_get_saved:
243 : : * Returns: (transfer full)
244 : : */
245 : 4 : GObject* gjs_test_tools_get_saved() {
246 [ - + ]: 4 : if (FinalizedObjectsLocked()->count(s_tmp_object))
247 : 0 : s_tmp_object = nullptr;
248 : :
249 : 4 : return s_tmp_object.exchange(nullptr);
250 : : }
251 : :
252 : : /**
253 : : * gjs_test_tools_steal_saved:
254 : : * Returns: (transfer none)
255 : : */
256 : 2 : GObject* gjs_test_tools_steal_saved() { return gjs_test_tools_get_saved(); }
257 : :
258 : 6 : void gjs_test_tools_save_weak(GObject* object) {
259 : 6 : g_weak_ref_set(&s_tmp_weak, object);
260 : 6 : }
261 : :
262 : : /**
263 : : * gjs_test_tools_peek_saved:
264 : : * Returns: (transfer none)
265 : : */
266 : 20 : GObject* gjs_test_tools_peek_saved() {
267 [ - + ]: 20 : if (FinalizedObjectsLocked()->count(s_tmp_object))
268 : 0 : return nullptr;
269 : :
270 : 20 : return s_tmp_object;
271 : : }
272 : :
273 : 0 : int gjs_test_tools_get_saved_ref_count() {
274 : 0 : GObject* saved = gjs_test_tools_peek_saved();
275 [ # # ]: 0 : return saved ? saved->ref_count : 0;
276 : : }
277 : :
278 : : /**
279 : : * gjs_test_tools_get_weak:
280 : : * Returns: (transfer full)
281 : : */
282 : 6 : GObject* gjs_test_tools_get_weak() {
283 : 6 : return static_cast<GObject*>(g_weak_ref_get(&s_tmp_weak));
284 : : }
285 : :
286 : : /**
287 : : * gjs_test_tools_get_weak_other_thread:
288 : : * Returns: (transfer full)
289 : : */
290 : 3 : GObject* gjs_test_tools_get_weak_other_thread(GError** error) {
291 : 3 : auto* thread = g_thread_try_new(
292 : 3 : "weak_get", [](void*) -> void* { return gjs_test_tools_get_weak(); },
293 : : NULL, error);
294 [ - + ]: 3 : if (!thread)
295 : 0 : return nullptr;
296 : :
297 : : return static_cast<GObject*>(
298 : : // cppcheck-suppress leakNoVarFunctionCall
299 : 3 : g_thread_join(thread));
300 : : }
301 : :
302 : : /**
303 : : * gjs_test_tools_get_disposed:
304 : : * Returns: (transfer none)
305 : : */
306 : 1 : GObject* gjs_test_tools_get_disposed(GObject* object) {
307 : 1 : g_object_run_dispose(G_OBJECT(object));
308 : 1 : return object;
309 : : }
310 : :
311 : : #ifdef G_OS_UNIX
312 : :
313 : : // Adapted from glnx_throw_errno_prefix()
314 : 0 : static gboolean throw_errno_prefix(GError** error, const char* prefix) {
315 : 0 : int errsv = errno;
316 : :
317 : 0 : g_set_error_literal(error, G_IO_ERROR, g_io_error_from_errno(errsv),
318 : : g_strerror(errsv));
319 : 0 : g_prefix_error(error, "%s: ", prefix);
320 : :
321 : 0 : errno = errsv;
322 : 0 : return FALSE;
323 : : }
324 : :
325 : : #endif /* G_OS_UNIX */
326 : :
327 : : /**
328 : : * gjs_open_bytes:
329 : : * @bytes: bytes to send to the pipe
330 : : * @error: Return location for a #GError, or %NULL
331 : : *
332 : : * Creates a pipe and sends @bytes to it, such that it is suitable for passing
333 : : * to g_subprocess_launcher_take_fd().
334 : : *
335 : : * Returns: file descriptor, or -1 on error
336 : : */
337 : 8 : int gjs_test_tools_open_bytes(GBytes* bytes, GError** error) {
338 : : int pipefd[2], result;
339 : : size_t count;
340 : : const void* buf;
341 : : ssize_t bytes_written;
342 : :
343 : 8 : g_return_val_if_fail(bytes, -1);
344 : 8 : g_return_val_if_fail(error == NULL || *error == NULL, -1);
345 : :
346 : : #ifdef G_OS_UNIX
347 [ - + ]: 8 : if (!g_unix_open_pipe(pipefd, FD_CLOEXEC, error))
348 : 0 : return -1;
349 : :
350 : 8 : buf = g_bytes_get_data(bytes, &count);
351 : :
352 : 8 : bytes_written = write(pipefd[1], buf, count);
353 [ - + ]: 8 : if (bytes_written < 0) {
354 : 0 : throw_errno_prefix(error, "write");
355 : 0 : return -1;
356 : : }
357 : :
358 [ - + ]: 8 : if ((size_t)bytes_written != count)
359 : 0 : g_warning("%s: %zu bytes sent, only %zd bytes written", __func__, count,
360 : : bytes_written);
361 : :
362 : 8 : result = close(pipefd[1]);
363 [ - + ]: 8 : if (result == -1) {
364 : 0 : throw_errno_prefix(error, "close");
365 : 0 : return -1;
366 : : }
367 : :
368 : 8 : return pipefd[0];
369 : : #else
370 : : g_error("%s is currently supported on UNIX only", __func__);
371 : : #endif
372 : : }
373 : :
374 : : /**
375 : : * gjs_test_tools_new_unaligned_bytes:
376 : : * @len: Length of buffer to allocate
377 : : *
378 : : * Creates a data buffer at a location 1 byte away from an 8-byte alignment
379 : : * boundary, to make sure that tests fail when SpiderMonkey enforces an
380 : : * alignment restriction on embedder data.
381 : : *
382 : : * The buffer is filled with repeated 0x00-0x07 bytes containing the least
383 : : * significant 3 bits of that byte's address.
384 : : *
385 : : * Returns: (transfer full): a #GBytes
386 : : */
387 : 1 : GBytes* gjs_test_tools_new_unaligned_bytes(size_t len) {
388 : 1 : auto* buffer = static_cast<char*>(g_aligned_alloc0(1, len + 1, 8));
389 [ + + ]: 50 : for (size_t ix = 0; ix < len + 1; ix++) {
390 : 49 : buffer[ix] = reinterpret_cast<uintptr_t>(buffer + ix) & 0x07;
391 : : }
392 : 1 : return g_bytes_new_with_free_func(buffer + 1, len, g_aligned_free, buffer);
393 : : }
394 : :
395 : : alignas(8) static const char static_bytes[] = "hello";
396 : :
397 : : /**
398 : : * gjs_test_tools_new_static_bytes:
399 : : *
400 : : * Returns a buffer that lives in static storage.
401 : : *
402 : : * Returns: (transfer full): a #GBytes
403 : : */
404 : 1 : GBytes* gjs_test_tools_new_static_bytes() {
405 : 1 : return g_bytes_new_static(static_bytes, 6);
406 : : }
|