tests: ensure struct are set in CPML sanity checks
[adg.git] / src / tests / adg-test.c
blobf87c9274dfe4804550716bee2633735bd3e2a9aa
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-test.h"
25 typedef struct {
26 GType type;
27 gpointer instance;
28 } _BoxedData;
30 typedef struct {
31 AdgTrapsFunc func;
32 gint n_fragments;
33 } _TrapsData;
36 /* Using adg_nop() would require to pull in the whole libadg stack:
37 * better to replicate that trivial function instead.
39 static void
40 _adg_nop(void)
44 void
45 adg_test_init(int *p_argc, char **p_argv[])
47 #if GLIB_CHECK_VERSION(2, 34, 0)
48 #else
49 /* On GLib older than 2.34.0 g_type_init() *must* be called */
50 g_type_init();
51 #endif
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/");
66 const gpointer
67 adg_test_invalid_pointer(void)
69 static int junk[10] = { 0 };
70 return junk;
73 cairo_t *
74 adg_test_cairo_context(void)
76 cairo_surface_t *surface;
77 cairo_t *cr;
79 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 800, 600);
80 cr = cairo_create(surface);
81 cairo_surface_destroy(surface);
83 return cr;
86 int
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);
93 return length;
96 const cairo_path_t *
97 adg_test_path(void)
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 = { 16, 17 }},
157 { .point = { 18, 19 }},
158 { .header = { CPML_ARC, 3 }},
159 { .point = { 20, 21 }},
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,
169 data,
170 G_N_ELEMENTS(data)
172 return &path;
175 static void
176 _adg_enum_checks(gconstpointer user_data)
178 GType type;
179 gpointer class;
181 type = GPOINTER_TO_INT(user_data);
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"));
192 void
193 adg_test_add_enum_checks(const gchar *testpath, GType type)
195 g_test_add_data_func(testpath, GINT_TO_POINTER(type),
196 (gpointer) _adg_enum_checks);
199 static void
200 _adg_boxed_checks(_BoxedData *boxed_data)
202 gpointer replica;
204 g_assert_true(G_TYPE_IS_BOXED(boxed_data->type));
206 g_assert_null(g_boxed_copy(boxed_data->type, NULL));
208 replica = g_boxed_copy(boxed_data->type, boxed_data->instance);
209 g_assert_nonnull(replica);
211 g_boxed_free(boxed_data->type, replica);
212 g_boxed_free(boxed_data->type, boxed_data->instance);
214 g_free(boxed_data);
217 void
218 adg_test_add_boxed_checks(const gchar *testpath, GType type, gpointer instance)
220 _BoxedData *boxed_data = g_new(_BoxedData, 1);
221 boxed_data->type = type;
222 boxed_data->instance = instance;
224 g_test_add_data_func(testpath, boxed_data,
225 (gpointer) _adg_boxed_checks);
228 static void
229 _adg_object_checks(gconstpointer user_data)
231 GType type = GPOINTER_TO_INT(user_data);
232 g_assert_true(G_TYPE_IS_OBJECT(type));
234 if (! G_TYPE_IS_ABSTRACT(type)) {
235 gpointer result = GINT_TO_POINTER(0xdead);
236 GObject *object = g_object_new(type, NULL);
238 g_assert_nonnull(object);
239 g_assert_true(G_IS_OBJECT(object));
241 /* Check that unknown or unexistent properties does
242 * not return values (result should pass unmodified)
244 g_assert_cmpint(GPOINTER_TO_INT(result), ==, 0xdead);
245 g_object_set(object, "unknown", NULL, NULL);
246 g_object_get(object, "unknown", &result, NULL);
247 g_assert_cmpint(GPOINTER_TO_INT(result), ==, 0xdead);
248 g_object_get(object, "unexistent", &result, NULL);
249 g_assert_cmpint(GPOINTER_TO_INT(result), ==, 0xdead);
251 /* Check object lifetime: this can be done with a weak pointer
252 * that will "nullifies" result after object is destructed
254 g_object_add_weak_pointer(object, &result);
255 g_object_ref(object);
256 g_object_unref(object);
257 g_assert_nonnull(result);
258 g_object_unref(object);
259 g_assert_null(result);
261 result = GINT_TO_POINTER(0xdead);
262 object = g_object_new(type, NULL);
264 g_object_add_weak_pointer(object, &result);
265 g_assert_nonnull(result);
266 g_object_ref(object);
267 g_object_ref(object);
268 g_object_run_dispose(object);
269 g_assert_null(result);
273 void
274 adg_test_add_object_checks(const gchar *testpath, GType type)
276 g_test_add_data_func(testpath, GINT_TO_POINTER(type),
277 (gpointer) _adg_object_checks);
280 static void
281 _adg_entity_checks(gconstpointer user_data)
283 GType type = GPOINTER_TO_INT(user_data);
284 g_assert_true(g_type_is_a(type, ADG_TYPE_ENTITY));
286 if (! G_TYPE_IS_ABSTRACT(type)) {
287 AdgEntity *entity = g_object_new(type, NULL);
288 const CpmlExtents *extents;
290 g_assert_nonnull(entity);
291 g_assert_true(ADG_IS_ENTITY(entity));
293 /* Ensure that the entity extents are not initially defined */
294 extents = adg_entity_get_extents(entity);
295 g_assert_false(extents->is_defined);
297 adg_entity_arrange(entity);
298 extents = adg_entity_get_extents(entity);
299 if (extents->is_defined) {
300 /* RENDERABLE ENTITY */
301 /* Check that invalidate() clears the cached extents */
302 adg_entity_invalidate(entity);
303 extents = adg_entity_get_extents(entity);
304 g_assert_false(extents->is_defined);
305 } else {
306 /* NON-RENDERABLE ENTITY (e.g., empty container) */
309 g_object_unref(entity);
313 void
314 adg_test_add_entity_checks(const gchar *testpath, GType type)
316 g_test_add_data_func(testpath, GINT_TO_POINTER(type),
317 (gpointer) _adg_entity_checks);
320 static void
321 _adg_global_space_checks(AdgEntity *entity)
323 cairo_t *cr;
324 const CpmlExtents *extents;
325 cairo_matrix_t scale2X;
326 gdouble width, height;
328 cr = adg_test_cairo_context();
329 cairo_matrix_init_scale(&scale2X, 2, 2);
331 /* Store the original extents size in width/height */
332 adg_entity_render(entity, cr);
333 extents = adg_entity_get_extents(entity);
334 g_assert_true(extents->is_defined);
335 width = extents->size.x;
336 height = extents->size.y;
338 /* Check that a zoom in global space scales is roughly equivalent to
339 * the same zoom on extents too (not excactly because of fonts) */
340 adg_entity_transform_global_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
341 adg_entity_invalidate(entity);
342 g_assert_false(extents->is_defined);
343 adg_entity_render(entity, cr);
344 extents = adg_entity_get_extents(entity);
345 g_assert_cmpfloat(extents->size.x, >, width * 1.7);
346 g_assert_cmpfloat(extents->size.x, <, width * 2.3);
347 g_assert_cmpfloat(extents->size.y, >, height * 1.7);
348 g_assert_cmpfloat(extents->size.y, <, height * 2.3);
350 /* Restore the original global scale */
351 cairo_matrix_invert(&scale2X);
352 adg_entity_transform_global_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
353 adg_entity_invalidate(entity);
354 g_assert_false(extents->is_defined);
355 adg_entity_render(entity, cr);
356 extents = adg_entity_get_extents(entity);
357 g_assert_cmpfloat(extents->size.x, ==, width);
358 g_assert_cmpfloat(extents->size.y, ==, height);
360 cairo_destroy(cr);
361 g_object_unref(entity);
364 void
365 adg_test_add_global_space_checks(const gchar *testpath, gpointer entity)
367 g_test_add_data_func(testpath, entity,
368 (gpointer) _adg_global_space_checks);
371 static void
372 _adg_local_space_checks(AdgEntity *entity)
374 cairo_t *cr;
375 const CpmlExtents *extents;
376 cairo_matrix_t scale2X;
377 gdouble width, height;
379 cr = adg_test_cairo_context();
380 cairo_matrix_init_scale(&scale2X, 2, 2);
382 /* Store the original extents size in width/height */
383 adg_entity_render(entity, cr);
384 extents = adg_entity_get_extents(entity);
385 g_assert_true(extents->is_defined);
386 width = extents->size.x;
387 height = extents->size.y;
389 /* Check that a scale in local space somewhat scales the extents too */
390 adg_entity_transform_local_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
391 adg_entity_invalidate(entity);
392 g_assert_false(extents->is_defined);
393 adg_entity_render(entity, cr);
394 extents = adg_entity_get_extents(entity);
395 g_assert_cmpfloat(extents->size.x, >, width);
396 g_assert_cmpfloat(extents->size.y, >, height);
398 /* Restore the original local scale */
399 cairo_matrix_invert(&scale2X);
400 adg_entity_transform_local_map(entity, &scale2X, ADG_TRANSFORM_BEFORE);
401 adg_entity_invalidate(entity);
402 g_assert_false(extents->is_defined);
403 adg_entity_render(entity, cr);
404 extents = adg_entity_get_extents(entity);
405 g_assert_cmpfloat(extents->size.x, ==, width);
406 g_assert_cmpfloat(extents->size.y, ==, height);
408 cairo_destroy(cr);
409 g_object_unref(entity);
412 void
413 adg_test_add_local_space_checks(const gchar *testpath, gpointer entity)
415 g_test_add_data_func(testpath, entity,
416 (gpointer) _adg_local_space_checks);
419 #if GLIB_CHECK_VERSION(2, 38, 0)
421 static void
422 _adg_trap(AdgTrapsFunc func, gint i)
424 if (g_test_subprocess()) {
425 func(i);
426 return;
428 g_test_trap_subprocess(NULL, 0, 0);
429 g_print("\b\b\b%2d ", i);
430 func(0);
433 #else
435 #include <stdlib.h>
437 #define ADG_TEST_TRAP_FLAGS (G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)
439 static void
440 _adg_trap(AdgTrapsFunc func, gint i)
442 if (g_test_trap_fork(0, ADG_TEST_TRAP_FLAGS)) {
443 func(i);
444 exit(0);
446 g_print("\b\b\b%2d ", i);
447 func(0);
450 #endif
452 static void
453 _adg_traps(_TrapsData *traps_data)
455 AdgTrapsFunc func = traps_data->func;
456 gint n_fragments = traps_data->n_fragments;
457 gint i;
459 g_free(traps_data);
461 for (i = 1; i <= n_fragments; ++i) {
462 _adg_trap(func, i);
466 void
467 adg_test_add_traps(const gchar *testpath, AdgTrapsFunc func, gint n_fragments)
469 _TrapsData *traps_data;
471 g_return_if_fail(n_fragments > 0);
473 traps_data = g_new(_TrapsData, 1);
474 traps_data->func = func;
475 traps_data->n_fragments = n_fragments;
476 g_test_add_data_func(testpath, traps_data, (gpointer) _adg_traps);