diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c index 9128230..7369d96 100644 --- a/libchimara/chimara-glk.c +++ b/libchimara/chimara-glk.c @@ -597,6 +597,11 @@ chimara_glk_get_preferred_height(GtkWidget *widget, int *minimal, int *natural) static winid_t allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing) { + if (allocation->width == 0 || allocation->height == 0) { + /* Just don't show this window */ + return win; + } + if(win->type == wintype_Pair) { g_mutex_lock(&win->lock); @@ -693,7 +698,18 @@ allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing) child1.width = child2.width = allocation->width; break; } - + + /* If either of the child windows get 0 size, hide that window and just + * give the full space to the other one */ + if (child1.width == 0 || child1.height == 0) { + allocate_recurse(win->window_node->children->next->data, allocation, spacing); + return win; + } + if (child2.width == 0 || child2.height == 0) { + allocate_recurse(win->window_node->children->data, allocation, spacing); + return win; + } + /* Recurse */ winid_t arrange1 = allocate_recurse(win->window_node->children->data, &child1, spacing); winid_t arrange2 = allocate_recurse(win->window_node->children->next->data, &child2, spacing); diff --git a/tests/meson.build b/tests/meson.build index 3f0520f..77897ff 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -27,6 +27,10 @@ glulxercise_runner = executable('glulxercise-runner', 'glulxercise-runner.c', 'keycode.c', include_directories: top_include, link_with: libchimara, dependencies: [gmodule, gtk]) +reftest_runner = executable('reftest-runner', + 'reftest-runner.c', 'reftest-compare.c', + include_directories: top_include, link_with: libchimara, + dependencies: [gmodule, gtk]) shared_module('first', 'first.c', name_prefix: '', include_directories: top_include, @@ -80,6 +84,23 @@ foreach t : unit_tests protocol: 'tap', env: test_env) endforeach +reftests = [ + 'zero-height-window', + 'zero-width-window', +] + +reftest_deps = [] +foreach t : reftests + plugin = shared_module(t, 'reftest/@0@.c'.format(t), name_prefix: '', + include_directories: [top_include, '../libchimara'], + link_args: plugin_link_args, link_depends: plugin_link_depends) + reftest_deps += plugin +endforeach + +reftest_dir = join_paths(meson.current_source_dir(), 'reftest') +test('reftests', reftest_runner, args: [reftest_dir, meson.current_build_dir()], + depends: reftest_deps, suite: 'reftest', protocol: 'tap', env: test_env) + glulxercise_tests = [ 'abbrevtest', 'abbrevtest-e', diff --git a/tests/reftest-compare.c b/tests/reftest-compare.c new file mode 100644 index 0000000..bdae12f --- /dev/null +++ b/tests/reftest-compare.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 Red Hat Inc. + * + * Author: + * Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +static void +get_surface_size(cairo_surface_t *surface, int *width, int *height) +{ + cairo_t *cr = cairo_create(surface); + + GdkRectangle area; + if (!gdk_cairo_get_clip_rectangle(cr, &area)) + g_assert_not_reached (); + + g_assert(area.x == 0 && area.y == 0); + g_assert(area.width > 0 && area.height > 0); + + cairo_destroy(cr); + + *width = area.width; + *height = area.height; +} + +static cairo_surface_t * +coerce_surface_for_comparison(cairo_surface_t *surface, int width, int height) +{ + cairo_surface_t *coerced = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cr = cairo_create(coerced); + + cairo_set_source_surface(cr, surface, 0, 0); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + + cairo_destroy(cr); + + g_assert(cairo_surface_status(coerced) == CAIRO_STATUS_SUCCESS); + + return coerced; +} + +/* Compares two CAIRO_FORMAT_ARGB32 buffers, returning NULL if the buffers are + * equal or a surface containing a diff between the two surfaces. + * + * This function should be rewritten to compare all formats supported by + * cairo_format_t instead of taking a mask as a parameter. + * + * This function is originally from cairo:test/buffer-diff.c. + * Copyright © 2004 Richard D. Worth + */ +static cairo_surface_t * +buffer_diff_core(const uint8_t *buf_a, int stride_a, const uint8_t *buf_b, int stride_b, int width, int height) +{ + uint8_t *buf_diff = NULL; + int stride_diff = 0; + cairo_surface_t *diff = NULL; + + for (int y = 0; y < height; y++) { + const uint32_t *row_a = (const uint32_t *) (buf_a + y * stride_a); + const uint32_t *row_b = (const uint32_t *) (buf_b + y * stride_b); + uint32_t *row = (uint32_t *) (buf_diff + y * stride_diff); + + for (int x = 0; x < width; x++) { + /* check if the pixels are the same */ + if (row_a[x] == row_b[x]) + continue; + + if (diff == NULL) { + diff = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); + g_assert(cairo_surface_status(diff) == CAIRO_STATUS_SUCCESS); + buf_diff = cairo_image_surface_get_data(diff); + stride_diff = cairo_image_surface_get_stride(diff); + row = (uint32_t *) (buf_diff + y * stride_diff); + } + + /* calculate a difference value for all 4 channels */ + uint32_t diff_pixel = 0; + for (int channel = 0; channel < 4; channel++) { + int value_a = (row_a[x] >> (channel * 8)) & 0xff; + int value_b = (row_b[x] >> (channel * 8)) & 0xff; + + unsigned diff = ABS(value_a - value_b); + diff *= 4; /* emphasize */ + if (diff) + diff += 128; /* make sure it's visible */ + if (diff > 255) + diff = 255; + diff_pixel |= diff << (channel * 8); + } + + if ((diff_pixel & 0x00ffffff) == 0) { + /* alpha only difference, convert to luminance */ + uint8_t alpha = diff_pixel >> 24; + diff_pixel = alpha * 0x010101; + } + + row[x] = diff_pixel; + } + } + + return diff; +} + +cairo_surface_t * +reftest_compare_surfaces(cairo_surface_t *surface1, cairo_surface_t *surface2) +{ + int w1, h1, w2, h2; + get_surface_size(surface1, &w1, &h1); + get_surface_size(surface2, &w2, &h2); + int w = MAX(w1, w2); + int h = MAX(h1, h2); + + cairo_surface_t *test_s1 = coerce_surface_for_comparison(surface1, w, h); + cairo_surface_t *test_s2 = coerce_surface_for_comparison(surface2, w, h); + + cairo_surface_t *diff = buffer_diff_core(cairo_image_surface_get_data(test_s1), + cairo_image_surface_get_stride(test_s1), + cairo_image_surface_get_data(test_s2), + cairo_image_surface_get_stride(test_s2), + w, h); + + cairo_surface_destroy(test_s1); + cairo_surface_destroy(test_s2); + + return diff; +} + diff --git a/tests/reftest-runner.c b/tests/reftest-runner.c new file mode 100644 index 0000000..0e2e1c5 --- /dev/null +++ b/tests/reftest-runner.c @@ -0,0 +1,221 @@ +#include "config.h" + +#include + +#include +#include +#include +#include + +/* in reftest-compare.c */ +extern cairo_surface_t *reftest_compare_surfaces(cairo_surface_t *, cairo_surface_t *); + +static const char *css = "\ +* {\ + all: unset;\ +}\ +window{\ + background-color: green;\ +}\ +scrolledwindow {\ + border: 10px red solid;\ + margin: 5px;\ + padding: 5px;\ + background-color: blue;\ +}"; + +static gboolean write_mode = FALSE; +static char **rest_args = NULL; +static GFile *source_dir = NULL; +static GFile *build_dir = NULL; + +static const GOptionEntry test_args[] = { + { "write", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &write_mode, "Write reference PNGs" }, + { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &rest_args, "SOURCEDIR BUILDDIR" }, + { NULL } +}; + +static void +parse_command_line(int *argc, char ***argv) +{ + g_autoptr(GError) error = NULL; + + g_autoptr(GOptionContext) context = g_option_context_new("SOURCEDIR BUILDDIR - run reftests"); + g_option_context_add_main_entries(context, test_args, NULL); + + if (!g_option_context_parse(context, argc, argv, &error)) + g_error("option parsing failed: %s", error->message); + + gtk_init(argc, argv); + + if (g_strv_length(rest_args) < 2) + g_error("Need source dir and build dir"); +} + +typedef struct { + GtkWidget *window; + GtkWidget *glk; + const char *test_name; + bool success : 1; +} TestData; + +static cairo_surface_t * +load_image(const char *test_name, const char *ext) +{ + g_autofree char *png_name = g_strconcat(test_name, ext, NULL); + g_autoptr(GFile) png = g_file_get_child(source_dir, png_name); + g_autofree char *path = g_file_get_path(png); + + cairo_surface_t *image = cairo_image_surface_create_from_png(path); + cairo_status_t ok = cairo_surface_status(image); + if (ok != CAIRO_STATUS_SUCCESS) + g_error("Error reading reftest file: %s", cairo_status_to_string(ok)); + + return image; +} + +static void +save_image(GFile *dir, const char *test_name, const char *ext, cairo_surface_t *image) +{ + g_autofree char *png_name = g_strconcat(test_name, ext, NULL); + g_autoptr(GFile) png = g_file_get_child(dir, png_name); + g_autofree char *path = g_file_get_path(png); + + cairo_status_t ok = cairo_surface_write_to_png(image, path); + if (ok != CAIRO_STATUS_SUCCESS) + g_error("Error writing reftest file: %s", cairo_status_to_string(ok)); + + g_print("# wrote %s\n", path); +} + +static void +on_glk_ready(ChimaraGlk *glk, TestData *data) +{ + cairo_surface_t *real_image = gtk_offscreen_window_get_surface(GTK_OFFSCREEN_WINDOW(data->window)); + + g_autofree char *png_name = g_strdup_printf("%s.png", data->test_name); + g_autoptr(GFile) png = g_file_get_child(source_dir, png_name); + g_autofree char *path = g_file_get_path(png); + + if (write_mode) { + save_image(source_dir, data->test_name, ".png", real_image); + } else { + cairo_surface_t *reference_image = load_image(data->test_name, ".png"); + + cairo_surface_t *diff_image = reftest_compare_surfaces(real_image, reference_image); + + if (diff_image) { + save_image(build_dir, data->test_name, ".actual.png", real_image); + save_image(build_dir, data->test_name, ".diff.png", diff_image); + cairo_surface_destroy(diff_image); + } else { + data->success = true; + } + + cairo_surface_destroy(reference_image); + } + + gtk_main_quit(); +} + +static char * +run_reftest(const char *test_name) +{ + g_autoptr(GError) error = NULL; + + TestData data; + data.window = gtk_offscreen_window_new(); + data.glk = chimara_glk_new(); + data.test_name = test_name; + data.success = false; + + gtk_widget_set_size_request(data.glk, 400, 400); + chimara_glk_set_spacing(CHIMARA_GLK(data.glk), 5); + gtk_container_add(GTK_CONTAINER(data.window), data.glk); + + g_autoptr(GtkCssProvider) border_provider = gtk_css_provider_new(); + if (!gtk_css_provider_load_from_data(border_provider, css, -1, &error)) { + gtk_widget_destroy(data.window); + return g_strdup_printf("CSS error: %s", error->message); + } + + gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), + GTK_STYLE_PROVIDER(border_provider), GTK_STYLE_PROVIDER_PRIORITY_USER); + + gtk_widget_show_all(data.window); + + g_signal_connect(data.glk, "waiting", G_CALLBACK(on_glk_ready), &data); + + g_autofree char *plugin_name = g_strdup_printf("%s.so", test_name); + g_autoptr(GFile) plugin = g_file_get_child(build_dir, plugin_name); + if (!chimara_glk_run_file(CHIMARA_GLK(data.glk), plugin, 0, NULL, &error)) { + gtk_widget_destroy(data.window); + return g_strdup_printf("Error starting Glk program: %s", error->message); + } + + gtk_main(); + + chimara_glk_stop(CHIMARA_GLK(data.glk)); + chimara_glk_wait(CHIMARA_GLK(data.glk)); + + gtk_widget_destroy(data.window); + + if (!data.success) + return g_strdup("Test didn't match reference image"); + + return NULL; +} + +int +main(int argc, char *argv[]) +{ + GError *error = NULL; + + /* Use cairo image surface for rendering, to avoid GPU scaling */ + g_setenv("GDK_RENDERING", "image", FALSE); + + parse_command_line(&argc, &argv); + + source_dir = g_file_new_for_commandline_arg(rest_args[0]); + build_dir = g_file_new_for_commandline_arg(rest_args[1]); + g_strfreev(rest_args); + + g_autoptr(GFileEnumerator) dir = g_file_enumerate_children(source_dir, "standard::*", + G_FILE_QUERY_INFO_NONE, NULL, &error); + if (!dir) { + g_print("Bail out! Can't open source dir: %s\n", error->message); + return 77; + } + + size_t total = 0; + + while (true) { + GFile *file; + if (!g_file_enumerator_iterate(dir, NULL, &file, NULL, &error)) { + g_print("Bail out! Can't read source dir: %s\n", error->message); + return 77; + } + if (!file) + break; + + g_autofree char *basename = g_file_get_basename(file); + if (!g_str_has_suffix(basename, ".c")) + continue; + + total++; + + g_autofree char *root = g_strndup(basename, strlen(basename) - 2); + g_autofree char *fail_reason = run_reftest(root); + if (fail_reason) + g_print("not ok %zu %s - %s\n", total, root, fail_reason); + else + g_print("ok %zu %s\n", total, root); + } + + g_print("1..%zu\n", total); + + g_object_unref(source_dir); + g_object_unref(build_dir); + + return 0; +} diff --git a/tests/reftest/zero-height-window.c b/tests/reftest/zero-height-window.c new file mode 100644 index 0000000..95afcf5 --- /dev/null +++ b/tests/reftest/zero-height-window.c @@ -0,0 +1,32 @@ +#include +#include + +#include "glk.h" + +static void +ready_for_snapshot(winid_t win) +{ + glk_tick(); + glk_request_timer_events(100); + event_t ev; + while (true) { + glk_select(&ev); + switch (ev.type) { + case evtype_Timer: + glk_request_char_event(win); + break; + case evtype_CharInput: + return; + } + } +} + +void +glk_main(void) +{ + winid_t win1 = glk_window_open(NULL, 0, 0, wintype_TextBuffer, 1); + winid_t win2 = glk_window_open(win1, winmethod_Above | winmethod_Proportional | winmethod_Border, 0, wintype_TextBuffer, 2); + ready_for_snapshot(win1); + glk_window_close(win2, NULL); + glk_window_close(win1, NULL); +} diff --git a/tests/reftest/zero-height-window.png b/tests/reftest/zero-height-window.png new file mode 100644 index 0000000..bda0941 Binary files /dev/null and b/tests/reftest/zero-height-window.png differ diff --git a/tests/reftest/zero-width-window.c b/tests/reftest/zero-width-window.c new file mode 100644 index 0000000..cb99350 --- /dev/null +++ b/tests/reftest/zero-width-window.c @@ -0,0 +1,32 @@ +#include +#include + +#include "glk.h" + +static void +ready_for_snapshot(winid_t win) +{ + glk_tick(); + glk_request_timer_events(100); + event_t ev; + while (true) { + glk_select(&ev); + switch (ev.type) { + case evtype_Timer: + glk_request_char_event(win); + break; + case evtype_CharInput: + return; + } + } +} + +void +glk_main(void) +{ + winid_t win1 = glk_window_open(NULL, 0, 0, wintype_TextBuffer, 1); + winid_t win2 = glk_window_open(win1, winmethod_Left | winmethod_Proportional | winmethod_Border, 0, wintype_TextBuffer, 2); + ready_for_snapshot(win1); + glk_window_close(win2, NULL); + glk_window_close(win1, NULL); +} diff --git a/tests/reftest/zero-width-window.png b/tests/reftest/zero-width-window.png new file mode 100644 index 0000000..bda0941 Binary files /dev/null and b/tests/reftest/zero-width-window.png differ