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 : 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 : 3 : FinalizedObjectsLocked()->insert(static_cast<GObject*>(data));
54 : 3 : });
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 : : typedef enum {
114 : : REF = 1 << 0,
115 : : UNREF = 1 << 1,
116 : : } RefType;
117 : :
118 : : typedef struct {
119 : : GObject* object;
120 : : RefType ref_type;
121 : : int delay;
122 : : } RefThreadData;
123 : :
124 : 23 : static RefThreadData* ref_thread_data_new(GObject* object, int interval,
125 : : RefType ref_type) {
126 : 23 : auto* ref_data = g_new(RefThreadData, 1);
127 : :
128 : 23 : ref_data->object = object;
129 : 23 : ref_data->delay = interval;
130 : 23 : ref_data->ref_type = ref_type;
131 : :
132 : 23 : monitor_object_finalization(object);
133 : :
134 : 23 : return ref_data;
135 : : }
136 : :
137 : 23 : static void* ref_thread_func(void* data) {
138 : 23 : GjsAutoPointer<RefThreadData, void, g_free> ref_data =
139 : 23 : static_cast<RefThreadData*>(data);
140 : :
141 [ - + ]: 23 : if (FinalizedObjectsLocked()->count(ref_data->object))
142 : 0 : return nullptr;
143 : :
144 [ + + ]: 23 : if (ref_data->delay > 0)
145 : 9 : g_usleep(ref_data->delay);
146 : :
147 [ - + ]: 23 : if (FinalizedObjectsLocked()->count(ref_data->object))
148 : 0 : return nullptr;
149 : :
150 [ + + ]: 23 : if (ref_data->ref_type & REF)
151 : 10 : g_object_ref(ref_data->object);
152 : :
153 [ + + ]: 23 : if (!(ref_data->ref_type & UNREF)) {
154 : 1 : return ref_data->object;
155 [ + + ]: 22 : } else if (ref_data->ref_type & REF) {
156 : 9 : g_usleep(ref_data->delay);
157 : :
158 [ - + ]: 9 : if (FinalizedObjectsLocked()->count(ref_data->object))
159 : 0 : return nullptr;
160 : : }
161 : :
162 [ + + ]: 22 : if (ref_data->object != s_tmp_object)
163 : 15 : g_object_steal_qdata(ref_data->object, finalize_quark());
164 : 22 : g_object_unref(ref_data->object);
165 : 22 : return nullptr;
166 : 23 : }
167 : :
168 : 12 : void gjs_test_tools_unref_other_thread(GObject* object, GError** error) {
169 : : auto* thread =
170 : 12 : g_thread_try_new("unref_object", ref_thread_func,
171 : 12 : ref_thread_data_new(object, -1, UNREF), error);
172 [ + - ]: 12 : if (thread)
173 : 12 : g_thread_join(thread);
174 : : // cppcheck-suppress memleak
175 : 12 : }
176 : :
177 : : /**
178 : : * gjs_test_tools_delayed_ref_other_thread:
179 : : * Returns: (transfer full)
180 : : */
181 : 1 : GThread* gjs_test_tools_delayed_ref_other_thread(GObject* object, int interval,
182 : : GError** error) {
183 : 1 : return g_thread_try_new("ref_object", ref_thread_func,
184 : 2 : ref_thread_data_new(object, interval, REF), error);
185 : : }
186 : :
187 : : /**
188 : : * gjs_test_tools_delayed_unref_other_thread:
189 : : * Returns: (transfer full)
190 : : */
191 : 1 : GThread* gjs_test_tools_delayed_unref_other_thread(GObject* object,
192 : : int interval,
193 : : GError** error) {
194 : 1 : return g_thread_try_new("unref_object", ref_thread_func,
195 : 1 : ref_thread_data_new(object, interval, UNREF),
196 : 1 : error);
197 : : }
198 : :
199 : : /**
200 : : * gjs_test_tools_delayed_ref_unref_other_thread:
201 : : * Returns: (transfer full)
202 : : */
203 : 9 : GThread* gjs_test_tools_delayed_ref_unref_other_thread(GObject* object,
204 : : int interval,
205 : : GError** error) {
206 : 9 : return g_thread_try_new(
207 : : "ref_unref_object", ref_thread_func,
208 : 9 : ref_thread_data_new(object, interval,
209 : : static_cast<RefType>(REF | UNREF)),
210 : 9 : error);
211 : : }
212 : :
213 : 2 : void gjs_test_tools_run_dispose_other_thread(GObject* object, GError** error) {
214 : 2 : auto* thread = g_thread_try_new(
215 : : "run_dispose",
216 : 4 : [](void* object) -> void* {
217 : 2 : g_object_run_dispose(G_OBJECT(object));
218 : 2 : return nullptr;
219 : : },
220 : : object, error);
221 : : // cppcheck-suppress leakNoVarFunctionCall
222 : 2 : g_thread_join(thread);
223 : : // cppcheck-suppress memleak
224 : 2 : }
225 : :
226 : : /**
227 : : * gjs_test_tools_get_saved:
228 : : * Returns: (transfer full)
229 : : */
230 : 4 : GObject* gjs_test_tools_get_saved() {
231 [ - + ]: 4 : if (FinalizedObjectsLocked()->count(s_tmp_object))
232 : 0 : s_tmp_object = nullptr;
233 : :
234 : 4 : return s_tmp_object.exchange(nullptr);
235 : : }
236 : :
237 : : /**
238 : : * gjs_test_tools_steal_saved:
239 : : * Returns: (transfer none)
240 : : */
241 : 2 : GObject* gjs_test_tools_steal_saved() { return gjs_test_tools_get_saved(); }
242 : :
243 : 6 : void gjs_test_tools_save_weak(GObject* object) {
244 : 6 : g_weak_ref_set(&s_tmp_weak, object);
245 : 6 : }
246 : :
247 : : /**
248 : : * gjs_test_tools_peek_saved:
249 : : * Returns: (transfer none)
250 : : */
251 : 20 : GObject* gjs_test_tools_peek_saved() {
252 [ - + ]: 20 : if (FinalizedObjectsLocked()->count(s_tmp_object))
253 : 0 : return nullptr;
254 : :
255 : 20 : return s_tmp_object;
256 : : }
257 : :
258 : 0 : int gjs_test_tools_get_saved_ref_count() {
259 : 0 : GObject* saved = gjs_test_tools_peek_saved();
260 [ # # ]: 0 : return saved ? saved->ref_count : 0;
261 : : }
262 : :
263 : : /**
264 : : * gjs_test_tools_get_weak:
265 : : * Returns: (transfer full)
266 : : */
267 : 6 : GObject* gjs_test_tools_get_weak() {
268 : 6 : return static_cast<GObject*>(g_weak_ref_get(&s_tmp_weak));
269 : : }
270 : :
271 : : /**
272 : : * gjs_test_tools_get_weak_other_thread:
273 : : * Returns: (transfer full)
274 : : */
275 : 3 : GObject* gjs_test_tools_get_weak_other_thread(GError** error) {
276 : 3 : auto* thread = g_thread_try_new(
277 : 3 : "weak_get", [](void*) -> void* { return gjs_test_tools_get_weak(); },
278 : : NULL, error);
279 [ - + ]: 3 : if (!thread)
280 : 0 : return nullptr;
281 : :
282 : : return static_cast<GObject*>(
283 : : // cppcheck-suppress leakNoVarFunctionCall
284 : 3 : g_thread_join(thread));
285 : : }
286 : :
287 : : /**
288 : : * gjs_test_tools_get_disposed:
289 : : * Returns: (transfer none)
290 : : */
291 : 1 : GObject* gjs_test_tools_get_disposed(GObject* object) {
292 : 1 : g_object_run_dispose(G_OBJECT(object));
293 : 1 : return object;
294 : : }
295 : :
296 : : #ifdef G_OS_UNIX
297 : :
298 : : // Adapted from glnx_throw_errno_prefix()
299 : 0 : static gboolean throw_errno_prefix(GError** error, const char* prefix) {
300 : 0 : int errsv = errno;
301 : :
302 : 0 : g_set_error_literal(error, G_IO_ERROR, g_io_error_from_errno(errsv),
303 : : g_strerror(errsv));
304 : 0 : g_prefix_error(error, "%s: ", prefix);
305 : :
306 : 0 : errno = errsv;
307 : 0 : return FALSE;
308 : : }
309 : :
310 : : #endif /* G_OS_UNIX */
311 : :
312 : : /**
313 : : * gjs_open_bytes:
314 : : * @bytes: bytes to send to the pipe
315 : : * @error: Return location for a #GError, or %NULL
316 : : *
317 : : * Creates a pipe and sends @bytes to it, such that it is suitable for passing
318 : : * to g_subprocess_launcher_take_fd().
319 : : *
320 : : * Returns: file descriptor, or -1 on error
321 : : */
322 : 8 : int gjs_test_tools_open_bytes(GBytes* bytes, GError** error) {
323 : : int pipefd[2], result;
324 : : size_t count;
325 : : const void* buf;
326 : : ssize_t bytes_written;
327 : :
328 : 8 : g_return_val_if_fail(bytes, -1);
329 : 8 : g_return_val_if_fail(error == NULL || *error == NULL, -1);
330 : :
331 : : #ifdef G_OS_UNIX
332 [ - + ]: 8 : if (!g_unix_open_pipe(pipefd, FD_CLOEXEC, error))
333 : 0 : return -1;
334 : :
335 : 8 : buf = g_bytes_get_data(bytes, &count);
336 : :
337 : 8 : bytes_written = write(pipefd[1], buf, count);
338 [ - + ]: 8 : if (bytes_written < 0) {
339 : 0 : throw_errno_prefix(error, "write");
340 : 0 : return -1;
341 : : }
342 : :
343 [ - + ]: 8 : if ((size_t)bytes_written != count)
344 : 0 : g_warning("%s: %zu bytes sent, only %zd bytes written", __func__, count,
345 : : bytes_written);
346 : :
347 : 8 : result = close(pipefd[1]);
348 [ - + ]: 8 : if (result == -1) {
349 : 0 : throw_errno_prefix(error, "close");
350 : 0 : return -1;
351 : : }
352 : :
353 : 8 : return pipefd[0];
354 : : #else
355 : : g_error("%s is currently supported on UNIX only", __func__);
356 : : #endif
357 : : }
358 : :
359 : : /**
360 : : * gjs_test_tools_new_unaligned_bytes:
361 : : * @len: Length of buffer to allocate
362 : : *
363 : : * Creates a data buffer at a location 1 byte away from an 8-byte alignment
364 : : * boundary, to make sure that tests fail when SpiderMonkey enforces an
365 : : * alignment restriction on embedder data.
366 : : *
367 : : * The buffer is filled with repeated 0x00-0x07 bytes containing the least
368 : : * significant 3 bits of that byte's address.
369 : : *
370 : : * Returns: (transfer full): a #GBytes
371 : : */
372 : 1 : GBytes* gjs_test_tools_new_unaligned_bytes(size_t len) {
373 : 1 : auto* buffer = static_cast<char*>(g_aligned_alloc0(1, len + 1, 8));
374 [ + + ]: 50 : for (size_t ix = 0; ix < len + 1; ix++) {
375 : 49 : buffer[ix] = reinterpret_cast<uintptr_t>(buffer + ix) & 0x07;
376 : : }
377 : 1 : return g_bytes_new_with_free_func(buffer + 1, len, g_aligned_free, buffer);
378 : : }
379 : :
380 : : alignas(8) static const char static_bytes[] = "hello";
381 : :
382 : : /**
383 : : * gjs_test_tools_new_static_bytes:
384 : : *
385 : : * Returns a buffer that lives in static storage.
386 : : *
387 : : * Returns: (transfer full): a #GBytes
388 : : */
389 : 1 : GBytes* gjs_test_tools_new_static_bytes() {
390 : 1 : return g_bytes_new_static(static_bytes, 6);
391 : : }
|