1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2007-2012 Kouhei Sutou <kou@clear-code.com>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif /* HAVE_CONFIG_H */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <glib.h>
27 #include <glib/gstdio.h>
28 #include <glib/gi18n-lib.h>
29 #include <gmodule.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
32
33 #include <cutter/cut-module-impl.h>
34 #include <cutter/cut-listener.h>
35 #include <cutter/cut-ui.h>
36 #include <cutter/cut-utils.h>
37 #include <cutter/cut-test-result.h>
38 #include <cutter/cut-test.h>
39 #include <cutter/cut-test-case.h>
40 #include <cutter/cut-verbose-level.h>
41 #include <cutter/cut-enum-types.h>
42 #include <cutter/cut-pipeline.h>
43 #include <cutter/cut-backtrace-entry.h>
44 #include <cutter/cut-logger.h>
45
46 #define CUT_TYPE_GTK_UI cut_type_gtk_ui
47 #define CUT_GTK_UI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUT_TYPE_GTK_UI, CutGtkUI))
48 #define CUT_GTK_UI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUT_TYPE_GTK_UI, CutGtkUIClass))
49 #define CUT_IS_GTK_UI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUT_TYPE_GTK_UI))
50 #define CUT_IS_GTK_UI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUT_TYPE_GTK_UI))
51 #define CUT_GTK_UI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), CUT_TYPE_GTK_UI, CutGtkUIClass))
52
53 typedef struct _CutGtkUI CutGtkUI;
54 typedef struct _CutGtkUIClass CutGtkUIClass;
55
56 typedef struct _RowInfo RowInfo;
57 struct _RowInfo
58 {
59 RowInfo *parent_row_info;
60 CutGtkUI *ui;
61 gchar *path;
62 guint n_tests;
63 guint n_completed_tests;
64 gint pulse;
65 guint update_pulse_id;
66 CutTestResultStatus status;
67 };
68
69 typedef struct _TestCaseRowInfo
70 {
71 RowInfo row_info;
72 CutTestCase *test_case;
73 } TestCaseRowInfo;
74
75 typedef struct _TestIteratorRowInfo
76 {
77 RowInfo row_info;
78 TestCaseRowInfo *test_case_row_info;
79 CutTestIterator *test_iterator;
80 } TestIteratorRowInfo;
81
82 typedef struct TestRowInfo
83 {
84 RowInfo row_info;
85 TestCaseRowInfo *test_case_row_info;
86 CutTest *test;
87 } TestRowInfo;
88
89 typedef struct IteratedTestRowInfo
90 {
91 RowInfo row_info;
92 TestIteratorRowInfo *test_iterator_row_info;
93 CutIteratedTest *iterated_test;
94 gchar *data_name;
95 } IteratedTestRowInfo;
96
97 struct _CutGtkUI
98 {
99 GObject object;
100
101 GtkWidget *window;
102 GtkProgressBar *progress_bar;
103 GtkTreeView *tree_view;
104 GtkTreeStore *logs;
105 GtkStatusbar *statusbar;
106 GtkLabel *summary;
107 GtkWidget *cancel_or_restart_button;
108
109 CutRunContext *run_context;
110
111 gboolean running;
112 guint n_tests;
113 guint n_completed_tests;
114
115 CutTestResultStatus status;
116 };
117
118 struct _CutGtkUIClass
119 {
120 GObjectClass parent_class;
121 };
122
123 enum
124 {
125 PROP_0
126 };
127
128 enum
129 {
130 COLUMN_COLOR,
131 COLUMN_TEST_STATUS,
132 COLUMN_STATUS_ICON,
133 COLUMN_PROGRESS_TEXT,
134 COLUMN_PROGRESS_VALUE,
135 COLUMN_PROGRESS_PULSE,
136 COLUMN_PROGRESS_VISIBLE,
137 COLUMN_NAME,
138 COLUMN_DESCRIPTION,
139 N_COLUMN
140 };
141
142 static GType cut_type_gtk_ui = 0;
143 static GObjectClass *parent_class;
144
145 static void dispose (GObject *object);
146 static void set_property (GObject *object,
147 guint prop_id,
148 const GValue *value,
149 GParamSpec *pspec);
150 static void get_property (GObject *object,
151 guint prop_id,
152 GValue *value,
153 GParamSpec *pspec);
154
155 static void attach_to_run_context (CutListener *listener,
156 CutRunContext *run_context);
157 static void detach_from_run_context (CutListener *listener,
158 CutRunContext *run_context);
159 static gboolean run (CutUI *ui,
160 CutRunContext *run_context);
161
162 static void
class_init(CutGtkUIClass * klass)163 class_init (CutGtkUIClass *klass)
164 {
165 GObjectClass *gobject_class;
166
167 parent_class = g_type_class_peek_parent(klass);
168
169 gobject_class = G_OBJECT_CLASS(klass);
170
171 gobject_class->dispose = dispose;
172 gobject_class->set_property = set_property;
173 gobject_class->get_property = get_property;
174 }
175
176 static void
setup_progress_bar(GtkBox * box,CutGtkUI * ui)177 setup_progress_bar (GtkBox *box, CutGtkUI *ui)
178 {
179 GtkWidget *progress_bar;
180
181 progress_bar = gtk_progress_bar_new();
182 gtk_box_pack_start(box, progress_bar, TRUE, TRUE, 0);
183
184 ui->progress_bar = GTK_PROGRESS_BAR(progress_bar);
185 gtk_progress_bar_set_pulse_step(ui->progress_bar, 0.01);
186 }
187
188 static void
push_message(CutGtkUI * ui,const gchar * context,const gchar * format,...)189 push_message (CutGtkUI *ui, const gchar *context, const gchar *format, ...)
190 {
191 guint context_id;
192 gchar *message;
193 va_list args;
194
195 context_id = gtk_statusbar_get_context_id(ui->statusbar, context);
196 va_start(args, format);
197 message = g_strdup_vprintf(format, args);
198 va_end(args);
199 gtk_statusbar_push(ui->statusbar, context_id, message);
200 g_free(message);
201 }
202
203 static void
pop_message(CutGtkUI * ui,const gchar * context)204 pop_message (CutGtkUI *ui, const gchar *context)
205 {
206 guint context_id;
207
208 context_id = gtk_statusbar_get_context_id(ui->statusbar, context);
209 gtk_statusbar_pop(ui->statusbar, context_id);
210 }
211
212 static void
remove_all_messages(CutGtkUI * ui,const gchar * context)213 remove_all_messages (CutGtkUI *ui, const gchar *context)
214 {
215 #if GTK_CHECK_VERSION(2, 22, 0)
216 guint context_id;
217
218 context_id = gtk_statusbar_get_context_id(ui->statusbar, context);
219 gtk_statusbar_remove_all(ui->statusbar, context_id);
220 #endif
221 }
222
223 static void
run_test(CutGtkUI * ui)224 run_test (CutGtkUI *ui)
225 {
226 ui->n_tests = 0;
227 ui->n_completed_tests = 0;
228 ui->status = CUT_TEST_RESULT_SUCCESS;
229
230 gtk_tree_store_clear(ui->logs);
231
232 remove_all_messages(ui, "test");
233 remove_all_messages(ui, "iterated-test");
234 remove_all_messages(ui, "test-suite");
235
236 cut_run_context_add_listener(ui->run_context, CUT_LISTENER(ui));
237 cut_run_context_start_async(ui->run_context);
238 }
239
240 static void
cb_cancel_or_restart(GtkToolButton * button,gpointer data)241 cb_cancel_or_restart (GtkToolButton *button, gpointer data)
242 {
243 CutGtkUI *ui = data;
244
245 if (strcmp(gtk_tool_button_get_stock_id(button), GTK_STOCK_CANCEL) == 0) {
246 gtk_tool_button_set_stock_id(button, GTK_STOCK_REDO);
247 cut_run_context_cancel(ui->run_context);
248 } else {
249 CutRunContext *pipeline;
250
251 gtk_tool_button_set_stock_id(button, GTK_STOCK_CANCEL);
252
253 pipeline = cut_pipeline_new_from_run_context(ui->run_context);
254 g_object_unref(ui->run_context);
255 ui->run_context = pipeline;
256 run_test(ui);
257 }
258 }
259
260 static void
cb_quit(GtkWidget * widget,gpointer data)261 cb_quit (GtkWidget *widget, gpointer data)
262 {
263 CutGtkUI *ui = CUT_GTK_UI(data);
264
265 gtk_widget_destroy(ui->window);
266 }
267
268 static void
cb_run_all_tests(GtkWidget * widget,gpointer data)269 cb_run_all_tests (GtkWidget *widget, gpointer data)
270 {
271 CutGtkUI *ui = CUT_GTK_UI(data);
272
273 cb_cancel_or_restart(GTK_TOOL_BUTTON(ui->cancel_or_restart_button),
274 (gpointer)ui);
275 }
276
277 static void
cb_run_test(GtkWidget * widget,gpointer data)278 cb_run_test (GtkWidget *widget, gpointer data)
279 {
280 }
281
282 #define CUT_WEBSITE_EN "http://cutter.sourceforge.net/"
283 #define CUT_WEBSITE_JA "http://cutter.sourceforge.net/index.html.ja"
284 #define CUT_TUTORIAL_EN "http://cutter.sourceforge.net/reference/tutorial.html"
285 #define CUT_TUTORIAL_JA "http://cutter.sourceforge.net/reference/ja/tutorial.html"
286 #define CUT_REFERENCE_EN "http://cutter.sourceforge.net/reference/"
287 #define CUT_REFERENCE_JA "http://cutter.sourceforge.net/reference/ja/"
288
289 static void
show_uri_fallback(const gchar * uri)290 show_uri_fallback (const gchar *uri)
291 {
292 #ifdef CUT_FALLBACK_BROWSER
293 GError *error = NULL;
294 GPtrArray *args;
295
296 args = g_ptr_array_new();
297 g_ptr_array_add(args, CUT_FALLBACK_BROWSER);
298 g_ptr_array_add(args, (gchar *)uri);
299 g_ptr_array_add(args, NULL);
300
301 g_spawn_async(NULL, (gchar **)args->pdata, NULL,
302 G_SPAWN_SEARCH_PATH, NULL, NULL, NULL,
303 &error);
304 g_ptr_array_unref(args);
305
306 if (!error)
307 return;
308
309 cut_log_warning("[ui][gtk] failed to show URI (fallback): <%s>: %s",
310 uri, error->message);
311 g_error_free(error);
312 #endif
313 }
314
315 static void
show_uri(const gchar * uri)316 show_uri (const gchar *uri)
317 {
318 GError *error = NULL;
319
320 gtk_show_uri(NULL, uri, gtk_get_current_event_time(), &error);
321 if (!error)
322 return;
323
324 cut_log_warning("[ui][gtk] failed to show URI: <%s>: %s",
325 uri, error->message);
326 g_error_free(error);
327
328 show_uri_fallback(uri);
329 }
330
331 static void
cb_show_uri(GtkWidget * widget,gpointer data)332 cb_show_uri (GtkWidget *widget, gpointer data)
333 {
334 GtkAction *action;
335 const gchar *name;
336 const gchar *uri = NULL;
337
338 action = GTK_ACTION(widget);
339 name = gtk_action_get_name(action);
340 if (strcmp(name, "WebsiteEn") == 0) {
341 uri = CUT_WEBSITE_EN;
342 } else if (strcmp(name, "WebsiteJa") == 0) {
343 uri = CUT_WEBSITE_JA;
344 } else if (strcmp(name, "TutorialEn") == 0) {
345 uri = CUT_TUTORIAL_EN;
346 } else if (strcmp(name, "TutorialJa") == 0) {
347 uri = CUT_TUTORIAL_JA;
348 } else if (strcmp(name, "ReferenceEn") == 0) {
349 uri = CUT_REFERENCE_EN;
350 } else if (strcmp(name, "ReferenceJa") == 0) {
351 uri = CUT_REFERENCE_JA;
352 }
353
354 if (!uri)
355 return;
356
357 show_uri(uri);
358 }
359
360 static GtkActionEntry menu_entries[] = {
361 /* name, stock_id, label, accelerator, tooltip, callback */
362 {"FileMenu", NULL, N_("_File"), NULL, "", NULL},
363 {"Quit", GTK_STOCK_QUIT, N_("_Quit"), NULL, "", G_CALLBACK(cb_quit)},
364 {"TestMenu", NULL, N_("_Test"), NULL, "", NULL},
365 {"RunAllTests", GTK_STOCK_REDO, N_("_Run all tests"), NULL, "",
366 G_CALLBACK(cb_run_all_tests)},
367 {"RunTest", GTK_STOCK_REDO, N_("_Run test"), NULL, "",
368 G_CALLBACK(cb_run_test)},
369 {"HelpMenu", NULL, N_("_Help"), NULL, "", NULL},
370 {"Website", NULL, N_("_Website"), NULL, "", NULL},
371 {"WebsiteEn", NULL, N_("_Website English"), NULL, "",
372 G_CALLBACK(cb_show_uri)},
373 {"WebsiteJa", NULL, N_("_Website Japanese"), NULL, "",
374 G_CALLBACK(cb_show_uri)},
375 {"Tutorial", NULL, N_("_Tutorial"), NULL, "", NULL},
376 {"TutorialEn", NULL, N_("_Tutorial English"), NULL, "",
377 G_CALLBACK(cb_show_uri)},
378 {"TutorialJa", NULL, N_("_Tutorial Japanese"), NULL, "",
379 G_CALLBACK(cb_show_uri)},
380 {"Reference", NULL, N_("_Reference"), NULL, "", NULL},
381 {"ReferenceEn", NULL, N_("_Reference English"), NULL, "",
382 G_CALLBACK(cb_show_uri)},
383 {"ReferenceJa", NULL, N_("_Reference Japanese"), NULL, "",
384 G_CALLBACK(cb_show_uri)}
385 };
386
387 static void
load_actions(CutGtkUI * ui,GtkUIManager * ui_manager)388 load_actions (CutGtkUI *ui, GtkUIManager *ui_manager)
389 {
390 GtkActionGroup *action_group;
391 gint i;
392
393 action_group = gtk_action_group_new("MenuActions");
394
395 for (i = 0; i < G_N_ELEMENTS(menu_entries); i++) {
396 gtk_action_group_add_actions(action_group, &(menu_entries[i]), 1, ui);
397 }
398 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
399
400 g_object_unref(action_group);
401 }
402
403 static void
load_ui_file(GtkUIManager * ui_manager)404 load_ui_file (GtkUIManager *ui_manager)
405 {
406 const gchar *ui_data_dir;
407 gchar *ui_file;
408
409 ui_data_dir = g_getenv("CUT_UI_DATA_DIR");
410 if (!ui_data_dir) {
411 #ifdef G_OS_WIN32
412 ui_data_dir = cut_win32_ui_data_dir();
413 #else
414 ui_data_dir = UI_DATA_DIR;
415 #endif
416 }
417
418 ui_file = g_build_filename(ui_data_dir, "gtk-menu.ui", NULL);
419 gtk_ui_manager_add_ui_from_file(ui_manager, ui_file, NULL);
420 g_free(ui_file);
421 }
422
423 static void
setup_menu_bar(GtkBox * box,CutGtkUI * ui)424 setup_menu_bar (GtkBox *box, CutGtkUI *ui)
425 {
426 GtkUIManager *ui_manager;
427 GtkWidget *menu_bar;
428
429 ui_manager = gtk_ui_manager_new();
430 load_actions(ui, ui_manager);
431 load_ui_file(ui_manager);
432 gtk_window_add_accel_group(GTK_WINDOW(ui->window),
433 gtk_ui_manager_get_accel_group(ui_manager));
434
435 menu_bar = gtk_ui_manager_get_widget(ui_manager, "/menu-bar");
436 if (menu_bar) {
437 gtk_box_pack_start(GTK_BOX(box), menu_bar, FALSE, FALSE, 0);
438 }
439
440 g_object_unref(ui_manager);
441 }
442
443 static void
setup_cancel_or_restart_button(GtkToolbar * toolbar,CutGtkUI * ui)444 setup_cancel_or_restart_button (GtkToolbar *toolbar, CutGtkUI *ui)
445 {
446 GtkToolItem *button;
447
448 button = gtk_tool_button_new_from_stock(GTK_STOCK_CANCEL);
449 gtk_toolbar_insert(toolbar, button, -1);
450
451 g_signal_connect(button, "clicked",
452 G_CALLBACK(cb_cancel_or_restart), ui);
453
454 ui->cancel_or_restart_button = GTK_WIDGET(button);
455 }
456
457 static void
setup_top_bar(GtkBox * box,CutGtkUI * ui)458 setup_top_bar (GtkBox *box, CutGtkUI *ui)
459 {
460 GtkWidget *hbox, *toolbar;
461
462 toolbar = gtk_toolbar_new();
463 gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), FALSE);
464 gtk_box_pack_start(GTK_BOX(box), toolbar, FALSE, TRUE, 0);
465 setup_cancel_or_restart_button(GTK_TOOLBAR(toolbar), ui);
466
467 hbox = gtk_hbox_new(FALSE, 0);
468 gtk_box_pack_start(box, hbox, FALSE, TRUE, 0);
469
470 setup_progress_bar(GTK_BOX(hbox), ui);
471 }
472
473 static void
setup_summary_label(GtkBox * box,CutGtkUI * ui)474 setup_summary_label (GtkBox *box, CutGtkUI *ui)
475 {
476 GtkWidget *summary;
477
478 summary = gtk_label_new(_("Ready"));
479 gtk_box_pack_start(box, summary, FALSE, TRUE, 0);
480
481 ui->summary = GTK_LABEL(summary);
482 }
483
484 static void
setup_all_test_result_view_columns(GtkTreeView * tree_view)485 setup_all_test_result_view_columns (GtkTreeView *tree_view)
486 {
487 GtkCellRenderer *renderer;
488 GtkTreeViewColumn *column;
489
490 column = gtk_tree_view_column_new();
491
492 renderer = gtk_cell_renderer_pixbuf_new();
493 gtk_tree_view_column_set_title(column, _("Name"));
494 gtk_tree_view_column_pack_start(column, renderer, FALSE);
495 gtk_tree_view_column_add_attribute(column, renderer,
496 "pixbuf", COLUMN_STATUS_ICON);
497
498 renderer = gtk_cell_renderer_progress_new();
499 gtk_tree_view_column_pack_end(column, renderer, FALSE);
500 gtk_tree_view_column_set_attributes(column, renderer,
501 "text", COLUMN_PROGRESS_TEXT,
502 "value", COLUMN_PROGRESS_VALUE,
503 "pulse", COLUMN_PROGRESS_PULSE,
504 "visible", COLUMN_PROGRESS_VISIBLE,
505 NULL);
506
507 renderer = gtk_cell_renderer_text_new();
508 gtk_tree_view_column_pack_start(column, renderer, TRUE);
509 gtk_tree_view_column_set_attributes(column, renderer,
510 "text", COLUMN_NAME,
511 "background", COLUMN_COLOR,
512 NULL);
513 gtk_tree_view_column_set_sort_column_id(column, COLUMN_NAME);
514 gtk_tree_view_append_column(tree_view, column);
515
516 renderer = g_object_new(GTK_TYPE_CELL_RENDERER_TEXT,
517 "font", "Monospace",
518 NULL);
519 column = gtk_tree_view_column_new_with_attributes(_("Description"), renderer,
520 "text", COLUMN_DESCRIPTION,
521 "background", COLUMN_COLOR,
522 NULL);
523 gtk_tree_view_append_column(tree_view, column);
524 }
525
526 static GtkWidget *
create_all_test_result_view(CutGtkUI * ui)527 create_all_test_result_view (CutGtkUI *ui)
528 {
529 GtkWidget *tree_view, *scrolled_window;
530 GtkTreeStore *tree_store;
531
532 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
533 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
534 GTK_POLICY_AUTOMATIC,
535 GTK_POLICY_AUTOMATIC);
536
537 tree_store = gtk_tree_store_new(N_COLUMN,
538 G_TYPE_STRING,
539 G_TYPE_INT,
540 GDK_TYPE_PIXBUF,
541 G_TYPE_STRING,
542 G_TYPE_INT,
543 G_TYPE_INT,
544 G_TYPE_BOOLEAN,
545 G_TYPE_STRING, G_TYPE_STRING);
546 ui->logs = tree_store;
547
548 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_store));
549 gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
550 ui->tree_view = GTK_TREE_VIEW(tree_view);
551 setup_all_test_result_view_columns(ui->tree_view);
552
553 return scrolled_window;
554 }
555
556 static GtkWidget *
create_partial_test_result_view(CutGtkUI * ui)557 create_partial_test_result_view (CutGtkUI *ui)
558 {
559 GtkWidget *scrolled_window;
560
561 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
562 return scrolled_window;
563 }
564
565 static void
setup_test_result_view(GtkBox * box,CutGtkUI * ui)566 setup_test_result_view (GtkBox *box, CutGtkUI *ui)
567 {
568 GtkWidget *hpaned;
569 GtkWidget *all_test_result_view, *partial_test_result_view;
570
571 hpaned = gtk_hpaned_new();
572
573 all_test_result_view = create_all_test_result_view(ui);
574 partial_test_result_view = create_partial_test_result_view(ui);
575
576 gtk_paned_pack1(GTK_PANED(hpaned), all_test_result_view, TRUE, TRUE);
577 gtk_paned_pack2(GTK_PANED(hpaned), partial_test_result_view, TRUE, TRUE);
578
579 gtk_box_pack_start(box, hpaned, TRUE, TRUE, 0);
580 }
581
582 static void
setup_statusbar(GtkBox * box,CutGtkUI * ui)583 setup_statusbar (GtkBox *box, CutGtkUI *ui)
584 {
585 GtkWidget *statusbar;
586
587 statusbar = gtk_statusbar_new();
588 gtk_box_pack_start(box, statusbar, FALSE, FALSE, 0);
589 ui->statusbar = GTK_STATUSBAR(statusbar);
590 }
591
592 static gboolean
cb_destroy(GtkWidget * widget,gpointer data)593 cb_destroy (GtkWidget *widget, gpointer data)
594 {
595 CutGtkUI *ui = data;
596
597 ui->window = NULL;
598 gtk_main_quit();
599
600 return TRUE;
601 }
602
603 static gboolean
cb_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer data)604 cb_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
605 {
606 if (event->state == GDK_CONTROL_MASK && event->keyval == GDK_q) {
607 gtk_widget_destroy(widget);
608 return TRUE;
609 }
610
611 return FALSE;
612 }
613
614 static void
setup_window(CutGtkUI * ui)615 setup_window (CutGtkUI *ui)
616 {
617 GtkWidget *window, *vbox;
618
619 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
620 gtk_window_set_default_size(GTK_WINDOW(window), 600, 500);
621 gtk_window_set_title(GTK_WINDOW(window), "Cutter");
622
623 g_signal_connect(window, "destroy",
624 G_CALLBACK(cb_destroy), ui);
625 g_signal_connect(window, "key-press-event",
626 G_CALLBACK(cb_key_press_event), NULL);
627
628 ui->window = window;
629
630 vbox = gtk_vbox_new(FALSE, 0);
631 gtk_container_add(GTK_CONTAINER(window), vbox);
632
633 setup_menu_bar(GTK_BOX(vbox), ui);
634 setup_top_bar(GTK_BOX(vbox), ui);
635 setup_summary_label(GTK_BOX(vbox), ui);
636 setup_test_result_view(GTK_BOX(vbox), ui);
637 setup_statusbar(GTK_BOX(vbox), ui);
638
639 gtk_window_set_focus(GTK_WINDOW(window), GTK_WIDGET(ui->tree_view));
640 }
641
642 static void
init(CutGtkUI * ui)643 init (CutGtkUI *ui)
644 {
645 ui->run_context = NULL;
646 ui->n_tests = 0;
647 ui->n_completed_tests = 0;
648 ui->status = CUT_TEST_RESULT_SUCCESS;
649
650 setup_window(ui);
651 }
652
653 static void
listener_init(CutListenerClass * listener)654 listener_init (CutListenerClass *listener)
655 {
656 listener->attach_to_run_context = attach_to_run_context;
657 listener->detach_from_run_context = detach_from_run_context;
658 }
659
660 static void
ui_init(CutUIClass * ui)661 ui_init (CutUIClass *ui)
662 {
663 ui->run = run;
664 }
665
666 static void
register_type(GTypeModule * type_module)667 register_type (GTypeModule *type_module)
668 {
669 static const GTypeInfo info =
670 {
671 sizeof (CutGtkUIClass),
672 (GBaseInitFunc) NULL,
673 (GBaseFinalizeFunc) NULL,
674 (GClassInitFunc) class_init,
675 NULL, /* class_finalize */
676 NULL, /* class_data */
677 sizeof(CutGtkUI),
678 0,
679 (GInstanceInitFunc) init,
680 };
681
682 static const GInterfaceInfo ui_info =
683 {
684 (GInterfaceInitFunc) ui_init,
685 NULL,
686 NULL
687 };
688
689 static const GInterfaceInfo listener_info =
690 {
691 (GInterfaceInitFunc) listener_init,
692 NULL,
693 NULL
694 };
695
696 cut_type_gtk_ui = g_type_module_register_type(type_module,
697 G_TYPE_OBJECT,
698 "CutGtkUI",
699 &info, 0);
700
701 g_type_module_add_interface(type_module,
702 cut_type_gtk_ui,
703 CUT_TYPE_UI,
704 &ui_info);
705
706 g_type_module_add_interface(type_module,
707 cut_type_gtk_ui,
708 CUT_TYPE_LISTENER,
709 &listener_info);
710 }
711
712 G_MODULE_EXPORT GList *
CUT_MODULE_IMPL_INIT(GTypeModule * type_module)713 CUT_MODULE_IMPL_INIT (GTypeModule *type_module)
714 {
715 GList *registered_types = NULL;
716
717 register_type(type_module);
718 if (cut_type_gtk_ui)
719 registered_types =
720 g_list_prepend(registered_types,
721 (gchar *)g_type_name(cut_type_gtk_ui));
722
723 return registered_types;
724 }
725
726 G_MODULE_EXPORT void
CUT_MODULE_IMPL_EXIT(void)727 CUT_MODULE_IMPL_EXIT (void)
728 {
729 }
730
731 G_MODULE_EXPORT GObject *
CUT_MODULE_IMPL_INSTANTIATE(const gchar * first_property,va_list var_args)732 CUT_MODULE_IMPL_INSTANTIATE (const gchar *first_property, va_list var_args)
733 {
734 return g_object_new_valist(CUT_TYPE_GTK_UI, first_property, var_args);
735 }
736
737 static void
dispose(GObject * object)738 dispose (GObject *object)
739 {
740 CutGtkUI *ui = CUT_GTK_UI(object);
741
742 if (ui->logs) {
743 g_object_unref(ui->logs);
744 ui->logs = NULL;
745 }
746 if (ui->window) {
747 gtk_widget_destroy(ui->window);
748 ui->window = NULL;
749 }
750
751 if (ui->run_context) {
752 g_object_unref(ui->run_context);
753 ui->run_context = NULL;
754 }
755
756 G_OBJECT_CLASS(parent_class)->dispose(object);
757 }
758
759 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)760 set_property (GObject *object,
761 guint prop_id,
762 const GValue *value,
763 GParamSpec *pspec)
764 {
765 switch (prop_id) {
766 default:
767 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
768 break;
769 }
770 }
771
772 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)773 get_property (GObject *object,
774 guint prop_id,
775 GValue *value,
776 GParamSpec *pspec)
777 {
778 switch (prop_id) {
779 default:
780 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
781 break;
782 }
783 }
784
785 static const gchar *
status_to_color(CutTestResultStatus status,gboolean only_if_not_success)786 status_to_color (CutTestResultStatus status, gboolean only_if_not_success)
787 {
788 const gchar *color = "white";
789
790 switch (status) {
791 case CUT_TEST_RESULT_SUCCESS:
792 if (only_if_not_success)
793 color = NULL;
794 else
795 color = "light green";
796 break;
797 case CUT_TEST_RESULT_NOTIFICATION:
798 color = "light blue";
799 break;
800 case CUT_TEST_RESULT_OMISSION:
801 color = "blue";
802 break;
803 case CUT_TEST_RESULT_PENDING:
804 color = "yellow";
805 break;
806 case CUT_TEST_RESULT_FAILURE:
807 color = "red";
808 break;
809 case CUT_TEST_RESULT_ERROR:
810 color = "purple";
811 break;
812 case CUT_TEST_RESULT_CRASH:
813 color = "red";
814 break;
815 default:
816 break;
817 }
818
819 return color;
820 }
821
822 static GdkPixbuf *
get_status_icon_by_id(GtkTreeView * tree_view,const gchar * stock_id)823 get_status_icon_by_id (GtkTreeView *tree_view, const gchar *stock_id)
824 {
825 return gtk_widget_render_icon(GTK_WIDGET(tree_view),
826 stock_id, GTK_ICON_SIZE_MENU,
827 NULL);
828 }
829
830 static GdkPixbuf *
get_status_icon(GtkTreeView * tree_view,CutTestResultStatus status)831 get_status_icon (GtkTreeView *tree_view, CutTestResultStatus status)
832 {
833 const gchar *stock_id = "";
834
835 switch (status) {
836 case CUT_TEST_RESULT_SUCCESS:
837 stock_id = GTK_STOCK_YES;
838 break;
839 case CUT_TEST_RESULT_NOTIFICATION:
840 stock_id = GTK_STOCK_DIALOG_WARNING;
841 break;
842 case CUT_TEST_RESULT_OMISSION:
843 stock_id = GTK_STOCK_DIALOG_ERROR;
844 break;
845 case CUT_TEST_RESULT_PENDING:
846 stock_id = GTK_STOCK_DIALOG_ERROR;
847 break;
848 case CUT_TEST_RESULT_FAILURE:
849 stock_id = GTK_STOCK_STOP;
850 break;
851 case CUT_TEST_RESULT_ERROR:
852 stock_id = GTK_STOCK_CANCEL;
853 break;
854 case CUT_TEST_RESULT_CRASH:
855 stock_id = GTK_STOCK_STOP;
856 break;
857 default:
858 stock_id = GTK_STOCK_INFO;
859 break;
860 }
861
862 return get_status_icon_by_id(tree_view, stock_id);
863 }
864
865 static gchar *
generate_summary_message(CutRunContext * run_context)866 generate_summary_message (CutRunContext *run_context)
867 {
868 guint n_tests, n_assertions, n_failures, n_errors;
869 guint n_pendings, n_notifications, n_omissions;
870
871 n_tests = cut_run_context_get_n_tests(run_context);
872 n_assertions = cut_run_context_get_n_assertions(run_context);
873 n_failures = cut_run_context_get_n_failures(run_context);
874 n_errors = cut_run_context_get_n_errors(run_context);
875 n_pendings = cut_run_context_get_n_pendings(run_context);
876 n_notifications = cut_run_context_get_n_notifications(run_context);
877 n_omissions = cut_run_context_get_n_omissions(run_context);
878
879 return g_strdup_printf(_("%d test(s), %d assertion(s), %d failure(s), "
880 "%d error(s), %d pending(s), %d omission(s), "
881 "%d notification(s)"),
882 n_tests, n_assertions, n_failures, n_errors,
883 n_pendings, n_omissions, n_notifications);
884 }
885
886 static gchar *
generate_short_summary_message(CutRunContext * run_context)887 generate_short_summary_message (CutRunContext *run_context)
888 {
889 guint n_tests, n_assertions, n_failures, n_errors;
890 guint n_pendings, n_notifications, n_omissions;
891
892 n_tests = cut_run_context_get_n_tests(run_context);
893 n_assertions = cut_run_context_get_n_assertions(run_context);
894 n_failures = cut_run_context_get_n_failures(run_context);
895 n_errors = cut_run_context_get_n_errors(run_context);
896 n_pendings = cut_run_context_get_n_pendings(run_context);
897 n_notifications = cut_run_context_get_n_notifications(run_context);
898 n_omissions = cut_run_context_get_n_omissions(run_context);
899
900 return g_strdup_printf(_("%dT:%dA:%dF:%d:E:%dP:%dO:%dN"),
901 n_tests, n_assertions, n_failures, n_errors,
902 n_pendings, n_omissions, n_notifications);
903 }
904
905 static void
update_button_sensitive(CutGtkUI * ui)906 update_button_sensitive (CutGtkUI *ui)
907 {
908 GtkToolButton *button;
909
910 button = GTK_TOOL_BUTTON(ui->cancel_or_restart_button);
911 if (ui->running) {
912 gtk_tool_button_set_stock_id(button, GTK_STOCK_CANCEL);
913 } else {
914 gtk_tool_button_set_stock_id(button, GTK_STOCK_REDO);
915 }
916 }
917
918 static void
update_progress_color(GtkProgressBar * bar,CutTestResultStatus status)919 update_progress_color (GtkProgressBar *bar, CutTestResultStatus status)
920 {
921 GtkStyle *style;
922
923 style = gtk_style_new();
924 gdk_color_parse(status_to_color(status, FALSE),
925 &(style->bg[GTK_STATE_PRELIGHT]));
926 gtk_widget_set_style(GTK_WIDGET(bar), style);
927 g_object_unref(style);
928 }
929
930 static void
update_progress_bar(CutGtkUI * ui)931 update_progress_bar (CutGtkUI *ui)
932 {
933 GtkProgressBar *bar;
934 guint n_tests, n_completed_tests;
935
936 n_tests = ui->n_tests;
937 n_completed_tests = ui->n_completed_tests;
938 bar = ui->progress_bar;
939
940 update_progress_color(bar, ui->status);
941
942 if (n_tests > 0) {
943 gdouble fraction;
944 gchar *text;
945
946 fraction = n_completed_tests / (gdouble)n_tests;
947 gtk_progress_bar_set_fraction(ui->progress_bar, fraction);
948
949 text = g_strdup_printf(_("%u/%u (%u%%): %.1fs"),
950 n_completed_tests, n_tests,
951 (guint)(fraction * 100),
952 cut_run_context_get_elapsed(ui->run_context));
953 gtk_progress_bar_set_text(bar, text);
954 g_free(text);
955 } else {
956 gtk_progress_bar_pulse(bar);
957 }
958 }
959
960 static void
cb_ready_test_suite(CutRunContext * run_context,CutTestSuite * test_suite,guint n_test_cases,guint n_tests,CutGtkUI * ui)961 cb_ready_test_suite (CutRunContext *run_context, CutTestSuite *test_suite,
962 guint n_test_cases, guint n_tests, CutGtkUI *ui)
963 {
964 ui->running = TRUE;
965 ui->n_tests = n_tests;
966
967 update_button_sensitive(ui);
968 push_message(ui, "test-suite",
969 _("Starting test suite %s..."),
970 cut_test_get_name(CUT_TEST(test_suite)));
971 }
972
973 static void
update_row_status(RowInfo * row_info)974 update_row_status (RowInfo *row_info)
975 {
976 CutGtkUI *ui;
977 GtkTreeIter iter;
978
979 ui = row_info->ui;
980 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
981 &iter, row_info->path)) {
982 GdkPixbuf *icon;
983 icon = get_status_icon(ui->tree_view, row_info->status);
984 gtk_tree_store_set(ui->logs, &iter,
985 COLUMN_STATUS_ICON, icon,
986 -1);
987 g_object_unref(icon);
988 }
989 }
990
991 static void
free_test_case_row_info(TestCaseRowInfo * info)992 free_test_case_row_info (TestCaseRowInfo *info)
993 {
994 RowInfo *row_info;
995
996 g_object_unref(info->test_case);
997
998 row_info = &(info->row_info);
999 g_object_unref(row_info->ui);
1000 g_free(row_info->path);
1001
1002 g_free(info);
1003 }
1004
1005 static void
disable_progress(RowInfo * row_info)1006 disable_progress (RowInfo *row_info)
1007 {
1008 CutGtkUI *ui;
1009 GtkTreeIter iter;
1010
1011 if (row_info->update_pulse_id) {
1012 g_source_remove(row_info->update_pulse_id);
1013 row_info->update_pulse_id = 0;
1014 }
1015
1016 ui = row_info->ui;
1017 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1018 &iter, row_info->path)) {
1019 gtk_tree_store_set(ui->logs, &iter,
1020 COLUMN_PROGRESS_VISIBLE, FALSE,
1021 COLUMN_PROGRESS_PULSE, -1,
1022 -1);
1023 }
1024 }
1025
1026 static void
free_test_row_info(TestRowInfo * info)1027 free_test_row_info (TestRowInfo *info)
1028 {
1029 RowInfo *row_info;
1030
1031 row_info = &(info->row_info);
1032 disable_progress(row_info);
1033
1034 g_object_unref(info->test);
1035 g_object_unref(row_info->ui);
1036 g_free(row_info->path);
1037
1038 g_free(info);
1039 }
1040
1041 static void
update_status(RowInfo * row_info,CutTestResultStatus status)1042 update_status (RowInfo *row_info, CutTestResultStatus status)
1043 {
1044 RowInfo *parent_row_info;
1045 CutGtkUI *ui;
1046
1047 ui = row_info->ui;
1048 row_info->status = status;
1049
1050 for (parent_row_info = row_info->parent_row_info;
1051 parent_row_info;
1052 parent_row_info = parent_row_info->parent_row_info) {
1053 if (parent_row_info->status < status) {
1054 parent_row_info->status = status;
1055 } else {
1056 break;
1057 }
1058 }
1059
1060 if (ui->status < status)
1061 ui->status = status;
1062 }
1063
1064 static gchar *
append_row(CutGtkUI * ui,const gchar * parent_path,const gchar * name,const gchar * description)1065 append_row (CutGtkUI *ui, const gchar *parent_path,
1066 const gchar *name, const gchar *description)
1067 {
1068 GtkTreeIter iter;
1069 GdkPixbuf *icon;
1070
1071 if (parent_path) {
1072 GtkTreeIter parent_iter;
1073 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1074 &parent_iter,
1075 parent_path);
1076 gtk_tree_store_append(ui->logs, &iter, &parent_iter);
1077 } else {
1078 gtk_tree_store_append(ui->logs, &iter, NULL);
1079 }
1080
1081 icon = get_status_icon_by_id(ui->tree_view, GTK_STOCK_MEDIA_PLAY);
1082 gtk_tree_store_set(ui->logs, &iter,
1083 COLUMN_NAME, name,
1084 COLUMN_DESCRIPTION, description,
1085 COLUMN_PROGRESS_PULSE, -1,
1086 COLUMN_PROGRESS_VISIBLE, TRUE,
1087 COLUMN_STATUS_ICON, icon,
1088 -1);
1089 g_object_unref(icon);
1090
1091 return gtk_tree_model_get_string_from_iter(GTK_TREE_MODEL(ui->logs),
1092 &iter);
1093 }
1094
1095 static void
update_row(CutGtkUI * ui,RowInfo * row_info)1096 update_row (CutGtkUI *ui, RowInfo *row_info)
1097 {
1098 GtkTreeIter iter;
1099
1100 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1101 &iter, row_info->path)) {
1102 gdouble fraction;
1103 gint percent;
1104 gchar *text;
1105 GdkPixbuf *icon;
1106
1107 fraction = row_info->n_completed_tests / (gdouble)(row_info->n_tests);
1108 percent = (gint)(fraction * 100);
1109 text = g_strdup_printf("%d/%d (%d%%)",
1110 row_info->n_completed_tests,
1111 row_info->n_tests, percent);
1112 icon = get_status_icon(ui->tree_view, row_info->status);
1113 gtk_tree_store_set(ui->logs, &iter,
1114 COLUMN_PROGRESS_TEXT, text,
1115 COLUMN_PROGRESS_VALUE, percent,
1116 COLUMN_STATUS_ICON, icon,
1117 COLUMN_COLOR, status_to_color(row_info->status, TRUE),
1118 -1);
1119 g_free(text);
1120 g_object_unref(icon);
1121 }
1122 }
1123
1124 static gboolean
timeout_cb_pulse_test(gpointer data)1125 timeout_cb_pulse_test (gpointer data)
1126 {
1127 RowInfo *row_info = data;
1128 CutGtkUI *ui;
1129 GtkTreeIter iter;
1130
1131 ui = row_info->ui;
1132 row_info->pulse++;
1133 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1134 &iter, row_info->path)) {
1135 gtk_tree_store_set(ui->logs, &iter,
1136 COLUMN_PROGRESS_PULSE, row_info->pulse,
1137 -1);
1138 }
1139 return ui->running;
1140 }
1141
1142 static void
expand_row(CutGtkUI * ui,const gchar * path)1143 expand_row (CutGtkUI *ui, const gchar *path)
1144 {
1145 GtkTreePath *tree_path;
1146
1147 tree_path = gtk_tree_path_new_from_string(path);
1148 gtk_tree_view_expand_to_path(ui->tree_view, tree_path);
1149 gtk_tree_view_scroll_to_cell(ui->tree_view, tree_path, NULL, TRUE, 0, 0.5);
1150 gtk_tree_path_free(tree_path);
1151 }
1152
1153 static void
update_test_row_status(RowInfo * row_info)1154 update_test_row_status (RowInfo *row_info)
1155 {
1156 CutGtkUI *ui;
1157 GtkTreeIter iter;
1158
1159 ui = row_info->ui;
1160
1161 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1162 &iter, row_info->path)) {
1163 GdkPixbuf *icon;
1164 icon = get_status_icon(ui->tree_view, row_info->status);
1165 gtk_tree_store_set(ui->logs, &iter,
1166 COLUMN_STATUS_ICON, icon,
1167 COLUMN_PROGRESS_VISIBLE, FALSE,
1168 COLUMN_COLOR, status_to_color(row_info->status, TRUE),
1169 -1);
1170 g_object_unref(icon);
1171
1172 if (row_info->status != CUT_TEST_RESULT_SUCCESS) {
1173 GtkTreePath *path;
1174 path = gtk_tree_model_get_path(GTK_TREE_MODEL(ui->logs), &iter);
1175 gtk_tree_view_expand_to_path(ui->tree_view, path);
1176 gtk_tree_view_scroll_to_cell(ui->tree_view, path, NULL,
1177 TRUE, 0, 0.5);
1178 gtk_tree_path_free(path);
1179 }
1180 }
1181 }
1182
1183
1184 static void
append_test_result_row_under(CutGtkUI * ui,CutTestResult * result,GtkTreeIter * test_row_iter,GtkTreeIter * result_row_iter)1185 append_test_result_row_under (CutGtkUI *ui, CutTestResult *result,
1186 GtkTreeIter *test_row_iter,
1187 GtkTreeIter *result_row_iter)
1188 {
1189 CutTestResultStatus status;
1190 const GList *node;
1191 GString *name;
1192 const gchar *message;
1193 GdkPixbuf *icon;
1194
1195 status = cut_test_result_get_status(result);
1196 message = cut_test_result_get_message(result);
1197
1198 name = g_string_new(NULL);
1199 for (node = cut_test_result_get_backtrace(result);
1200 node;
1201 node = g_list_next(node)) {
1202 CutBacktraceEntry *entry = node->data;
1203
1204 cut_backtrace_entry_format_string(entry, name);
1205 if (g_list_next(node))
1206 g_string_append(name, "\n");
1207 }
1208
1209 icon = get_status_icon(ui->tree_view, status);
1210
1211 gtk_tree_store_append(ui->logs, result_row_iter, test_row_iter);
1212 gtk_tree_store_set(ui->logs, result_row_iter,
1213 COLUMN_NAME, name->str,
1214 COLUMN_DESCRIPTION, message,
1215 COLUMN_STATUS_ICON, icon,
1216 COLUMN_COLOR, status_to_color(status, TRUE),
1217 -1);
1218 g_string_free(name, TRUE);
1219 g_object_unref(icon);
1220 }
1221
1222 static void
append_test_result_row(RowInfo * row_info,CutTestResult * result)1223 append_test_result_row (RowInfo *row_info, CutTestResult *result)
1224 {
1225 CutGtkUI *ui;
1226 GtkTreeIter test_row_iter;
1227
1228 ui = row_info->ui;
1229 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1230 &test_row_iter, row_info->path)) {
1231 GtkTreePath *path;
1232 GtkTreeIter iter;
1233
1234 append_test_result_row_under(ui, result, &test_row_iter, &iter);
1235
1236 path = gtk_tree_model_get_path(GTK_TREE_MODEL(ui->logs), &iter);
1237 gtk_tree_view_expand_to_path(ui->tree_view, path);
1238 gtk_tree_view_scroll_to_cell(ui->tree_view, path, NULL,
1239 TRUE, 0, 0.5);
1240 gtk_tree_path_free(path);
1241 }
1242
1243 g_object_unref(result);
1244 }
1245
1246 static void
update_summary(CutGtkUI * ui)1247 update_summary (CutGtkUI *ui)
1248 {
1249 gchar *summary, *short_summary, *title;
1250
1251 summary = generate_summary_message(ui->run_context);
1252 gtk_label_set_text(ui->summary, summary);
1253 g_free(summary);
1254
1255 short_summary = generate_short_summary_message(ui->run_context);
1256 title = g_strdup_printf("%s - Cutter", short_summary);
1257 gtk_window_set_title(GTK_WINDOW(ui->window), title);
1258 g_free(short_summary);
1259 g_free(title);
1260 }
1261
1262 static void
cb_pass_assertion(CutRunContext * run_context,CutTest * test,CutTestContext * test_context,gpointer data)1263 cb_pass_assertion (CutRunContext *run_context,
1264 CutTest *test, CutTestContext *test_context,
1265 gpointer data)
1266 {
1267 RowInfo *row_info = data;
1268
1269 /* slow */
1270 if (g_random_int_range(0, 1000) == 0) {
1271 update_summary(row_info->ui);
1272 }
1273 }
1274
1275 static void
cb_success_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1276 cb_success_test (CutRunContext *run_context,
1277 CutTest *test, CutTestContext *context, CutTestResult *result,
1278 gpointer data)
1279 {
1280 RowInfo *row_info = data;
1281
1282 if (row_info->status == -1) {
1283 row_info->status = CUT_TEST_RESULT_SUCCESS;
1284
1285 update_test_row_status(row_info);
1286 }
1287 }
1288
1289 static void
cb_failure_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1290 cb_failure_test (CutRunContext *run_context,
1291 CutTest *test, CutTestContext *context, CutTestResult *result,
1292 gpointer data)
1293 {
1294 RowInfo *row_info = data;
1295
1296 update_status(row_info, CUT_TEST_RESULT_FAILURE);
1297
1298 update_test_row_status(row_info);
1299 append_test_result_row(row_info, result);
1300 }
1301
1302 static void
cb_error_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1303 cb_error_test (CutRunContext *run_context,
1304 CutTest *test, CutTestContext *context, CutTestResult *result,
1305 gpointer data)
1306 {
1307 RowInfo *row_info = data;
1308
1309 update_status(row_info, CUT_TEST_RESULT_ERROR);
1310
1311 update_test_row_status(row_info);
1312 append_test_result_row(row_info, result);
1313 }
1314
1315 static void
cb_pending_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1316 cb_pending_test (CutRunContext *run_context,
1317 CutTest *test, CutTestContext *context, CutTestResult *result,
1318 gpointer data)
1319 {
1320 RowInfo *row_info = data;
1321
1322 update_status(row_info, CUT_TEST_RESULT_PENDING);
1323
1324 update_test_row_status(row_info);
1325 append_test_result_row(row_info, result);
1326 }
1327
1328 static void
cb_notification_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1329 cb_notification_test (CutRunContext *run_context,
1330 CutTest *test, CutTestContext *context,
1331 CutTestResult *result, gpointer data)
1332 {
1333 RowInfo *row_info = data;
1334
1335 update_status(row_info, CUT_TEST_RESULT_NOTIFICATION);
1336
1337 update_test_row_status(row_info);
1338 append_test_result_row(row_info, result);
1339 }
1340
1341 static void
cb_omission_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1342 cb_omission_test (CutRunContext *run_context,
1343 CutTest *test, CutTestContext *context,
1344 CutTestResult *result, gpointer data)
1345 {
1346 RowInfo *row_info = data;
1347
1348 update_status(row_info, CUT_TEST_RESULT_OMISSION);
1349
1350 update_test_row_status(row_info);
1351 append_test_result_row(row_info, result);
1352 }
1353
1354 static void
cb_crash_test(CutRunContext * run_context,CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)1355 cb_crash_test (CutRunContext *run_context,
1356 CutTest *test, CutTestContext *context,
1357 CutTestResult *result, gpointer data)
1358 {
1359 RowInfo *row_info = data;
1360
1361 update_status(row_info, CUT_TEST_RESULT_CRASH);
1362
1363 update_test_row_status(row_info);
1364 append_test_result_row(row_info, result);
1365 }
1366
1367 static void
increment_n_completed_tests(RowInfo * row_info)1368 increment_n_completed_tests (RowInfo *row_info)
1369 {
1370 CutGtkUI *ui;
1371 RowInfo *parent_row_info;
1372
1373 ui = row_info->ui;
1374 for (parent_row_info = row_info->parent_row_info;
1375 parent_row_info;
1376 parent_row_info = parent_row_info->parent_row_info) {
1377 parent_row_info->n_completed_tests++;
1378 update_row(ui, parent_row_info);
1379 }
1380
1381 ui->n_completed_tests++;
1382 update_summary(ui);
1383 }
1384
1385 static void
cb_complete_test(CutRunContext * run_context,CutTest * test,CutTestContext * test_context,gboolean success,gpointer data)1386 cb_complete_test (CutRunContext *run_context,
1387 CutTest *test, CutTestContext *test_context,
1388 gboolean success, gpointer data)
1389 {
1390 RowInfo *row_info;
1391 TestRowInfo *info = data;
1392 CutGtkUI *ui;
1393
1394 row_info = &(info->row_info);
1395 increment_n_completed_tests(row_info);
1396 ui = row_info->ui;
1397 pop_message(ui, "test");
1398 free_test_row_info(info);
1399
1400 update_progress_bar(ui);
1401
1402 #define DISCONNECT(name, user_data) \
1403 g_signal_handlers_disconnect_by_func(run_context, \
1404 G_CALLBACK(cb_ ## name), \
1405 user_data)
1406 DISCONNECT(pass_assertion, row_info);
1407 DISCONNECT(success_test, row_info);
1408 DISCONNECT(failure_test, row_info);
1409 DISCONNECT(error_test, row_info);
1410 DISCONNECT(pending_test, row_info);
1411 DISCONNECT(notification_test, row_info);
1412 DISCONNECT(omission_test, row_info);
1413 DISCONNECT(crash_test, row_info);
1414 DISCONNECT(complete_test, info);
1415 #undef DISCONNECT
1416 }
1417
1418 static void
cb_start_test(CutRunContext * run_context,CutTest * test,CutTestContext * test_context,gpointer data)1419 cb_start_test (CutRunContext *run_context,
1420 CutTest *test,
1421 CutTestContext *test_context, gpointer data)
1422 {
1423 RowInfo *row_info;
1424 TestRowInfo *info;
1425 CutGtkUI *ui;
1426
1427 info = g_new0(TestRowInfo, 1);
1428 info->test_case_row_info = data;
1429 info->test = g_object_ref(test);
1430
1431 row_info = &(info->row_info);
1432 row_info->parent_row_info = &(info->test_case_row_info->row_info);
1433 ui = row_info->parent_row_info->ui;
1434 row_info->ui = g_object_ref(ui);
1435 row_info->status = -1;
1436 row_info->pulse = 0;
1437 row_info->update_pulse_id = 0;
1438 row_info->path = append_row(ui, info->test_case_row_info->row_info.path,
1439 cut_test_get_name(test),
1440 cut_test_get_description(test));
1441
1442 push_message(ui, "test",
1443 _("Running test: %s"), cut_test_get_name(test));
1444 /* Always expand running test case row. Is it OK? */
1445 expand_row(ui, row_info->path);
1446 row_info->update_pulse_id = g_timeout_add(10, timeout_cb_pulse_test,
1447 row_info);
1448
1449
1450 #define CONNECT(name, user_data) \
1451 g_signal_connect(run_context, #name, G_CALLBACK(cb_ ## name), user_data)
1452
1453 CONNECT(pass_assertion, row_info);
1454 CONNECT(success_test, row_info);
1455 CONNECT(failure_test, row_info);
1456 CONNECT(error_test, row_info);
1457 CONNECT(pending_test, row_info);
1458 CONNECT(notification_test, row_info);
1459 CONNECT(omission_test, row_info);
1460 CONNECT(crash_test, row_info);
1461 CONNECT(complete_test, info);
1462 #undef CONNECT
1463 }
1464
1465 static void
free_iterated_test_row_info(IteratedTestRowInfo * info)1466 free_iterated_test_row_info (IteratedTestRowInfo *info)
1467 {
1468 RowInfo *row_info;
1469
1470 row_info = &(info->row_info);
1471 disable_progress(row_info);
1472
1473 g_object_unref(info->iterated_test);
1474 g_free(info->data_name);
1475 g_object_unref(row_info->ui);
1476 g_free(row_info->path);
1477
1478 g_free(info);
1479 }
1480
1481 static void
cb_complete_iterated_test(CutRunContext * run_context,CutIteratedTest * iterated_test,CutTestContext * test_context,gboolean success,gpointer data)1482 cb_complete_iterated_test (CutRunContext *run_context,
1483 CutIteratedTest *iterated_test,
1484 CutTestContext *test_context,
1485 gboolean success, gpointer data)
1486 {
1487 RowInfo *row_info;
1488 IteratedTestRowInfo *info = data;
1489 CutGtkUI *ui;
1490
1491 row_info = &(info->row_info);
1492 ui = row_info->ui;
1493 increment_n_completed_tests(row_info);
1494 pop_message(ui, "iterated-test");
1495 free_iterated_test_row_info(info);
1496
1497 #define DISCONNECT(name, user_data) \
1498 g_signal_handlers_disconnect_by_func(run_context, \
1499 G_CALLBACK(cb_ ## name), \
1500 user_data)
1501 DISCONNECT(pass_assertion, row_info);
1502 DISCONNECT(success_test, row_info);
1503 DISCONNECT(failure_test, row_info);
1504 DISCONNECT(error_test, row_info);
1505 DISCONNECT(pending_test, row_info);
1506 DISCONNECT(notification_test, row_info);
1507 DISCONNECT(omission_test, row_info);
1508 DISCONNECT(crash_test, row_info);
1509 DISCONNECT(complete_iterated_test, info);
1510 #undef DISCONNECT
1511 }
1512
1513 static void
cb_start_iterated_test(CutRunContext * run_context,CutIteratedTest * iterated_test,CutTestContext * test_context,gpointer data)1514 cb_start_iterated_test (CutRunContext *run_context,
1515 CutIteratedTest *iterated_test,
1516 CutTestContext *test_context, gpointer data)
1517 {
1518 RowInfo *row_info;
1519 IteratedTestRowInfo *info;
1520 CutGtkUI *ui;
1521
1522 info = g_new0(IteratedTestRowInfo, 1);
1523 info->test_iterator_row_info = data;
1524 info->iterated_test = g_object_ref(iterated_test);
1525 if (cut_test_context_have_data(test_context)) {
1526 CutTestData *data;
1527 data = cut_test_context_get_current_data(test_context);
1528 info->data_name = g_strdup(cut_test_data_get_name(data));
1529 }
1530 if (!info->data_name) {
1531 info->data_name = g_strdup(cut_test_get_name(CUT_TEST(iterated_test)));
1532 }
1533 row_info = &(info->row_info);
1534 row_info->parent_row_info = &(info->test_iterator_row_info->row_info);
1535 ui = row_info->parent_row_info->ui;
1536 row_info->ui = g_object_ref(ui);
1537 row_info->status = -1;
1538 row_info->pulse = 0;
1539 row_info->update_pulse_id = 0;
1540 row_info->path = append_row(ui,
1541 row_info->parent_row_info->path,
1542 info->data_name, NULL);
1543
1544 push_message(ui,
1545 "iterated-test",
1546 _("Running iterated test: %s (%s)"),
1547 cut_test_get_name(CUT_TEST(info->iterated_test)),
1548 info->data_name);
1549 /* Always expand running test case row. Is it OK? */
1550 expand_row(ui, row_info->path);
1551
1552 #define CONNECT(name, user_data) \
1553 g_signal_connect(run_context, #name, G_CALLBACK(cb_ ## name), user_data)
1554
1555 CONNECT(pass_assertion, row_info);
1556 CONNECT(success_test, row_info);
1557 CONNECT(failure_test, row_info);
1558 CONNECT(error_test, row_info);
1559 CONNECT(pending_test, row_info);
1560 CONNECT(notification_test, row_info);
1561 CONNECT(omission_test, row_info);
1562 CONNECT(crash_test, row_info);
1563 CONNECT(complete_iterated_test, info);
1564 #undef CONNECT
1565 }
1566
1567 static void
collapse_test_iterator_row(TestIteratorRowInfo * info)1568 collapse_test_iterator_row (TestIteratorRowInfo *info)
1569 {
1570 RowInfo *row_info;
1571 CutGtkUI *ui;
1572 GtkTreeIter iter;
1573
1574 row_info = &(info->row_info);
1575 ui = row_info->ui;
1576
1577 if (row_info->status == CUT_TEST_RESULT_SUCCESS &&
1578 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1579 &iter, row_info->path)) {
1580
1581 GtkTreePath *path;
1582 path = gtk_tree_model_get_path(GTK_TREE_MODEL(ui->logs), &iter);
1583 gtk_tree_view_collapse_row(ui->tree_view, path);
1584 gtk_tree_path_free(path);
1585 }
1586 }
1587
1588 static void
free_test_iterator_row_info(TestIteratorRowInfo * info)1589 free_test_iterator_row_info (TestIteratorRowInfo *info)
1590 {
1591 RowInfo *row_info;
1592 CutGtkUI *ui;
1593
1594 row_info = &(info->row_info);
1595 ui = row_info->ui;
1596
1597 g_object_unref(info->test_iterator);
1598 g_object_unref(ui);
1599 g_free(row_info->path);
1600
1601 g_free(info);
1602 }
1603
1604 static void
cb_complete_test_iterator(CutRunContext * run_context,CutTestCase * test_case,gboolean success,gpointer data)1605 cb_complete_test_iterator (CutRunContext *run_context,
1606 CutTestCase *test_case, gboolean success,
1607 gpointer data)
1608 {
1609 RowInfo *row_info;
1610 TestIteratorRowInfo *info = data;
1611
1612 row_info = &(info->row_info);
1613 update_summary(row_info->ui);
1614 update_row_status(row_info);
1615 collapse_test_iterator_row(info);
1616 free_test_iterator_row_info(info);
1617
1618 g_signal_handlers_disconnect_by_func(run_context,
1619 G_CALLBACK(cb_start_iterated_test),
1620 data);
1621 g_signal_handlers_disconnect_by_func(run_context,
1622 G_CALLBACK(cb_complete_test_iterator),
1623 data);
1624 }
1625
1626 static void
cb_ready_test_iterator(CutRunContext * run_context,CutTestIterator * test_iterator,guint n_tests,TestCaseRowInfo * test_case_row_info)1627 cb_ready_test_iterator (CutRunContext *run_context,
1628 CutTestIterator *test_iterator, guint n_tests,
1629 TestCaseRowInfo *test_case_row_info)
1630 {
1631 RowInfo *row_info;
1632 TestIteratorRowInfo *info;
1633 CutGtkUI *ui;
1634
1635 info = g_new0(TestIteratorRowInfo, 1);
1636 info->test_case_row_info = test_case_row_info;
1637 info->test_iterator = g_object_ref(test_iterator);
1638 row_info = &(info->row_info);
1639 row_info->parent_row_info = &(test_case_row_info->row_info);
1640 ui = row_info->parent_row_info->ui;
1641 row_info->ui = g_object_ref(ui);
1642 row_info->n_tests = n_tests;
1643 row_info->n_completed_tests = 0;
1644 row_info->status = CUT_TEST_RESULT_SUCCESS;
1645
1646 ui->n_tests += n_tests - 1;
1647 row_info->parent_row_info->n_tests += n_tests - 1;
1648
1649 row_info->path = append_row(ui,
1650 row_info->parent_row_info->path,
1651 cut_test_get_name(CUT_TEST(test_iterator)),
1652 cut_test_get_description(CUT_TEST(test_iterator)));
1653
1654 g_signal_connect(run_context, "start-iterated-test",
1655 G_CALLBACK(cb_start_iterated_test), info);
1656 g_signal_connect(run_context, "complete-test-iterator",
1657 G_CALLBACK(cb_complete_test_iterator), info);
1658 }
1659
1660 static void
collapse_test_case_row(TestCaseRowInfo * info)1661 collapse_test_case_row (TestCaseRowInfo *info)
1662 {
1663 CutGtkUI *ui;
1664 GtkTreeIter iter;
1665
1666 ui = info->row_info.ui;
1667
1668 if (info->row_info.status == CUT_TEST_RESULT_SUCCESS &&
1669 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(ui->logs),
1670 &iter, info->row_info.path)) {
1671
1672 GtkTreePath *path;
1673 path = gtk_tree_model_get_path(GTK_TREE_MODEL(ui->logs), &iter);
1674 gtk_tree_view_collapse_row(ui->tree_view, path);
1675 gtk_tree_path_free(path);
1676 }
1677 }
1678
1679 static void
cb_complete_test_case(CutRunContext * run_context,CutTestCase * test_case,gboolean success,gpointer data)1680 cb_complete_test_case (CutRunContext *run_context,
1681 CutTestCase *test_case, gboolean success,
1682 gpointer data)
1683 {
1684 RowInfo *row_info;
1685 TestCaseRowInfo *info = data;
1686
1687 row_info = &(info->row_info);
1688 update_summary(row_info->ui);
1689 update_row_status(row_info);
1690 collapse_test_case_row(info);
1691 free_test_case_row_info(info);
1692 g_signal_handlers_disconnect_by_func(run_context,
1693 G_CALLBACK(cb_start_test),
1694 data);
1695 g_signal_handlers_disconnect_by_func(run_context,
1696 G_CALLBACK(cb_ready_test_iterator),
1697 data);
1698 g_signal_handlers_disconnect_by_func(run_context,
1699 G_CALLBACK(cb_complete_test_case),
1700 data);
1701 }
1702
1703 static void
cb_ready_test_case(CutRunContext * run_context,CutTestCase * test_case,guint n_tests,CutGtkUI * ui)1704 cb_ready_test_case (CutRunContext *run_context, CutTestCase *test_case,
1705 guint n_tests, CutGtkUI *ui)
1706 {
1707 RowInfo *row_info;
1708 TestCaseRowInfo *info;
1709
1710 info = g_new0(TestCaseRowInfo, 1);
1711 info->test_case = g_object_ref(test_case);
1712 row_info = &(info->row_info);
1713 row_info->parent_row_info = NULL;
1714 row_info->ui = g_object_ref(ui);
1715 row_info->n_tests = n_tests;
1716 row_info->n_completed_tests = 0;
1717 row_info->status = CUT_TEST_RESULT_SUCCESS;
1718
1719 row_info->path = append_row(row_info->ui, NULL,
1720 cut_test_get_name(CUT_TEST(test_case)),
1721 cut_test_get_description(CUT_TEST(test_case)));
1722
1723 g_signal_connect(run_context, "start-test",
1724 G_CALLBACK(cb_start_test), info);
1725 g_signal_connect(run_context, "ready-test-iterator",
1726 G_CALLBACK(cb_ready_test_iterator), info);
1727 g_signal_connect(run_context, "complete-test-case",
1728 G_CALLBACK(cb_complete_test_case), info);
1729 }
1730
1731 static void
cb_complete_run(CutRunContext * run_context,gboolean success,CutGtkUI * ui)1732 cb_complete_run (CutRunContext *run_context, gboolean success, CutGtkUI *ui)
1733 {
1734 ui->running = FALSE;
1735 update_button_sensitive(ui);
1736 }
1737
1738 static void
cb_complete_test_suite(CutRunContext * run_context,CutTestSuite * test_suite,gboolean success,CutGtkUI * ui)1739 cb_complete_test_suite (CutRunContext *run_context,
1740 CutTestSuite *test_suite,
1741 gboolean success,
1742 CutGtkUI *ui)
1743 {
1744 gchar *summary;
1745
1746 pop_message(ui, "test-suite");
1747
1748 summary = generate_summary_message(ui->run_context);
1749 push_message(ui, "test-suite",
1750 _("Finished in %0.1f seconds: %s"),
1751 cut_run_context_get_elapsed(ui->run_context),
1752 summary);
1753 g_free(summary);
1754 }
1755
1756 static void
cb_error(CutRunContext * run_context,GError * error,CutGtkUI * ui)1757 cb_error (CutRunContext *run_context, GError *error, CutGtkUI *ui)
1758 {
1759 g_print("SystemError: %s:%d: %s\n",
1760 g_quark_to_string(error->domain),
1761 error->code,
1762 error->message);
1763 }
1764
1765 static void
connect_to_run_context(CutGtkUI * ui,CutRunContext * run_context)1766 connect_to_run_context (CutGtkUI *ui, CutRunContext *run_context)
1767 {
1768 #define CONNECT(name) \
1769 g_signal_connect(run_context, #name, G_CALLBACK(cb_ ## name), ui)
1770
1771 CONNECT(ready_test_suite);
1772 CONNECT(ready_test_case);
1773
1774 CONNECT(complete_test_suite);
1775 CONNECT(complete_run);
1776
1777 CONNECT(error);
1778 #undef CONNECT
1779 }
1780
1781 static void
disconnect_from_run_context(CutGtkUI * ui,CutRunContext * run_context)1782 disconnect_from_run_context (CutGtkUI *ui, CutRunContext *run_context)
1783 {
1784 #define DISCONNECT(name) \
1785 g_signal_handlers_disconnect_by_func(run_context, \
1786 G_CALLBACK(cb_ ## name), \
1787 ui)
1788
1789 DISCONNECT(ready_test_suite);
1790 DISCONNECT(ready_test_case);
1791
1792 DISCONNECT(complete_test_suite);
1793 DISCONNECT(complete_run);
1794
1795 DISCONNECT(error);
1796 #undef DISCONNECT
1797 }
1798
1799 static void
attach_to_run_context(CutListener * listener,CutRunContext * run_context)1800 attach_to_run_context (CutListener *listener,
1801 CutRunContext *run_context)
1802 {
1803 connect_to_run_context(CUT_GTK_UI(listener), run_context);
1804 }
1805
1806 static void
detach_from_run_context(CutListener * listener,CutRunContext * run_context)1807 detach_from_run_context (CutListener *listener,
1808 CutRunContext *run_context)
1809 {
1810 disconnect_from_run_context(CUT_GTK_UI(listener), run_context);
1811 }
1812
1813 static gboolean
run(CutUI * ui,CutRunContext * run_context)1814 run (CutUI *ui, CutRunContext *run_context)
1815 {
1816 CutGtkUI *gtk_ui = CUT_GTK_UI(ui);
1817 CutRunContext *pipeline;
1818
1819 if (CUT_IS_STREAM_READER(run_context)) {
1820 pipeline = run_context;
1821 g_object_ref(pipeline);
1822 } else {
1823 pipeline = cut_pipeline_new_from_run_context(run_context);
1824 }
1825
1826 gtk_ui->run_context = pipeline;
1827 gtk_widget_show_all(gtk_ui->window);
1828 gtk_tree_store_clear(gtk_ui->logs);
1829
1830 run_test(gtk_ui);
1831
1832 gtk_main();
1833
1834 return TRUE;
1835 }
1836
1837 /*
1838 vi:ts=4:nowrap:ai:expandtab:sw=4
1839 */
1840