Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2018 Endless Mobile, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2.1 of the License, or (at your option) any later version.
9 : : *
10 : : * This library is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General Public
16 : : * License along with this library; if not, write to the Free Software
17 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : *
19 : : * Authors:
20 : : * - Philip Withnall <withnall@endlessm.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <glib.h>
26 : : #include <glib-object.h>
27 : : #include <gobject/gvaluecollector.h>
28 : : #include <libglib-testing/signal-logger.h>
29 : :
30 : :
31 : : /**
32 : : * SECTION:signal-logger
33 : : * @short_description: GObject signal logging and checking
34 : : * @stability: Unstable
35 : : * @include: libglib-testing/signal-logger.h
36 : : *
37 : : * #GtSignalLogger is an object which allows logging of signals emitted from
38 : : * zero or more #GObjects, and later comparison of those signals against what
39 : : * was expected to be emitted.
40 : : *
41 : : * A single #GtSignalLogger instance can be used for multiple #GObjects, and can
42 : : * outlive the objects themselves. It can be connected to several different
43 : : * signals, emissions of which will all be added to the same queue (ordered by
44 : : * emission time).
45 : : *
46 : : * Testing of the emitted signals is performed by popping emissions off the
47 : : * queue and comparing them to what was expected. Macros are provided to assert
48 : : * that the next emission on the queue was for a specific signal — or callers
49 : : * may unconditionally pop the next emission and compare its properties
50 : : * themselves.
51 : : *
52 : : * By default, a #GtSignalLogger will not assert that its emission queue is
53 : : * empty on destruction: that is up to the caller, and it is highly recommended
54 : : * that gt_signal_logger_assert_no_emissions() is called before a signal logger
55 : : * is destroyed, or after a particular unit test is completed.
56 : : *
57 : : * Since: 0.1.0
58 : : */
59 : :
60 : : /**
61 : : * GtSignalLogger:
62 : : *
63 : : * An object which allows signal emissions from zero or more #GObjects to be
64 : : * logged easily, without needing to write specific callback functions for any
65 : : * of them.
66 : : *
67 : : * Since: 0.1.0
68 : : */
69 : : struct _GtSignalLogger
70 : : {
71 : : /* Log of the signal emissions. Head emission was the first emitted. */
72 : : GPtrArray *log; /* (element-type GtSignalLoggerEmission) (owned) */
73 : : /* Set of currently connected signal handler closures. */
74 : : GPtrArray *closures; /* (element-type GtLoggedClosure) (owned) */
75 : : };
76 : :
77 : : /**
78 : : * GtLoggedClosure:
79 : : *
80 : : * A closure representing a connection from @logger to the given @signal_name
81 : : * on @obj.
82 : : *
83 : : * The closure will be kept alive until the @logger is destroyed, though it will
84 : : * be invalidated and disconnected earlier if @obj is finalised.
85 : : *
86 : : * Since: 0.1.0
87 : : */
88 : : typedef struct
89 : : {
90 : : GClosure closure;
91 : :
92 : : GtSignalLogger *logger; /* (not owned) */
93 : :
94 : : /* Pointer to the object instance this closure is connected to; no ref is
95 : : * held, and the object may be finalised before the closure, so this should
96 : : * only be used as an opaque pointer; add a #GWeakRef if the object needs to
97 : : * be accessed in future. */
98 : : gpointer obj; /* (not owned) */
99 : : /* A copy of `G_OBJECT_TYPE_NAME (obj)` for use when @obj may be invalid. */
100 : : gchar *obj_type_name; /* (owned) */
101 : :
102 : : /* Name of the signal this closure is connected to, including detail
103 : : * (if applicable). */
104 : : gchar *signal_name; /* (owned) */
105 : : gulong signal_id; /* 0 when disconnected */
106 : : } GtLoggedClosure;
107 : :
108 : : /**
109 : : * GtSignalLoggerEmission:
110 : : *
111 : : * The details of a particular signal emission, including its parameter values.
112 : : *
113 : : * @param_values does not include the object instance.
114 : : *
115 : : * Since: 0.1.0
116 : : */
117 : : struct _GtSignalLoggerEmission
118 : : {
119 : : /* The closure this emission was captured by. */
120 : : GtLoggedClosure *closure; /* (owned) */
121 : : /* Array of parameter values, not including the object instance. */
122 : : GValue *param_values; /* (array length=n_param_values) */
123 : : gsize n_param_values;
124 : : };
125 : :
126 : : /**
127 : : * gt_signal_logger_emission_free:
128 : : * @emission: (transfer full): a #GtSignalLoggerEmission
129 : : *
130 : : * Free a #GtSignalLoggerEmission.
131 : : *
132 : : * Since: 0.1.0
133 : : */
134 : : void
135 : 0 : gt_signal_logger_emission_free (GtSignalLoggerEmission *emission)
136 : : {
137 [ # # ]: 0 : for (gsize i = 0; i < emission->n_param_values; i++)
138 : 0 : g_value_unset (&emission->param_values[i]);
139 : 0 : g_free (emission->param_values);
140 : :
141 : 0 : g_closure_unref ((GClosure *) emission->closure);
142 : 0 : g_free (emission);
143 : 0 : }
144 : :
145 : : /* Version of G_VALUE_LCOPY() that allows %NULL return locations. */
146 : : #define VALUE_LCOPY(value, var_args, __error) \
147 : : G_STMT_START { \
148 : : const GValue *_value = (value); \
149 : : GType _value_type = G_VALUE_TYPE (_value); \
150 : : GTypeValueTable *_vtable = g_type_value_table_peek (_value_type); \
151 : : const gchar *_lcopy_format = _vtable->lcopy_format; \
152 : : GTypeCValue _cvalues[G_VALUE_COLLECT_FORMAT_MAX_LENGTH] = { { 0, }, }; \
153 : : guint _n_values = 0; \
154 : : \
155 : : while (*_lcopy_format != '\0') \
156 : : { \
157 : : g_assert (*_lcopy_format == G_VALUE_COLLECT_POINTER); \
158 : : _cvalues[_n_values++].v_pointer = va_arg ((var_args), gpointer); \
159 : : _lcopy_format++; \
160 : : } \
161 : : \
162 : : if (_n_values == 2 && !!_cvalues[0].v_pointer != !!_cvalues[1].v_pointer) \
163 : : *(__error) = g_strdup_printf ("all return locations need the same nullability"); \
164 : : else if (_cvalues[0].v_pointer != NULL) \
165 : : *(__error) = _vtable->lcopy_value (_value, _n_values, _cvalues, 0); \
166 : : } G_STMT_END
167 : :
168 : : /**
169 : : * gt_signal_logger_emission_get_params:
170 : : * @self: a #GtSignalLoggerEmission
171 : : * @...: return locations for the signal parameters
172 : : *
173 : : * Get the parameters emitted in this signal emission. They are returned in the
174 : : * return locations provided as varargs. These locations must have the right
175 : : * type for the parameters of the signal which was emitted.
176 : : *
177 : : * To ignore a particular parameter, pass %NULL as the one (or more) return
178 : : * locations for that parameter.
179 : : *
180 : : * Since: 0.1.0
181 : : */
182 : : void
183 : 0 : gt_signal_logger_emission_get_params (GtSignalLoggerEmission *self,
184 : : ...)
185 : : {
186 : : va_list ap;
187 : :
188 : 0 : va_start (ap, self);
189 : :
190 [ # # ]: 0 : for (gsize i = 0; i < self->n_param_values; i++)
191 : : {
192 : 0 : g_autofree gchar *error_message = NULL;
193 [ # # # # : 0 : VALUE_LCOPY (&self->param_values[i], ap, &error_message);
# # # # #
# ]
194 : :
195 : : /* Error messages are not fatal, as they typically indicate that the user
196 : : * has passed in %NULL rather than a valid return pointer. We can recover
197 : : * from that. */
198 [ # # ]: 0 : if (error_message != NULL)
199 : 0 : g_debug ("Error copying GValue %" G_GSIZE_FORMAT " from emission of %s::%s from %p: %s",
200 : : i, self->closure->obj_type_name, self->closure->signal_name,
201 : : self->closure->obj, error_message);
202 : : }
203 : :
204 : 0 : va_end (ap);
205 : 0 : }
206 : :
207 : : static void
208 : 0 : gt_logged_closure_marshal (GClosure *closure,
209 : : GValue *return_value,
210 : : guint n_param_values,
211 : : const GValue *param_values,
212 : : gpointer invocation_hint,
213 : : gpointer marshal_data)
214 : : {
215 : 0 : GtLoggedClosure *self = (GtLoggedClosure *) closure;
216 : :
217 : : /* Log the @param_values. Ignore the @return_value, and the first of
218 : : * @param_values (which is the object instance). */
219 [ # # ]: 0 : g_assert (n_param_values >= 1);
220 : :
221 : 0 : g_autoptr(GtSignalLoggerEmission) emission = g_new0 (GtSignalLoggerEmission, 1);
222 : 0 : emission->closure = (GtLoggedClosure *) g_closure_ref ((GClosure *) self);
223 : 0 : emission->n_param_values = n_param_values - 1;
224 : 0 : emission->param_values = g_new0 (GValue, emission->n_param_values);
225 : :
226 [ # # ]: 0 : for (gsize i = 0; i < emission->n_param_values; i++)
227 : : {
228 : 0 : g_value_init (&emission->param_values[i], G_VALUE_TYPE (¶m_values[i + 1]));
229 : 0 : g_value_copy (¶m_values[i + 1], &emission->param_values[i]);
230 : : }
231 : :
232 : 0 : g_ptr_array_add (self->logger->log, g_steal_pointer (&emission));
233 : 0 : }
234 : :
235 : : static void
236 : 0 : gt_logged_closure_invalidate (gpointer user_data,
237 : : GClosure *closure)
238 : : {
239 : 0 : GtLoggedClosure *self = (GtLoggedClosure *) closure;
240 : :
241 : 0 : self->signal_id = 0;
242 : 0 : }
243 : :
244 : : static void
245 : 0 : gt_logged_closure_finalize (gpointer user_data,
246 : : GClosure *closure)
247 : : {
248 : 0 : GtLoggedClosure *self = (GtLoggedClosure *) closure;
249 : :
250 : : /* Deliberately don’t g_ptr_array_remove() the closure from the
251 : : * self->logger->closures list, since finalize() can only be called when the
252 : : * final reference to the closure is dropped, and self->logger->closures holds
253 : : * a reference, so we must be being finalised from there (or that GPtrArray
254 : : * has already been finalised). */
255 : :
256 : 0 : g_free (self->obj_type_name);
257 : 0 : g_free (self->signal_name);
258 : :
259 [ # # ]: 0 : g_assert (self->signal_id == 0);
260 : 0 : }
261 : :
262 : : /**
263 : : * gt_logged_closure_new:
264 : : * @logger: (transfer none): logger to connect the closure to
265 : : * @obj: (not nullable) (transfer none): #GObject to connect the closure to
266 : : * @signal_name: (not nullable): signal name to connect the closure to
267 : : *
268 : : * Create a new #GtLoggedClosure for @logger, @obj and @signal_name. @obj must
269 : : * be a valid object instance at this point (it may later be finalised before
270 : : * the closure).
271 : : *
272 : : * This does not connect the closure to @signal_name on @obj. Use
273 : : * gt_signal_logger_connect() for that.
274 : : *
275 : : * Returns: (transfer full): a new closure
276 : : * Since: 0.1.0
277 : : */
278 : : static GClosure *
279 : 0 : gt_logged_closure_new (GtSignalLogger *logger,
280 : : GObject *obj,
281 : : const gchar *signal_name)
282 : : {
283 : 0 : g_autoptr(GClosure) closure = g_closure_new_simple (sizeof (GtLoggedClosure), NULL);
284 : :
285 : 0 : GtLoggedClosure *self = (GtLoggedClosure *) closure;
286 : 0 : self->logger = logger;
287 : 0 : self->obj = obj;
288 : 0 : self->obj_type_name = g_strdup (G_OBJECT_TYPE_NAME (obj));
289 : 0 : self->signal_name = g_strdup (signal_name);
290 : 0 : self->signal_id = 0;
291 : :
292 : 0 : g_closure_add_invalidate_notifier (closure, NULL, (GClosureNotify) gt_logged_closure_invalidate);
293 : 0 : g_closure_add_finalize_notifier (closure, NULL, (GClosureNotify) gt_logged_closure_finalize);
294 : 0 : g_closure_set_marshal (closure, gt_logged_closure_marshal);
295 : :
296 : 0 : g_ptr_array_add (logger->closures, g_closure_ref (closure));
297 : :
298 : 0 : return g_steal_pointer (&closure);
299 : : }
300 : :
301 : : /**
302 : : * gt_signal_logger_new:
303 : : *
304 : : * Create a new #GtSignalLogger. Add signals to it to log using
305 : : * gt_signal_logger_connect().
306 : : *
307 : : * Returns: (transfer full): a new #GtSignalLogger
308 : : * Since: 0.1.0
309 : : */
310 : : GtSignalLogger *
311 : 1 : gt_signal_logger_new (void)
312 : : {
313 : 2 : g_autoptr(GtSignalLogger) logger = g_new0 (GtSignalLogger, 1);
314 : :
315 : 1 : logger->log = g_ptr_array_new_with_free_func ((GDestroyNotify) gt_signal_logger_emission_free);
316 : 1 : logger->closures = g_ptr_array_new_with_free_func ((GDestroyNotify) g_closure_unref);
317 : :
318 : 1 : return g_steal_pointer (&logger);
319 : : }
320 : :
321 : : /**
322 : : * gt_signal_logger_free:
323 : : * @self: (transfer full): a #GtSignalLogger
324 : : *
325 : : * Free a #GtSignalLogger. This will disconnect all its closures from the
326 : : * signals they are connected to.
327 : : *
328 : : * This function may be called when there are signal emissions left in the
329 : : * logged stack, but typically you will want to call
330 : : * gt_signal_logger_assert_no_emissions() first.
331 : : *
332 : : * Since: 0.1.0
333 : : */
334 : : void
335 : 1 : gt_signal_logger_free (GtSignalLogger *self)
336 : : {
337 [ - + ]: 1 : g_return_if_fail (self != NULL);
338 : :
339 : : /* Disconnect all the closures, since we don’t care about logging any more. */
340 [ - + ]: 1 : for (gsize i = 0; i < self->closures->len; i++)
341 : : {
342 : 0 : GClosure *closure = g_ptr_array_index (self->closures, i);
343 : :
344 : 0 : g_closure_invalidate (closure);
345 : : }
346 : :
347 : 1 : g_ptr_array_unref (self->closures);
348 : 1 : g_ptr_array_unref (self->log);
349 : :
350 : 1 : g_free (self);
351 : : }
352 : :
353 : : /**
354 : : * gt_signal_logger_connect:
355 : : * @self: a #GtSignalLogger
356 : : * @obj: (type GObject): a #GObject to connect to
357 : : * @signal_name: the signal on @obj to connect to
358 : : *
359 : : * A convenience wrapper around g_signal_connect() which connects the
360 : : * #GtSignalLogger to the given @signal_name on @obj so that emissions of it
361 : : * will be logged.
362 : : *
363 : : * The closure will be disconnected (and the returned signal connection ID
364 : : * invalidated) when:
365 : : *
366 : : * * @obj is finalised
367 : : * * The closure is freed or removed
368 : : * * The signal logger is freed
369 : : *
370 : : * This does not keep a strong reference to @obj.
371 : : *
372 : : * Returns: signal connection ID, as returned from g_signal_connect()
373 : : * Since: 0.1.0
374 : : */
375 : : gulong
376 : 0 : gt_signal_logger_connect (GtSignalLogger *self,
377 : : gpointer obj,
378 : : const gchar *signal_name)
379 : : {
380 [ # # ]: 0 : g_return_val_if_fail (self != NULL, 0);
381 [ # # ]: 0 : g_return_val_if_fail (G_IS_OBJECT (obj), 0);
382 [ # # ]: 0 : g_return_val_if_fail (signal_name != NULL, 0);
383 : :
384 : 0 : g_autoptr(GClosure) closure = gt_logged_closure_new (self, obj, signal_name);
385 : 0 : GtLoggedClosure *c = (GtLoggedClosure *) closure;
386 : 0 : c->signal_id = g_signal_connect_closure (obj, signal_name, g_closure_ref (closure), FALSE);
387 : 0 : return c->signal_id;
388 : : }
389 : :
390 : : /**
391 : : * gt_signal_logger_get_n_emissions:
392 : : * @self: a #GtSignalLogger
393 : : *
394 : : * Get the number of signal emissions which have been logged (and not popped)
395 : : * since the logger was initialised.
396 : : *
397 : : * Returns: number of signal emissions
398 : : * Since: 0.1.0
399 : : */
400 : : gsize
401 : 1 : gt_signal_logger_get_n_emissions (GtSignalLogger *self)
402 : : {
403 [ - + ]: 1 : g_return_val_if_fail (self != NULL, 0);
404 : :
405 : 1 : return self->log->len;
406 : : }
407 : :
408 : : /**
409 : : * gt_signal_logger_pop_emission:
410 : : * @self: a #GtSignalLogger
411 : : * @out_obj: (out) (transfer none) (optional) (not nullable): return location
412 : : * for the object instance which emitted the signal
413 : : * @out_obj_type_name: (out) (transfer full) (optional) (not nullable): return
414 : : * location for the name of the type of @out_obj
415 : : * @out_signal_name: (out) (transfer full) (optional) (not nullable): return
416 : : * location for the name of the emitted signal
417 : : * @out_emission: (out) (transfer full) (optional) (not nullable): return
418 : : * location for the signal emission closure containing emission parameters
419 : : *
420 : : * Pop the oldest signal emission off the stack of logged emissions, and return
421 : : * its object, signal name and parameters in the given return locations. All
422 : : * return locations are optional: if they are all %NULL, this function just
423 : : * performs a pop.
424 : : *
425 : : * If there are no signal emissions on the logged stack, %FALSE is returned.
426 : : *
427 : : * @out_obj does not return a reference to the object instance, as it may have
428 : : * been finalised since the signal emission was logged. It should be treated as
429 : : * an opaque pointer. The type name of the object is given as
430 : : * @out_obj_type_name, which is guaranteed to be valid.
431 : : *
432 : : * Returns: %TRUE if an emission was popped and returned, %FALSE otherwise
433 : : * Since: 0.1.0
434 : : */
435 : : gboolean
436 : 0 : gt_signal_logger_pop_emission (GtSignalLogger *self,
437 : : gpointer *out_obj,
438 : : gchar **out_obj_type_name,
439 : : gchar **out_signal_name,
440 : : GtSignalLoggerEmission **out_emission)
441 : : {
442 [ # # ]: 0 : g_return_val_if_fail (self != NULL, FALSE);
443 : :
444 [ # # ]: 0 : if (self->log->len == 0)
445 : : {
446 [ # # ]: 0 : if (out_obj != NULL)
447 : 0 : *out_obj = NULL;
448 [ # # ]: 0 : if (out_obj_type_name != NULL)
449 : 0 : *out_obj_type_name = NULL;
450 [ # # ]: 0 : if (out_signal_name != NULL)
451 : 0 : *out_signal_name = NULL;
452 [ # # ]: 0 : if (out_emission != NULL)
453 : 0 : *out_emission = NULL;
454 : :
455 : 0 : return FALSE;
456 : : }
457 : :
458 : : /* FIXME: Could do with g_ptr_array_steal() here.
459 : : * https://bugzilla.gnome.org/show_bug.cgi?id=795376 */
460 : 0 : g_ptr_array_set_free_func (self->log, NULL);
461 : 0 : g_autoptr(GtSignalLoggerEmission) emission = g_steal_pointer (&self->log->pdata[0]);
462 : 0 : g_ptr_array_remove_index (self->log, 0);
463 : 0 : g_ptr_array_set_free_func (self->log, (GDestroyNotify) gt_signal_logger_emission_free);
464 : :
465 [ # # ]: 0 : if (out_obj != NULL)
466 : 0 : *out_obj = emission->closure->obj;
467 [ # # ]: 0 : if (out_obj_type_name != NULL)
468 : 0 : *out_obj_type_name = g_strdup (emission->closure->obj_type_name);
469 [ # # ]: 0 : if (out_signal_name != NULL)
470 : 0 : *out_signal_name = g_strdup (emission->closure->signal_name);
471 [ # # ]: 0 : if (out_emission != NULL)
472 : 0 : *out_emission = g_steal_pointer (&emission);
473 : :
474 : 0 : return TRUE;
475 : : }
476 : :
477 : : /**
478 : : * gt_signal_logger_format_emission:
479 : : * @obj: a #GObject instance which emitted a signal
480 : : * @obj_type_name: a copy of `G_OBJECT_TYPE_NAME (obj)` for use when @obj may
481 : : * be invalid
482 : : * @signal_name: name of the emitted signal
483 : : * @emission: details of the signal emission
484 : : *
485 : : * Format a signal emission in a human readable form, typically for logging it
486 : : * to some debug output.
487 : : *
488 : : * The returned string does not have a trailing newline character (`\n`).
489 : : *
490 : : * @obj may have been finalised, and is just treated as an opaque pointer.
491 : : *
492 : : * Returns: (transfer full): human readable string detailing the signal emission
493 : : * Since: 0.1.0
494 : : */
495 : : gchar *
496 : 0 : gt_signal_logger_format_emission (gpointer obj,
497 : : const gchar *obj_type_name,
498 : : const gchar *signal_name,
499 : : const GtSignalLoggerEmission *emission)
500 : : {
501 [ # # ]: 0 : g_return_val_if_fail (obj != NULL, NULL); /* deliberately not a G_IS_OBJECT() check */
502 [ # # ]: 0 : g_return_val_if_fail (signal_name != NULL, NULL);
503 [ # # ]: 0 : g_return_val_if_fail (emission != NULL, NULL);
504 : :
505 : 0 : g_autoptr(GString) str = g_string_new ("");
506 : 0 : g_string_append_printf (str, "%s::%s from %p (",
507 : : obj_type_name, signal_name, obj);
508 : :
509 [ # # ]: 0 : for (gsize i = 0; i < emission->n_param_values; i++)
510 : : {
511 [ # # ]: 0 : if (i > 0)
512 : 0 : g_string_append (str, ", ");
513 : :
514 : 0 : g_auto(GValue) str_value = G_VALUE_INIT;
515 : 0 : g_value_init (&str_value, G_TYPE_STRING);
516 : :
517 [ # # ]: 0 : if (g_value_transform (&emission->param_values[i], &str_value))
518 : 0 : g_string_append (str, g_value_get_string (&str_value));
519 : : else
520 : 0 : g_string_append_printf (str, "GValue of type %s",
521 : 0 : G_VALUE_TYPE_NAME (&emission->param_values[i]));
522 : : }
523 : :
524 [ # # ]: 0 : if (emission->n_param_values == 0)
525 : 0 : g_string_append (str, "no arguments");
526 : 0 : g_string_append (str, ")");
527 : :
528 : 0 : return g_string_free (g_steal_pointer (&str), FALSE);
529 : : }
530 : :
531 : : /**
532 : : * gt_signal_logger_format_emissions:
533 : : * @self: a #GtSignalLogger
534 : : *
535 : : * Format all the signal emissions on the logging stack in the #GtSignalLogger,
536 : : * in a human readable format, one per line. The returned string does not end
537 : : * in a newline character (`\n`). Each signal emission is formatted using
538 : : * gt_signal_logger_format_emission().
539 : : *
540 : : * Returns: (transfer full): human readable list of all the signal emissions
541 : : * currently in the logger, or an empty string if the logger is empty
542 : : * Since: 0.1.0
543 : : */
544 : : gchar *
545 : 0 : gt_signal_logger_format_emissions (GtSignalLogger *self)
546 : : {
547 [ # # ]: 0 : g_return_val_if_fail (self != NULL, NULL);
548 : :
549 : : /* Work out the width of the counter we need to number the emissions. */
550 : 0 : guint width = 1;
551 : 0 : gsize n_emissions = self->log->len;
552 [ # # ]: 0 : while (n_emissions >= 10)
553 : : {
554 : 0 : n_emissions /= 10;
555 : 0 : width++;
556 : : }
557 : :
558 : : /* Format each emission and list them. */
559 : 0 : g_autoptr(GString) str = g_string_new ("");
560 : :
561 [ # # ]: 0 : for (gsize i = 0; i < self->log->len; i++)
562 : : {
563 : 0 : const GtSignalLoggerEmission *emission = g_ptr_array_index (self->log, i);
564 : :
565 [ # # ]: 0 : if (i > 0)
566 : 0 : g_string_append (str, "\n");
567 : :
568 : 0 : g_autofree gchar *emission_str = gt_signal_logger_format_emission (emission->closure->obj,
569 : 0 : emission->closure->obj_type_name,
570 : 0 : emission->closure->signal_name,
571 : : emission);
572 : 0 : g_string_append_printf (str, " %*" G_GSIZE_FORMAT ". %s", (int) width, i + 1, emission_str);
573 : : }
574 : :
575 : 0 : return g_string_free (g_steal_pointer (&str), FALSE);
576 : : }
|