tests: export signal testing from adg-test.c
[adg.git] / src / tests / adg-test.c
blobd54b6f06fd77050200ac178939a099fc0ac7ee43
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2015 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 #include "adg-internal.h"
22 #include "adg-model.h"
23 #include "adg-trail.h"
24 #include "adg-path.h"
25 #include "adg-container.h"
26 #include "adg-stroke.h"
27 #include "adg-test.h"
30 typedef struct {
31 GType type;
32 gpointer instance;
33 } _BoxedData;
35 typedef struct {
36 AdgTrapsFunc func;
37 gint n_fragments;
38 } _TrapsData;
40 typedef struct {
41 gpointer instance;
42 gulong handler;
43 gboolean flag;
44 } _SignalData;
47 /* Using adg_nop() would require to pull in the whole libadg stack:
48 * better to replicate that trivial function instead.
50 static void
51 _adg_nop(void)
55 void
56 adg_test_init(int *p_argc, char **p_argv[])
58 #if GLIB_CHECK_VERSION(2, 34, 0)
59 #else
60 /* On GLib older than 2.34.0 g_type_init() *must* be called */
61 g_type_init();
62 #endif
63 g_test_init(p_argc, p_argv, NULL);
65 g_log_set_always_fatal(0);
67 /* When building in silent mode (default), the ADG_QUIET
68 * environment variable is set to 1 by the Makefile and the
69 * warnings are discarded to reduce visual cluttering.
71 if (g_getenv("ADG_QUIET") != NULL)
72 g_log_set_default_handler((GLogFunc) _adg_nop, NULL);
74 g_test_bug_base("http://dev.entidi.com/p/adg/issues/%s/");
77 const gpointer
78 adg_test_invalid_pointer(void)
80 static int junk[10] = { 0 };
81 return junk;
84 cairo_t *
85 adg_test_cairo_context(void)
87 cairo_surface_t *surface;
88 cairo_t *cr;
90 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 800, 600);
91 cr = cairo_create(surface);
92 cairo_surface_destroy(surface);
94 return cr;
97 int
98 adg_test_cairo_num_data(cairo_t *cr)
100 cairo_path_t *path = cairo_copy_path(cr);
101 int length = path->num_data;
102 cairo_path_destroy(path);
104 return length;
107 const cairo_path_t *
108 adg_test_path(void)
110 static cairo_path_data_t data[] = {
112 /* First segment: a valid segment with all primitive types */
113 { .header = { CPML_MOVE, 2 }},
114 { .point = { 0, 1 }},
115 { .header = { CPML_LINE, 2 }},
116 { .point = { 3, 1 }},
117 { .header = { CPML_ARC, 3 }},
118 { .point = { 4, 5 }},
119 { .point = { 6, 7 }},
120 { .header = { CPML_CURVE, 4 }},
121 { .point = { 8, 9 }},
122 { .point = { 10, 11 }},
123 { .point = { -2, 2 }},
124 { .header = { CPML_CLOSE, 1 }},
126 /* Useless CPML_MOVE */
127 { .header = { CPML_MOVE, 2 }},
128 { .point = { 0, 0 }},
130 /* Second segment: a couple of lines of length 1 and 2;
131 * line 2 intersects line 1 of the first segment in (1, 1) */
132 { .header = { CPML_MOVE, 2 }},
133 { .point = { 0, 0 }},
134 { .header = { CPML_LINE, 2 }},
135 { .point = { 1, 0 }},
136 { .header = { CPML_LINE, 2 }},
137 { .point = { 1, 2 }},
139 /* Another useless CPML_MOVE with useless embedded data */
140 { .header = { CPML_MOVE, 4 }},
141 { .point = { 1, 2 }},
142 { .point = { 3, 4 }},
143 { .point = { 5, 6 }},
145 /* Third segment: a Bézier curve with a trailing CPML_CLOSE */
146 { .header = { CPML_MOVE, 2 }},
147 { .point = { 10, 13 }},
148 { .header = { CPML_CURVE, 4 }},
149 { .point = { 8, 9 }},
150 { .point = { 10, 11 }},
151 { .point = { 12, 13 }},
152 { .header = { CPML_CLOSE, 1 }},
154 /* A valid cairo segment considered invalid by CPML
155 * because does not have a leading CPML_MOVE */
156 { .header = { CPML_LINE, 2 }},
157 { .point = { 10, 0 }},
158 { .header = { CPML_CLOSE, 1 }},
160 /* Another valid cairo segment invalid in CPML */
161 { .header = { CPML_CLOSE, 1 }},
163 /* Forth segment: a couple of arcs */
164 { .header = { CPML_MOVE, 2 }},
165 { .point = { 14, 15 }},
166 { .header = { CPML_ARC, 3 }},
167 { .point = { 17, 16 }},
168 { .point = { 18, 19 }},
169 { .header = { CPML_ARC, 3 }},
170 { .point = { 21, 20 }},
171 { .point = { 22, 23 }},
173 /* Fifth segment: a floating CPML_CLOSE */
174 { .header = { CPML_MOVE, 2 }},
175 { .point = { 24, 25 }},
176 { .header = { CPML_CLOSE, 1 }}
178 static cairo_path_t path = {
179 CAIRO_STATUS_SUCCESS,
180 data,
181 G_N_ELEMENTS(data)
183 return &path;
186 static void
187 _adg_enum_checks(GType *p_type)
189 GType type = *p_type;
190 gpointer class;
192 g_free(p_type);
194 g_assert_true(G_TYPE_IS_ENUM(type));
196 class = g_type_class_ref(type);
197 g_assert_nonnull(class);
199 g_assert_null(g_enum_get_value(class, -1));
200 g_assert_nonnull(g_enum_get_value(class, 0));
201 g_assert_null(g_enum_get_value_by_name(class, "unexistent value"));
204 void
205 adg_test_add_enum_checks(const gchar *testpath, GType type)
207 GType *p_type = g_new(GType, 1);
208 *p_type = type;
209 g_test_add_data_func(testpath, p_type, (gpointer) _adg_enum_checks);
212 static void
213 _adg_boxed_checks(_BoxedData *boxed_data)
215 gpointer replica;
217 g_assert_true(G_TYPE_IS_BOXED(boxed_data->type));
219 g_assert_null(g_boxed_copy(boxed_data->type, NULL));
221 replica = g_boxed_copy(boxed_data->type, boxed_data->instance);
222 g_assert_nonnull(replica);
224 g_boxed_free(boxed_data->type, replica);
225 g_boxed_free(boxed_data->type, boxed_data->instance);
227 g_free(boxed_data);
230 void
231 adg_test_add_boxed_checks(const gchar *testpath, GType type, gpointer instance)
233 _BoxedData *boxed_data = g_new(_BoxedData, 1);
234 boxed_data->type = type;
235 boxed_data->instance = instance;
237 g_test_add_data_func(testpath, boxed_data,
238 (gpointer) _adg_boxed_checks);
241 static void
242 _adg_object_checks(GType *p_type)
244 GType type = *p_type;
246 g_free(p_type);
248 g_assert_true(G_TYPE_IS_OBJECT(type));
250 if (! G_TYPE_IS_ABSTRACT(type)) {
251 gpointer result = GINT_TO_POINTER(0xdead);
252 GObject *object = g_object_new(type, NULL);
254 g_assert_nonnull(object);
255 g_assert_true(G_IS_OBJECT(object));
257 /* Check that unknown or unexistent properties does
258 * not return values (result should pass unmodified)
260 g_assert_cmpint(GPOINTER_TO_INT(result), ==, 0xdead);
261 g_object_set(object, "unknown", NULL, NULL);
262 g_object_get(object, "unknown", &result, NULL);
263 g_assert_cmpint(GPOINTER_TO_INT(result), ==, 0xdead);
264 g_object_get(object, "unexistent", &result, NULL);
265 g_assert_cmpint(GPOINTER_TO_INT(result), ==, 0xdead);
267 /* Check object lifetime: this can be done with a weak pointer
268 * that will "nullifies" result after object is destructed
270 g_object_add_weak_pointer(object, &result);
271 g_object_ref(object);
272 g_object_unref(object);
273 g_assert_nonnull(result);
274 g_object_unref(object);
275 g_assert_null(result);
277 result = GINT_TO_POINTER(0xdead);
278 object = g_object_new(type, NULL);
280 g_object_add_weak_pointer(object, &result);
281 g_assert_nonnull(result);
282 g_object_ref(object);
283 g_object_ref(object);
284 g_object_run_dispose(object);
285 g_assert_null(result);
289 void
290 adg_test_add_object_checks(const gchar *testpath, GType type)
292 GType *p_type = g_new(GType, 1);
293 *p_type = type;
294 g_test_add_data_func(testpath, p_type, (gpointer) _adg_object_checks);
297 static void
298 _adg_test_set(gboolean *p_flag)
300 *p_flag = TRUE;
303 static _SignalData signal_data;
305 void
306 adg_test_signal(gpointer instance, const gchar *detailed_signal)
308 signal_data.instance = instance;
309 signal_data.handler = g_signal_connect_swapped(instance,
310 detailed_signal,
311 G_CALLBACK(_adg_test_set),
312 &signal_data.flag);
313 signal_data.flag = FALSE;
316 gboolean
317 adg_test_signal_check(gboolean disconnect)
319 gboolean last_flag;
321 if (disconnect) {
322 g_signal_handler_disconnect(signal_data.instance, signal_data.handler);
323 signal_data.instance = NULL;
326 last_flag = signal_data.flag;
327 signal_data.flag = FALSE;
329 return last_flag;
332 static void
333 _adg_entity_checks(GType *p_type)
335 GType type = *p_type;
337 g_free(p_type);
339 g_assert_true(g_type_is_a(type, ADG_TYPE_ENTITY));
341 if (! G_TYPE_IS_ABSTRACT(type)) {
342 AdgEntity *entity = g_object_new(type, NULL);
343 const CpmlExtents *extents;
345 g_assert_nonnull(entity);
346 g_assert_true(ADG_IS_ENTITY(entity));
348 /* Ensure that the entity extents are not initially defined */
349 extents = adg_entity_get_extents(entity);
350 g_assert_false(extents->is_defined);
352 adg_test_signal(entity, "arrange");
353 adg_entity_arrange(entity);
354 g_assert_true(adg_test_signal_check(TRUE));
356 extents = adg_entity_get_extents(entity);
357 if (extents->is_defined) {
358 /* RENDERABLE ENTITY */
359 /* Check that invalidate() clears the cached extents */
360 adg_test_signal(entity, "invalidate");
361 adg_entity_invalidate(entity);
362 g_assert_true(adg_test_signal_check(TRUE));
364 extents = adg_entity_get_extents(entity);
365 g_assert_false(extents->is_defined);
366 } else {
367 /* NON-RENDERABLE ENTITY (e.g., empty container) */
370 g_object_unref(entity);
374 void
375 adg_test_add_entity_checks(const gchar *testpath, GType type)
377 GType *p_type = g_new(GType, 1);
378 *p_type = type;
379 g_test_add_data_func(testpath, p_type, (gpointer) _adg_entity_checks);
382 static void
383 _adg_increment(GObject *object, gint *counter)
385 ++(*counter);
388 static void
389 _adg_container_checks(GType *p_type)
391 GType type = *p_type;
393 g_free(p_type);
395 g_assert_true(g_type_is_a(type, ADG_TYPE_CONTAINER));
397 if (! G_TYPE_IS_ABSTRACT(type)) {
398 AdgContainer *container;
399 AdgPath *path;
400 AdgStroke *stroke;
401 const CpmlExtents *extents;
402 gint counter;
404 container = g_object_new(type, NULL);
406 g_assert_nonnull(container);
407 g_assert_true(ADG_IS_CONTAINER(container));
409 counter = 0;
410 adg_container_foreach(container, G_CALLBACK(_adg_increment), &counter);
411 g_assert_cmpint(counter, ==, 0);
413 path = adg_path_new();
414 adg_path_move_to_explicit(path, -123456, -789012);
415 adg_path_line_to_explicit(path, 654321, 210987);
416 stroke = adg_stroke_new(ADG_TRAIL(path));
417 g_object_unref(path);
419 /* stroke has a floating reference that will be owned by container */
420 g_assert_null(adg_entity_get_parent(ADG_ENTITY(stroke)));
421 adg_test_signal(container, "add");
422 adg_container_add(container, ADG_ENTITY(stroke));
423 g_assert_true(adg_test_signal_check(TRUE));
424 g_assert_true(adg_entity_get_parent(ADG_ENTITY(stroke)) == ADG_ENTITY(container));
426 /* Ensure container-add add a reference to stroke */
427 g_assert_true(ADG_IS_STROKE(stroke));
429 /* The following command should basically be a no-op */
430 adg_container_add(container, ADG_ENTITY(stroke));
432 counter = 0;
433 adg_container_foreach(container, G_CALLBACK(_adg_increment), &counter);
434 g_assert_cmpint(counter, ==, 1);
436 /* Check the extents are *at least* as big as stroke. We cannot use
437 * equality because some container can have margins (e.g. AdgCanvas) */
438 adg_entity_arrange(ADG_ENTITY(container));
439 extents = adg_entity_get_extents(ADG_ENTITY(container));
440 g_assert_true(extents->is_defined);
441 g_assert_cmpfloat(extents->org.x, <=, -123456);
442 g_assert_cmpfloat(extents->org.y, <=, -789012);
443 g_assert_cmpfloat(extents->size.x, >=, 123456 + 654321);
444 g_assert_cmpfloat(extents->size.y, >=, 789012 + 210987);
446 /* Keep stroke around */
447 g_object_ref(stroke);
449 adg_test_signal(container, "remove");
450 adg_container_remove(container, ADG_ENTITY(stroke));
451 g_assert_true(adg_test_signal_check(TRUE));
452 g_assert_null(adg_entity_get_parent(ADG_ENTITY(stroke)));
454 counter = 0;
455 adg_container_foreach(container, G_CALLBACK(_adg_increment), &counter);
456 g_assert_cmpint(counter, ==, 0);
458 adg_entity_arrange(ADG_ENTITY(container));
459 g_assert_cmpfloat(extents->org.x, >, -123456);
460 g_assert_cmpfloat(extents->org.y, >, -789012);
461 g_assert_cmpfloat(extents->size.x, <, 123456 + 654321);
462 g_assert_cmpfloat(extents->size.y, <, 789012 + 210987);
464 /* Check destroying a child remove it from container */
465 adg_container_add(container, ADG_ENTITY(stroke));
466 g_object_unref(stroke);
468 counter = 0;
469 adg_container_foreach(container, G_CALLBACK(_adg_increment), &counter);
470 g_assert_cmpint(counter, ==, 1);
472 g_object_run_dispose(G_OBJECT(stroke));
474 counter = 0;
475 adg_container_foreach(container, G_CALLBACK(_adg_increment), &counter);
476 g_assert_cmpint(counter, ==, 0);
478 g_object_unref(container);
482 void
483 adg_test_add_container_checks(const gchar *testpath, GType type)
485 GType *p_type = g_new(GType, 1);
486 *p_type = type;
487 g_test_add_data_func(testpath, p_type, (gpointer) _adg_container_checks);
490 static void
491 _adg_global_space_checks(AdgEntity *entity)
493 cairo_t *cr;
494 const CpmlExtents *extents;
495 cairo_matrix_t scale2X;
496 gdouble width, height;
498 cr = adg_test_cairo_context();
499 cairo_matrix_init_scale(&scale2X, 2, 2);
501 /* Store the original extents size in width/height */
502 adg_entity_render(entity, cr);
503 extents = adg_entity_get_extents(entity);
504 g_assert_true(extents->is_defined);
505 width = extents->size.x;
506 height = extents->size.y;
508 /* Check explicit global-changed emission */
509 adg_test_signal(entity, "global-changed");
510 adg_entity_global_changed(entity);
511 g_assert_true(adg_test_signal_check(FALSE));
513 /* Check that a zoom in global space scales is roughly equivalent to
514 * the same zoom on extents too (not excactly because of fonts) */
515 adg_entity_transform_global_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
516 /* The "global-changed" signal handler has not been called here because
517 * its emission is lazy, that is it will be emitted only when needed */
518 g_assert_false(adg_test_signal_check(FALSE));
520 adg_entity_invalidate(entity);
521 g_assert_false(extents->is_defined);
522 /* Still no global-changed signal emitted */
523 g_assert_false(adg_test_signal_check(FALSE));
525 adg_entity_arrange(entity);
526 /* The "global-changed" signal has been emitted in the arrange phase */
527 g_assert_true(adg_test_signal_check(TRUE));
529 adg_test_signal(entity, "render");
530 adg_entity_render(entity, cr);
531 g_assert_true(adg_test_signal_check(TRUE));
533 /* Let's call render twice to check caching does not break anything */
534 adg_test_signal(entity, "render");
535 adg_entity_render(entity, cr);
536 g_assert_true(adg_test_signal_check(TRUE));
538 extents = adg_entity_get_extents(entity);
539 g_assert_cmpfloat(extents->size.x, >, width * 1.7);
540 g_assert_cmpfloat(extents->size.x, <, width * 2.3);
541 g_assert_cmpfloat(extents->size.y, >, height * 1.7);
542 g_assert_cmpfloat(extents->size.y, <, height * 2.3);
544 /* Restore the original global scale */
545 cairo_matrix_invert(&scale2X);
546 adg_entity_transform_global_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
547 adg_entity_invalidate(entity);
548 g_assert_false(extents->is_defined);
549 adg_entity_render(entity, cr);
550 extents = adg_entity_get_extents(entity);
551 g_assert_cmpfloat(extents->size.x, ==, width);
552 g_assert_cmpfloat(extents->size.y, ==, height);
554 cairo_destroy(cr);
555 g_object_unref(entity);
558 void
559 adg_test_add_global_space_checks(const gchar *testpath, gpointer entity)
561 g_test_add_data_func(testpath, entity,
562 (gpointer) _adg_global_space_checks);
565 static void
566 _adg_local_space_checks(AdgEntity *entity)
568 cairo_t *cr;
569 const CpmlExtents *extents;
570 cairo_matrix_t scale2X;
571 gdouble width, height;
573 cr = adg_test_cairo_context();
574 cairo_matrix_init_scale(&scale2X, 2, 2);
576 /* Store the original extents size in width/height */
577 adg_test_signal(entity, "render");
578 adg_entity_render(entity, cr);
579 g_assert_true(adg_test_signal_check(TRUE));
581 extents = adg_entity_get_extents(entity);
582 g_assert_true(extents->is_defined);
583 width = extents->size.x;
584 height = extents->size.y;
586 /* Check that a scale in local space somewhat scales the extents too */
587 adg_test_signal(entity, "local-changed");
588 adg_entity_transform_local_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
589 g_assert_false(adg_test_signal_check(FALSE));
591 adg_entity_invalidate(entity);
592 g_assert_false(extents->is_defined);
593 adg_entity_render(entity, cr);
594 /* The "local-changed" signal has been emitted here because "render" must
595 * also call "arrange" that, in turn, should trigger "local-changed" */
596 g_assert_true(adg_test_signal_check(TRUE));
598 extents = adg_entity_get_extents(entity);
599 g_assert_cmpfloat(extents->size.x, >, width);
600 g_assert_cmpfloat(extents->size.y, >, height);
602 /* Restore the original local scale */
603 cairo_matrix_invert(&scale2X);
604 adg_entity_transform_local_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
605 adg_entity_invalidate(entity);
606 g_assert_false(extents->is_defined);
607 adg_entity_render(entity, cr);
608 extents = adg_entity_get_extents(entity);
609 g_assert_cmpfloat(extents->size.x, ==, width);
610 g_assert_cmpfloat(extents->size.y, ==, height);
612 cairo_destroy(cr);
613 g_object_unref(entity);
616 void
617 adg_test_add_local_space_checks(const gchar *testpath, gpointer entity)
619 g_test_add_data_func(testpath, entity,
620 (gpointer) _adg_local_space_checks);
623 #if GLIB_CHECK_VERSION(2, 38, 0)
625 static void
626 _adg_trap(AdgTrapsFunc func, gint i)
628 if (g_test_subprocess()) {
629 func(i);
630 return;
632 g_test_trap_subprocess(NULL, 0, 0);
633 g_print("\b\b\b%2d ", i);
634 func(0);
637 #else
639 #include <stdlib.h>
641 #define ADG_TEST_TRAP_FLAGS (G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)
643 static void
644 _adg_trap(AdgTrapsFunc func, gint i)
646 if (g_test_trap_fork(0, ADG_TEST_TRAP_FLAGS)) {
647 func(i);
648 exit(0);
650 g_print("\b\b\b%2d ", i);
651 func(0);
654 #endif
656 static void
657 _adg_traps(_TrapsData *traps_data)
659 AdgTrapsFunc func = traps_data->func;
660 gint n_fragments = traps_data->n_fragments;
661 gint i;
663 g_free(traps_data);
665 for (i = 1; i <= n_fragments; ++i) {
666 _adg_trap(func, i);
670 void
671 adg_test_add_traps(const gchar *testpath, AdgTrapsFunc func, gint n_fragments)
673 _TrapsData *traps_data;
675 g_return_if_fail(n_fragments > 0);
677 traps_data = g_new(_TrapsData, 1);
678 traps_data->func = func;
679 traps_data->n_fragments = n_fragments;
680 g_test_add_data_func(testpath, traps_data, (gpointer) _adg_traps);