Commit 135e67f1 authored by Christian Hergert's avatar Christian Hergert

testing: implement rudimentary unit testing

Provides basic implementation for unit test plumbing. The UI is
both minimal and incomplete, but enough to land on master so
that future pieces can be implemented later in the cycle.

The test provider is responsible for loading and running the
test. I anticipate that we'll need to tweak the API so that we
can run the tests under gdb. Additionally, we need to track
output so it can be displayed.
parent 0367944e
......@@ -17,3 +17,11 @@ treeview.i-wanna-be-listbox:selected:backdrop {
color: @theme_unfocused_selected_fg_color;
background-color: @theme_unfocused_selected_bg_color;
}
ideeditorsidebar treeview.testing-tree {
-GtkTreeView-expander-size: 0;
-GtkTreeView-horizontal-separator: 0;
-GtkTreeView-vertical-separator: 6;
-gtk-icon-source: none;
padding-left: 6px;
}
......@@ -39,6 +39,7 @@
#include "debugger/ide-debug-manager.h"
#include "devices/ide-device-manager.h"
#include "doap/ide-doap.h"
#include "documentation/ide-documentation.h"
#include "plugins/ide-extension-util.h"
#include "projects/ide-project-files.h"
#include "projects/ide-project-item.h"
......@@ -49,7 +50,7 @@
#include "search/ide-search-engine.h"
#include "search/ide-search-provider.h"
#include "snippets/ide-source-snippets-manager.h"
#include "documentation/ide-documentation.h"
#include "testing/ide-test-manager.h"
#include "transfers/ide-transfer-manager.h"
#include "util/ide-async-helper.h"
#include "util/ide-settings.h"
......@@ -78,6 +79,7 @@ struct _IdeContext
IdeRuntimeManager *runtime_manager;
IdeSearchEngine *search_engine;
IdeSourceSnippetsManager *snippets_manager;
IdeTestManager *test_manager;
IdeProject *project;
GFile *project_file;
gchar *root_build_dir;
......@@ -534,6 +536,7 @@ ide_context_finalize (GObject *object)
g_clear_object (&self->project_file);
g_clear_object (&self->recent_manager);
g_clear_object (&self->runtime_manager);
g_clear_object (&self->test_manager);
g_clear_object (&self->unsaved_files);
g_clear_object (&self->vcs);
......@@ -817,6 +820,10 @@ ide_context_init (IdeContext *self)
"context", self,
NULL);
self->test_manager = g_object_new (IDE_TYPE_TEST_MANAGER,
"context", self,
NULL);
self->unsaved_files = g_object_new (IDE_TYPE_UNSAVED_FILES,
"context", self,
NULL);
......@@ -1117,6 +1124,28 @@ ide_context_init_snippets (gpointer source_object,
g_object_ref (task));
}
static void
ide_context_init_tests (gpointer source_object,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
IdeContext *self = source_object;
g_autoptr(GTask) task = NULL;
g_autoptr(GError) error = NULL;
g_return_if_fail (IDE_IS_CONTEXT (self));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_priority (task, G_PRIORITY_LOW);
g_task_set_source_tag (task, ide_context_init_tests);
if (!g_initable_init (G_INITABLE (self->test_manager), cancellable, &error))
g_task_return_error (task, g_steal_pointer (&error));
else
g_task_return_boolean (task, TRUE);
}
static void
ide_context_service_added (PeasExtensionSet *set,
PeasPluginInfo *info,
......@@ -1648,6 +1677,7 @@ ide_context_init_async (GAsyncInitable *initable,
ide_context_init_build_manager,
ide_context_init_run_manager,
ide_context_init_diagnostics_manager,
ide_context_init_tests,
ide_context_init_loaded,
NULL);
}
......@@ -2319,6 +2349,24 @@ ide_context_get_debug_manager (IdeContext *self)
return self->debug_manager;
}
/**
* ide_context_get_test_manager:
* @self: An #IdeTestManager
*
* Gets the test manager for the #IdeContext.
*
* Returns: (transfer none): An #IdeTestManager
*
* Since: 3.28
*/
IdeTestManager *
ide_context_get_test_manager (IdeContext *self)
{
g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
return self->test_manager;
}
/**
* ide_context_add_pausable:
* @self: an #IdeContext
......
......@@ -47,6 +47,7 @@ IdeSettings *ide_context_get_settings (IdeContext
const gchar *schema_id,
const gchar *relative_path);
IdeSourceSnippetsManager *ide_context_get_snippets_manager (IdeContext *self);
IdeTestManager *ide_context_get_test_manager (IdeContext *self);
IdeUnsavedFiles *ide_context_get_unsaved_files (IdeContext *self);
IdeVcs *ide_context_get_vcs (IdeContext *self);
const gchar *ide_context_get_root_build_dir (IdeContext *self);
......
......@@ -16,6 +16,7 @@
#include "sourceview/ide-cursor.h"
#include "sourceview/ide-source-view.h"
#include "symbols/ide-symbol.h"
#include "testing/ide-test.h"
#include "threading/ide-thread-pool.h"
#include "transfers/ide-transfer.h"
#include "vcs/ide-vcs-config.h"
......
......@@ -129,6 +129,10 @@ typedef struct _IdeSubprocessLauncher IdeSubprocessLauncher;
typedef struct _IdeSymbol IdeSymbol;
typedef struct _IdeSymbolResolver IdeSymbolResolver;
typedef struct _IdeTest IdeTest;
typedef struct _IdeTestManager IdeTestManager;
typedef struct _IdeTestProvider IdeTestProvider;
typedef struct _IdeTransferManager IdeTransferManager;
typedef struct _IdeTransfer IdeTransfer;
......
......@@ -165,6 +165,8 @@ G_BEGIN_DECLS
#include "symbols/ide-tags-builder.h"
#include "template/ide-project-template.h"
#include "template/ide-template-provider.h"
#include "testing/ide-test.h"
#include "testing/ide-test-provider.h"
#include "threading/ide-thread-pool.h"
#include "transfers/ide-pkcon-transfer.h"
#include "transfers/ide-transfer.h"
......
......@@ -81,6 +81,7 @@
<file preprocess="xml-stripblanks" alias="ide-preferences-language-row.ui">preferences/ide-preferences-language-row.ui</file>
<file preprocess="xml-stripblanks" alias="ide-preferences-window.ui">preferences/ide-preferences-window.ui</file>
<file preprocess="xml-stripblanks" alias="ide-run-button.ui">runner/ide-run-button.ui</file>
<file preprocess="xml-stripblanks" alias="ide-test-panel.ui">testing/ide-test-panel.ui</file>
<file preprocess="xml-stripblanks" alias="ide-transfer-row.ui">transfers/ide-transfer-row.ui</file>
<file preprocess="xml-stripblanks" alias="ide-transfers-button.ui">transfers/ide-transfers-button.ui</file>
<file preprocess="xml-stripblanks" alias="ide-shortcuts-window.ui">keybindings/ide-shortcuts-window.ui</file>
......@@ -107,6 +108,10 @@
<file alias="directory.plugin">directory/directory.plugin</file>
</gresource>
<gresource prefix="/org/gnome/builder/plugins/testing">
<file alias="testing.plugin">testing/testing.plugin</file>
</gresource>
<gresource prefix="/org/gnome/builder/plugins/webkit">
<file alias="webkit.plugin">webkit/webkit.plugin</file>
</gresource>
......
......@@ -87,6 +87,7 @@ subdir('sourceview')
subdir('subprocess')
subdir('symbols')
subdir('template')
subdir('testing')
subdir('threading')
subdir('transfers')
subdir('util')
......
/* ide-test-editor-addin.c
*
* Copyright (C) 2017 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "ide-test-editor-addin"
#include <glib/gi18n.h>
#include "ide-context.h"
#include "editor/ide-editor-addin.h"
#include "editor/ide-editor-perspective.h"
#include "editor/ide-editor-sidebar.h"
#include "testing/ide-test-editor-addin.h"
#include "testing/ide-test-panel.h"
#include "util/ide-gtk.h"
struct _IdeTestEditorAddin
{
GObject parent_instance;
IdeTestPanel *panel;
};
static void
ide_test_editor_addin_load (IdeEditorAddin *addin,
IdeEditorPerspective *editor)
{
IdeTestEditorAddin *self = (IdeTestEditorAddin *)addin;
IdeEditorSidebar *sidebar;
IdeTestManager *manager;
IdeContext *context;
g_assert (IDE_IS_TEST_EDITOR_ADDIN (self));
g_assert (IDE_IS_EDITOR_PERSPECTIVE (editor));
context = ide_widget_get_context (GTK_WIDGET (editor));
manager = ide_context_get_test_manager (context);
self->panel = g_object_new (IDE_TYPE_TEST_PANEL,
"manager", manager,
"visible", TRUE,
NULL);
g_signal_connect (self->panel,
"destroy",
G_CALLBACK (gtk_widget_destroy),
&self->panel);
sidebar = ide_editor_perspective_get_sidebar (editor);
ide_editor_sidebar_add_section (sidebar,
"tests",
_("Unit Tests"),
"builder-unit-tests-symbolic",
NULL,
NULL,
GTK_WIDGET (self->panel),
400);
}
static void
ide_test_editor_addin_unload (IdeEditorAddin *addin,
IdeEditorPerspective *editor)
{
IdeTestEditorAddin *self = (IdeTestEditorAddin *)addin;
g_assert (IDE_IS_TEST_EDITOR_ADDIN (self));
g_assert (IDE_IS_EDITOR_PERSPECTIVE (editor));
if (self->panel)
gtk_widget_destroy (GTK_WIDGET (self->panel));
}
static void
editor_addin_iface_init (IdeEditorAddinInterface *iface)
{
iface->load = ide_test_editor_addin_load;
iface->unload = ide_test_editor_addin_unload;
}
G_DEFINE_TYPE_WITH_CODE (IdeTestEditorAddin, ide_test_editor_addin, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (IDE_TYPE_EDITOR_ADDIN, editor_addin_iface_init))
static void
ide_test_editor_addin_class_init (IdeTestEditorAddinClass *klass)
{
}
static void
ide_test_editor_addin_init (IdeTestEditorAddin *self)
{
}
/* ide-test-editor-addin.h
*
* Copyright (C) 2017 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <glib-object.h>
G_BEGIN_DECLS
#define IDE_TYPE_TEST_EDITOR_ADDIN (ide_test_editor_addin_get_type())
G_DECLARE_FINAL_TYPE (IdeTestEditorAddin, ide_test_editor_addin, IDE, TEST_EDITOR_ADDIN, GObject)
G_END_DECLS
This diff is collapsed.
/* ide-test-manager.h
*
* Copyright © 2017 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ide-object.h"
G_BEGIN_DECLS
#define IDE_TYPE_TEST_MANAGER (ide_test_manager_get_type())
G_DECLARE_FINAL_TYPE (IdeTestManager, ide_test_manager, IDE, TEST_MANAGER, IdeObject)
gboolean ide_test_manager_get_loading (IdeTestManager *self);
void ide_test_manager_run_async (IdeTestManager *self,
IdeTest *test,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean ide_test_manager_run_finish (IdeTestManager *self,
GAsyncResult *result,
GError **error);
void ide_test_manager_run_all_async (IdeTestManager *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean ide_test_manager_run_all_finish (IdeTestManager *self,
GAsyncResult *result,
GError **error);
G_END_DECLS
/* ide-test-panel.c
*
* Copyright (C) 2017 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "ide-test-panel"
#include "ide-context.h"
#include "ide-debug.h"
#include "buildsystem/ide-build-manager.h"
#include "buildsystem/ide-build-pipeline.h"
#include "testing/ide-test.h"
#include "testing/ide-test-manager.h"
#include "testing/ide-test-panel.h"
#include "testing/ide-test-private.h"
#include "util/ide-gtk.h"
struct _IdeTestPanel
{
GtkBin parent_instance;
/* Owned references */
IdeTestManager *manager;
/* Template references */
GtkScrolledWindow *scroller;
GtkStack *stack;
GtkTreeView *tree_view;
};
enum {
PROP_0,
PROP_MANAGER,
N_PROPS
};
G_DEFINE_TYPE (IdeTestPanel, ide_test_panel, GTK_TYPE_BIN)
static GParamSpec *properties [N_PROPS];
static void
ide_test_panel_row_activated (IdeTestPanel *self,
GtkTreePath *path,
GtkTreeViewColumn *column,
GtkTreeView *tree_view)
{
GtkTreeModel *model;
GtkTreeIter iter;
IDE_ENTRY;
g_assert (IDE_IS_TEST_PANEL (self));
g_assert (path != NULL);
g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
g_assert (GTK_IS_TREE_VIEW (tree_view));
model = gtk_tree_view_get_model (tree_view);
if (gtk_tree_model_get_iter (model, &iter, path))
{
g_autoptr(IdeTest) test = NULL;
if (gtk_tree_model_iter_n_children (model, &iter))
{
if (gtk_tree_view_row_expanded (self->tree_view, path))
gtk_tree_view_collapse_row (self->tree_view, path);
else
gtk_tree_view_expand_row (self->tree_view, path, TRUE);
return;
}
gtk_tree_model_get (model, &iter,
IDE_TEST_COLUMN_TEST, &test,
-1);
if (test != NULL)
{
IdeTestProvider *provider = _ide_test_get_provider (test);
IdeContext *context = ide_widget_get_context (GTK_WIDGET (self));
IdeBuildManager *build_manager = ide_context_get_build_manager (context);
IdeBuildPipeline *pipeline = ide_build_manager_get_pipeline (build_manager);
/* TODO: Everything...
*
* - We need to track output from the test
* - We need to track failure/success from the test
* - We need to allow the user to jump to the assertion failure if there was one
* - We need to allow the user to run the test w/ the debugger
*/
ide_test_provider_run_async (provider,
test,
pipeline,
NULL,
NULL,
NULL);
}
}
IDE_EXIT;
}
static void
ide_test_panel_pixbuf_cell_data_func (GtkCellLayout *layout,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
IdeTestPanel *self = user_data;
g_autofree gchar *title = NULL;
g_autoptr(IdeTest) test = NULL;
const gchar *icon_name = NULL;
g_assert (GTK_IS_TREE_VIEW_COLUMN (layout));
g_assert (GTK_IS_CELL_RENDERER_PIXBUF (cell));
g_assert (GTK_IS_TREE_MODEL (model));
g_assert (iter != NULL);
g_assert (IDE_IS_TEST_PANEL (self));
gtk_tree_model_get (model, iter,
IDE_TEST_COLUMN_GROUP, &title,
IDE_TEST_COLUMN_TEST, &test,
-1);
if (title)
{
GtkTreePath *path = gtk_tree_model_get_path (model, iter);
if (gtk_tree_view_row_expanded (self->tree_view, path))
g_object_set (cell, "icon-name", "folder-open-symbolic", NULL);
else
g_object_set (cell, "icon-name", "folder-symbolic", NULL);
gtk_tree_path_free (path);
return;
}
icon_name = ide_test_get_icon_name (test);
g_object_set (cell, "icon-name", icon_name, NULL);
}
static void
ide_test_panel_text_cell_data_func (GtkCellLayout *layout,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
g_autofree gchar *title = NULL;
g_autoptr(IdeTest) test = NULL;
g_assert (GTK_IS_TREE_VIEW_COLUMN (layout));
g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
g_assert (GTK_IS_TREE_MODEL (model));
g_assert (iter != NULL);
g_assert (IDE_IS_TEST_PANEL (user_data));
gtk_tree_model_get (model, iter,
IDE_TEST_COLUMN_GROUP, &title,
IDE_TEST_COLUMN_TEST, &test,
-1);
if (title)
g_object_set (cell, "text", title, NULL);
else if (test)
g_object_set (cell, "text", ide_test_get_display_name (test), NULL);
else
g_object_set (cell, "text", NULL, NULL);
/* TODO: extract test info/failures/etc */
}
static void
ide_test_panel_row_inserted (IdeTestPanel *self,
GtkTreePath *path,
GtkTreeIter *iter,
GtkTreeModel *model)
{
g_assert (IDE_IS_TEST_PANEL (self));
g_assert (path != NULL);
g_assert (iter != NULL);
g_assert (GTK_IS_TREE_MODEL (model));
if (self->tree_view != NULL)
gtk_tree_view_expand_to_path (self->tree_view, path);
}
static void
ide_test_panel_notify_loading (IdeTestPanel *self,
GParamSpec *pspec,
IdeTestManager *manager)
{
g_assert (IDE_IS_TEST_PANEL (self));
g_assert (IDE_IS_TEST_MANAGER (manager));
if (ide_test_manager_get_loading (manager))
gtk_stack_set_visible_child_name (self->stack, "empty");
else
gtk_stack_set_visible_child_name (self->stack, "tests");
}
static void
ide_test_panel_constructed (GObject *object)
{
IdeTestPanel *self = (IdeTestPanel *)object;
g_assert (IDE_IS_TEST_PANEL (self));
G_OBJECT_CLASS (ide_test_panel_parent_class)->constructed (object);
if (self->manager != NULL)
{
GtkTreeModel *model;
model = _ide_test_manager_get_model (self->manager);
gtk_tree_view_set_model (self->tree_view, model);
g_signal_connect_object (model,
"row-inserted",
G_CALLBACK (ide_test_panel_row_inserted),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->manager,
"notify::loading",
G_CALLBACK (ide_test_panel_notify_loading),
self,
G_CONNECT_SWAPPED);
ide_test_panel_notify_loading (self, NULL, self->manager);
}
}
static void
ide_test_panel_destroy (GtkWidget *widget)
{
IdeTestPanel *self = (IdeTestPanel *)widget;
g_assert (IDE_IS_TEST_PANEL (self));
g_clear_object (&self->manager);
GTK_WIDGET_CLASS (ide_test_panel_parent_class)->destroy (widget);
}
static void
ide_test_panel_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeTestPanel *self = IDE_TEST_PANEL (object);
switch (prop_id)
{
case PROP_MANAGER:
g_value_set_object (value, self->manager);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_test_panel_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeTestPanel *self = IDE_TEST_PANEL (object);
switch (prop_id)
{
case PROP_MANAGER:
self->manager = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_test_panel_class_init (IdeTestPanelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->constructed = ide_test_panel_constructed;
object_class->get_property = ide_test_panel_get_property;
object_class->set_property = ide_test_panel_set_property;
widget_class->destroy = ide_test_panel_destroy;
properties [PROP_MANAGER] =
g_param_spec_object ("manager",
"Manager",
"The test manager for the panel",
IDE_TYPE_TEST_MANAGER,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-test-panel.ui");
gtk_widget_class_bind_template_child (widget_class, IdeTestPanel, scroller);
gtk_widget_class_bind_template_child (widget_class, IdeTestPanel, stack);
gtk_widget_class_bind_template_child (widget_class, IdeTestPanel, tree_view);
gtk_widget_class_bind_template_callback (widget_class, ide_test_panel_row_activated);
}
static void
ide_test_panel_init (IdeTestPanel *self)
{
GtkCellRenderer *cell;
GtkTreeViewColumn *column;
gtk_widget_init_template (GTK_WIDGET (self));
column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
"visible", TRUE,
NULL);
cell = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
"xpad", 3,
NULL);
gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column),
cell,
ide_test_panel_pixbuf_cell_data_func,
self,
NULL);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, FALSE);
cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
"ellipsize", PANGO_ELLIPSIZE_END,
"xalign", 0.0f,
NULL);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column),
cell,
ide_test_panel_text_cell_data_func,
self,
NULL);
gtk_tree_view_append_column (self->tree_view, column);