tests: avoid 3P arcs lying on the same line
[adg.git] / src / tests / adg-test.c
blob7988e6b9ee0ae24ea7dcbdd172ffb1da54d00350
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 = { 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,
169 data,
170 G_N_ELEMENTS(data)
172 return &path;
175 static void
176 _adg_enum_checks(const GType *p_type)
178 GType type;
179 gpointer class;
181 type = *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"));
192 void
193 adg_test_add_enum_checks(const gchar *testpath, GType type)
195 GType *p_type = g_new(GType, 1);
196 *p_type = type;
197 g_test_add_data_func_full(testpath, p_type,
198 (gpointer) _adg_enum_checks, g_free);
201 static void
202 _adg_boxed_checks(_BoxedData *boxed_data)
204 gpointer replica;
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);
216 g_free(boxed_data);
219 void
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);
230 static void
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);
275 void
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);
282 static void
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);
307 } else {
308 /* NON-RENDERABLE ENTITY (e.g., empty container) */
311 g_object_unref(entity);
315 void
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);
322 static void
323 _adg_global_space_checks(AdgEntity *entity)
325 cairo_t *cr;
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);
362 cairo_destroy(cr);
363 g_object_unref(entity);
366 void
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);
373 static void
374 _adg_local_space_checks(AdgEntity *entity)
376 cairo_t *cr;
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);
410 cairo_destroy(cr);
411 g_object_unref(entity);
414 void
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)
423 static void
424 _adg_trap(AdgTrapsFunc func, gint i)
426 if (g_test_subprocess()) {
427 func(i);
428 return;
430 g_test_trap_subprocess(NULL, 0, 0);
431 g_print("\b\b\b%2d ", i);
432 func(0);
435 #else
437 #include <stdlib.h>
439 #define ADG_TEST_TRAP_FLAGS (G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)
441 static void
442 _adg_trap(AdgTrapsFunc func, gint i)
444 if (g_test_trap_fork(0, ADG_TEST_TRAP_FLAGS)) {
445 func(i);
446 exit(0);
448 g_print("\b\b\b%2d ", i);
449 func(0);
452 #endif
454 static void
455 _adg_traps(_TrapsData *traps_data)
457 AdgTrapsFunc func = traps_data->func;
458 gint n_fragments = traps_data->n_fragments;
459 gint i;
461 g_free(traps_data);
463 for (i = 1; i <= n_fragments; ++i) {
464 _adg_trap(func, i);
468 void
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);