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"
25 #include "adg-container.h"
26 #include "adg-stroke.h"
47 /* Using adg_nop() would require to pull in the whole libadg stack:
48 * better to replicate that trivial function instead.
56 adg_test_init(int *p_argc
, char **p_argv
[])
58 #if GLIB_CHECK_VERSION(2, 34, 0)
60 /* On GLib older than 2.34.0 g_type_init() *must* be called */
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/");
78 adg_test_invalid_pointer(void)
80 static int junk
[10] = { 0 };
85 adg_test_cairo_context(void)
87 cairo_surface_t
*surface
;
90 surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, 800, 600);
91 cr
= cairo_create(surface
);
92 cairo_surface_destroy(surface
);
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
);
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
,
187 _adg_enum_checks(GType
*p_type
)
189 GType type
= *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"));
205 adg_test_add_enum_checks(const gchar
*testpath
, GType type
)
207 GType
*p_type
= g_new(GType
, 1);
209 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_enum_checks
);
213 _adg_boxed_checks(_BoxedData
*boxed_data
)
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
);
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
);
242 _adg_object_checks(GType
*p_type
)
244 GType type
= *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
);
290 adg_test_add_object_checks(const gchar
*testpath
, GType type
)
292 GType
*p_type
= g_new(GType
, 1);
294 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_object_checks
);
298 _adg_test_set(gboolean
*p_flag
)
303 static _SignalData signal_data
;
306 adg_test_signal(gpointer instance
, const gchar
*detailed_signal
)
308 signal_data
.instance
= instance
;
309 signal_data
.handler
= g_signal_connect_swapped(instance
,
311 G_CALLBACK(_adg_test_set
),
313 signal_data
.flag
= FALSE
;
317 adg_test_signal_check(gboolean 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
;
333 _adg_entity_checks(GType
*p_type
)
335 GType type
= *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
);
367 /* NON-RENDERABLE ENTITY (e.g., empty container) */
370 g_object_unref(entity
);
375 adg_test_add_entity_checks(const gchar
*testpath
, GType type
)
377 GType
*p_type
= g_new(GType
, 1);
379 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_entity_checks
);
383 _adg_increment(GObject
*object
, gint
*counter
)
389 _adg_container_checks(GType
*p_type
)
391 GType type
= *p_type
;
395 g_assert_true(g_type_is_a(type
, ADG_TYPE_CONTAINER
));
397 if (! G_TYPE_IS_ABSTRACT(type
)) {
398 AdgContainer
*container
;
401 const CpmlExtents
*extents
;
404 container
= g_object_new(type
, NULL
);
406 g_assert_nonnull(container
);
407 g_assert_true(ADG_IS_CONTAINER(container
));
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
));
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
)));
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
);
469 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
470 g_assert_cmpint(counter
, ==, 1);
472 g_object_run_dispose(G_OBJECT(stroke
));
475 adg_container_foreach(container
, G_CALLBACK(_adg_increment
), &counter
);
476 g_assert_cmpint(counter
, ==, 0);
478 g_object_unref(container
);
483 adg_test_add_container_checks(const gchar
*testpath
, GType type
)
485 GType
*p_type
= g_new(GType
, 1);
487 g_test_add_data_func(testpath
, p_type
, (gpointer
) _adg_container_checks
);
491 _adg_global_space_checks(AdgEntity
*entity
)
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
);
555 g_object_unref(entity
);
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
);
566 _adg_local_space_checks(AdgEntity
*entity
)
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
);
613 g_object_unref(entity
);
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)
626 _adg_trap(AdgTrapsFunc func
, gint i
)
628 if (g_test_subprocess()) {
632 g_test_trap_subprocess(NULL
, 0, 0);
633 g_print("\b\b\b%2d ", i
);
641 #define ADG_TEST_TRAP_FLAGS (G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)
644 _adg_trap(AdgTrapsFunc func
, gint i
)
646 if (g_test_trap_fork(0, ADG_TEST_TRAP_FLAGS
)) {
650 g_print("\b\b\b%2d ", i
);
657 _adg_traps(_TrapsData
*traps_data
)
659 AdgTrapsFunc func
= traps_data
->func
;
660 gint n_fragments
= traps_data
->n_fragments
;
665 for (i
= 1; i
<= n_fragments
; ++i
) {
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
);