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"
36 /* Using adg_nop() would require to pull in the whole libadg stack:
37 * better to replicate that trivial function instead.
45 adg_test_init(int *p_argc
, char **p_argv
[])
47 #if GLIB_CHECK_VERSION(2, 34, 0)
49 /* On GLib older than 2.34.0 g_type_init() *must* be called */
52 g_test_init(p_argc
, p_argv
, NULL
);
54 g_log_set_always_fatal(0);
56 /* When building in silent mode (default), the ADG_QUIET
57 * environment variable is set to 1 by the Makefile and the
58 * warnings are discarded to reduce visual cluttering.
60 if (g_getenv("ADG_QUIET") != NULL
)
61 g_log_set_default_handler((GLogFunc
) _adg_nop
, NULL
);
63 g_test_bug_base("http://dev.entidi.com/p/adg/issues/%s/");
67 adg_test_invalid_pointer(void)
69 static int junk
[10] = { 0 };
74 adg_test_cairo_context(void)
76 cairo_surface_t
*surface
;
79 surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, 800, 600);
80 cr
= cairo_create(surface
);
81 cairo_surface_destroy(surface
);
87 adg_test_cairo_num_data(cairo_t
*cr
)
89 cairo_path_t
*path
= cairo_copy_path(cr
);
90 int length
= path
->num_data
;
91 cairo_path_destroy(path
);
99 static cairo_path_data_t data
[] = {
101 /* First segment: a valid segment with all primitive types */
102 { .header
= { CPML_MOVE
, 2 }},
103 { .point
= { 0, 1 }},
104 { .header
= { CPML_LINE
, 2 }},
105 { .point
= { 3, 1 }},
106 { .header
= { CPML_ARC
, 3 }},
107 { .point
= { 4, 5 }},
108 { .point
= { 6, 7 }},
109 { .header
= { CPML_CURVE
, 4 }},
110 { .point
= { 8, 9 }},
111 { .point
= { 10, 11 }},
112 { .point
= { -2, 2 }},
113 { .header
= { CPML_CLOSE
, 1 }},
115 /* Useless CPML_MOVE */
116 { .header
= { CPML_MOVE
, 2 }},
117 { .point
= { 0, 0 }},
119 /* Second segment: a couple of lines of length 1 and 2;
120 * line 2 intersects line 1 of the first segment in (1, 1) */
121 { .header
= { CPML_MOVE
, 2 }},
122 { .point
= { 0, 0 }},
123 { .header
= { CPML_LINE
, 2 }},
124 { .point
= { 1, 0 }},
125 { .header
= { CPML_LINE
, 2 }},
126 { .point
= { 1, 2 }},
128 /* Another useless CPML_MOVE with useless embedded data */
129 { .header
= { CPML_MOVE
, 4 }},
130 { .point
= { 1, 2 }},
131 { .point
= { 3, 4 }},
132 { .point
= { 5, 6 }},
134 /* Third segment: a Bézier curve with a trailing CPML_CLOSE */
135 { .header
= { CPML_MOVE
, 2 }},
136 { .point
= { 10, 13 }},
137 { .header
= { CPML_CURVE
, 4 }},
138 { .point
= { 8, 9 }},
139 { .point
= { 10, 11 }},
140 { .point
= { 12, 13 }},
141 { .header
= { CPML_CLOSE
, 1 }},
143 /* A valid cairo segment considered invalid by CPML
144 * because does not have a leading CPML_MOVE */
145 { .header
= { CPML_LINE
, 2 }},
146 { .point
= { 10, 0 }},
147 { .header
= { CPML_CLOSE
, 1 }},
149 /* Another valid cairo segment invalid in CPML */
150 { .header
= { CPML_CLOSE
, 1 }},
152 /* Forth segment: a couple of arcs */
153 { .header
= { CPML_MOVE
, 2 }},
154 { .point
= { 14, 15 }},
155 { .header
= { CPML_ARC
, 3 }},
156 { .point
= { 17, 16 }},
157 { .point
= { 18, 19 }},
158 { .header
= { CPML_ARC
, 3 }},
159 { .point
= { 21, 20 }},
160 { .point
= { 22, 23 }},
162 /* Fifth segment: a floating CPML_CLOSE */
163 { .header
= { CPML_MOVE
, 2 }},
164 { .point
= { 24, 25 }},
165 { .header
= { CPML_CLOSE
, 1 }}
167 static cairo_path_t path
= {
168 CAIRO_STATUS_SUCCESS
,
176 _adg_enum_checks(const GType
*p_type
)
182 g_assert_true(G_TYPE_IS_ENUM(type
));
184 class = g_type_class_ref(type
);
185 g_assert_nonnull(class);
187 g_assert_null(g_enum_get_value(class, -1));
188 g_assert_nonnull(g_enum_get_value(class, 0));
189 g_assert_null(g_enum_get_value_by_name(class, "unexistent value"));
193 adg_test_add_enum_checks(const gchar
*testpath
, GType type
)
195 GType
*p_type
= g_new(GType
, 1);
197 g_test_add_data_func_full(testpath
, p_type
,
198 (gpointer
) _adg_enum_checks
, g_free
);
202 _adg_boxed_checks(_BoxedData
*boxed_data
)
206 g_assert_true(G_TYPE_IS_BOXED(boxed_data
->type
));
208 g_assert_null(g_boxed_copy(boxed_data
->type
, NULL
));
210 replica
= g_boxed_copy(boxed_data
->type
, boxed_data
->instance
);
211 g_assert_nonnull(replica
);
213 g_boxed_free(boxed_data
->type
, replica
);
214 g_boxed_free(boxed_data
->type
, boxed_data
->instance
);
220 adg_test_add_boxed_checks(const gchar
*testpath
, GType type
, gpointer instance
)
222 _BoxedData
*boxed_data
= g_new(_BoxedData
, 1);
223 boxed_data
->type
= type
;
224 boxed_data
->instance
= instance
;
226 g_test_add_data_func(testpath
, boxed_data
,
227 (gpointer
) _adg_boxed_checks
);
231 _adg_object_checks(gconstpointer user_data
)
233 GType type
= GPOINTER_TO_INT(user_data
);
234 g_assert_true(G_TYPE_IS_OBJECT(type
));
236 if (! G_TYPE_IS_ABSTRACT(type
)) {
237 gpointer result
= GINT_TO_POINTER(0xdead);
238 GObject
*object
= g_object_new(type
, NULL
);
240 g_assert_nonnull(object
);
241 g_assert_true(G_IS_OBJECT(object
));
243 /* Check that unknown or unexistent properties does
244 * not return values (result should pass unmodified)
246 g_assert_cmpint(GPOINTER_TO_INT(result
), ==, 0xdead);
247 g_object_set(object
, "unknown", NULL
, NULL
);
248 g_object_get(object
, "unknown", &result
, NULL
);
249 g_assert_cmpint(GPOINTER_TO_INT(result
), ==, 0xdead);
250 g_object_get(object
, "unexistent", &result
, NULL
);
251 g_assert_cmpint(GPOINTER_TO_INT(result
), ==, 0xdead);
253 /* Check object lifetime: this can be done with a weak pointer
254 * that will "nullifies" result after object is destructed
256 g_object_add_weak_pointer(object
, &result
);
257 g_object_ref(object
);
258 g_object_unref(object
);
259 g_assert_nonnull(result
);
260 g_object_unref(object
);
261 g_assert_null(result
);
263 result
= GINT_TO_POINTER(0xdead);
264 object
= g_object_new(type
, NULL
);
266 g_object_add_weak_pointer(object
, &result
);
267 g_assert_nonnull(result
);
268 g_object_ref(object
);
269 g_object_ref(object
);
270 g_object_run_dispose(object
);
271 g_assert_null(result
);
276 adg_test_add_object_checks(const gchar
*testpath
, GType type
)
278 g_test_add_data_func(testpath
, GINT_TO_POINTER(type
),
279 (gpointer
) _adg_object_checks
);
283 _adg_entity_checks(gconstpointer user_data
)
285 GType type
= GPOINTER_TO_INT(user_data
);
286 g_assert_true(g_type_is_a(type
, ADG_TYPE_ENTITY
));
288 if (! G_TYPE_IS_ABSTRACT(type
)) {
289 AdgEntity
*entity
= g_object_new(type
, NULL
);
290 const CpmlExtents
*extents
;
292 g_assert_nonnull(entity
);
293 g_assert_true(ADG_IS_ENTITY(entity
));
295 /* Ensure that the entity extents are not initially defined */
296 extents
= adg_entity_get_extents(entity
);
297 g_assert_false(extents
->is_defined
);
299 adg_entity_arrange(entity
);
300 extents
= adg_entity_get_extents(entity
);
301 if (extents
->is_defined
) {
302 /* RENDERABLE ENTITY */
303 /* Check that invalidate() clears the cached extents */
304 adg_entity_invalidate(entity
);
305 extents
= adg_entity_get_extents(entity
);
306 g_assert_false(extents
->is_defined
);
308 /* NON-RENDERABLE ENTITY (e.g., empty container) */
311 g_object_unref(entity
);
316 adg_test_add_entity_checks(const gchar
*testpath
, GType type
)
318 g_test_add_data_func(testpath
, GINT_TO_POINTER(type
),
319 (gpointer
) _adg_entity_checks
);
323 _adg_global_space_checks(AdgEntity
*entity
)
326 const CpmlExtents
*extents
;
327 cairo_matrix_t scale2X
;
328 gdouble width
, height
;
330 cr
= adg_test_cairo_context();
331 cairo_matrix_init_scale(&scale2X
, 2, 2);
333 /* Store the original extents size in width/height */
334 adg_entity_render(entity
, cr
);
335 extents
= adg_entity_get_extents(entity
);
336 g_assert_true(extents
->is_defined
);
337 width
= extents
->size
.x
;
338 height
= extents
->size
.y
;
340 /* Check that a zoom in global space scales is roughly equivalent to
341 * the same zoom on extents too (not excactly because of fonts) */
342 adg_entity_transform_global_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
343 adg_entity_invalidate(entity
);
344 g_assert_false(extents
->is_defined
);
345 adg_entity_render(entity
, cr
);
346 extents
= adg_entity_get_extents(entity
);
347 g_assert_cmpfloat(extents
->size
.x
, >, width
* 1.7);
348 g_assert_cmpfloat(extents
->size
.x
, <, width
* 2.3);
349 g_assert_cmpfloat(extents
->size
.y
, >, height
* 1.7);
350 g_assert_cmpfloat(extents
->size
.y
, <, height
* 2.3);
352 /* Restore the original global scale */
353 cairo_matrix_invert(&scale2X
);
354 adg_entity_transform_global_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
355 adg_entity_invalidate(entity
);
356 g_assert_false(extents
->is_defined
);
357 adg_entity_render(entity
, cr
);
358 extents
= adg_entity_get_extents(entity
);
359 g_assert_cmpfloat(extents
->size
.x
, ==, width
);
360 g_assert_cmpfloat(extents
->size
.y
, ==, height
);
363 g_object_unref(entity
);
367 adg_test_add_global_space_checks(const gchar
*testpath
, gpointer entity
)
369 g_test_add_data_func(testpath
, entity
,
370 (gpointer
) _adg_global_space_checks
);
374 _adg_local_space_checks(AdgEntity
*entity
)
377 const CpmlExtents
*extents
;
378 cairo_matrix_t scale2X
;
379 gdouble width
, height
;
381 cr
= adg_test_cairo_context();
382 cairo_matrix_init_scale(&scale2X
, 2, 2);
384 /* Store the original extents size in width/height */
385 adg_entity_render(entity
, cr
);
386 extents
= adg_entity_get_extents(entity
);
387 g_assert_true(extents
->is_defined
);
388 width
= extents
->size
.x
;
389 height
= extents
->size
.y
;
391 /* Check that a scale in local space somewhat scales the extents too */
392 adg_entity_transform_local_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
393 adg_entity_invalidate(entity
);
394 g_assert_false(extents
->is_defined
);
395 adg_entity_render(entity
, cr
);
396 extents
= adg_entity_get_extents(entity
);
397 g_assert_cmpfloat(extents
->size
.x
, >, width
);
398 g_assert_cmpfloat(extents
->size
.y
, >, height
);
400 /* Restore the original local scale */
401 cairo_matrix_invert(&scale2X
);
402 adg_entity_transform_local_map(entity
, &scale2X
, ADG_TRANSFORM_BEFORE
);
403 adg_entity_invalidate(entity
);
404 g_assert_false(extents
->is_defined
);
405 adg_entity_render(entity
, cr
);
406 extents
= adg_entity_get_extents(entity
);
407 g_assert_cmpfloat(extents
->size
.x
, ==, width
);
408 g_assert_cmpfloat(extents
->size
.y
, ==, height
);
411 g_object_unref(entity
);
415 adg_test_add_local_space_checks(const gchar
*testpath
, gpointer entity
)
417 g_test_add_data_func(testpath
, entity
,
418 (gpointer
) _adg_local_space_checks
);
421 #if GLIB_CHECK_VERSION(2, 38, 0)
424 _adg_trap(AdgTrapsFunc func
, gint i
)
426 if (g_test_subprocess()) {
430 g_test_trap_subprocess(NULL
, 0, 0);
431 g_print("\b\b\b%2d ", i
);
439 #define ADG_TEST_TRAP_FLAGS (G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)
442 _adg_trap(AdgTrapsFunc func
, gint i
)
444 if (g_test_trap_fork(0, ADG_TEST_TRAP_FLAGS
)) {
448 g_print("\b\b\b%2d ", i
);
455 _adg_traps(_TrapsData
*traps_data
)
457 AdgTrapsFunc func
= traps_data
->func
;
458 gint n_fragments
= traps_data
->n_fragments
;
463 for (i
= 1; i
<= n_fragments
; ++i
) {
469 adg_test_add_traps(const gchar
*testpath
, AdgTrapsFunc func
, gint n_fragments
)
471 _TrapsData
*traps_data
;
473 g_return_if_fail(n_fragments
> 0);
475 traps_data
= g_new(_TrapsData
, 1);
476 traps_data
->func
= func
;
477 traps_data
->n_fragments
= n_fragments
;
478 g_test_add_data_func(testpath
, traps_data
, (gpointer
) _adg_traps
);