Commit f655e46c authored by Benjamin Berg's avatar Benjamin Berg Committed by Georges Basile Stavracas Neto

tests/network: Add basic testing of network panel

This adds tests for the network panel based on the test service found in
NetworkManager. Another possible solution may be to use the one from
dbusmock, however NetworkManager already has readily available code to
write tests in C which makes checking the widget hierarchy easier.
parent 7630bf96
__pycache__
......@@ -30,6 +30,11 @@ enable_tracing = get_option('tracing')
config_h = configuration_data()
py3 = import('python3')
python = py3.find_python()
config_h.set_quoted('TEST_NM_PYTHON', python.path())
# defines
set_defines = [
# package
......
......@@ -69,7 +69,7 @@ sources += gnome.compile_resources(
cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)
panels_libs += static_library(
network_panel_lib = static_library(
cappletname,
sources: sources,
include_directories: [top_inc, common_inc],
......@@ -77,3 +77,4 @@ panels_libs += static_library(
c_args: cflags,
link_with: libconnection_editor
)
panels_libs += network_panel_lib
subdir('common')
subdir('datetime')
subdir('network')
subdir('printers')
subdir('info')
/*
* Copyright (c) 2009, 2010 Intel, Inc.
* Copyright (c) 2010, 2018 Red Hat, Inc.
* Copyright (c) 2016 Endless, Inc.
*
* The Control Center 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 2 of the License, or (at your
* option) any later version.
*
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Benjamin Berg <bberg@redhat.com>
*/
#define G_LOG_DOMAIN "cc-test-window"
#include <config.h>
#include "cc-test-window.h"
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <string.h>
#include "shell/cc-panel.h"
#include "shell/cc-shell.h"
#include "cc-util.h"
struct _CcTestWindow
{
GtkWindow parent;
GtkWidget *main_box;
GtkWidget *header;
CcPanel *active_panel;
};
static void cc_shell_iface_init (CcShellInterface *iface);
G_DEFINE_TYPE_WITH_CODE (CcTestWindow, cc_test_window, GTK_TYPE_WINDOW,
G_IMPLEMENT_INTERFACE (CC_TYPE_SHELL, cc_shell_iface_init))
enum
{
PROP_0,
PROP_ACTIVE_PANEL
};
static void
set_active_panel (CcTestWindow *shell,
CcPanel *panel)
{
g_assert (CC_IS_SHELL (shell));
g_assert (CC_IS_PANEL (panel));
/* Only allow setting to a non NULL value once. */
g_assert (shell->active_panel == NULL);
if (panel)
{
shell->active_panel = g_object_ref (panel);
gtk_container_add_with_properties (GTK_CONTAINER (shell->main_box), GTK_WIDGET (panel),
"pack-type", GTK_PACK_END,
"expand", TRUE,
"fill", TRUE,
NULL);
}
}
/* CcShell implementation */
static gboolean
cc_test_window_set_active_panel_from_id (CcShell *shell,
const gchar *start_id,
GVariant *parameters,
GError **error)
{
/* Not implemented */
g_assert_not_reached ();
}
static void
cc_test_window_embed_widget_in_header (CcShell *shell,
GtkWidget *widget)
{
CcTestWindow *self = CC_TEST_WINDOW (shell);
/* add to main box */
gtk_container_add_with_properties (GTK_CONTAINER (self->main_box), GTK_WIDGET (widget),
"pack-type", GTK_PACK_START,
"expand", FALSE,
"fill", TRUE,
NULL);
gtk_widget_show (widget);
}
static GtkWidget *
cc_test_window_get_toplevel (CcShell *shell)
{
return GTK_WIDGET (shell);
}
static void
cc_shell_iface_init (CcShellInterface *iface)
{
iface->set_active_panel_from_id = cc_test_window_set_active_panel_from_id;
iface->embed_widget_in_header = cc_test_window_embed_widget_in_header;
iface->get_toplevel = cc_test_window_get_toplevel;
}
/* GObject Implementation */
static void
cc_test_window_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
CcTestWindow *self = CC_TEST_WINDOW (object);
switch (property_id)
{
case PROP_ACTIVE_PANEL:
g_value_set_object (value, self->active_panel);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
cc_test_window_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
CcTestWindow *shell = CC_TEST_WINDOW (object);
switch (property_id)
{
case PROP_ACTIVE_PANEL:
set_active_panel (shell, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
cc_test_window_dispose (GObject *object)
{
CcTestWindow *self = CC_TEST_WINDOW (object);
g_clear_object (&self->active_panel);
G_OBJECT_CLASS (cc_test_window_parent_class)->dispose (object);
}
static void
cc_test_window_class_init (CcTestWindowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = cc_test_window_get_property;
object_class->set_property = cc_test_window_set_property;
object_class->dispose = cc_test_window_dispose;
g_object_class_override_property (object_class, PROP_ACTIVE_PANEL, "active-panel");
}
static void
cc_test_window_init (CcTestWindow *self)
{
gtk_widget_set_size_request (GTK_WIDGET (self), 500, 800);
self->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
gtk_container_add (GTK_CONTAINER (self), self->main_box);
}
CcTestWindow *
cc_test_window_new (void)
{
return g_object_new (CC_TYPE_TEST_WINDOW,
"resizable", TRUE,
"title", "Test Settings",
"window-position", GTK_WIN_POS_CENTER,
NULL);
}
/*
* Copyright (c) 2010 Intel, Inc.
* Copyright (c) 2018 Red Hat, Inc.
*
* The Control Center 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 2 of the License, or (at your
* option) any later version.
*
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Benjamin Berg <bberg@redhat.com>
*/
#pragma once
#include <glib-object.h>
#include "shell/cc-shell.h"
G_BEGIN_DECLS
#define CC_TYPE_TEST_WINDOW (cc_test_window_get_type ())
G_DECLARE_FINAL_TYPE (CcTestWindow, cc_test_window, CC, TEST_WINDOW, GtkWindow)
CcTestWindow *cc_test_window_new (void);
G_END_DECLS
includes = [top_inc, include_directories('../../panels/network', 'nm-utils')]
cflags = [
'-DTEST_SRCDIR="@0@"'.format(meson.current_source_dir()),
'-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_WITH_GLIB',
'-DNETWORKMANAGER_COMPILATION_TEST',
'-DTEST_NM_SERVICE="@0@"'.format(join_paths(meson.source_root(), 'tests', 'network', 'nm-utils', 'test-networkmanager-service.py')),
]
exe = executable(
'test-network-panel',
['test-network-panel.c', 'cc-test-window.c', 'nm-utils/nm-test-utils-impl.c'],
include_directories: includes + [common_inc],
dependencies: common_deps + network_manager_deps + [libtestshell_dep],
link_with: [network_panel_lib],
c_args: cflags
)
envs = [
'G_MESSAGES_DEBUG=all',
'BUILDDIR=' + meson.current_build_dir(),
'TOP_BUILDDIR=' + meson.build_root()
]
test('test-network-panel',
find_program('test-network-panel.py'),
env: envs,
timeout: 10
)
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* 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 2, 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright 2010 - 2014, 2018 Red Hat, Inc.
*
*/
/* Static functions to help with testing */
/* nmtst_create_minimal_connection is copied from nm-test-utils.h. */
static inline NMConnection *
nmtst_create_minimal_connection (const char *id, const char *uuid, const char *type, NMSettingConnection **out_s_con)
{
NMConnection *con;
NMSetting *s_base = NULL;
NMSettingConnection *s_con;
gs_free char *uuid_free = NULL;
g_assert (id);
if (uuid)
g_assert (nm_utils_is_uuid (uuid));
else
uuid = uuid_free = nm_utils_uuid_generate ();
if (type) {
GType type_g;
type_g = nm_setting_lookup_type (type);
g_assert (type_g != G_TYPE_INVALID);
s_base = g_object_new (type_g, NULL);
g_assert (NM_IS_SETTING (s_base));
}
con = nm_simple_connection_new ();
s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
g_object_set (s_con,
NM_SETTING_CONNECTION_ID, id,
NM_SETTING_CONNECTION_UUID, uuid,
NM_SETTING_CONNECTION_TYPE, type,
NULL);
nm_connection_add_setting (con, NM_SETTING (s_con));
if (s_base)
nm_connection_add_setting (con, s_base);
if (out_s_con)
*out_s_con = s_con;
return con;
}
typedef struct {
GMainLoop *loop;
NMDevice *device;
NMClient *client;
NMActiveConnection *ac;
const gchar * const *client_props;
const gchar * const *device_props;
int client_remaining;
int device_remaining;
int other_remaining;
} EventWaitInfo;
#define WAIT_CHECK_REMAINING() \
if (info->client_remaining == 0 && info->device_remaining == 0 && info->other_remaining == 0) { \
g_debug ("Got expected events, quitting mainloop"); \
g_main_loop_quit (info->loop); \
} \
if (info->client_remaining < 0 || info->device_remaining < 0 || info->other_remaining < 0) { \
g_error ("Pending events are negative: client: %d, device: %d, other: %d", info->client_remaining, info->device_remaining, info->other_remaining); \
g_assert_not_reached (); \
}
#define WAIT_DECL() \
EventWaitInfo info = {0}; \
gint _timeout_id;
#define WAIT_DEVICE(_device, count, ...) \
info.device = (_device); \
info.device_remaining = (count); \
{ const gchar * const *props = (const char const *[]){ __VA_ARGS__, NULL }; \
info.device_props = props; } \
g_signal_connect ((_device), "notify", G_CALLBACK (device_notify_cb), &info);
#define WAIT_CLIENT(_client, count, ...) \
info.client = (_client); \
info.client_remaining = (count); \
info.client_props = (const char *[]) {__VA_ARGS__, NULL}; \
g_signal_connect ((_client), "notify", G_CALLBACK (client_notify_cb), &info);
#define WAIT_DESTROY() \
g_source_remove (_timeout_id); \
if (info.device) \
g_signal_handlers_disconnect_by_func (info.device, G_CALLBACK (device_notify_cb), &info); \
if (info.client) \
g_signal_handlers_disconnect_by_func (info.client, G_CALLBACK (client_notify_cb), &info); \
g_main_loop_unref (info.loop);
#define WAIT_FINISHED(timeout) \
info.loop = g_main_loop_new (NULL, FALSE); \
_timeout_id = g_timeout_add_seconds ((timeout), timeout_cb, &info); \
g_main_loop_run (info.loop); \
WAIT_DESTROY()
static gboolean
timeout_cb (gpointer user_data)
{
EventWaitInfo *info = user_data;
if (info)
g_error ("Pending events are: client: %d, device: %d, other: %d", info->client_remaining, info->device_remaining, info->other_remaining); \
g_assert_not_reached ();
return G_SOURCE_REMOVE;
}
static void
device_notify_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data)
{
EventWaitInfo *info = user_data;
g_assert (device == info->device);
if (!g_strv_contains (info->device_props, g_param_spec_get_name (pspec))) {
g_debug ("Ignoring notification for device property %s", g_param_spec_get_name (pspec));
return;
}
g_debug ("Counting notification for device property %s", g_param_spec_get_name (pspec));
info->device_remaining--;
WAIT_CHECK_REMAINING()
}
static void
client_notify_cb (NMClient *client, GParamSpec *pspec, gpointer user_data)
{
EventWaitInfo *info = user_data;
g_assert (client == info->client);
if (!g_strv_contains (info->client_props, g_param_spec_get_name (pspec))) {
g_debug ("Ignoring notification for client property %s", g_param_spec_get_name (pspec));
return;
}
g_debug ("Counting notification for client property %s", g_param_spec_get_name (pspec));
info->client_remaining--;
WAIT_CHECK_REMAINING()
}
static void
nmtst_set_device_state (NMTstcServiceInfo *sinfo, NMDevice *device, NMDeviceState state, NMDeviceStateReason reason)
{
GError *error = NULL;
WAIT_DECL()
g_debug ("Setting device %s state to %d with reason %d", nm_device_get_iface (device), state, reason);
g_dbus_proxy_call_sync (sinfo->proxy,
"SetDeviceState",
g_variant_new ("(suu)", nm_object_get_path (NM_OBJECT (device)), state, reason),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
3000,
NULL,
&error);
g_assert_no_error (error);
WAIT_DEVICE(device, 1, "state-reason")
WAIT_FINISHED(5)
}
static void
nmtst_set_wired_speed (NMTstcServiceInfo *sinfo, NMDevice *device, guint32 speed)
{
GError *error = NULL;
WAIT_DECL()
g_debug ("Setting device %s speed to %d", nm_device_get_iface (device), speed);
g_dbus_proxy_call_sync (sinfo->proxy,
"SetWiredSpeed",
g_variant_new ("(su)", nm_object_get_path (NM_OBJECT (device)), speed),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
3000,
NULL,
&error);
g_assert_no_error (error);
WAIT_DEVICE(device, 2, "speed", "carrier")
WAIT_FINISHED(5)
}
static void
device_removed_cb (NMClient *client,
NMDevice *device,
gpointer user_data)
{
EventWaitInfo *info = user_data;
g_assert (device);
g_assert (device == info->device);
info->other_remaining--;
WAIT_CHECK_REMAINING()
}
static void
nmtst_remove_device (NMTstcServiceInfo *sinfo, NMClient *client, NMDevice *device)
{
GError *error = NULL;
WAIT_DECL()
g_object_ref (device);
g_dbus_proxy_call_sync (sinfo->proxy,
"RemoveDevice",
g_variant_new ("(s)", nm_object_get_path (NM_OBJECT (device))),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
3000,
NULL,
&error);
g_assert_no_error (error);
info.device = device;
info.client = client;
info.other_remaining = 1;
g_signal_connect (client, "device-removed",
G_CALLBACK (device_removed_cb), &info);
WAIT_FINISHED(5)
g_object_unref(device);
g_signal_handlers_disconnect_by_func (client, device_removed_cb, &info);
}
static void
add_and_activate_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
NMClient *client = NM_CLIENT (object);
EventWaitInfo *info = user_data;
GError *error = NULL;
info->ac = nm_client_add_and_activate_connection_finish (client, result, &error);
g_assert_no_error (error);
g_assert (info->ac != NULL);
info->other_remaining--;
WAIT_CHECK_REMAINING()
}
static NMActiveConnection*
nmtst_add_and_activate_connection (NMTstcServiceInfo *sinfo, NMClient *client, NMDevice *device, NMConnection *conn)
{
WAIT_DECL()
nm_client_add_and_activate_connection_async (client, conn, device, NULL,
NULL, add_and_activate_cb, &info);
info.other_remaining = 1;
WAIT_CLIENT(client, 1, NM_CLIENT_ACTIVE_CONNECTIONS);
WAIT_DEVICE(device, 1, NM_DEVICE_ACTIVE_CONNECTION);
g_object_unref (conn);
WAIT_FINISHED(5)
g_assert (info.ac != NULL);
return info.ac;
}
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (c) 2010-2014, 2018 Red Hat, Inc.
*
* The Control Center 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 2 of the License, or (at your
* option) any later version.
*
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Benjamin Berg <bberg@redhat.com>
*/
#define G_LOG_DOMAIN "test-network-panel"
#include "nm-macros-internal.h"
#include <NetworkManager.h>
#include <nm-client.h>
#include <nm-utils.h>
#include "nm-test-libnm-utils.h"
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <gtk/gtk.h>
#include "cc-test-window.h"
#include "shell/cc-object-storage.h"
#include "nmtst-helpers.h"
typedef struct {
NMTstcServiceInfo *sinfo;
NMClient *client;
NMDevice *main_ether;
GtkWidget *shell;
CcPanel *panel;
} NetworkPanelFixture;
extern GType cc_network_panel_get_type (void);
static void
fixture_set_up_empty (NetworkPanelFixture *fixture,
gconstpointer user_data)
{
g_autoptr(GError) error = NULL;
cc_object_storage_initialize ();
/* Bring up the libnm service. */
fixture->sinfo = nmtstc_service_init ();
fixture->client = nm_client_new (NULL, &error);
g_assert_no_error (error);
fixture->shell = GTK_WIDGET (cc_test_window_new ());
fixture->panel = g_object_new (cc_network_panel_get_type (),
"shell", CC_SHELL (fixture->shell),
NULL);
g_object_ref (fixture->panel);
cc_shell_set_active_panel (CC_SHELL (fixture->shell), fixture->panel);
gtk_widget_show_all (fixture->shell);
}
static void
fixture_tear_down (NetworkPanelFixture *fixture,
gconstpointer user_data)
{
g_clear_object (&fixture->panel);
g_clear_pointer (&fixture->shell, gtk_widget_destroy);
cc_object_storage_destroy ();
g_clear_pointer (&fixture->sinfo, nmtstc_service_cleanup);
}
static void
fixture_set_up_wired (NetworkPanelFixture *fixture,
gconstpointer user_data)
{
NMDevice *second;
fixture_set_up_empty (fixture, user_data);
fixture->main_ether = nmtstc_service_add_wired_device (fixture->sinfo,
fixture->client,
"eth1000",
"52:54:00:ab:db:23",
NULL);
/* Add/remove one to catch issues with signal disconnects. */
second = nmtstc_service_add_wired_device (fixture->sinfo,
fixture->client,
"eth1001",
"52:54:00:ab:db:24",
NULL);
nmtst_remove_device (fixture->sinfo, fixture->client, second);
}
/*****************************************************************************/
static void
test_device_add (NetworkPanelFixture *fixture,
gconstpointer user_data)
{
const gchar *device_path;
/* Tell the test service to add a new device.
* We use some weird numbers so that the devices will not exist on the
* host system. */
fixture->main_ether = nmtstc_service_add_wired_device (fixture->sinfo,
fixture->client,
"eth1000",
"52:54:00:ab:db:23",
NULL);
device_path = nm_object_get_path (NM_OBJECT (fixture->main_ether));
g_debug("Device added: %s\n", device_path);
g_assert (gtk_test_find_label(fixture->shell, "Wired") != NULL);
}
static void
test_second_device_add (NetworkPanelFixture *fixture,
gconstpointer user_data)
{
NMDevice *device;
const gchar *device_path;