1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 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"
25 #include "adg-container.h"
26 #include "adg-stroke.h"
27 #include "adg-table.h"
28 #include "adg-title-block.h"
29 #include "adg-canvas.h"
50 /* Using adg_nop() would require to pull in the whole libadg stack:
51 * better to replicate that trivial function instead.
59 adg_test_init(int *p_argc
, char **p_argv
[])
61 #if GLIB_CHECK_VERSION(2, 34, 0)
63 /* On GLib older than 2.34.0 g_type_init() *must* be called */
66 g_test_init(p_argc
, p_argv
, NULL
);
68 g_log_set_always_fatal(0);
70 /* When building in silent mode (default), the ADG_QUIET
71 * environment variable is set to 1 by the Makefile and the
72 * warnings are discarded to reduce visual cluttering.
74 if (g_getenv("ADG_QUIET") != NULL
)
75 g_log_set_default_handler((GLogFunc
) _adg_nop
, NULL
);
77 g_test_bug_base("https://track.entidi.com/%s");
81 adg_test_invalid_pointer(void)
83 static int junk
[10] = { 0 };
88 adg_test_cairo_context(void)
90 cairo_surface_t
*surface
;
93 surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, 800, 600);
94 cr
= cairo_create(surface
);
95 cairo_surface_destroy(surface
);
101 adg_test_cairo_num_data(cairo_t
*cr
)
103 cairo_path_t
*path
= cairo_copy_path(cr
);
104 int length
= path
->num_data
;
105 cairo_path_destroy(path
);
113 static cairo_path_data_t data
[] = {
115 /* First segment: a valid segment with all primitive types */
116 { .header
= { CPML_MOVE
, 2 }},
117 { .point
= { 0, 1 }},
118 { .header
= { CPML_LINE
, 2 }},
119 { .point
= { 3, 1 }},
120 { .header
= { CPML_ARC
, 3 }},
121 { .point
= { 4, 5 }},
122 { .point
= { 6, 7 }},
123 { .header
= { CPML_CURVE
, 4 }},
124 { .point
= { 8, 9 }},
125 { .point
= { 10, 11 }},
126 { .point
= { -2, 2 }},
127 { .header
= { CPML_CLOSE
, 1 }},
129 /* Useless CPML_MOVE */
130 { .header
= { CPML_MOVE
, 2 }},
131 { .point
= { 0, 0 }},
133 /* Second segment: a couple of lines of length 1 and 2;
134 * line 2 intersects line 1 of the first segment in (1, 1) */
135 { .header
= { CPML_MOVE
, 2 }},
136 { .point
= { 0, 0 }},
137 { .header
= { CPML_LINE
, 2 }},
138 { .point
= { 1, 0 }},
139 { .header
= { CPML_LINE
, 2 }},
140 { .point
= { 1, 2 }},
142 /* Another useless CPML_MOVE with useless embedded data */
143 { .header
= { CPML_MOVE
, 4 }},
144 { .point
= { 1, 2 }},
145 { .point
= { 3, 4 }},
146 { .point
= { 5, 6 }},
148 /* Third segment: a Bézier curve with a trailing CPML_CLOSE */
149 { .header
= { CPML_MOVE
, 2 }},
150 { .point
= { 10, 13 }},
151 { .header
= { CPML_CURVE
, 4 }},
152 { .point
= { 8, 9 }},
153 { .point
= { 10, 11 }},
154 { .point
= { 12, 13 }},
155 { .header
= { CPML_CLOSE
, 1 }},
157 /* A valid cairo segment considered invalid by CPML
158 * because does not have a leading CPML_MOVE */
159 { .header
= { CPML_LINE
, 2 }},
160 { .point
= { 10, 0 }},
161 { .header
= { CPML_CLOSE
, 1 }},
163 /* Another valid cairo segment invalid in CPML */
164 { .header
= { CPML_CLOSE
, 1 }},
166 /* Forth segment: a couple of arcs */
167 { .header
= { CPML_MOVE
, 2 }},
168 { .point
= { 14, 15 }},
169 { .header
= { CPML_ARC
, 3 }},
170 { .point
= { 17, 16 }},
171 { .point
= { 18, 19 }},
172 { .header
= { CPML_ARC
, 3 }},
173 { .point
= { 21, 20 }},
174 { .point
= { 22, 23 }},
176 /* Fifth segment: a floating CPML_CLOSE */
177 { .header
= { CPML_MOVE
, 2 }},
178 { .point
= { 24, 25 }},
179 { .header
= { CPML_CLOSE
, 1 }}
181 static cairo_path_t path
= {
182 CAIRO_STATUS_SUCCESS
,
190 adg_test_canvas(void)
196 path
= adg_path_new();
197 adg_path_move_to_explicit(path
, 0, 0);
198 adg_path_line_to_explicit(path
, 1, 1);
199 stroke
= adg_stroke_new(ADG_TRAIL(path
));
200 g_object_unref(path
);
202 canvas
= adg_canvas_new();
203 adg_canvas_set_margins(canvas
, 0, 0, 0, 0);
204 adg_canvas_set_paddings(canvas
, 0, 0, 0, 0);
206 adg_container_add(ADG_CONTAINER(canvas
), ADG_ENTITY(stroke
));
212 _adg_enum_checks(GType
*p_type
)
214 GType type
= *p_type
;
219 g_assert_true(G_TYPE_IS_ENUM(type
));
221 class = g_type_class_ref(type
);
222 g_assert_nonnull(class);
224 g_assert_null(g_enum_get_value(class, -1));
225 g_assert_nonnull(g_enum_get_value(class, 0));
226 g_assert_null(g_enum_get_value_by_name(class, "unexistent value"));
230 adg_test_add_enum_checks(const gchar
*testpath
, GType type
)
232 GType
*p_type
= g_new(GType
, 1);
234 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_enum_checks
);
238 _adg_boxed_checks(_BoxedData
*boxed_data
)
242 g_assert_true(G_TYPE_IS_BOXED(boxed_data
->type
));
244 g_assert_null(g_boxed_copy(boxed_data
->type
, NULL
));
246 replica
= g_boxed_copy(boxed_data
->type
, boxed_data
->instance
);
247 g_assert_nonnull(replica
);
249 g_boxed_free(boxed_data
->type
, replica
);
250 g_boxed_free(boxed_data
->type
, boxed_data
->instance
);
256 adg_test_add_boxed_checks(const gchar
*testpath
, GType type
, gpointer instance
)
258 _BoxedData
*boxed_data
= g_new(_BoxedData
, 1);
259 boxed_data
->type
= type
;
260 boxed_data
->instance
= instance
;
262 g_test_add_data_func(testpath
, boxed_data
,
263 (gpointer
) _adg_boxed_checks
);
267 _adg_object_checks(GType
*p_type
)
269 GType type
= *p_type
;
273 g_assert_true(G_TYPE_IS_OBJECT(type
));
275 if (! G_TYPE_IS_ABSTRACT(type
)) {
276 gpointer result
= GINT_TO_POINTER(0xdead);
277 GObject
*object
= g_object_new(type
, NULL
);
279 g_assert_nonnull(object
);
280 g_assert_true(G_IS_OBJECT(object
));
282 /* Convert the floating reference, if present, to a hard reference so
283 * we can handle GTK+ widgets (that usually have a floating reference)
284 * and other object (that do not usually have floating references) in
287 if (g_object_is_floating(object
)) {
288 g_object_ref_sink(object
);
291 /* Check that unknown or unexistent properties does
292 * not return values (result should pass unmodified)
294 g_assert_cmpint(GPOINTER_TO_INT(result
), ==, 0xdead);
295 g_object_set(object
, "unknown", NULL
, NULL
);
296 g_object_get(object
, "unknown", &result
, NULL
);
297 g_assert_cmpint(GPOINTER_TO_INT(result
), ==, 0xdead);
298 g_object_get(object
, "unexistent", &result
, NULL
);
299 g_assert_cmpint(GPOINTER_TO_INT(result
), ==, 0xdead);
301 /* Check object lifetime: this can be done with a weak pointer
302 * that will "nullifies" result after object is destructed
304 g_object_add_weak_pointer(object
, &result
);
305 g_object_ref(object
);
306 g_object_unref(object
);
307 g_assert_nonnull(result
);
308 g_object_unref(object
);
309 g_assert_null(result
);
311 result
= GINT_TO_POINTER(0xdead);
312 object
= g_object_new(type
, NULL
);
314 g_object_add_weak_pointer(object
, &result
);
315 g_assert_nonnull(result
);
316 g_object_ref(object
);
317 g_object_ref(object
);
318 g_object_run_dispose(object
);
319 g_assert_null(result
);
324 adg_test_add_object_checks(const gchar
*testpath
, GType type
)
326 GType
*p_type
= g_new(GType
, 1);
328 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_object_checks
);
332 _adg_test_set(gboolean
*p_flag
)
337 static _SignalData signal_data
;
340 adg_test_signal(gpointer instance
, const gchar
*detailed_signal
)
342 signal_data
.instance
= instance
;
344 /* The swapped variant *must* be used, otherwise the position of flag
345 * would depend on the number of parameters of the signal. This also
346 * implies I cannot use _adg_increment() here. */
347 signal_data
.handler
= g_signal_connect_swapped(instance
,
349 G_CALLBACK(_adg_test_set
),
351 signal_data
.flag
= FALSE
;
355 adg_test_signal_check(gboolean disconnect
)
360 /* A handler cannot be disconnected twice */
361 g_assert(signal_data
.instance
!= NULL
);
362 g_signal_handler_disconnect(signal_data
.instance
, signal_data
.handler
);
363 signal_data
.instance
= NULL
;
366 last_flag
= signal_data
.flag
;
367 signal_data
.flag
= FALSE
;
373 _adg_model_checks(GType
*p_type
)
375 GType type
= *p_type
;
379 g_assert_true(g_type_is_a(type
, ADG_TYPE_MODEL
));
381 if (! G_TYPE_IS_ABSTRACT(type
)) {
382 AdgModel
*model
= g_object_new(type
, NULL
);
384 g_assert_nonnull(model
);
385 g_assert_true(ADG_IS_MODEL(model
));
387 /* Check the clear signal */
388 adg_test_signal(model
, "clear");
389 adg_model_clear(model
);
390 g_assert_true(adg_test_signal_check(TRUE
));
392 /* Check the reset signal */
393 adg_test_signal(model
, "reset");
394 adg_model_reset(model
);
395 g_assert_true(adg_test_signal_check(TRUE
));
397 /* Check the reset signal triggers the clear signal */
398 adg_test_signal(model
, "clear");
399 adg_model_reset(model
);
400 g_assert_true(adg_test_signal_check(TRUE
));
402 /* Check the changed signal */
403 adg_test_signal(model
, "changed");
404 adg_model_changed(model
);
405 g_assert_true(adg_test_signal_check(TRUE
));
407 g_object_unref(model
);
412 adg_test_add_model_checks(const gchar
*testpath
, GType type
)
414 GType
*p_type
= g_new(GType
, 1);
416 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_model_checks
);
420 _adg_entity_checks(GType
*p_type
)
422 GType type
= *p_type
;
426 g_assert_true(g_type_is_a(type
, ADG_TYPE_ENTITY
));
428 if (! G_TYPE_IS_ABSTRACT(type
)) {
429 AdgEntity
*entity
= g_object_new(type
, NULL
);
430 const CpmlExtents
*extents
;
432 g_assert_nonnull(entity
);
433 g_assert_true(ADG_IS_ENTITY(entity
));
435 /* Ensure that the entity extents are not initially defined */
436 extents
= adg_entity_get_extents(entity
);
437 g_assert_false(extents
->is_defined
);
439 adg_test_signal(entity
, "arrange");
440 adg_entity_arrange(entity
);
441 g_assert_true(adg_test_signal_check(TRUE
));
443 extents
= adg_entity_get_extents(entity
);
444 if (extents
->is_defined
) {
445 /* RENDERABLE ENTITY */
446 /* Check that invalidate() clears the cached extents */
447 adg_test_signal(entity
, "invalidate");
448 adg_entity_invalidate(entity
);
449 g_assert_true(adg_test_signal_check(TRUE
));
451 extents
= adg_entity_get_extents(entity
);
452 g_assert_false(extents
->is_defined
);
454 /* NON-RENDERABLE ENTITY (e.g., empty container) */
457 g_object_unref(entity
);
462 adg_test_add_entity_checks(const gchar
*testpath
, GType type
)
464 GType
*p_type
= g_new(GType
, 1);
466 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_entity_checks
);
470 _adg_increment(GObject
*object
, gint
*counter
)
476 _adg_container_checks(GType
*p_type
)
478 GType type
= *p_type
;
482 g_assert_true(g_type_is_a(type
, ADG_TYPE_CONTAINER
));
484 if (! G_TYPE_IS_ABSTRACT(type
)) {
485 AdgContainer
*container
;
488 const CpmlExtents
*extents
;
491 container
= g_object_new(type
, NULL
);
493 g_assert_nonnull(container
);
494 g_assert_true(ADG_IS_CONTAINER(container
));
497 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
498 g_assert_cmpint(counter
, ==, 0);
500 path
= adg_path_new();
501 adg_path_move_to_explicit(path
, -123456, -789012);
502 adg_path_line_to_explicit(path
, 654321, 210987);
503 stroke
= adg_stroke_new(ADG_TRAIL(path
));
504 g_object_unref(path
);
506 /* stroke has a floating reference that will be owned by container */
507 g_assert_null(adg_entity_get_parent(ADG_ENTITY(stroke
)));
508 adg_test_signal(container
, "add");
509 adg_container_add(container
, ADG_ENTITY(stroke
));
510 g_assert_true(adg_test_signal_check(TRUE
));
511 g_assert_true(adg_entity_get_parent(ADG_ENTITY(stroke
)) == ADG_ENTITY(container
));
513 /* Ensure container-add add a reference to stroke */
514 g_assert_true(ADG_IS_STROKE(stroke
));
516 /* The following command should basically be a no-op */
517 adg_container_add(container
, ADG_ENTITY(stroke
));
520 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
521 g_assert_cmpint(counter
, ==, 1);
523 /* Check the extents are *at least* as big as stroke. We cannot use
524 * equality because some container can have margins (e.g. AdgCanvas) */
525 adg_entity_arrange(ADG_ENTITY(container
));
526 extents
= adg_entity_get_extents(ADG_ENTITY(container
));
527 g_assert_true(extents
->is_defined
);
528 g_assert_cmpfloat(extents
->org
.x
, <=, -123456);
529 g_assert_cmpfloat(extents
->org
.y
, <=, -789012);
530 g_assert_cmpfloat(extents
->size
.x
, >=, 123456 + 654321);
531 g_assert_cmpfloat(extents
->size
.y
, >=, 789012 + 210987);
533 /* Keep stroke around */
534 g_object_ref(stroke
);
536 adg_test_signal(container
, "remove");
537 adg_container_remove(container
, ADG_ENTITY(stroke
));
538 g_assert_true(adg_test_signal_check(TRUE
));
539 g_assert_null(adg_entity_get_parent(ADG_ENTITY(stroke
)));
542 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
543 g_assert_cmpint(counter
, ==, 0);
545 adg_entity_arrange(ADG_ENTITY(container
));
546 g_assert_cmpfloat(extents
->org
.x
, >, -123456);
547 g_assert_cmpfloat(extents
->org
.y
, >, -789012);
548 g_assert_cmpfloat(extents
->size
.x
, <, 123456 + 654321);
549 g_assert_cmpfloat(extents
->size
.y
, <, 789012 + 210987);
551 /* Check destroying a child remove it from container */
552 adg_container_add(container
, ADG_ENTITY(stroke
));
553 g_object_unref(stroke
);
556 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
557 g_assert_cmpint(counter
, ==, 1);
559 g_object_run_dispose(G_OBJECT(stroke
));
562 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
563 g_assert_cmpint(counter
, ==, 0);
565 g_object_unref(container
);
570 adg_test_add_container_checks(const gchar
*testpath
, GType type
)
572 GType
*p_type
= g_new(GType
, 1);
574 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_container_checks
);
578 _adg_global_space_checks(AdgEntity
*entity
)
581 const CpmlExtents
*extents
;
582 cairo_matrix_t scale2X
;
583 gdouble width
, height
;
585 cr
= adg_test_cairo_context();
586 cairo_matrix_init_scale(&scale2X
, 2, 2);
588 adg_switch_extents(g_test_rand_bit());
590 /* Store the original extents size in width/height */
591 adg_entity_render(entity
, cr
);
592 extents
= adg_entity_get_extents(entity
);
593 g_assert_true(extents
->is_defined
);
594 width
= extents
->size
.x
;
595 height
= extents
->size
.y
;
597 /* Check explicit global-changed emission */
598 adg_test_signal(entity
, "global-changed");
599 adg_entity_global_changed(entity
);
600 g_assert_true(adg_test_signal_check(FALSE
));
602 /* Check that a zoom in global space scales is roughly equivalent to
603 * the same zoom on extents too (not excactly because of fonts) */
604 adg_entity_transform_global_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
605 /* The "global-changed" signal handler has not been called here because
606 * its emission is lazy, that is it will be emitted only when needed */
607 g_assert_false(adg_test_signal_check(FALSE
));
609 adg_entity_invalidate(entity
);
610 g_assert_false(extents
->is_defined
);
611 /* Still no global-changed signal emitted */
612 g_assert_false(adg_test_signal_check(FALSE
));
614 adg_entity_arrange(entity
);
615 /* The "global-changed" signal has been emitted in the arrange phase */
616 g_assert_true(adg_test_signal_check(TRUE
));
618 adg_test_signal(entity
, "render");
619 adg_entity_render(entity
, cr
);
620 g_assert_true(adg_test_signal_check(TRUE
));
622 /* Let's call render twice to check caching does not break anything */
623 adg_test_signal(entity
, "render");
624 adg_entity_render(entity
, cr
);
625 g_assert_true(adg_test_signal_check(TRUE
));
627 extents
= adg_entity_get_extents(entity
);
628 g_assert_cmpfloat(extents
->size
.x
, >, width
* 1.7);
629 g_assert_cmpfloat(extents
->size
.x
, <, width
* 2.3);
630 g_assert_cmpfloat(extents
->size
.y
, >, height
* 1.7);
631 g_assert_cmpfloat(extents
->size
.y
, <, height
* 2.3);
633 /* Restore the original global scale */
634 cairo_matrix_invert(&scale2X
);
635 adg_entity_transform_global_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
636 adg_entity_invalidate(entity
);
637 g_assert_false(extents
->is_defined
);
638 adg_entity_render(entity
, cr
);
639 extents
= adg_entity_get_extents(entity
);
640 g_assert_cmpfloat(extents
->size
.x
, ==, width
);
641 g_assert_cmpfloat(extents
->size
.y
, ==, height
);
644 g_object_unref(entity
);
648 adg_test_add_global_space_checks(const gchar
*testpath
, gpointer entity
)
650 g_test_add_data_func(testpath
, entity
,
651 (gpointer
) _adg_global_space_checks
);
655 _adg_local_space_checks(AdgEntity
*entity
)
658 const CpmlExtents
*extents
;
659 cairo_matrix_t scale2X
;
660 gdouble width
, height
;
662 cr
= adg_test_cairo_context();
663 cairo_matrix_init_scale(&scale2X
, 2, 2);
665 /* Store the original extents size in width/height */
666 adg_test_signal(entity
, "render");
667 adg_entity_render(entity
, cr
);
668 g_assert_true(adg_test_signal_check(TRUE
));
670 extents
= adg_entity_get_extents(entity
);
671 g_assert_true(extents
->is_defined
);
672 width
= extents
->size
.x
;
673 height
= extents
->size
.y
;
675 /* Check that a scale in local space somewhat scales the extents too */
676 adg_test_signal(entity
, "local-changed");
677 adg_entity_transform_local_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
678 g_assert_false(adg_test_signal_check(FALSE
));
680 adg_entity_invalidate(entity
);
681 g_assert_false(extents
->is_defined
);
682 adg_entity_render(entity
, cr
);
683 /* The "local-changed" signal has been emitted here because "render" must
684 * also call "arrange" that, in turn, should trigger "local-changed" */
685 g_assert_true(adg_test_signal_check(TRUE
));
687 extents
= adg_entity_get_extents(entity
);
688 g_assert_cmpfloat(extents
->size
.x
, >, width
);
689 g_assert_cmpfloat(extents
->size
.y
, >, height
);
691 /* Restore the original local scale */
692 cairo_matrix_invert(&scale2X
);
693 adg_entity_transform_local_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
694 adg_entity_invalidate(entity
);
695 g_assert_false(extents
->is_defined
);
696 adg_entity_render(entity
, cr
);
697 extents
= adg_entity_get_extents(entity
);
698 g_assert_cmpfloat(extents
->size
.x
, ==, width
);
699 g_assert_cmpfloat(extents
->size
.y
, ==, height
);
702 g_object_unref(entity
);
706 adg_test_add_local_space_checks(const gchar
*testpath
, gpointer entity
)
708 g_test_add_data_func(testpath
, entity
,
709 (gpointer
) _adg_local_space_checks
);
712 #if GLIB_CHECK_VERSION(2, 38, 0)
715 _adg_trap(AdgTrapsFunc func
, gint i
)
717 if (g_test_subprocess()) {
721 g_test_trap_subprocess(NULL
, 0, 0);
722 g_print("\b\b\b%2d ", i
);
730 #define ADG_TEST_TRAP_FLAGS (G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)
733 _adg_trap(AdgTrapsFunc func
, gint i
)
735 if (g_test_trap_fork(0, ADG_TEST_TRAP_FLAGS
)) {
739 g_print("\b\b\b%2d ", i
);
746 _adg_traps(_TrapsData
*traps_data
)
748 AdgTrapsFunc func
= traps_data
->func
;
749 gint n_fragments
= traps_data
->n_fragments
;
754 for (i
= 1; i
<= n_fragments
; ++i
) {
760 adg_test_add_traps(const gchar
*testpath
, AdgTrapsFunc func
, gint n_fragments
)
762 _TrapsData
*traps_data
;
764 g_return_if_fail(n_fragments
> 0);
766 traps_data
= g_new(_TrapsData
, 1);
767 traps_data
->func
= func
;
768 traps_data
->n_fragments
= n_fragments
;
769 g_test_add_data_func(testpath
, traps_data
, (gpointer
) _adg_traps
);