diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:45:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:45:20 +0000 |
commit | ae1c76ff830d146d41e88d6fba724c0a54bce868 (patch) | |
tree | 3c354bec95af07be35fc71a4b738268496f1a1c4 /tests | |
parent | Initial commit. (diff) | |
download | gnome-control-center-ae1c76ff830d146d41e88d6fba724c0a54bce868.tar.xz gnome-control-center-ae1c76ff830d146d41e88d6fba724c0a54bce868.zip |
Adding upstream version 1:43.6.upstream/1%43.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
64 files changed, 8186 insertions, 0 deletions
diff --git a/tests/common/hostnames-test.txt b/tests/common/hostnames-test.txt new file mode 100644 index 0000000..5a31ce2 --- /dev/null +++ b/tests/common/hostnames-test.txt @@ -0,0 +1,11 @@ +# Pretty hostname, tab, display hostname, tab, real hostname +Lennart's PC Lennarts-PC lennarts-pc +Müllers Computer Mullers-Computer mullers-computer +Voran! Voran voran +Es war einmal ein Männlein Es-war-einmal-ein-Mannlein es-war-einmal-ein-mannlein +Jawoll. Ist doch wahr! Jawoll-Ist-doch-wahr jawoll-ist-doch-wahr +レナート localhost localhost +!!! localhost localhost +...zack!!! zack!... zack-zack zack-zack +Bãstien's computer... Foo-bar Bastiens-computer-Foo-bar bastiens-computer-foo-bar + localhost localhost diff --git a/tests/common/meson.build b/tests/common/meson.build new file mode 100644 index 0000000..0550092 --- /dev/null +++ b/tests/common/meson.build @@ -0,0 +1,22 @@ + + +test_units = [ + 'test-hostname', + # 'test-time-entry', # FIXME +] + +cflags = [ + '-DTEST_SRCDIR="@0@"'.format(meson.current_source_dir()), + '-DTEST_TOPSRCDIR="@0@"'.format(meson.source_root()) +] + +foreach unit: test_units + exe = executable( + unit, + unit + '.c', + include_directories : [ top_inc, common_inc ], + dependencies : common_deps + [libwidgets_dep], + c_args : cflags, + ) + test(unit, exe) +endforeach diff --git a/tests/common/ssids-test.txt b/tests/common/ssids-test.txt new file mode 100644 index 0000000..0545437 --- /dev/null +++ b/tests/common/ssids-test.txt @@ -0,0 +1,3 @@ +GNOME GNOME +0123456789abcdefghijklmnopqrstuvwxyz 0123456789abcdefghijklmnopqrstuv +レナート レナート diff --git a/tests/common/test-hostname.c b/tests/common/test-hostname.c new file mode 100644 index 0000000..0e3a3ae --- /dev/null +++ b/tests/common/test-hostname.c @@ -0,0 +1,123 @@ +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <locale.h> + +#include "hostname-helper.h" + +static void +test_hostname (void) +{ + g_autofree gchar *contents = NULL; + guint i; + g_auto(GStrv) lines = NULL; + + if (g_file_get_contents (TEST_SRCDIR "/hostnames-test.txt", &contents, NULL, NULL) == FALSE) { + g_warning ("Failed to load '%s'", TEST_SRCDIR "/hostnames-test.txt"); + g_test_fail (); + return; + } + + lines = g_strsplit (contents, "\n", -1); + if (lines == NULL) { + g_warning ("Test file is empty"); + g_test_fail (); + return; + } + + for (i = 0; lines[i] != NULL; i++) { + g_auto(GStrv) items = NULL; + g_autofree gchar *utf8 = NULL; + g_autofree gchar *result1 = NULL; + g_autofree gchar *result2 = NULL; + + if (*lines[i] == '#') + continue; + if (*lines[i] == '\0') + break; + + items = g_strsplit (lines[i], "\t", -1); + utf8 = g_locale_from_utf8 (items[0], -1, NULL, NULL, NULL); + + result1 = pretty_hostname_to_static (items[0], FALSE); + if (g_strcmp0 (result1, items[2]) != 0) { + g_error ("Result for '%s' doesn't match '%s' (got: '%s')", + utf8, items[2], result1); + g_test_fail (); + } else { + g_debug ("Result for '%s' matches '%s'", + utf8, result1); + } + + result2 = pretty_hostname_to_static (items[0], TRUE); + if (g_strcmp0 (result2, items[1]) != 0) { + g_error ("Result for '%s' doesn't match '%s' (got: '%s')", + utf8, items[1], result2); + g_test_fail (); + } else { + g_debug ("Result for '%s' matches '%s'", + utf8, result2); + } + } +} + +static void +test_ssid (void) +{ + g_autofree gchar *contents = NULL; + guint i; + g_auto(GStrv) lines = NULL; + + if (g_file_get_contents (TEST_SRCDIR "/ssids-test.txt", &contents, NULL, NULL) == FALSE) { + g_warning ("Failed to load '%s'", TEST_SRCDIR "/ssids-test.txt"); + g_test_fail (); + return; + } + + lines = g_strsplit (contents, "\n", -1); + if (lines == NULL) { + g_warning ("Test file is empty"); + g_test_fail (); + return; + } + + for (i = 0; lines[i] != NULL; i++) { + g_autofree gchar *ssid = NULL; + g_auto(GStrv) items = NULL; + + if (*lines[i] == '#') + continue; + if (*lines[i] == '\0') + break; + + items = g_strsplit (lines[i], "\t", -1); + ssid = pretty_hostname_to_ssid (items[0]); + g_assert_cmpstr (ssid, ==, items[1]); + } +} + +int main (int argc, char **argv) +{ + char *locale; + + /* Running in some locales will + * break the tests as "ü" will be transliterated to + * "ue" in de_DE, and 'u"' in the C locale. + * + * Work around that by forcing en_US with UTF-8 in + * our tests + * https://bugzilla.gnome.org/show_bug.cgi?id=650342 */ + locale = setlocale (LC_ALL, "en_US.UTF-8"); + if (locale == NULL) { + g_debug("Missing en_US.UTF-8 locale, ignoring test."); + return 0; + } + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/common/hostname", test_hostname); + g_test_add_func ("/common/ssid", test_ssid); + + return g_test_run (); +} diff --git a/tests/common/test-time-entry.c b/tests/common/test-time-entry.c new file mode 100644 index 0000000..e099efa --- /dev/null +++ b/tests/common/test-time-entry.c @@ -0,0 +1,483 @@ +#include <locale.h> +#include <gtk/gtk.h> +/* #include "cc-datetime-resources.h" */ + +#undef NDEBUG +#undef G_DISABLE_ASSERT +#undef G_DISABLE_CHECKS +#undef G_DISABLE_CAST_CHECKS +#undef G_LOG_DOMAIN + +#include "cc-time-entry.c" + +static void +test_time (CcTimeEntry *time_entry, + guint hour, + guint minute, + gboolean is_24h) +{ + g_autofree char *str = NULL; + const char *entry_str; + guint entry_hour, entry_minute; + + g_assert_true (CC_IS_TIME_ENTRY (time_entry)); + g_assert_cmpint (hour, >=, 0); + g_assert_cmpint (hour, <= , 23); + g_assert_cmpint (minute, >=, 0); + g_assert_cmpint (minute, <= , 59); + + entry_hour = cc_time_entry_get_hour (time_entry); + g_assert_cmpint (entry_hour, ==, hour); + + entry_minute = cc_time_entry_get_minute (time_entry); + g_assert_cmpint (entry_minute, ==, minute); + + /* Convert 24 hour time to 12 hour */ + if (!is_24h) + { + /* 00:00 is 12:00 AM */ + if (hour == 0) + hour = 12; + else if (hour > 12) + hour = hour - 12; + } + + str = g_strdup_printf ("%02d:%02d", hour, minute); + entry_str = gtk_entry_get_text (GTK_ENTRY (time_entry)); + g_assert_cmpstr (entry_str, ==, str); +} + +static void +test_time_24h (void) +{ + GtkWidget *entry; + CcTimeEntry *time_entry; + + entry = cc_time_entry_new (); + time_entry = CC_TIME_ENTRY (entry); + g_object_ref_sink (entry); + g_assert (CC_IS_TIME_ENTRY (entry)); + + for (guint i = 0; i <= 25; i++) + { + guint hour; + gboolean is_am; + + cc_time_entry_set_time (time_entry, i, 0); + g_assert_false (cc_time_entry_get_am_pm (time_entry)); + + test_time (time_entry, i < 24 ? i : 23, 0, TRUE); + + hour = cc_time_entry_get_hour (time_entry); + is_am = cc_time_entry_get_is_am (time_entry); + if (hour < 12) + g_assert_true (is_am); + else + g_assert_false (is_am); + } + + g_object_unref (entry); +} + +static void +test_time_12h (void) +{ + GtkWidget *entry; + CcTimeEntry *time_entry; + + entry = cc_time_entry_new (); + time_entry = CC_TIME_ENTRY (entry); + g_object_ref_sink (entry); + g_assert (CC_IS_TIME_ENTRY (entry)); + + cc_time_entry_set_am_pm (time_entry, FALSE); + g_assert_false (cc_time_entry_get_am_pm (time_entry)); + cc_time_entry_set_am_pm (time_entry, TRUE); + g_assert_true (cc_time_entry_get_am_pm (time_entry)); + + for (guint i = 0; i <= 25; i++) + { + guint hour; + gboolean is_am; + + cc_time_entry_set_time (time_entry, i, 0); + + test_time (time_entry, i < 24 ? i : 23, 0, FALSE); + + hour = cc_time_entry_get_hour (time_entry); + is_am = cc_time_entry_get_is_am (time_entry); + + if (hour < 12) + g_assert_true (is_am); + else + g_assert_false (is_am); + } + + g_object_unref (entry); +} + +static void +test_time_hour_24h (void) +{ + GtkWidget *entry; + CcTimeEntry *time_entry; + int hour; + + entry = cc_time_entry_new (); + time_entry = CC_TIME_ENTRY (entry); + g_assert (CC_IS_TIME_ENTRY (entry)); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_set_position (GTK_EDITABLE (entry), 0); + + for (guint i = 1; i <= 25; i++) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_UP); + + /* Wrap if above limit */ + hour = i; + if (hour >= 24) + hour = hour - 24; + + test_time (time_entry, hour, 0, TRUE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + hour = 0; + + for (int i = 25; i >= 0; i--) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_DOWN); + + /* Wrap if below limit */ + hour--; + if (hour < 0) + hour = 23; + + test_time (time_entry, hour, 0, TRUE); + } + + /* Put cursor at the one’s place and repeat the tests */ + gtk_editable_set_position (GTK_EDITABLE (entry), 1); + cc_time_entry_set_time (time_entry, 0, 0); + + for (guint i = 1; i <= 25; i++) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_UP); + + /* Wrap if above limit */ + hour = i; + if (hour >= 24) + hour = hour - 24; + + test_time (time_entry, hour, 0, TRUE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + hour = 0; + + for (int i = 25; i >= 0; i--) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_DOWN); + + /* Wrap if below limit */ + hour--; + if (hour < 0) + hour = 23; + + test_time (time_entry, hour, 0, TRUE); + } + + g_object_ref_sink (entry); + g_object_unref (entry); +} + +static void +test_time_minute_24h (void) +{ + GtkWidget *entry; + CcTimeEntry *time_entry; + int minute; + + entry = cc_time_entry_new (); + time_entry = CC_TIME_ENTRY (entry); + g_assert (CC_IS_TIME_ENTRY (entry)); + + cc_time_entry_set_time (time_entry, 0, 0); + /* Set cursor at 10’s place of minute */ + gtk_editable_set_position (GTK_EDITABLE (entry), SEPARATOR_INDEX + 1); + + for (guint i = 1; i <= 61; i++) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_UP); + + /* Wrap if above limit */ + minute = i; + if (minute >= 60) + minute = minute - 60; + + test_time (time_entry, 0, minute, TRUE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_set_position (GTK_EDITABLE (entry), SEPARATOR_INDEX + 1); + minute = 0; + + for (int i = 61; i >= 0; i--) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_DOWN); + + /* Wrap if below limit */ + minute--; + if (minute < 0) + minute = 59; + + test_time (time_entry, 0, minute, TRUE); + } + + /* Put cursor at the minute one’s place and repeat the tests */ + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_set_position (GTK_EDITABLE (entry), SEPARATOR_INDEX + 2); + + for (guint i = 1; i <= 61; i++) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_UP); + + /* Wrap if above limit */ + minute = i; + if (minute >= 60) + minute = minute - 60; + + test_time (time_entry, 0, minute, TRUE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_set_position (GTK_EDITABLE (entry), SEPARATOR_INDEX + 2); + minute = 0; + + for (int i = 61; i >= 0; i--) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_DOWN); + + /* Wrap if below limit */ + minute--; + if (minute < 0) + minute = 59; + + test_time (time_entry, 0, minute, TRUE); + } + + g_object_ref_sink (entry); + g_object_unref (entry); +} + +static void +test_time_hour_12h (void) +{ + GtkWidget *entry; + CcTimeEntry *time_entry; + int hour; + + entry = cc_time_entry_new (); + time_entry = CC_TIME_ENTRY (entry); + g_assert (CC_IS_TIME_ENTRY (entry)); + + cc_time_entry_set_am_pm (time_entry, TRUE); + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_set_position (GTK_EDITABLE (entry), 0); + + for (guint i = 1; i <= 14; i++) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_UP); + + /* Wrap if above limit */ + hour = i; + if (hour >= 12) + hour = hour - 12; + + test_time (time_entry, hour, 0, FALSE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + hour = 12; + + for (int i = 23; i >= 0; i--) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_DOWN); + + /* Wrap if below limit */ + hour--; + if (hour < 0) + hour = 11; /* Hour varies from 0 to 11 (0 is 12) */ + + test_time (time_entry, hour, 0, FALSE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + /* Put cursor at the one’s place and repeat the tests */ + gtk_editable_set_position (GTK_EDITABLE (entry), 1); + + for (guint i = 1; i <= 14; i++) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_UP); + + /* Wrap if above limit */ + hour = i; + if (hour >= 12) + hour = hour - 12; + + test_time (time_entry, hour, 0, FALSE); + } + + cc_time_entry_set_time (time_entry, 0, 0); + hour = 0; + + for (int i = 23; i >= 0; i--) + { + g_signal_emit_by_name (entry, "change-value", GTK_SCROLL_STEP_DOWN); + + /* Wrap if below limit */ + hour--; + if (hour < 0) + hour = 11; /* Hour varies from 0 to 11 (0 is 12) */ + + test_time (time_entry, hour, 0, FALSE); + } + + g_object_ref_sink (entry); + g_object_unref (entry); +} + +static void +test_time_insertion (void) +{ + GtkWidget *entry; + CcTimeEntry *time_entry; + int position; + + entry = cc_time_entry_new (); + time_entry = CC_TIME_ENTRY (entry); + g_assert (CC_IS_TIME_ENTRY (entry)); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_set_position (GTK_EDITABLE (entry), 0); + + /* Test hour */ + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 0; + /* 00:00 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "0", -1, &position); + test_time (time_entry, 0, 0, TRUE); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 0; + /* 20:00 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "2", -1, &position); + test_time (time_entry, 20, 0, TRUE); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 0; + /* 30:00 => 23:00 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "3", -1, &position); + test_time (time_entry, 23, 0, TRUE); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 1; + /* 09:00 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "9", -1, &position); + test_time (time_entry, 9, 0, TRUE); + + /* Test minute */ + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = SEPARATOR_INDEX + 1; + /* 00:10 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "1", -1, &position); + test_time (time_entry, 0, 10, TRUE); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = SEPARATOR_INDEX + 1; + /* 00:30 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "3", -1, &position); + test_time (time_entry, 0, 30, TRUE); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = SEPARATOR_INDEX + 1; + /* 00:60 => 00:59 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "6", -1, &position); + test_time (time_entry, 0, 59, TRUE); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = SEPARATOR_INDEX + 2; + /* 00:00 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "0", -1, &position); + test_time (time_entry, 0, 0, TRUE); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = SEPARATOR_INDEX + 2; + /* 00:01 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "1", -1, &position); + test_time (time_entry, 0, 1, TRUE); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = SEPARATOR_INDEX + 2; + /* 00:09 */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "9", -1, &position); + test_time (time_entry, 0, 9, TRUE); + + /* 12 Hour mode */ + cc_time_entry_set_am_pm (time_entry, TRUE); + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 0; + /* 12:00 AM */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "1", -1, &position); + /* 12 AM Hour in 12H mode is 00 Hour in 24H mode */ + test_time (time_entry, 0, 0, FALSE); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 1; + /* 11:00 AM */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "1", -1, &position); + test_time (time_entry, 11, 0, FALSE); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 3; + /* 12:10 AM */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "1", -1, &position); + test_time (time_entry, 0, 10, FALSE); + + cc_time_entry_set_time (time_entry, 0, 0); + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + position = 4; + /* 12:09 AM */ + gtk_editable_insert_text (GTK_EDITABLE (entry), "9", -1, &position); + test_time (time_entry, 0, 9, FALSE); + + g_object_ref_sink (entry); + g_object_unref (entry); +} + +int +main (int argc, + char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + gtk_init (NULL, NULL); + + g_setenv ("G_DEBUG", "fatal_warnings", FALSE); + + g_test_add_func ("/datetime/time-24h", test_time_24h); + g_test_add_func ("/datetime/time-12h", test_time_12h); + g_test_add_func ("/datetime/time-hour-24h", test_time_hour_24h); + g_test_add_func ("/datetime/time-minute-24h", test_time_minute_24h); + g_test_add_func ("/datetime/time-hour-12h", test_time_hour_12h); + g_test_add_func ("/datetime/time-insertion", test_time_insertion); + + return g_test_run (); +} diff --git a/tests/datetime/meson.build b/tests/datetime/meson.build new file mode 100644 index 0000000..c9f0787 --- /dev/null +++ b/tests/datetime/meson.build @@ -0,0 +1,34 @@ + +test_units = [ + 'test-timezone', + 'test-timezone-gfx', + 'test-endianess', +] + +env = [ + 'G_MESSAGES_DEBUG=all', + 'BUILDDIR=' + meson.current_build_dir(), + 'TOP_BUILDDIR=' + meson.build_root(), +# Disable ATK, this should not be required but it caused CI failures -- 2018-12-07 + 'NO_AT_BRIDGE=1' +] +cflags = [ + '-DTEST_SRCDIR="@0@"'.format(meson.current_source_dir()), + '-DSRCDIR="@0@"'.format(meson.source_root() + '/panels/datetime') +] + +foreach unit: test_units + exe = executable( + unit, + [unit + '.c'], + dependencies : common_deps + [m_dep, datetime_panel_lib_dep], + c_args : cflags + ) +endforeach + +test( + 'test-datetime', + find_program('test-datetime.py'), + env : env, + timeout : 60 +) diff --git a/tests/datetime/test-datetime.py b/tests/datetime/test-datetime.py new file mode 100644 index 0000000..cc69cae --- /dev/null +++ b/tests/datetime/test-datetime.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# Copyright © 2018 Red Hat, Inc +# 2018 Endless Mobile, Inc +# +# 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 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/>. +# +# Authors: Benjamin Berg <bberg@redhat.com> +# Georges Basile Stavracas Neto <georges@endlessm.com> + +import os +import sys +import unittest + +try: + import dbusmock +except ImportError: + sys.stderr.write('You need python-dbusmock (http://pypi.python.org/pypi/python-dbusmock) for this test suite.\n') + sys.exit(1) + +# Add the shared directory to the search path +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'shared')) + +from gtest import GTest +from x11session import X11SessionTestCase + +BUILDDIR = os.environ.get('BUILDDIR', os.path.join(os.path.dirname(__file__))) + + +class EndianessTestCase(X11SessionTestCase, GTest): + g_test_exe = os.path.join(BUILDDIR, 'test-endianess') + + +class TimezoneTestCase(X11SessionTestCase, GTest): + g_test_exe = os.path.join(BUILDDIR, 'test-timezone') + + +class TimezoneGfxTestCase(X11SessionTestCase, GTest): + g_test_exe = os.path.join(BUILDDIR, 'test-timezone-gfx') + + +if __name__ == '__main__': + _test = unittest.TextTestRunner(stream=sys.stdout, verbosity=2) + unittest.main(testRunner=_test) diff --git a/tests/datetime/test-endianess.c b/tests/datetime/test-endianess.c new file mode 100644 index 0000000..9cb9200 --- /dev/null +++ b/tests/datetime/test-endianess.c @@ -0,0 +1,65 @@ +#include <glib.h> +#include <glib/gi18n.h> +#include <locale.h> +#include "date-endian.h" + +static int verbose = 0; + +static void +print_endianess (const char *lang) +{ + DateEndianess endianess; + + if (lang != NULL) { + setlocale (LC_TIME, lang); + endianess = date_endian_get_for_lang (lang, verbose); + } else { + endianess = date_endian_get_default (verbose); + } + if (verbose) + g_print ("\t\t%s\n", date_endian_to_string (endianess)); +} + +static void +test_endianess (void) +{ + g_autoptr(GDir) dir = NULL; + const char *name; + + dir = g_dir_open ("/usr/share/i18n/locales/", 0, NULL); + if (dir == NULL) { + /* Try with /usr/share/locale/ + * https://bugzilla.gnome.org/show_bug.cgi?id=646780 */ + dir = g_dir_open ("/usr/share/locale/", 0, NULL); + if (dir == NULL) { + g_assert_not_reached (); + } + } + + while ((name = g_dir_read_name (dir)) != NULL) + print_endianess (name); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + bind_textdomain_codeset ("libc", "UTF-8"); + + g_test_init (&argc, &argv, NULL); + + g_setenv ("G_DEBUG", "fatal_warnings", FALSE); + + if (argv[1] != NULL) { + verbose = 1; + + if (g_str_equal (argv[1], "-c")) + print_endianess (NULL); + else + print_endianess (argv[1]); + return 0; + } + + g_test_add_func ("/datetime/endianess", test_endianess); + + return g_test_run (); +} diff --git a/tests/datetime/test-timezone-gfx.c b/tests/datetime/test-timezone-gfx.c new file mode 100644 index 0000000..1c25d48 --- /dev/null +++ b/tests/datetime/test-timezone-gfx.c @@ -0,0 +1,75 @@ +#include <config.h> +#include <locale.h> +#include <gtk/gtk.h> + +#include "cc-datetime-resources.h" +#include "tz.h" + +static void +test_timezone_gfx (gconstpointer data) +{ + g_autoptr(TzDB) db = NULL; + GPtrArray *locs; + const char *pixmap_dir; + guint i; + + pixmap_dir = data; + db = tz_load_db (); + locs = tz_get_locations (db); + + for (i = 0; i < locs->len ; i++) + { + g_autofree gchar *filename = NULL; + g_autofree gchar *path = NULL; + TzLocation *location; + TzInfo *info; + gdouble selected_offset; + gchar buf[16]; + + location = locs->pdata[i]; + info = tz_info_from_location (location); + selected_offset = tz_location_get_utc_offset (location) / (60.0 * 60.0) + (info->daylight ? -1.0 : 0.0); + tz_info_free (info); + + filename = g_strdup_printf ("timezone_%s.png", g_ascii_formatd (buf, sizeof (buf), "%g", selected_offset)); + path = g_build_filename (pixmap_dir, filename, NULL); + + if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) + { + g_message ("File '%s' missing for zone '%s'", filename, location->zone); + g_test_fail (); + } + } +} + +gint +main (gint argc, + gchar **argv) +{ + gchar *pixmap_dir; + + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_setenv ("G_DEBUG", "fatal_warnings", FALSE); + + g_resources_register (cc_datetime_get_resource ()); + + if (argc == 2) + { + pixmap_dir = g_strdup (argv[1]); + } + else if (argc == 1) + { + pixmap_dir = g_strdup (SRCDIR "/data/"); + } + else + { + g_message ("Usage: %s [PIXMAP DIRECTORY]", argv[0]); + return 1; + } + + g_test_add_data_func ("/datetime/timezone-gfx", pixmap_dir, test_timezone_gfx); + + return g_test_run (); +} diff --git a/tests/datetime/test-timezone.c b/tests/datetime/test-timezone.c new file mode 100644 index 0000000..5962fc4 --- /dev/null +++ b/tests/datetime/test-timezone.c @@ -0,0 +1,65 @@ +#include <locale.h> +#include <gtk/gtk.h> +#include "cc-datetime-resources.h" +#include "cc-timezone-map.h" + +static void +test_timezone (void) +{ + g_autoptr(GHashTable) ht = NULL; + CcTimezoneMap *map; + TzDB *tz_db; + guint i; + + ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + map = cc_timezone_map_new (); + tz_db = tz_load_db (); + + g_assert_nonnull (tz_db); + g_assert_nonnull (tz_db->locations); + + for (i = 0; tz_db->locations && i < tz_db->locations->len; i++) + { + g_autofree gchar *clean_tz = NULL; + TzLocation *location = NULL; + + location = g_ptr_array_index (tz_db->locations, i); + clean_tz = tz_info_get_clean_name (tz_db, location->zone); + + if (!cc_timezone_map_set_timezone (map, location->zone)) + { + if (!g_hash_table_contains (ht, clean_tz)) + { + if (g_strcmp0 (clean_tz, location->zone) == 0) + g_printerr ("Failed to locate timezone '%s'\n", location->zone); + else + g_printerr ("Failed to locate timezone '%s' (original name: '%s')\n", clean_tz, location->zone); + g_hash_table_insert (ht, g_strdup (clean_tz), GINT_TO_POINTER (TRUE)); + } + + /* We don't warn for those, we'll just fallback + * in the panel code */ + if (!g_str_equal (clean_tz, "posixrules") && !g_str_equal (clean_tz, "Factory")) + g_test_fail (); + } + } + + tz_db_free (tz_db); +} + +gint +main (gint argc, + gchar **argv) +{ + setlocale (LC_ALL, ""); + gtk_init (NULL, NULL); + g_test_init (&argc, &argv, NULL); + + g_resources_register (cc_datetime_get_resource ()); + + g_setenv ("G_DEBUG", "fatal_warnings", FALSE); + + g_test_add_func ("/datetime/timezone", test_timezone); + + return g_test_run (); +} diff --git a/tests/info/info-cleanup-test.txt b/tests/info/info-cleanup-test.txt new file mode 100644 index 0000000..a0e25fb --- /dev/null +++ b/tests/info/info-cleanup-test.txt @@ -0,0 +1,19 @@ +Intel(R) Core(TM) i5-4590T CPU @ 2.00GHz Intel® Core™ i5-4590T +Intel(R) Ivybridge Mobile Intel® Ivybridge Mobile +Intel(R) Ivybridge Mobile Intel® Ivybridge Mobile +Gallium 0.4 on AMD KAVERI AMD KAVERI +AMD KAVERI (DRM 2.48.0 / 4.9.0-0.rc4.git2.2.fc26.x86_64, LLVM3) AMD KAVERI +AMD KAVERI (DRM 2.48.0 / 4.9.0-0.rc4.git2.2.fc26.x86_64, LLVM3 AMD KAVERI +Gallium 0.4 on AMD KAVERI (DRM 2.48.0 / 4.9.0-0.rc4.git2.2.fc26.x86_64, LLVM3) AMD KAVERI +Gallium 0.4 on AMD KAVERI (DRM 2.48.0 / 4.9.0-0.rc4.git2.2.fc26.x86_64, LLVM3 AMD KAVERI +AMD Ryzen Threadripper 1950X 16-Core Processor AMD Ryzen™ Threadripper™ 1950X +AMD Radeon RX 580 Series (POLARIS10, DRM 3.42.0, 5.15.13-1-ck-generic-v3, LLVM 13.0.0) AMD Radeon™ RX 580 Series +AMD EPYC 7251 8-Core Processor AMD EPYC™ 7251 +Gallium 0.4 on SVGA3D SVGA3D +Gallium 0.4 on llvmpipe (LLVM 3.4, 128 bits) Software Rendering +llvmpipe (LLVM 12.0.1, 256 bits) Software Rendering +AMD FX(tm)-8350 Eight-Core Processor AMD FX™-8350 +AMD Athlon(tm) II X3 435 Processor AMD Athlon™ II X3 435 +NVIDIA GeForce GT 710/PCIe/SSE2 NVIDIA GeForce GT 710 +Mesa DRI Intel(R) 965GM x86/MMX/SSE2 Intel® 965GM +NVIDIA GeForce RTX 3090/PCIe/SSE2 NVIDIA GeForce RTX™ 3090 diff --git a/tests/info/meson.build b/tests/info/meson.build new file mode 100644 index 0000000..0f5d7a9 --- /dev/null +++ b/tests/info/meson.build @@ -0,0 +1,21 @@ + +test_units = [ + 'test-info-cleanup' +] + +includes = [top_inc, include_directories('../../panels/info-overview')] +cflags = '-DTEST_SRCDIR="@0@"'.format(meson.current_source_dir()) + +foreach unit: test_units + exe = executable( + unit, + [unit + '.c'], + include_directories : includes, + dependencies : common_deps, + link_with : [info_panel_lib], + c_args : cflags + ) + + test(unit, exe) +endforeach + diff --git a/tests/info/test-info-cleanup.c b/tests/info/test-info-cleanup.c new file mode 100644 index 0000000..3e6bebd --- /dev/null +++ b/tests/info/test-info-cleanup.c @@ -0,0 +1,81 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2010 Red Hat, Inc + * Copyright (C) 2008 William Jon McCann <jmccann@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 2 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/>. + * + */ + +#include <config.h> + +#include <glib.h> +#include <locale.h> +#include "info-cleanup.h" + +static void +test_info (void) +{ + g_autofree gchar *contents = NULL; + guint i; + g_auto(GStrv) lines = NULL; + + if (g_file_get_contents (TEST_SRCDIR "/info-cleanup-test.txt", &contents, NULL, NULL) == FALSE) { + g_warning ("Failed to load '%s'", TEST_SRCDIR "/info-cleanup-test.txt"); + g_test_fail (); + return; + } + + lines = g_strsplit (contents, "\n", -1); + if (lines == NULL) { + g_warning ("Test file is empty"); + g_test_fail (); + return; + } + + for (i = 0; lines[i] != NULL; i++) { + g_auto(GStrv) items = NULL; + g_autofree gchar *utf8 = NULL; + g_autofree gchar *result = NULL; + + if (*lines[i] == '#') + continue; + if (*lines[i] == '\0') + break; + + items = g_strsplit (lines[i], "\t", -1); + utf8 = g_locale_from_utf8 (items[0], -1, NULL, NULL, NULL); + result = info_cleanup (items[0]); + if (g_strcmp0 (result, items[1]) != 0) { + g_error ("Result for '%s' doesn't match '%s' (got: '%s')", + utf8, items[1], result); + g_test_fail (); + } else { + g_debug ("Result for '%s' matches '%s'", + utf8, result); + } + } +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_setenv ("G_DEBUG", "fatal_warnings", FALSE); + + g_test_add_func ("/info/info", test_info); + + return g_test_run (); +} diff --git a/tests/interactive-panels/applications/gtp-dynamic-panel.desktop.in b/tests/interactive-panels/applications/gtp-dynamic-panel.desktop.in new file mode 100644 index 0000000..8a550ba --- /dev/null +++ b/tests/interactive-panels/applications/gtp-dynamic-panel.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Dynamic Panel +Comment=Panel that can be hidden from the sidebar +Exec=gnome-control-center dynamic-panels +Icon=weather-clear-night +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; +OnlyShowIn=GNOME;Unity;
\ No newline at end of file diff --git a/tests/interactive-panels/applications/gtp-header-widget.desktop.in b/tests/interactive-panels/applications/gtp-header-widget.desktop.in new file mode 100644 index 0000000..4ffbf20 --- /dev/null +++ b/tests/interactive-panels/applications/gtp-header-widget.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Header +Comment=Panel that embeds a widget at the header bar +Exec=gnome-control-center thunderbolt +Icon=view-sort-descending +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; +OnlyShowIn=GNOME;Unity;
\ No newline at end of file diff --git a/tests/interactive-panels/applications/gtp-sidebar-widget.desktop.in b/tests/interactive-panels/applications/gtp-sidebar-widget.desktop.in new file mode 100644 index 0000000..6c96c2d --- /dev/null +++ b/tests/interactive-panels/applications/gtp-sidebar-widget.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Another Sidebar Widget +Comment=Panel that embeds a widget at the sidebar +Exec=gnome-control-center sidebar-widget +Icon=go-first-symbolic +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-DetailsSettings; +OnlyShowIn=GNOME;Unity; diff --git a/tests/interactive-panels/applications/gtp-static-init.desktop.in b/tests/interactive-panels/applications/gtp-static-init.desktop.in new file mode 100644 index 0000000..e8ccdf5 --- /dev/null +++ b/tests/interactive-panels/applications/gtp-static-init.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Static Initializer +Comment=Panel with a static initializer function +Exec=gnome-control-center static-init +Icon=view-continuous +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; +OnlyShowIn=GNOME;Unity;
\ No newline at end of file diff --git a/tests/interactive-panels/applications/gtp-toplevel-sidebar-widget.desktop.in b/tests/interactive-panels/applications/gtp-toplevel-sidebar-widget.desktop.in new file mode 100644 index 0000000..a6d4aa8 --- /dev/null +++ b/tests/interactive-panels/applications/gtp-toplevel-sidebar-widget.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Sidebar Widget +Comment=Panel that embeds a widget at the sidebar +Exec=gnome-control-center sidebar-widget +Icon=go-first-symbolic +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; +OnlyShowIn=GNOME;Unity;
\ No newline at end of file diff --git a/tests/interactive-panels/applications/meson.build b/tests/interactive-panels/applications/meson.build new file mode 100644 index 0000000..82eed4d --- /dev/null +++ b/tests/interactive-panels/applications/meson.build @@ -0,0 +1,22 @@ +################# +# Desktop files # +################# + +desktop_files = [ + 'dynamic-panel', + 'header-widget', + 'sidebar-widget', + 'static-init', + 'toplevel-sidebar-widget', +] + +foreach desktop_file : desktop_files + i18n.merge_file( + type : 'desktop', + input : 'gtp-@0@.desktop.in'.format(desktop_file), + output : 'gnome-@0@-panel.desktop'.format(desktop_file), + po_dir : po_dir, + build_by_default : true, + install : false, + ) +endforeach diff --git a/tests/interactive-panels/gtp-dynamic-panel.c b/tests/interactive-panels/gtp-dynamic-panel.c new file mode 100644 index 0000000..31efa30 --- /dev/null +++ b/tests/interactive-panels/gtp-dynamic-panel.c @@ -0,0 +1,85 @@ +/* gtp-dynamic-panel.c + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "gtp-dynamic-panel" + +#include "gtp-dynamic-panel.h" + +#include "shell/cc-application.h" +#include "shell/cc-shell-model.h" + +struct _GtpDynamicPanel +{ + CcPanel parent; +}; + +G_DEFINE_TYPE (GtpDynamicPanel, gtp_dynamic_panel, CC_TYPE_PANEL) + +/* Auxiliary methods */ + +static void +set_visibility (CcPanelVisibility visibility) +{ + GApplication *application; + CcShellModel *model; + + application = g_application_get_default (); + model = cc_application_get_model (CC_APPLICATION (application)); + + cc_shell_model_set_panel_visibility (model, "dynamic-panel", visibility); +} + +/* Callbacks */ + +static gboolean +show_panel_cb (gpointer data) +{ + g_debug ("Showing panel"); + + set_visibility (CC_PANEL_VISIBLE); + + return G_SOURCE_REMOVE; +} + +static void +on_button_clicked_cb (GtkButton *button, + GtpDynamicPanel *self) +{ + g_debug ("Hiding panel"); + + set_visibility (CC_PANEL_HIDDEN); + g_timeout_add_seconds (3, show_panel_cb, self); +} + +static void +gtp_dynamic_panel_class_init (GtpDynamicPanelClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/tests/panels/gtp-dynamic-panel.ui"); + + gtk_widget_class_bind_template_callback (widget_class, on_button_clicked_cb); +} + +static void +gtp_dynamic_panel_init (GtpDynamicPanel *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/tests/interactive-panels/gtp-dynamic-panel.h b/tests/interactive-panels/gtp-dynamic-panel.h new file mode 100644 index 0000000..84ad5cc --- /dev/null +++ b/tests/interactive-panels/gtp-dynamic-panel.h @@ -0,0 +1,30 @@ +/* gtp-dynamic-panel.h + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define GTP_TYPE_DYNAMIC_PANEL (gtp_dynamic_panel_get_type()) +G_DECLARE_FINAL_TYPE (GtpDynamicPanel, gtp_dynamic_panel, GTP, DYNAMIC_PANEL, CcPanel) + +G_END_DECLS diff --git a/tests/interactive-panels/gtp-dynamic-panel.ui b/tests/interactive-panels/gtp-dynamic-panel.ui new file mode 100644 index 0000000..30e1bdd --- /dev/null +++ b/tests/interactive-panels/gtp-dynamic-panel.ui @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.22"/> + <template class="GtpDynamicPanel" parent="CcPanel"> + <child type="content"> + <object class="AdwStatusPage"> + <property name="icon-name">weather-clear-night-symbolic</property> + <property name="title">Dynamic Panel</property> + <property name="description">Dynamic panels may hide if you don't have some hardware. Use the button below to toggle the panel visibility:</property> + + <child> + <object class="GtkButton"> + <property name="halign">center</property> + <property name="label">Hide</property> + <signal name="clicked" handler="on_button_clicked_cb" object="GtpDynamicPanel" swapped="no" /> + <style> + <class name="pill" /> + </style> + </object> + </child> + + </object> + </child> + + </template> +</interface> + diff --git a/tests/interactive-panels/gtp-header-widget.c b/tests/interactive-panels/gtp-header-widget.c new file mode 100644 index 0000000..b27327a --- /dev/null +++ b/tests/interactive-panels/gtp-header-widget.c @@ -0,0 +1,42 @@ +/* gtp-header-widget.c + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gtp-header-widget.h" + +struct _GtpHeaderWidget +{ + CcPanel parent; +}; + +G_DEFINE_TYPE (GtpHeaderWidget, gtp_header_widget, CC_TYPE_PANEL) + +static void +gtp_header_widget_class_init (GtpHeaderWidgetClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/tests/panels/gtp-header-widget.ui"); +} + +static void +gtp_header_widget_init (GtpHeaderWidget *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/tests/interactive-panels/gtp-header-widget.h b/tests/interactive-panels/gtp-header-widget.h new file mode 100644 index 0000000..99f7ce0 --- /dev/null +++ b/tests/interactive-panels/gtp-header-widget.h @@ -0,0 +1,30 @@ +/* gtp-header-widget.h + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define GTP_TYPE_HEADER_WIDGET (gtp_header_widget_get_type()) +G_DECLARE_FINAL_TYPE (GtpHeaderWidget, gtp_header_widget, GTP, HEADER_WIDGET, CcPanel) + +G_END_DECLS diff --git a/tests/interactive-panels/gtp-header-widget.ui b/tests/interactive-panels/gtp-header-widget.ui new file mode 100644 index 0000000..8eb9b88 --- /dev/null +++ b/tests/interactive-panels/gtp-header-widget.ui @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GtpHeaderWidget" parent="CcPanel"> + + <child type="titlebar-end"> + <object class="GtkLabel" id="header_widget"> + <property name="label">I'm a header widget</property> + </object> + </child> + + <child type="content"> + <object class="AdwStatusPage"> + <property name="icon-name">go-top-symbolic</property> + <property name="title">Header Widgets</property> + <property name="description">Embed widgets in the header bar</property> + </object> + </child> + </template> +</interface> + diff --git a/tests/interactive-panels/gtp-sidebar-widget.c b/tests/interactive-panels/gtp-sidebar-widget.c new file mode 100644 index 0000000..1395724 --- /dev/null +++ b/tests/interactive-panels/gtp-sidebar-widget.c @@ -0,0 +1,56 @@ +/* gtp-sidebar-widget.c + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gtp-sidebar-widget.h" + +struct _GtpSidebarWidget +{ + CcPanel parent; + + GtkWidget *sidebar_widget; +}; + +G_DEFINE_TYPE (GtpSidebarWidget, gtp_sidebar_widget, CC_TYPE_PANEL) + +static GtkWidget* +gtp_sidebar_widget_get_sidebar_widget (CcPanel *panel) +{ + GtpSidebarWidget *self = GTP_SIDEBAR_WIDGET (panel); + return self->sidebar_widget; +} + +static void +gtp_sidebar_widget_class_init (GtpSidebarWidgetClass *klass) +{ + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + panel_class->get_sidebar_widget = gtp_sidebar_widget_get_sidebar_widget; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/tests/panels/gtp-sidebar-widget.ui"); + + gtk_widget_class_bind_template_child (widget_class, GtpSidebarWidget, sidebar_widget); +} + +static void +gtp_sidebar_widget_init (GtpSidebarWidget *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/tests/interactive-panels/gtp-sidebar-widget.h b/tests/interactive-panels/gtp-sidebar-widget.h new file mode 100644 index 0000000..3bf8dfe --- /dev/null +++ b/tests/interactive-panels/gtp-sidebar-widget.h @@ -0,0 +1,30 @@ +/* gtp-sidebar-widget.h + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define GTP_TYPE_SIDEBAR_WIDGET (gtp_sidebar_widget_get_type()) +G_DECLARE_FINAL_TYPE (GtpSidebarWidget, gtp_sidebar_widget, GTP, SIDEBAR_WIDGET, CcPanel) + +G_END_DECLS diff --git a/tests/interactive-panels/gtp-sidebar-widget.ui b/tests/interactive-panels/gtp-sidebar-widget.ui new file mode 100644 index 0000000..cba80fc --- /dev/null +++ b/tests/interactive-panels/gtp-sidebar-widget.ui @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GtpSidebarWidget" parent="CcPanel"> + + <child type="content"> + <object class="AdwStatusPage"> + <property name="icon_name">go-first-symbolic</property> + <property name="title">Sidebar</property> + <property name="description">Add a custom sidebar to the panel</property> + </object> + </child> + + </template> + + <object class="GtkLabel" id="sidebar_widget"> + <property name="label">I'm a sidebar widget</property> + </object> +</interface> + diff --git a/tests/interactive-panels/gtp-static-init.c b/tests/interactive-panels/gtp-static-init.c new file mode 100644 index 0000000..c6b7a4f --- /dev/null +++ b/tests/interactive-panels/gtp-static-init.c @@ -0,0 +1,49 @@ +/* gtp-static-init.c + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gtp-static-init.h" + +struct _GtpStaticInit +{ + CcPanel parent; +}; + +G_DEFINE_TYPE (GtpStaticInit, gtp_static_init, CC_TYPE_PANEL) + +void +gtp_static_init_func (void) +{ + g_message ("GtpStaticInit: running outside the panel instance"); +} + +static void +gtp_static_init_class_init (GtpStaticInitClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/tests/panels/gtp-static-init.ui"); + +} + +static void +gtp_static_init_init (GtpStaticInit *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/tests/interactive-panels/gtp-static-init.h b/tests/interactive-panels/gtp-static-init.h new file mode 100644 index 0000000..1dbbea2 --- /dev/null +++ b/tests/interactive-panels/gtp-static-init.h @@ -0,0 +1,32 @@ +/* gtp-static-init.h + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define GTP_TYPE_STATIC_INIT (gtp_static_init_get_type()) +G_DECLARE_FINAL_TYPE (GtpStaticInit, gtp_static_init, GTP, STATIC_INIT, CcPanel) + +void gtp_static_init_func (void); + +G_END_DECLS diff --git a/tests/interactive-panels/gtp-static-init.ui b/tests/interactive-panels/gtp-static-init.ui new file mode 100644 index 0000000..32b02a9 --- /dev/null +++ b/tests/interactive-panels/gtp-static-init.ui @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GtpStaticInit" parent="CcPanel"> + + <child type="content"> + <object class="AdwStatusPage"> + <property name="icon_name">view-continuous-symbolic</property> + <property name="title">Static Initializer</property> + <property name="description">A panel with a static initializer</property> + </object> + </child> + + </template> +</interface> + diff --git a/tests/interactive-panels/main.c b/tests/interactive-panels/main.c new file mode 100644 index 0000000..0b0faaa --- /dev/null +++ b/tests/interactive-panels/main.c @@ -0,0 +1,80 @@ +/* main.c + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include <stdlib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "shell/cc-application.h" +#include "shell/cc-panel-loader.h" +#include "shell/resources.h" +#include "test-panels-resources.h" + +#include "gtp-dynamic-panel.h" +#include "gtp-header-widget.h" +#include "gtp-sidebar-widget.h" +#include "gtp-static-init.h" + +/* Test panels */ +static CcPanelLoaderVtable test_panels[] = { + { "dynamic-panel", gtp_dynamic_panel_get_type, NULL }, + { "header-widget", gtp_header_widget_get_type, NULL }, + { "sidebar-widget", gtp_sidebar_widget_get_type, NULL }, + { "static-init", gtp_static_init_get_type, gtp_static_init_func }, + { "toplevel-sidebar-widget", gtp_sidebar_widget_get_type, NULL }, +}; + +static void +override_xdg_data_dirs (void) +{ + g_autoptr(GString) extended_xdg_data_dirs = NULL; + const gchar *xdg_data_dirs; + + extended_xdg_data_dirs = g_string_new ("."); + xdg_data_dirs = g_getenv ("XDG_DATA_DIRS"); + + if (xdg_data_dirs) + g_string_append_printf (extended_xdg_data_dirs, ":%s", xdg_data_dirs); + + g_setenv ("XDG_DATA_DIRS", extended_xdg_data_dirs->str, TRUE); +} + +gint +main (gint argc, + gchar *argv[]) +{ + g_autoptr(GtkApplication) application = NULL; + + /* Manually register GResources */ + g_resources_register (gnome_control_center_get_resource ()); + g_resources_register (test_panels_get_resource ()); + + /* Override the panels vtable with the test panels */ + cc_panel_loader_override_vtable (test_panels, G_N_ELEMENTS (test_panels)); + + /* Override XDG_DATA_DIRS */ + override_xdg_data_dirs (); + + application = cc_application_new (); + + return g_application_run (G_APPLICATION (application), argc, argv); +} diff --git a/tests/interactive-panels/meson.build b/tests/interactive-panels/meson.build new file mode 100644 index 0000000..04856ee --- /dev/null +++ b/tests/interactive-panels/meson.build @@ -0,0 +1,49 @@ +subdir('applications') + +########### +# Sources # +########### + +sources = files( + 'gtp-dynamic-panel.c', + 'gtp-header-widget.c', + 'gtp-sidebar-widget.c', + 'gtp-static-init.c', + 'main.c', +) + + +############## +# GResources # +############## + +resource_data = files( + 'gtp-dynamic-panel.ui', + 'gtp-header-widget.ui', + 'gtp-sidebar-widget.ui', + 'gtp-static-init.ui', +) + +sources += gnome.compile_resources( + 'test-panels-resources', + 'panels.gresource.xml', + source_dir : '.', + c_name : 'test_panels', + dependencies : resource_data, + export : true, +) + + +###################### +# interactive-panels # +###################### + +includes = [top_inc] + +exe = executable( + 'test-interactive-panels', + sources, + include_directories : includes, + dependencies : shell_deps + [libtestshell_dep], + c_args : cflags +) diff --git a/tests/interactive-panels/panels.gresource.xml b/tests/interactive-panels/panels.gresource.xml new file mode 100644 index 0000000..9fc6605 --- /dev/null +++ b/tests/interactive-panels/panels.gresource.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/tests/panels"> + <file preprocess="xml-stripblanks">gtp-dynamic-panel.ui</file> + <file preprocess="xml-stripblanks">gtp-header-widget.ui</file> + <file preprocess="xml-stripblanks">gtp-sidebar-widget.ui</file> + <file preprocess="xml-stripblanks">gtp-static-init.ui</file> + </gresource> +</gresources> + diff --git a/tests/keyboard/meson.build b/tests/keyboard/meson.build new file mode 100644 index 0000000..4b724b4 --- /dev/null +++ b/tests/keyboard/meson.build @@ -0,0 +1,33 @@ +test_units = [ + 'test-keyboard-shortcuts' +] + +env = [ + 'G_MESSAGES_DEBUG=all', + 'BUILDDIR=' + meson.current_build_dir(), + 'TOP_BUILDDIR=' + meson.build_root(), +# Disable ATK, this should not be required but it caused CI failures -- 2018-12-07 + 'NO_AT_BRIDGE=1' +] +cflags = [ + '-DTEST_SRCDIR="@0@"'.format(meson.current_source_dir()) +] +includes = [top_inc, include_directories('../../panels/keyboard')] + +foreach unit: test_units + exe = executable( + unit, + [unit + '.c'], + dependencies : common_deps, + include_directories : includes, + link_with : [keyboard_panel_lib], + c_args : cflags + ) +endforeach + +test( + 'test-keyboard', + find_program('test-keyboard.py'), + env : env, + timeout : 60 +) diff --git a/tests/keyboard/test-keyboard-shortcuts.c b/tests/keyboard/test-keyboard-shortcuts.c new file mode 100644 index 0000000..37d8613 --- /dev/null +++ b/tests/keyboard/test-keyboard-shortcuts.c @@ -0,0 +1,161 @@ +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <locale.h> +#include <stdlib.h> +#include "keyboard-shortcuts.h" + +#define NUM_LAYOUTS 4 + +typedef struct _shortcut_test { + guint16 keycode; + GdkModifierType modifiers; + char *expected_results[NUM_LAYOUTS]; +} shortcut_test; + +/* keycodes taken from /usr/share/X11/xkb/keycodes/evdev */ +shortcut_test shortcut_tests[] = { + { + 24 /* first key after tab in first letter row */, + GDK_SHIFT_MASK | GDK_SUPER_MASK, + { + "<Shift><Super>q" /* us */, + "<Shift><Super>apostrophe" /* us+dvorak */, + "<Shift><Super>a" /* fr+azerty */, + "<Shift><Super>Cyrillic_shorti" /* ru */ + /* "<Shift><Super>q" would be valid, too */, + }, + }, + { + 13 /* fifth key in num row */, + GDK_SUPER_MASK, + { + "<Super>4" /* us */, + "<Super>4" /* us+dvorak */, + "<Super>4" /* fr+azerty */, + "<Super>4" /* ru */, + }, + }, + { + 13 /* fifth key in num row */, + GDK_SHIFT_MASK | GDK_SUPER_MASK, + { + "<Shift><Super>4" /* us */, + "<Shift><Super>4" /* us+dvorak */, + "<Shift><Super>4" /* fr+azerty */, + "<Shift><Super>4" /* ru */, + }, + }, + { + 65 /* space key */, + GDK_SHIFT_MASK | GDK_SUPER_MASK, + { + "<Shift><Super>space" /* us */, + "<Shift><Super>space" /* us+dvorak */, + "<Shift><Super>space" /* fr+azerty */, + "<Shift><Super>space" /* ru */, + }, + }, + { + 23 /* tab key */, + GDK_SHIFT_MASK | GDK_SUPER_MASK, + { + "<Shift><Super>Tab" /* us */, + "<Shift><Super>Tab" /* us+dvorak */, + "<Shift><Super>Tab" /* fr+azerty */, + "<Shift><Super>Tab" /* ru */, + }, + }, + { + 107 /* print screen/sysrq key */, + GDK_ALT_MASK, + { + "<Alt>Print" /* us */, + "<Alt>Print" /* us+dvorak */, + "<Alt>Print" /* fr+azerty */, + "<Alt>Print" /* ru */, + }, + }, +}; + +static void +test_event_translation (shortcut_test *shortcut_test) +{ + g_autofree char *translated_name = NULL; + guint keyval; + GdkModifierType modifiers; + + for (int group = 0; group < NUM_LAYOUTS; group++) + { + if (!shortcut_test->expected_results[group]) + continue; + + normalize_keyval_and_mask (shortcut_test->keycode, + shortcut_test->modifiers, + group, + &keyval, + &modifiers); + + translated_name = gtk_accelerator_name (keyval, modifiers); + + if (g_strcmp0 (translated_name, shortcut_test->expected_results[group]) != 0) + { + g_error ("Result for keycode %u with modifieres %u for " + "group %d doesn't match '%s' (got: '%s')", + shortcut_test->keycode, + shortcut_test->modifiers, + group, + shortcut_test->expected_results[group], + translated_name); + g_test_fail (); + } + } +} + +static void +set_keyboard_layouts (char *layouts, + char *variants, + char *options) +{ + GSubprocess *proc; + GError *error = NULL; + + proc = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE, + &error, + "setxkbmap", + "-layout", layouts, + "-variant", variants, + "-option", options, + "-model", "pc105", + NULL); + + if (!proc || !g_subprocess_wait_check(proc, NULL, &error)) + { + g_critical ("Failed to set layout: %s", error->message); + exit (1); + } + + g_object_unref (proc); +} + +static void +run_shortcut_tests (void) +{ + set_keyboard_layouts ("us,us,fr,ru", ",dvorak,azerty,", ""); + + for (int i = 0; i < G_N_ELEMENTS(shortcut_tests); i++) + test_event_translation (&shortcut_tests[i]); +} + +int main (int argc, char **argv) +{ + g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); + g_setenv ("GDK_BACKEND", "x11", TRUE); + g_setenv ("LC_ALL", "C", TRUE); + + gtk_test_init (&argc, &argv, NULL); + + g_test_add_func ("/keyboard/shortcut-translation", + run_shortcut_tests); + + return g_test_run (); +} diff --git a/tests/keyboard/test-keyboard.py b/tests/keyboard/test-keyboard.py new file mode 100644 index 0000000..f6295f2 --- /dev/null +++ b/tests/keyboard/test-keyboard.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright © 2018 Red Hat, Inc +# 2021 Sebastian Keller +# +# 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 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/>. +# +# Authors: Benjamin Berg <bberg@redhat.com> +# Sebastian Keller <skeller@gnome.org> + +import os +import sys +import unittest + +try: + import dbusmock +except ImportError: + sys.stderr.write('You need python-dbusmock (http://pypi.python.org/pypi/python-dbusmock) for this test suite.\n') + sys.exit(1) + +# Add the shared directory to the search path +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'shared')) + +from gtest import GTest +from x11session import X11SessionTestCase + +BUILDDIR = os.environ.get('BUILDDIR', os.path.join(os.path.dirname(__file__))) + + +class PanelTestCase(X11SessionTestCase, GTest): + g_test_exe = os.path.join(BUILDDIR, 'test-keyboard-shortcuts') + + +if __name__ == '__main__': + # avoid writing to stderr + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..69667ac --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,11 @@ +subdir('common') +#subdir('datetime') +if host_is_linux + subdir('network') +endif + +subdir('interactive-panels') + +subdir('printers') +subdir('info') +subdir('keyboard') diff --git a/tests/network/cc-test-window.c b/tests/network/cc-test-window.c new file mode 100644 index 0000000..3e27fa9 --- /dev/null +++ b/tests/network/cc-test-window.c @@ -0,0 +1,181 @@ +/* + * 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 <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_box_append (GTK_BOX (shell->main_box), GTK_WIDGET (panel)); + } +} + +/* 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 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->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_window_set_child (GTK_WINDOW (self), self->main_box); +} + +CcTestWindow * +cc_test_window_new (void) +{ + return g_object_new (CC_TYPE_TEST_WINDOW, + "resizable", TRUE, + "title", "Test Settings", + NULL); +} diff --git a/tests/network/cc-test-window.h b/tests/network/cc-test-window.h new file mode 100644 index 0000000..abbdb61 --- /dev/null +++ b/tests/network/cc-test-window.h @@ -0,0 +1,35 @@ +/* + * 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 diff --git a/tests/network/meson.build b/tests/network/meson.build new file mode 100644 index 0000000..34f7c74 --- /dev/null +++ b/tests/network/meson.build @@ -0,0 +1,49 @@ + + +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(), +# Disable ATK, this should not be required but it caused CI failures -- 2018-12-07 + 'NO_AT_BRIDGE=1' +] + +test( + 'test-network-panel', + find_program('test-network-panel.py'), + env : envs, + timeout : 60 +) + +exe = executable( + 'test-wifi-panel-text', + ['test-wifi-text.c'], + include_directories : includes + [common_inc], + dependencies : common_deps + network_manager_deps + [libtestshell_dep], + link_with : [network_panel_lib], + c_args : cflags, +) + +test( + 'test-wif-panel-text', + exe, + env : envs, + timeout : 60 +) diff --git a/tests/network/nm-utils/README b/tests/network/nm-utils/README new file mode 100644 index 0000000..2b613ed --- /dev/null +++ b/tests/network/nm-utils/README @@ -0,0 +1,17 @@ +These files are either copied from NetworkManager or just empty files. The +files live in the nn-utils subdirectory as that makes the relative +includes that they contain work fine. + +The test-networkmanager-service.py is also from NetworkManager. It is +however extended for our use here. + +gsystem-local-alloc.h: shared/nm-utils/gsystem-local-alloc.h +nm-glib.h: shared/nm-utils/nm-glib.h +nm-macros-internal.h: shared/nm-utils/nm-macros-internal.h +nm-default.h: shared/nm-default.h +nm-dbus-compat.h: shared/nm-dbus-compat.h +nm-default.h: shared/nm-default.h +nm-test-utils-impl.c: shared/nm-test-utils-impl.c +nm-shared-utils.h: empty +nm-test-libnm-utils.h: empty +nm-hash-utils.h: empty diff --git a/tests/network/nm-utils/gsystem-local-alloc.h b/tests/network/nm-utils/gsystem-local-alloc.h new file mode 100644 index 0000000..51b6251 --- /dev/null +++ b/tests/network/nm-utils/gsystem-local-alloc.h @@ -0,0 +1,208 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2012 Colin Walters <walters@verbum.org>. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GSYSTEM_LOCAL_ALLOC_H__ +#define __GSYSTEM_LOCAL_ALLOC_H__ + +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define GS_DEFINE_CLEANUP_FUNCTION(Type, name, func) \ + static inline void name (void *v) \ + { \ + func (*(Type*)v); \ + } + +#define GS_DEFINE_CLEANUP_FUNCTION0(Type, name, func) \ + static inline void name (void *v) \ + { \ + if (*(Type*)v) \ + func (*(Type*)v); \ + } + +/* These functions shouldn't be invoked directly; + * they are stubs that: + * 1) Take a pointer to the location (typically itself a pointer). + * 2) Provide %NULL-safety where it doesn't exist already (e.g. g_object_unref) + */ + +/** + * gs_free: + * + * Call g_free() on a variable location when it goes out of scope. + */ +#define gs_free __attribute__ ((cleanup(gs_local_free))) +GS_DEFINE_CLEANUP_FUNCTION(void*, gs_local_free, g_free) + +/** + * gs_unref_object: + * + * Call g_object_unref() on a variable location when it goes out of + * scope. Note that unlike g_object_unref(), the variable may be + * %NULL. + */ +#define gs_unref_object __attribute__ ((cleanup(gs_local_obj_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref) + +/** + * gs_unref_variant: + * + * Call g_variant_unref() on a variable location when it goes out of + * scope. Note that unlike g_variant_unref(), the variable may be + * %NULL. + */ +#define gs_unref_variant __attribute__ ((cleanup(gs_local_variant_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GVariant*, gs_local_variant_unref, g_variant_unref) + +/** + * gs_free_variant_iter: + * + * Call g_variant_iter_free() on a variable location when it goes out of + * scope. + */ +#define gs_free_variant_iter __attribute__ ((cleanup(gs_local_variant_iter_free))) +GS_DEFINE_CLEANUP_FUNCTION0(GVariantIter*, gs_local_variant_iter_free, g_variant_iter_free) + +/** + * gs_free_variant_builder: + * + * Call g_variant_builder_unref() on a variable location when it goes out of + * scope. + */ +#define gs_unref_variant_builder __attribute__ ((cleanup(gs_local_variant_builder_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GVariantBuilder*, gs_local_variant_builder_unref, g_variant_builder_unref) + +/** + * gs_unref_array: + * + * Call g_array_unref() on a variable location when it goes out of + * scope. Note that unlike g_array_unref(), the variable may be + * %NULL. + + */ +#define gs_unref_array __attribute__ ((cleanup(gs_local_array_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GArray*, gs_local_array_unref, g_array_unref) + +/** + * gs_unref_ptrarray: + * + * Call g_ptr_array_unref() on a variable location when it goes out of + * scope. Note that unlike g_ptr_array_unref(), the variable may be + * %NULL. + + */ +#define gs_unref_ptrarray __attribute__ ((cleanup(gs_local_ptrarray_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref) + +/** + * gs_unref_hashtable: + * + * Call g_hash_table_unref() on a variable location when it goes out + * of scope. Note that unlike g_hash_table_unref(), the variable may + * be %NULL. + */ +#define gs_unref_hashtable __attribute__ ((cleanup(gs_local_hashtable_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GHashTable*, gs_local_hashtable_unref, g_hash_table_unref) + +/** + * gs_free_list: + * + * Call g_list_free() on a variable location when it goes out + * of scope. + */ +#define gs_free_list __attribute__ ((cleanup(gs_local_free_list))) +GS_DEFINE_CLEANUP_FUNCTION(GList*, gs_local_free_list, g_list_free) + +/** + * gs_free_slist: + * + * Call g_slist_free() on a variable location when it goes out + * of scope. + */ +#define gs_free_slist __attribute__ ((cleanup(gs_local_free_slist))) +GS_DEFINE_CLEANUP_FUNCTION(GSList*, gs_local_free_slist, g_slist_free) + +/** + * gs_free_checksum: + * + * Call g_checksum_free() on a variable location when it goes out + * of scope. Note that unlike g_checksum_free(), the variable may + * be %NULL. + */ +#define gs_free_checksum __attribute__ ((cleanup(gs_local_checksum_free))) +GS_DEFINE_CLEANUP_FUNCTION0(GChecksum*, gs_local_checksum_free, g_checksum_free) + +/** + * gs_unref_bytes: + * + * Call g_bytes_unref() on a variable location when it goes out + * of scope. Note that unlike g_bytes_unref(), the variable may + * be %NULL. + */ +#define gs_unref_bytes __attribute__ ((cleanup(gs_local_bytes_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GBytes*, gs_local_bytes_unref, g_bytes_unref) + +/** + * gs_strfreev: + * + * Call g_strfreev() on a variable location when it goes out of scope. + */ +#define gs_strfreev __attribute__ ((cleanup(gs_local_strfreev))) +GS_DEFINE_CLEANUP_FUNCTION(char**, gs_local_strfreev, g_strfreev) + +/** + * gs_free_error: + * + * Call g_error_free() on a variable location when it goes out of scope. + */ +#define gs_free_error __attribute__ ((cleanup(gs_local_free_error))) +GS_DEFINE_CLEANUP_FUNCTION0(GError*, gs_local_free_error, g_error_free) + +/** + * gs_unref_keyfile: + * + * Call g_key_file_unref() on a variable location when it goes out of scope. + */ +#define gs_unref_keyfile __attribute__ ((cleanup(gs_local_keyfile_unref))) +GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, gs_local_keyfile_unref, g_key_file_unref) + +static inline void +gs_cleanup_close_fdp (int *fdp) +{ + int fd; + + g_assert (fdp); + + fd = *fdp; + if (fd != -1) + (void) close (fd); +} + +/** + * gs_fd_close: + * + * Call close() on a variable location when it goes out of scope. + */ +#define gs_fd_close __attribute__((cleanup(gs_cleanup_close_fdp))) + +G_END_DECLS + +#endif diff --git a/tests/network/nm-utils/nm-dbus-compat.h b/tests/network/nm-utils/nm-dbus-compat.h new file mode 100644 index 0000000..dd97b5f --- /dev/null +++ b/tests/network/nm-utils/nm-dbus-compat.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * 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 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2015 Red Hat, Inc. + */ + +#ifndef __NM_DBUS_COMPAT_H__ +#define __NM_DBUS_COMPAT_H__ + +/* Copied from <dbus/dbus-shared.h> */ + +/* Bus names */ + +/** The bus name used to talk to the bus itself. */ +#define DBUS_SERVICE_DBUS "org.freedesktop.DBus" + +/* Paths */ +/** The object path used to talk to the bus itself. */ +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" +/** The object path used in local/in-process-generated messages. */ +#define DBUS_PATH_LOCAL "/org/freedesktop/DBus/Local" + +/* Interfaces, these #define don't do much other than + * catch typos at compile time + */ +/** The interface exported by the object with #DBUS_SERVICE_DBUS and #DBUS_PATH_DBUS */ +#define DBUS_INTERFACE_DBUS "org.freedesktop.DBus" +/** The interface supported by introspectable objects */ +#define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable" +/** The interface supported by objects with properties */ +#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" +/** The interface supported by most dbus peers */ +#define DBUS_INTERFACE_PEER "org.freedesktop.DBus.Peer" + +/** This is a special interface whose methods can only be invoked + * by the local implementation (messages from remote apps aren't + * allowed to specify this interface). + */ +#define DBUS_INTERFACE_LOCAL "org.freedesktop.DBus.Local" + +/* Owner flags */ +#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 /**< Allow another service to become the primary owner if requested */ +#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 /**< Request to replace the current primary owner */ +#define DBUS_NAME_FLAG_DO_NOT_QUEUE 0x4 /**< If we can not become the primary owner do not place us in the queue */ + +/* Replies to request for a name */ +#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 /**< Service has become the primary owner of the requested name */ +#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 /**< Service could not become the primary owner and has been placed in the queue */ +#define DBUS_REQUEST_NAME_REPLY_EXISTS 3 /**< Service is already in the queue */ +#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 /**< Service is already the primary owner */ + +/* Replies to releasing a name */ +#define DBUS_RELEASE_NAME_REPLY_RELEASED 1 /**< Service was released from the given name */ +#define DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2 /**< The given name does not exist on the bus */ +#define DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3 /**< Service is not an owner of the given name */ + +/* Replies to service starts */ +#define DBUS_START_REPLY_SUCCESS 1 /**< Service was auto started */ +#define DBUS_START_REPLY_ALREADY_RUNNING 2 /**< Service was already running */ + +#endif /* __NM_DBUS_COMPAT_H__ */ diff --git a/tests/network/nm-utils/nm-default.h b/tests/network/nm-utils/nm-default.h new file mode 100644 index 0000000..b9be476 --- /dev/null +++ b/tests/network/nm-utils/nm-default.h @@ -0,0 +1,316 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEFAULT_H__ +#define __NM_DEFAULT_H__ + +#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB (1 << 0) +#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB (1 << 1) +#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG (1 << 2) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM (1 << 3) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE (1 << 4) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE (1 << 5) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL (1 << 6) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE (1 << 7) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL (1 << 8) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB (1 << 9) +#define NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON (1 << 10) +#define NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD (1 << 11) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM_UTIL ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM_GLIB ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_LIBNM_UTIL \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_CLIENT ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_DAEMON ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \ + | NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_DAEMON \ + | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_GLIB ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + ) + +#ifndef NETWORKMANAGER_COMPILATION +#error Define NETWORKMANAGER_COMPILATION accordingly +#endif + +#ifndef G_LOG_DOMAIN +#if defined(NETWORKMANAGER_COMPILATION_TEST) +#define G_LOG_DOMAIN "test" +#elif NETWORKMANAGER_COMPILATION & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON +#define G_LOG_DOMAIN "NetworkManager" +#else +#error Need to define G_LOG_DOMAIN +#endif +#elif defined (NETWORKMANAGER_COMPILATION_TEST) || (NETWORKMANAGER_COMPILATION & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON) +#error Do not define G_LOG_DOMAIN with NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON +#endif + +/*****************************************************************************/ + +/* always include these headers for our internal source files. */ + +#ifndef ___CONFIG_H__ +#define ___CONFIG_H__ +#include <config.h> +#endif + +/* for internal compilation we don't want the deprecation macros + * to be in effect. Define the widest range of versions to effectively + * disable deprecation checks */ +#define NM_VERSION_MIN_REQUIRED NM_VERSION_0_9_8 + +#ifndef NM_MORE_ASSERTS +#define NM_MORE_ASSERTS 0 +#endif + +#if NM_MORE_ASSERTS == 0 +/* The cast macros like NM_TYPE() are implemented via G_TYPE_CHECK_INSTANCE_CAST() + * and _G_TYPE_CIC(). The latter, by default performs runtime checks of the type + * by calling g_type_check_instance_cast(). + * This check has a certain overhead without being helpful. + * + * Example 1: + * static void foo (NMType *obj) + * { + * access_obj_without_check (obj); + * } + * foo ((NMType *) obj); + * // There is no runtime check and passing an invalid pointer + * // leads to a crash. + * + * Example 2: + * static void foo (NMType *obj) + * { + * access_obj_without_check (obj); + * } + * foo (NM_TYPE (obj)); + * // There is a runtime check which prints a g_warning(), but that doesn't + * // avoid the crash as NM_TYPE() cannot do anything then passing on the + * // invalid pointer. + * + * Example 3: + * static void foo (NMType *obj) + * { + * g_return_if_fail (NM_IS_TYPE (obj)); + * access_obj_without_check (obj); + * } + * foo ((NMType *) obj); + * // There is a runtime check which prints a g_critical() which also avoids + * // the crash. That is actually helpful to catch bugs and avoid crashes. + * + * Example 4: + * static void foo (NMType *obj) + * { + * g_return_if_fail (NM_IS_TYPE (obj)); + * access_obj_without_check (obj); + * } + * foo (NM_TYPE (obj)); + * // The runtime check is performed twice, with printing a g_warning() and + * // a g_critical() and avoiding the crash. + * + * Example 3 is how it should be done. Type checks in NM_TYPE() are pointless. + * Disable them for our production builds. + */ +#ifndef G_DISABLE_CAST_CHECKS +#define G_DISABLE_CAST_CHECKS +#endif +#endif + +#if NM_MORE_ASSERTS == 0 +#ifndef G_DISABLE_CAST_CHECKS +/* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking + * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necesary + * because commonly this cast is needed during something like + * + * g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + * + * Note that in if the variant type would be invalid, the check still + * wouldn't make the buggy code magically work. Instead of passing a + * bogus type string (bad), it would pass %NULL to g_variant_builder_init() + * (also bad). + * + * Also, a function like g_variant_builder_init() already validates + * the input type via something like + * + * g_return_if_fail (g_variant_type_is_container (type)); + * + * So, by having G_VARIANT_TYPE() also validate the type, we validate + * twice, whereas the first validation is rather pointless because it + * doesn't prevent the function to be called with invalid arguments. + * + * Just patch G_VARIANT_TYPE() to perform no check. + */ +#undef G_VARIANT_TYPE +#define G_VARIANT_TYPE(type_string) ((const GVariantType *) (type_string)) +#endif +#endif + +#include <stdlib.h> + +/*****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB + +#include <glib.h> + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB +#error Cannot define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG and NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB +#endif +#include <glib/gi18n.h> +#elif (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB +#include <glib/gi18n-lib.h> +#endif + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS == 0 + +/* glib assertions (g_return_*(), g_assert*()) contain a textual representation + * of the checked statement. This part of the assertion blows up the size of the + * binary. Unless we compile a debug-build with NM_MORE_ASSERTS, drop these + * parts. Note that the failed assertion still prints the file and line where the + * assertion fails. That shall suffice. */ + +static inline void +_nm_g_return_if_fail_warning (const char *log_domain, + const char *file, + int line) +{ + char file_buf[256 + 15]; + + g_snprintf (file_buf, sizeof (file_buf), "((%s:%d))", file, line); + g_return_if_fail_warning (log_domain, file_buf, "<dropped>"); +} + +#define g_return_if_fail_warning(log_domain, pretty_function, expression) \ + _nm_g_return_if_fail_warning (log_domain, __FILE__, __LINE__) + +#define g_assertion_message_expr(domain, file, line, func, expr) \ + g_assertion_message_expr(domain, file, line, "<unknown-fcn>", (expr) ? "<dropped>" : NULL) + +#undef g_return_val_if_reached +#define g_return_val_if_reached(val) \ + G_STMT_START { \ + g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): should not be reached", \ + __FILE__, \ + __LINE__, \ + "<dropped>"); \ + return (val); \ + } G_STMT_END + +#undef g_return_if_reached +#define g_return_if_reached() \ + G_STMT_START { \ + g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): should not be reached", \ + __FILE__, \ + __LINE__, \ + "<dropped>"); \ + return; \ + } G_STMT_END + +#define NM_ASSERT_G_RETURN_EXPR(expr) "<dropped>" +#define NM_ASSERT_NO_MSG 1 + +#else + +#define NM_ASSERT_G_RETURN_EXPR(expr) ""expr"" +#define NM_ASSERT_NO_MSG 0 + +#endif + +/*****************************************************************************/ + +#include "nm-utils/nm-macros-internal.h" +#include "nm-utils/nm-shared-utils.h" + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL +/* no hash-utils in legacy code. */ +#else +#include "nm-utils/nm-hash-utils.h" +#endif + +/*****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL) +#include "nm-version.h" +#endif + +/*****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON +#include "nm-types.h" +#include "nm-logging.h" +#endif + +#if ((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM) && !((NETWORKMANAGER_COMPILATION) & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)) +#include "NetworkManager.h" +#endif + +#endif /* NM_NETWORKMANAGER_COMPILATION_WITH_GLIB */ + +/*****************************************************************************/ + +#endif /* __NM_DEFAULT_H__ */ diff --git a/tests/network/nm-utils/nm-glib.h b/tests/network/nm-utils/nm-glib.h new file mode 100644 index 0000000..f1498dc --- /dev/null +++ b/tests/network/nm-utils/nm-glib.h @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * 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 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2008 - 2018 Red Hat, Inc. + */ + +#ifndef __NM_GLIB_H__ +#define __NM_GLIB_H__ + + +#include <gio/gio.h> +#include <string.h> + +#include "gsystem-local-alloc.h" + +#ifdef __clang__ + +#undef G_GNUC_BEGIN_IGNORE_DEPRECATIONS +#undef G_GNUC_END_IGNORE_DEPRECATIONS + +#define G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") + +#define G_GNUC_END_IGNORE_DEPRECATIONS \ + _Pragma("clang diagnostic pop") + +#endif + +/* g_assert_cmpmem() is only available since glib 2.46. */ +#if !GLIB_CHECK_VERSION (2, 45, 7) +#define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ + gconstpointer __m1 = m1, __m2 = m2; \ + int __l1 = l1, __l2 = l2; \ + if (__l1 != __l2) \ + g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #l1 " (len(" #m1 ")) == " #l2 " (len(" #m2 "))", __l1, "==", __l2, 'i'); \ + else if (memcmp (__m1, __m2, __l1) != 0) \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "assertion failed (" #m1 " == " #m2 ")"); \ + } G_STMT_END +#endif + +/* Rumtime check for glib version. First do a compile time check which + * (if satisfied) shortcuts the runtime check. */ +static inline gboolean +nm_glib_check_version (guint major, guint minor, guint micro) +{ + return GLIB_CHECK_VERSION (major, minor, micro) + || ( ( glib_major_version > major) + || ( glib_major_version == major + && glib_minor_version > minor) + || ( glib_major_version == major + && glib_minor_version == minor + && glib_micro_version < micro)); +} + +#if !GLIB_CHECK_VERSION(2, 44, 0) +static inline gpointer +g_steal_pointer (gpointer pp) +{ + gpointer *ptr = (gpointer *) pp; + gpointer ref; + + ref = *ptr; + *ptr = NULL; + + return ref; +} + +/* type safety */ +#define g_steal_pointer(pp) \ + (0 ? (*(pp)) : (g_steal_pointer) (pp)) +#endif + + +static inline gboolean +_nm_g_strv_contains (const gchar * const *strv, + const gchar *str) +{ +#if !GLIB_CHECK_VERSION(2, 44, 0) + g_return_val_if_fail (strv != NULL, FALSE); + g_return_val_if_fail (str != NULL, FALSE); + + for (; *strv != NULL; strv++) { + if (g_str_equal (str, *strv)) + return TRUE; + } + + return FALSE; +#else + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return g_strv_contains (strv, str); + G_GNUC_END_IGNORE_DEPRECATIONS +#endif +} +#define g_strv_contains _nm_g_strv_contains + +#if !GLIB_CHECK_VERSION (2, 56, 0) +#define g_object_ref(Obj) ((typeof(Obj)) g_object_ref (Obj)) +#define g_object_ref_sink(Obj) ((typeof(Obj)) g_object_ref_sink (Obj)) +#endif + +#ifndef g_autofree +/* we still don't rely on recent glib to provide g_autofree. Hence, we continue + * to use our gs_* free macros that we took from libgsystem. + * + * To ease migration towards g_auto*, add a compat define for g_autofree. */ +#define g_autofree gs_free +#endif + +#endif /* __NM_GLIB_H__ */ diff --git a/tests/network/nm-utils/nm-hash-utils.h b/tests/network/nm-utils/nm-hash-utils.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/network/nm-utils/nm-hash-utils.h diff --git a/tests/network/nm-utils/nm-macros-internal.h b/tests/network/nm-utils/nm-macros-internal.h new file mode 100644 index 0000000..f72ab2a --- /dev/null +++ b/tests/network/nm-utils/nm-macros-internal.h @@ -0,0 +1,1384 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2014 Red Hat, Inc. + */ + +#ifndef __NM_MACROS_INTERNAL_H__ +#define __NM_MACROS_INTERNAL_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#define _nm_packed __attribute__ ((packed)) +#define _nm_unused __attribute__ ((unused)) +#define _nm_pure __attribute__ ((pure)) +#define _nm_const __attribute__ ((const)) +#define _nm_printf(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#define _nm_align(s) __attribute__ ((aligned (s))) +#define _nm_alignof(type) __alignof (type) +#define _nm_alignas(type) _nm_align (_nm_alignof (type)) + +#if __GNUC__ >= 7 +#define _nm_fallthrough __attribute__ ((fallthrough)) +#else +#define _nm_fallthrough +#endif + +/*****************************************************************************/ + +#ifdef thread_local +#define _nm_thread_local thread_local +/* + * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ + * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 + */ +#elif __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) +#define _nm_thread_local _Thread_local +#else +#define _nm_thread_local __thread +#endif + +/*****************************************************************************/ + +#include "nm-glib.h" + +/*****************************************************************************/ + +#define nm_offsetofend(t,m) (G_STRUCT_OFFSET (t,m) + sizeof (((t *) NULL)->m)) + +#define nm_auto(fcn) __attribute__ ((cleanup(fcn))) + +static inline int nm_close (int fd); + +/** + * nm_auto_free: + * + * Call free() on a variable location when it goes out of scope. + */ +#define nm_auto_free nm_auto(_nm_auto_free_impl) +GS_DEFINE_CLEANUP_FUNCTION(void*, _nm_auto_free_impl, free) + +static inline void +nm_free_secret (char *secret) +{ + if (secret) { + memset (secret, 0, strlen (secret)); + g_free (secret); + } +} + +static inline void +_nm_auto_free_secret_impl (char **v) +{ + nm_free_secret (*v); +} + +/** + * nm_auto_free_secret: + * + * Call g_free() on a variable location when it goes out of scope. + * Also, previously, calls memset(loc, 0, strlen(loc)) to clear out + * the secret. + */ +#define nm_auto_free_secret nm_auto(_nm_auto_free_secret_impl) + +static inline void +_nm_auto_unset_gvalue_impl (GValue *v) +{ + g_value_unset (v); +} +#define nm_auto_unset_gvalue nm_auto(_nm_auto_unset_gvalue_impl) + +static inline void +_nm_auto_unref_gtypeclass (gpointer v) +{ + if (v && *((gpointer *) v)) + g_type_class_unref (*((gpointer *) v)); +} +#define nm_auto_unref_gtypeclass nm_auto(_nm_auto_unref_gtypeclass) + +static inline void +_nm_auto_free_gstring_impl (GString **str) +{ + if (*str) + g_string_free (*str, TRUE); +} +#define nm_auto_free_gstring nm_auto(_nm_auto_free_gstring_impl) + +static inline void +_nm_auto_close_impl (int *pfd) +{ + if (*pfd >= 0) { + int errsv = errno; + + (void) nm_close (*pfd); + errno = errsv; + } +} +#define nm_auto_close nm_auto(_nm_auto_close_impl) + +static inline void +_nm_auto_fclose_impl (FILE **pfd) +{ + if (*pfd) { + int errsv = errno; + + (void) fclose (*pfd); + errno = errsv; + } +} +#define nm_auto_fclose nm_auto(_nm_auto_fclose_impl) + +static inline void +_nm_auto_protect_errno (int *p_saved_errno) +{ + errno = *p_saved_errno; +} +#define NM_AUTO_PROTECT_ERRNO(errsv_saved) nm_auto(_nm_auto_protect_errno) _nm_unused const int errsv_saved = (errno) + +/*****************************************************************************/ + +/* http://stackoverflow.com/a/11172679 */ +#define _NM_UTILS_MACRO_FIRST(...) __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway) +#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...) first + +#define _NM_UTILS_MACRO_REST(...) __NM_UTILS_MACRO_REST_HELPER(__NM_UTILS_MACRO_REST_NUM(__VA_ARGS__), __VA_ARGS__) +#define __NM_UTILS_MACRO_REST_HELPER(qty, ...) __NM_UTILS_MACRO_REST_HELPER2(qty, __VA_ARGS__) +#define __NM_UTILS_MACRO_REST_HELPER2(qty, ...) __NM_UTILS_MACRO_REST_HELPER_##qty(__VA_ARGS__) +#define __NM_UTILS_MACRO_REST_HELPER_ONE(first) +#define __NM_UTILS_MACRO_REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__ +#define __NM_UTILS_MACRO_REST_NUM(...) \ + __NM_UTILS_MACRO_REST_SELECT_30TH(__VA_ARGS__, \ + TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ + TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ + TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ + TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ + TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ + TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway) +#define __NM_UTILS_MACRO_REST_SELECT_30TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, ...) a30 + +/*****************************************************************************/ + +/* http://stackoverflow.com/a/2124385/354393 */ + +#define NM_NARG(...) \ + _NM_NARG(__VA_ARGS__,_NM_NARG_RSEQ_N()) +#define _NM_NARG(...) \ + _NM_NARG_ARG_N(__VA_ARGS__) +#define _NM_NARG_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define _NM_NARG_RSEQ_N() \ + 63,62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9,8,7,6,5,4,3,2,1,0 + +/*****************************************************************************/ + +#if defined (__GNUC__) +#define _NM_PRAGMA_WARNING_DO(warning) G_STRINGIFY(GCC diagnostic ignored warning) +#elif defined (__clang__) +#define _NM_PRAGMA_WARNING_DO(warning) G_STRINGIFY(clang diagnostic ignored warning) +#endif + +/* you can only suppress a specific warning that the compiler + * understands. Otherwise you will get another compiler warning + * about invalid pragma option. + * It's not that bad however, because gcc and clang often have the + * same name for the same warning. */ + +#if defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#define NM_PRAGMA_WARNING_DISABLE(warning) \ + _Pragma("GCC diagnostic push") \ + _Pragma(_NM_PRAGMA_WARNING_DO(warning)) +#elif defined (__clang__) +#define NM_PRAGMA_WARNING_DISABLE(warning) \ + _Pragma("clang diagnostic push") \ + _Pragma(_NM_PRAGMA_WARNING_DO("-Wunknown-warning-option")) \ + _Pragma(_NM_PRAGMA_WARNING_DO(warning)) +#else +#define NM_PRAGMA_WARNING_DISABLE(warning) +#endif + +#if defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#define NM_PRAGMA_WARNING_REENABLE \ + _Pragma("GCC diagnostic pop") +#elif defined (__clang__) +#define NM_PRAGMA_WARNING_REENABLE \ + _Pragma("clang diagnostic pop") +#else +#define NM_PRAGMA_WARNING_REENABLE +#endif + +/*****************************************************************************/ + +/** + * NM_G_ERROR_MSG: + * @error: (allow-none): the #GError instance + * + * All functions must follow the convention that when they + * return a failure, they must also set the GError to a valid + * message. For external API however, we want to be extra + * careful before accessing the error instance. Use NM_G_ERROR_MSG() + * which is safe to use on NULL. + * + * Returns: the error message. + **/ +static inline const char * +NM_G_ERROR_MSG (GError *error) +{ + return error ? (error->message ? : "(null)") : "(no-error)"; \ +} + +/*****************************************************************************/ + +/* macro to return strlen() of a compile time string. */ +#define NM_STRLEN(str) ( sizeof ("" str) - 1 ) + +/* returns the length of a NULL terminated array of pointers, + * like g_strv_length() does. The difference is: + * - it operats on arrays of pointers (of any kind, requiring no cast). + * - it accepts NULL to return zero. */ +#define NM_PTRARRAY_LEN(array) \ + ({ \ + typeof (*(array)) *const _array = (array); \ + gsize _n = 0; \ + \ + if (_array) { \ + _nm_unused gconstpointer _type_check_is_pointer = _array[0]; \ + \ + while (_array[_n]) \ + _n++; \ + } \ + _n; \ + }) + +/* Note: @value is only evaluated when *out_val is present. + * Thus, + * NM_SET_OUT (out_str, g_strdup ("hallo")); + * does the right thing. + */ +#define NM_SET_OUT(out_val, value) \ + G_STMT_START { \ + typeof(*(out_val)) *_out_val = (out_val); \ + \ + if (_out_val) { \ + *_out_val = (value); \ + } \ + } G_STMT_END + +/*****************************************************************************/ + +#ifndef _NM_CC_SUPPORT_AUTO_TYPE +#if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9 ))) +#define _NM_CC_SUPPORT_AUTO_TYPE 1 +#else +#define _NM_CC_SUPPORT_AUTO_TYPE 0 +#endif +#endif + +#ifndef _NM_CC_SUPPORT_GENERIC +#if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9 ))) || (defined (__clang__)) +#define _NM_CC_SUPPORT_GENERIC 1 +#else +#define _NM_CC_SUPPORT_GENERIC 0 +#endif +#endif + +#if _NM_CC_SUPPORT_AUTO_TYPE +#define _nm_auto_type __auto_type +#endif + +#if _NM_CC_SUPPORT_GENERIC +#define _NM_CONSTCAST_FULL_1(type, obj_expr, obj) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) +#define _NM_CONSTCAST_FULL_2(type, obj_expr, obj, alias_type2) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const alias_type2 *const: ((const type *) (obj)), \ + const alias_type2 * : ((const type *) (obj)), \ + alias_type2 *const: (( type *) (obj)), \ + alias_type2 * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) +#define _NM_CONSTCAST_FULL_3(type, obj_expr, obj, alias_type2, alias_type3) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const alias_type2 *const: ((const type *) (obj)), \ + const alias_type2 * : ((const type *) (obj)), \ + alias_type2 *const: (( type *) (obj)), \ + alias_type2 * : (( type *) (obj)), \ + const alias_type3 *const: ((const type *) (obj)), \ + const alias_type3 * : ((const type *) (obj)), \ + alias_type3 *const: (( type *) (obj)), \ + alias_type3 * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) +#define _NM_CONSTCAST_FULL_4(type, obj_expr, obj, alias_type2, alias_type3, alias_type4) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const alias_type2 *const: ((const type *) (obj)), \ + const alias_type2 * : ((const type *) (obj)), \ + alias_type2 *const: (( type *) (obj)), \ + alias_type2 * : (( type *) (obj)), \ + const alias_type3 *const: ((const type *) (obj)), \ + const alias_type3 * : ((const type *) (obj)), \ + alias_type3 *const: (( type *) (obj)), \ + alias_type3 * : (( type *) (obj)), \ + const alias_type4 *const: ((const type *) (obj)), \ + const alias_type4 * : ((const type *) (obj)), \ + alias_type4 *const: (( type *) (obj)), \ + alias_type4 * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) +#define _NM_CONSTCAST_FULL_x(type, obj_expr, obj, n, ...) (_NM_CONSTCAST_FULL_##n (type, obj_expr, obj, ##__VA_ARGS__)) +#define _NM_CONSTCAST_FULL_y(type, obj_expr, obj, n, ...) (_NM_CONSTCAST_FULL_x (type, obj_expr, obj, n, ##__VA_ARGS__)) +#define NM_CONSTCAST_FULL( type, obj_expr, obj, ...) (_NM_CONSTCAST_FULL_y (type, obj_expr, obj, NM_NARG (dummy, ##__VA_ARGS__), ##__VA_ARGS__)) +#else +#define NM_CONSTCAST_FULL( type, obj_expr, obj, ...) ((type *) (obj)) +#endif + +#define NM_CONSTCAST(type, obj, ...) \ + NM_CONSTCAST_FULL(type, (obj), (obj), ##__VA_ARGS__) + +#if _NM_CC_SUPPORT_GENERIC +#define NM_UNCONST_PTR(type, arg) \ + _Generic ((arg), \ + const type *: ((type *) (arg)), \ + type *: ((type *) (arg))) +#else +#define NM_UNCONST_PTR(type, arg) \ + ((type *) (arg)) +#endif + +#if _NM_CC_SUPPORT_GENERIC +#define NM_UNCONST_PPTR(type, arg) \ + _Generic ((arg), \ + const type * *: ((type **) (arg)), \ + type * *: ((type **) (arg)), \ + const type *const*: ((type **) (arg)), \ + type *const*: ((type **) (arg))) +#else +#define NM_UNCONST_PPTR(type, arg) \ + ((type **) (arg)) +#endif + +#define NM_GOBJECT_CAST(type, obj, is_check, ...) \ + ({ \ + const void *_obj = (obj); \ + \ + nm_assert (_obj || (is_check (_obj))); \ + NM_CONSTCAST_FULL (type, (obj), _obj, GObject, ##__VA_ARGS__); \ + }) + +#define NM_GOBJECT_CAST_NON_NULL(type, obj, is_check, ...) \ + ({ \ + const void *_obj = (obj); \ + \ + nm_assert (is_check (_obj)); \ + NM_CONSTCAST_FULL (type, (obj), _obj, GObject, ##__VA_ARGS__); \ + }) + +#if _NM_CC_SUPPORT_GENERIC +/* returns @value, if the type of @value matches @type. + * This requires support for C11 _Generic(). If no support is + * present, this returns @value directly. + * + * It's useful to check the let the compiler ensure that @value is + * of a certain type. */ +#define _NM_ENSURE_TYPE(type, value) (_Generic ((value), type: (value))) +#else +#define _NM_ENSURE_TYPE(type, value) (value) +#endif + +#if _NM_CC_SUPPORT_GENERIC +#define NM_PROPAGATE_CONST(test_expr, ptr) \ + (_Generic ((test_expr), \ + const typeof (*(test_expr)) *: ((const typeof (*(ptr)) *) (ptr)), \ + default: (_Generic ((test_expr), \ + typeof (*(test_expr)) *: (ptr))))) +#else +#define NM_PROPAGATE_CONST(test_expr, ptr) (ptr) +#endif + +/*****************************************************************************/ + +#define _NM_IN_SET_EVAL_1( op, _x, y) (_x == (y)) +#define _NM_IN_SET_EVAL_2( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_1 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_3( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_2 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_4( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_3 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_5( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_4 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_6( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_5 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_7( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_6 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_8( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_7 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_9( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_8 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_10(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_9 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_11(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_10 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_12(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_11 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_13(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_12 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_14(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_13 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_15(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_14 (op, _x, __VA_ARGS__) +#define _NM_IN_SET_EVAL_16(op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_15 (op, _x, __VA_ARGS__) + +#define _NM_IN_SET_EVAL_N2(op, _x, n, ...) (_NM_IN_SET_EVAL_##n(op, _x, __VA_ARGS__)) +#define _NM_IN_SET_EVAL_N(op, type, x, n, ...) \ + ({ \ + type _x = (x); \ + \ + /* trigger a -Wenum-compare warning */ \ + nm_assert (TRUE || _x == (x)); \ + \ + !!_NM_IN_SET_EVAL_N2(op, _x, n, __VA_ARGS__); \ + }) + +#define _NM_IN_SET(op, type, x, ...) _NM_IN_SET_EVAL_N(op, type, x, NM_NARG (__VA_ARGS__), __VA_ARGS__) + +/* Beware that this does short-circuit evaluation (use "||" instead of "|") + * which has a possibly unexpected non-function-like behavior. + * Use NM_IN_SET_SE if you need all arguments to be evaluted. */ +#define NM_IN_SET(x, ...) _NM_IN_SET(||, typeof (x), x, __VA_ARGS__) + +/* "SE" stands for "side-effect". Contrary to NM_IN_SET(), this does not do + * short-circuit evaluation, which can make a difference if the arguments have + * side-effects. */ +#define NM_IN_SET_SE(x, ...) _NM_IN_SET(|, typeof (x), x, __VA_ARGS__) + +/* the *_TYPED forms allow to explicitly select the type of "x". This is useful + * if "x" doesn't support typeof (bitfields) or you want to gracefully convert + * a type using automatic type conversion rules (but not forcing the conversion + * with a cast). */ +#define NM_IN_SET_TYPED(type, x, ...) _NM_IN_SET(||, type, x, __VA_ARGS__) +#define NM_IN_SET_SE_TYPED(type, x, ...) _NM_IN_SET(|, type, x, __VA_ARGS__) + +/*****************************************************************************/ + +static inline gboolean +_NM_IN_STRSET_streq (const char *x, const char *s) +{ + return s && strcmp (x, s) == 0; +} + +#define _NM_IN_STRSET_EVAL_1( op, _x, y) _NM_IN_STRSET_streq (_x, y) +#define _NM_IN_STRSET_EVAL_2( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_1 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_3( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_2 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_4( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_3 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_5( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_4 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_6( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_5 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_7( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_6 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_8( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_7 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_9( op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_8 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_10(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_9 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_11(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_10 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_12(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_11 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_13(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_12 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_14(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_13 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_15(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_14 (op, _x, __VA_ARGS__) +#define _NM_IN_STRSET_EVAL_16(op, _x, y, ...) _NM_IN_STRSET_streq (_x, y) op _NM_IN_STRSET_EVAL_15 (op, _x, __VA_ARGS__) + +#define _NM_IN_STRSET_EVAL_N2(op, _x, n, ...) (_NM_IN_STRSET_EVAL_##n(op, _x, __VA_ARGS__)) +#define _NM_IN_STRSET_EVAL_N(op, x, n, ...) \ + ({ \ + const char *_x = (x); \ + ( ((_x == NULL) && _NM_IN_SET_EVAL_N2 (op, ((const char *) NULL), n, __VA_ARGS__)) \ + || ((_x != NULL) && _NM_IN_STRSET_EVAL_N2 (op, _x, n, __VA_ARGS__)) \ + ); \ + }) + +/* Beware that this does short-circuit evaluation (use "||" instead of "|") + * which has a possibly unexpected non-function-like behavior. + * Use NM_IN_STRSET_SE if you need all arguments to be evaluted. */ +#define NM_IN_STRSET(x, ...) _NM_IN_STRSET_EVAL_N(||, x, NM_NARG (__VA_ARGS__), __VA_ARGS__) + +/* "SE" stands for "side-effect". Contrary to NM_IN_STRSET(), this does not do + * short-circuit evaluation, which can make a difference if the arguments have + * side-effects. */ +#define NM_IN_STRSET_SE(x, ...) _NM_IN_STRSET_EVAL_N(|, x, NM_NARG (__VA_ARGS__), __VA_ARGS__) + +#define NM_STRCHAR_ALL(str, ch_iter, predicate) \ + ({ \ + gboolean _val = TRUE; \ + const char *_str = (str); \ + \ + if (_str) { \ + for (;;) { \ + const char ch_iter = _str[0]; \ + \ + if (ch_iter != '\0') { \ + if (predicate) {\ + _str++; \ + continue; \ + } \ + _val = FALSE; \ + } \ + break; \ + } \ + } \ + _val; \ + }) + +#define NM_STRCHAR_ANY(str, ch_iter, predicate) \ + ({ \ + gboolean _val = FALSE; \ + const char *_str = (str); \ + \ + if (_str) { \ + for (;;) { \ + const char ch_iter = _str[0]; \ + \ + if (ch_iter != '\0') { \ + if (predicate) { \ + ; \ + } else { \ + _str++; \ + continue; \ + } \ + _val = TRUE; \ + } \ + break; \ + } \ + } \ + _val; \ + }) + +/*****************************************************************************/ + +/* NM_CACHED_QUARK() returns the GQuark for @string, but caches + * it in a static variable to speed up future lookups. + * + * @string must be a string literal. + */ +#define NM_CACHED_QUARK(string) \ + ({ \ + static GQuark _nm_cached_quark = 0; \ + \ + (G_LIKELY (_nm_cached_quark != 0) \ + ? _nm_cached_quark \ + : (_nm_cached_quark = g_quark_from_static_string (""string""))); \ + }) + +/* NM_CACHED_QUARK_FCN() is essentially the same as G_DEFINE_QUARK + * with two differences: + * - @string must be a quoted string-literal + * - @fcn must be the full function name, while G_DEFINE_QUARK() appends + * "_quark" to the function name. + * Both properties of G_DEFINE_QUARK() are non favorable, because you can no + * longer grep for string/fcn -- unless you are aware that you are searching + * for G_DEFINE_QUARK() and omit quotes / append _quark(). With NM_CACHED_QUARK_FCN(), + * ctags/cscope can locate the use of @fcn (though it doesn't recognize that + * NM_CACHED_QUARK_FCN() defines it). + */ +#define NM_CACHED_QUARK_FCN(string, fcn) \ +GQuark \ +fcn (void) \ +{ \ + return NM_CACHED_QUARK (string); \ +} + +/*****************************************************************************/ + +#define nm_streq(s1, s2) (strcmp (s1, s2) == 0) +#define nm_streq0(s1, s2) (g_strcmp0 (s1, s2) == 0) + +/*****************************************************************************/ + +static inline GString * +nm_gstring_prepare (GString **l) +{ + if (*l) + g_string_set_size (*l, 0); + else + *l = g_string_sized_new (30); + return *l; +} + +static inline const char * +nm_str_not_empty (const char *str) +{ + return str && str[0] ? str : NULL; +} + +static inline char * +nm_strdup_not_empty (const char *str) +{ + return str && str[0] ? g_strdup (str) : NULL; +} + +static inline char * +nm_str_realloc (char *str) +{ + gs_free char *s = str; + + /* Returns a new clone of @str and frees @str. The point is that @str + * possibly points to a larger chunck of memory. We want to freshly allocate + * a buffer. + * + * We could use realloc(), but that might not do anything or leave + * @str in its memory pool for chunks of a different size (bad for + * fragmentation). + * + * This is only useful when we want to keep the buffer around for a long + * time and want to re-allocate a more optimal buffer. */ + + return g_strdup (s); +} + +/*****************************************************************************/ + +#define NM_PRINT_FMT_QUOTED(cond, prefix, str, suffix, str_else) \ + (cond) ? (prefix) : "", \ + (cond) ? (str) : (str_else), \ + (cond) ? (suffix) : "" +#define NM_PRINT_FMT_QUOTE_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg), "\"", "(null)") + +/*****************************************************************************/ + +/* glib/C provides the following kind of assertions: + * - assert() -- disable with NDEBUG + * - g_return_if_fail() -- disable with G_DISABLE_CHECKS + * - g_assert() -- disable with G_DISABLE_ASSERT + * but they are all enabled by default and usually even production builds have + * these kind of assertions enabled. It also means, that disabling assertions + * is an untested configuration, and might have bugs. + * + * Add our own assertion macro nm_assert(), which is disabled by default and must + * be explicitly enabled. They are useful for more expensive checks or checks that + * depend less on runtime conditions (that is, are generally expected to be true). */ + +#ifndef NM_MORE_ASSERTS +#define NM_MORE_ASSERTS 0 +#endif + +#if NM_MORE_ASSERTS +#define nm_assert(cond) G_STMT_START { g_assert (cond); } G_STMT_END +#define nm_assert_se(cond) G_STMT_START { if (G_LIKELY (cond)) { ; } else { g_assert (FALSE && (cond)); } } G_STMT_END +#define nm_assert_not_reached() G_STMT_START { g_assert_not_reached (); } G_STMT_END +#else +#define nm_assert(cond) G_STMT_START { if (FALSE) { if (cond) { } } } G_STMT_END +#define nm_assert_se(cond) G_STMT_START { if (G_LIKELY (cond)) { ; } } G_STMT_END +#define nm_assert_not_reached() G_STMT_START { ; } G_STMT_END +#endif + +/*****************************************************************************/ + +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ +typedef enum { \ + _PROPERTY_ENUMS_0, \ + __VA_ARGS__ \ + _PROPERTY_ENUMS_LAST, \ +} _PropertyEnums; \ +static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, } + +#define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ +NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \ +static inline void \ +_notify (obj_type *obj, _PropertyEnums prop) \ +{ \ + nm_assert (G_IS_OBJECT (obj)); \ + nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \ + g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \ +} + +/*****************************************************************************/ + +#define _NM_GET_PRIVATE(self, type, is_check, ...) (&(NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__)->_priv)) +#if _NM_CC_SUPPORT_AUTO_TYPE +#define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) \ + ({ \ + _nm_auto_type _self = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \ + \ + NM_PROPAGATE_CONST (_self, _self->_priv); \ + }) +#else +#define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) (NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__)->_priv) +#endif + +/*****************************************************************************/ + +static inline gpointer +nm_g_object_ref (gpointer obj) +{ + /* g_object_ref() doesn't accept NULL. */ + if (obj) + g_object_ref (obj); + return obj; +} +#define nm_g_object_ref(obj) ((typeof (obj)) nm_g_object_ref (obj)) + +static inline void +nm_g_object_unref (gpointer obj) +{ + /* g_object_unref() doesn't accept NULL. Usually, we workaround that + * by using g_clear_object(), but sometimes that is not convenient + * (for example as destroy function for a hash table that can contain + * NULL values). */ + if (obj) + g_object_unref (obj); +} + +/* Assigns GObject @obj to destination @pdst, and takes an additional ref. + * The previous value of @pdst is unrefed. + * + * It makes sure to first increase the ref-count of @obj, and handles %NULL + * @obj correctly. + * */ +#define nm_g_object_ref_set(pp, obj) \ + ({ \ + typeof (*(pp)) *const _pp = (pp); \ + typeof (**_pp) *const _obj = (obj); \ + typeof (**_pp) *_p; \ + gboolean _changed = FALSE; \ + \ + if ( _pp \ + && ((_p = *_pp) != _obj)) { \ + if (_obj) { \ + nm_assert (G_IS_OBJECT (_obj)); \ + g_object_ref (_obj); \ + } \ + if (_p) { \ + nm_assert (G_IS_OBJECT (_p)); \ + *_pp = NULL; \ + g_object_unref (_p); \ + } \ + *_pp = _obj; \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +#define nm_clear_pointer(pp, destroy) \ + ({ \ + typeof (*(pp)) *_pp = (pp); \ + typeof (*_pp) _p; \ + gboolean _changed = FALSE; \ + \ + if ( _pp \ + && (_p = *_pp)) { \ + _nm_unused gconstpointer _p_check_is_pointer = _p; \ + \ + *_pp = NULL; \ + /* g_clear_pointer() assigns @destroy first to a local variable, so that + * you can call "g_clear_pointer (pp, (GDestroyNotify) destroy);" without + * gcc emitting a warning. We don't do that, hence, you cannot cast + * "destroy" first. + * + * On the upside: you are not supposed to cast fcn, because the pointer + * types are preserved. If you really need a cast, you should cast @pp. + * But that is hardly ever necessary. */ \ + (destroy) (_p); \ + \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +/* basically, replaces + * g_clear_pointer (&location, g_free) + * with + * nm_clear_g_free (&location) + * + * Another advantage is that by using a macro and typeof(), it is more + * typesafe and gives you for example a compiler warning when pp is a const + * pointer or points to a const-pointer. + */ +#define nm_clear_g_free(pp) \ + nm_clear_pointer (pp, g_free) + +#define nm_clear_g_object(pp) \ + nm_clear_pointer (pp, g_object_unref) + +static inline gboolean +nm_clear_g_source (guint *id) +{ + guint v; + + if ( id + && (v = *id)) { + *id = 0; + g_source_remove (v); + return TRUE; + } + return FALSE; +} + +static inline gboolean +nm_clear_g_signal_handler (gpointer self, gulong *id) +{ + gulong v; + + if ( id + && (v = *id)) { + *id = 0; + g_signal_handler_disconnect (self, v); + return TRUE; + } + return FALSE; +} + +static inline gboolean +nm_clear_g_variant (GVariant **variant) +{ + GVariant *v; + + if ( variant + && (v = *variant)) { + *variant = NULL; + g_variant_unref (v); + return TRUE; + } + return FALSE; +} + +static inline gboolean +nm_clear_g_cancellable (GCancellable **cancellable) +{ + GCancellable *v; + + if ( cancellable + && (v = *cancellable)) { + *cancellable = NULL; + g_cancellable_cancel (v); + g_object_unref (v); + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +/* Determine whether @x is a power of two (@x being an integer type). + * Basically, this returns TRUE, if @x has exactly one bit set. + * For negative values and zero, this always returns FALSE. */ +#define nm_utils_is_power_of_two(x) ({ \ + typeof(x) __x = (x); \ + \ + ( (__x > ((typeof(__x)) 0)) \ + && ((__x & (__x - (((typeof(__x)) 1)))) == ((typeof(__x)) 0))); \ + }) + +/*****************************************************************************/ + +#define NM_UTILS_LOOKUP_DEFAULT(v) return (v) +#define NM_UTILS_LOOKUP_DEFAULT_WARN(v) g_return_val_if_reached (v) +#define NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(v) { nm_assert_not_reached (); return (v); } +#define NM_UTILS_LOOKUP_ITEM(v, n) (void) 0; case v: return (n); (void) 0 +#define NM_UTILS_LOOKUP_STR_ITEM(v, n) NM_UTILS_LOOKUP_ITEM(v, ""n"") +#define NM_UTILS_LOOKUP_ITEM_IGNORE(v) (void) 0; case v: break; (void) 0 +#define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER() (void) 0; default: break; (void) 0 + +#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \ +scope result_type \ +fcn_name (lookup_type val) \ +{ \ + switch (val) { \ + (void) 0, \ + __VA_ARGS__ \ + (void) 0; \ + }; \ + { unknown_val; } \ +} + +#define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \ + _NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) +#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \ + _NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) + +/* Call the string-lookup-table function @fcn_name. If the function returns + * %NULL, the numeric index is converted to string using a alloca() buffer. + * Beware: this macro uses alloca(). */ +#define NM_UTILS_LOOKUP_STR(fcn_name, idx) \ + ({ \ + typeof (idx) _idx = (idx); \ + const char *_s; \ + \ + _s = fcn_name (_idx); \ + if (!_s) { \ + _s = g_alloca (30); \ + \ + g_snprintf ((char *) _s, 30, "(%lld)", (long long) _idx); \ + } \ + _s; \ + }) + +/*****************************************************************************/ + +/* check if @flags has exactly one flag (@check) set. You should call this + * only with @check being a compile time constant and a power of two. */ +#define NM_FLAGS_HAS(flags, check) \ + ( G_STATIC_ASSERT_EXPR ((check) > 0 && ((check) & ((check) - 1)) == 0), NM_FLAGS_ANY ((flags), (check)) ) + +#define NM_FLAGS_ANY(flags, check) ( ( ((flags) & (check)) != 0 ) ? TRUE : FALSE ) +#define NM_FLAGS_ALL(flags, check) ( ( ((flags) & (check)) == (check) ) ? TRUE : FALSE ) + +#define NM_FLAGS_SET(flags, val) ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + _flags | _val; \ + }) + +#define NM_FLAGS_UNSET(flags, val) ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + _flags & (~_val); \ + }) + +#define NM_FLAGS_ASSIGN(flags, val, assign) ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + (assign) \ + ? _flags | (_val) \ + : _flags & (~_val); \ + }) + +/*****************************************************************************/ + +#define _NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, ORIG_FUNC, VERSIONED_FUNC, ARGS_TYPED, ARGS) \ +RETURN_TYPE VERSIONED_FUNC ARGS_TYPED; \ +RETURN_TYPE VERSIONED_FUNC ARGS_TYPED \ +{ \ + return ORIG_FUNC ARGS; \ +} \ +RETURN_TYPE ORIG_FUNC ARGS_TYPED; \ +__asm__(".symver "G_STRINGIFY(VERSIONED_FUNC)", "G_STRINGIFY(ORIG_FUNC)"@"G_STRINGIFY(VERSION)) + +#define NM_BACKPORT_SYMBOL(VERSION, RETURN_TYPE, FUNC, ARGS_TYPED, ARGS) \ +_NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, FUNC, _##FUNC##_##VERSION, ARGS_TYPED, ARGS) + +/*****************************************************************************/ + +#define nm_str_skip_leading_spaces(str) \ + ({ \ + typeof (*(str)) *_str = (str); \ + _nm_unused const char *_str_type_check = _str; \ + \ + if (_str) { \ + while (g_ascii_isspace (_str[0])) \ + _str++; \ + } \ + _str; \ + }) + +static inline char * +nm_strstrip (char *str) +{ + /* g_strstrip doesn't like NULL. */ + return str ? g_strstrip (str) : NULL; +} + +static inline const char * +nm_strstrip_avoid_copy (const char *str, char **str_free) +{ + gsize l; + char *s; + + nm_assert (str_free && !*str_free); + + if (!str) + return NULL; + + str = nm_str_skip_leading_spaces (str); + l = strlen (str); + if ( l == 0 + || !g_ascii_isspace (str[l - 1])) + return str; + while ( l > 0 + && g_ascii_isspace (str[l - 1])) + l--; + + s = g_new (char, l + 1); + memcpy (s, str, l); + s[l] = '\0'; + *str_free = s; + return s; +} + +/* g_ptr_array_sort()'s compare function takes pointers to the + * value. Thus, you cannot use strcmp directly. You can use + * nm_strcmp_p(). + * + * Like strcmp(), this function is not forgiving to accept %NULL. */ +static inline int +nm_strcmp_p (gconstpointer a, gconstpointer b) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return strcmp (s1, s2); +} + +/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data(). + * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different + * signature. I guess, in glib there are knowledgeable people that ensure + * that this additional argument doesn't cause problems due to different ABI + * for every architecture that glib supports. + * For NetworkManager, we'd rather avoid such stunts. + **/ +static inline int +nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return strcmp (s1, s2); +} + +static inline int +nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const guint32 a = *((const guint32 *) p_a); + const guint32 b = *((const guint32 *) p_b); + + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +static inline int +nm_cmp_int2ptr_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + /* p_a and p_b are two pointers to a pointer, where the pointer is + * interpreted as a integer using GPOINTER_TO_INT(). + * + * That is the case of a hash-table that uses GINT_TO_POINTER() to + * convert integers as pointers, and the resulting keys-as-array + * array. */ + const int a = GPOINTER_TO_INT (*((gconstpointer *) p_a)); + const int b = GPOINTER_TO_INT (*((gconstpointer *) p_b)); + + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +/*****************************************************************************/ + +/* Taken from systemd's UNIQ_T and UNIQ macros. */ + +#define NM_UNIQ_T(x, uniq) G_PASTE(__unique_prefix_, G_PASTE(x, uniq)) +#define NM_UNIQ __COUNTER__ + +/*****************************************************************************/ + +/* glib's MIN()/MAX() macros don't have function-like behavior, in that they evaluate + * the argument possibly twice. + * + * Taken from systemd's MIN()/MAX() macros. */ + +#define NM_MIN(a, b) __NM_MIN(NM_UNIQ, a, NM_UNIQ, b) +#define __NM_MIN(aq, a, bq, b) \ + ({ \ + typeof (a) NM_UNIQ_T(A, aq) = (a); \ + typeof (b) NM_UNIQ_T(B, bq) = (b); \ + ((NM_UNIQ_T(A, aq) < NM_UNIQ_T(B, bq)) ? NM_UNIQ_T(A, aq) : NM_UNIQ_T(B, bq)); \ + }) + +#define NM_MAX(a, b) __NM_MAX(NM_UNIQ, a, NM_UNIQ, b) +#define __NM_MAX(aq, a, bq, b) \ + ({ \ + typeof (a) NM_UNIQ_T(A, aq) = (a); \ + typeof (b) NM_UNIQ_T(B, bq) = (b); \ + ((NM_UNIQ_T(A, aq) > NM_UNIQ_T(B, bq)) ? NM_UNIQ_T(A, aq) : NM_UNIQ_T(B, bq)); \ + }) + +#define NM_CLAMP(x, low, high) __NM_CLAMP(NM_UNIQ, x, NM_UNIQ, low, NM_UNIQ, high) +#define __NM_CLAMP(xq, x, lowq, low, highq, high) \ + ({ \ + typeof(x)NM_UNIQ_T(X,xq) = (x); \ + typeof(low) NM_UNIQ_T(LOW,lowq) = (low); \ + typeof(high) NM_UNIQ_T(HIGH,highq) = (high); \ + \ + ( (NM_UNIQ_T(X,xq) > NM_UNIQ_T(HIGH,highq)) \ + ? NM_UNIQ_T(HIGH,highq) \ + : (NM_UNIQ_T(X,xq) < NM_UNIQ_T(LOW,lowq)) \ + ? NM_UNIQ_T(LOW,lowq) \ + : NM_UNIQ_T(X,xq)); \ + }) + +/*****************************************************************************/ + +static inline guint +nm_encode_version (guint major, guint minor, guint micro) +{ + /* analog to the preprocessor macro NM_ENCODE_VERSION(). */ + return (major << 16) | (minor << 8) | micro; +} + +static inline void +nm_decode_version (guint version, guint *major, guint *minor, guint *micro) +{ + *major = (version & 0xFFFF0000u) >> 16; + *minor = (version & 0x0000FF00u) >> 8; + *micro = (version & 0x000000FFu); +} + +/*****************************************************************************/ + +/* taken from systemd's DECIMAL_STR_MAX() + * + * Returns the number of chars needed to format variables of the + * specified type as a decimal string. Adds in extra space for a + * negative '-' prefix (hence works correctly on signed + * types). Includes space for the trailing NUL. */ +#define NM_DECIMAL_STR_MAX(type) \ + (2+(sizeof(type) <= 1 ? 3 : \ + sizeof(type) <= 2 ? 5 : \ + sizeof(type) <= 4 ? 10 : \ + sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) + +/*****************************************************************************/ + +/* if @str is NULL, return "(null)". Otherwise, allocate a buffer using + * alloca() of and fill it with @str. @str will be quoted with double quote. + * If @str is longer then @trunc_at, the string is truncated and the closing + * quote is instead '^' to indicate truncation. + * + * Thus, the maximum stack allocated buffer will be @trunc_at+3. */ +#define nm_strquote_a(trunc_at, str) \ + ({ \ + const char *const _str = (str); \ + \ + (_str \ + ? ({ \ + const gsize _trunc_at = (trunc_at); \ + const gsize _strlen_trunc = NM_MIN (strlen (_str), _trunc_at); \ + char *_buf; \ + \ + _buf = g_alloca (_strlen_trunc + 3); \ + _buf[0] = '"'; \ + memcpy (&_buf[1], _str, _strlen_trunc); \ + _buf[_strlen_trunc + 1] = _str[_strlen_trunc] ? '^' : '"'; \ + _buf[_strlen_trunc + 2] = '\0'; \ + _buf; \ + }) \ + : "(null)"); \ + }) + +#define nm_sprintf_buf(buf, format, ...) \ + ({ \ + char * _buf = (buf); \ + int _buf_len; \ + \ + /* some static assert trying to ensure that the buffer is statically allocated. + * It disallows a buffer size of sizeof(gpointer) to catch that. */ \ + G_STATIC_ASSERT (G_N_ELEMENTS (buf) == sizeof (buf) && sizeof (buf) != sizeof (char *)); \ + _buf_len = g_snprintf (_buf, sizeof (buf), \ + ""format"", ##__VA_ARGS__); \ + nm_assert (_buf_len < sizeof (buf)); \ + _buf; \ + }) + +#define nm_sprintf_bufa(n_elements, format, ...) \ + ({ \ + char *_buf; \ + int _buf_len; \ + typeof (n_elements) _n_elements = (n_elements); \ + \ + _buf = g_alloca (_n_elements); \ + _buf_len = g_snprintf (_buf, _n_elements, \ + ""format"", ##__VA_ARGS__); \ + nm_assert (_buf_len < _n_elements); \ + _buf; \ + }) + +/* aims to alloca() a buffer and fill it with printf(format, name). + * Note that format must not contain any format specifier except + * "%s". + * If the resulting string would be too large for stack allocation, + * it allocates a buffer with g_malloc() and assigns it to *p_val_to_free. */ +#define nm_construct_name_a(format, name, p_val_to_free) \ + ({ \ + const char *const _name = (name); \ + char **const _p_val_to_free = (p_val_to_free); \ + const gsize _name_len = strlen (_name); \ + char *_buf2; \ + \ + nm_assert (_p_val_to_free && !*_p_val_to_free); \ + if (NM_STRLEN (format) + _name_len < 200) \ + _buf2 = nm_sprintf_bufa (NM_STRLEN (format) + _name_len, format, _name); \ + else { \ + _buf2 = g_strdup_printf (format, _name); \ + *_p_val_to_free = _buf2; \ + } \ + (const char *) _buf2; \ + }) + +/*****************************************************************************/ + +/** + * The boolean type _Bool is C99 while we mostly stick to C89. However, _Bool is too + * convenient to miss and is effectively available in gcc and clang. So, just use it. + * + * Usually, one would include "stdbool.h" to get the "bool" define which aliases + * _Bool. We provide this define here, because we want to make use of it anywhere. + * (also, stdbool.h is again C99). + * + * Using _Bool has advantages over gboolean: + * + * - commonly _Bool is one byte large, instead of gboolean's 4 bytes (because gboolean + * is a typedef for gint). Especially when having boolean fields in a struct, we can + * thereby easily save some space. + * + * - _Bool type guarantees that two "true" expressions compare equal. E.g. the follwing + * will not work: + * gboolean v1 = 1; + * gboolean v2 = 2; + * g_assert_cmpint (v1, ==, v2); // will fail + * For that, we often to use !! to coerce gboolean values to 0 or 1: + * g_assert_cmpint (!!v2, ==, TRUE); + * With _Bool type, this will be handled properly by the compiler. + * + * - For structs, we might want to safe even more space and use bitfields: + * struct s1 { + * gboolean v1:1; + * }; + * But the problem here is that gboolean is signed, so that + * v1 will be either 0 or -1 (not 1, TRUE). Thus, the following + * fails: + * struct s1 s = { .v1 = TRUE, }; + * g_assert_cmpint (s1.v1, ==, TRUE); + * It will however work just fine with bool/_Bool while retaining the + * notion of having a boolean value. + * + * Also, add the defines for "true" and "false". Those are nicely highlighted by the editor + * as special types, contrary to glib's "TRUE"/"FALSE". + */ + +#ifndef bool +#define bool _Bool +#define true 1 +#define false 0 +#endif + + +#ifdef _G_BOOLEAN_EXPR +/* g_assert() uses G_LIKELY(), which in turn uses _G_BOOLEAN_EXPR(). + * As glib's implementation uses a local variable _g_boolean_var_, + * we cannot do + * g_assert (some_macro ()); + * where some_macro() itself expands to ({g_assert(); ...}). + * In other words, you cannot have a g_assert() inside a g_assert() + * without getting a -Werror=shadow failure. + * + * Workaround that by re-defining _G_BOOLEAN_EXPR() + **/ +#undef _G_BOOLEAN_EXPR +#define __NM_G_BOOLEAN_EXPR_IMPL(v, expr) \ + ({ \ + int NM_UNIQ_T(V, v); \ + \ + if (expr) \ + NM_UNIQ_T(V, v) = 1; \ + else \ + NM_UNIQ_T(V, v) = 0; \ + NM_UNIQ_T(V, v); \ + }) +#define _G_BOOLEAN_EXPR(expr) __NM_G_BOOLEAN_EXPR_IMPL (NM_UNIQ, expr) +#endif + +/*****************************************************************************/ + +/** + * nm_steal_int: + * @p_val: pointer to an int type. + * + * Returns: *p_val and sets *p_val to zero the same time. + * Accepts %NULL, in which case also numeric 0 will be returned. + */ +#define nm_steal_int(p_val) \ + ({ \ + typeof (p_val) const _p_val = (p_val); \ + typeof (*_p_val) _val = 0; \ + \ + if ( _p_val \ + && (_val = *_p_val)) { \ + *_p_val = 0; \ + } \ + _val; \ + }) + +static inline int +nm_steal_fd (int *p_fd) +{ + int fd; + + if ( p_fd + && ((fd = *p_fd) >= 0)) { + *p_fd = -1; + return fd; + } + return -1; +} + +/** + * nm_close: + * + * Like close() but throws an assertion if the input fd is + * invalid. Closing an invalid fd is a programming error, so + * it's better to catch it early. + */ +static inline int +nm_close (int fd) +{ + int r; + + r = close (fd); + nm_assert (r != -1 || fd < 0 || errno != EBADF); + return r; +} + +#endif /* __NM_MACROS_INTERNAL_H__ */ diff --git a/tests/network/nm-utils/nm-shared-utils.h b/tests/network/nm-utils/nm-shared-utils.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/network/nm-utils/nm-shared-utils.h diff --git a/tests/network/nm-utils/nm-test-libnm-utils.h b/tests/network/nm-utils/nm-test-libnm-utils.h new file mode 100644 index 0000000..2ea781b --- /dev/null +++ b/tests/network/nm-utils/nm-test-libnm-utils.h @@ -0,0 +1,105 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 2014 - 2015 Red Hat, Inc. + */ + +#include "NetworkManager.h" + +#include "nm-test-utils.h" + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB +#include "nm-dbus-glib-types.h" +#endif + +/*****************************************************************************/ + +typedef struct { + GDBusConnection *bus; + GDBusProxy *proxy; + GPid pid; + int keepalive_fd; +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + struct { + DBusGConnection *bus; + } libdbus; +#endif +} NMTstcServiceInfo; + +NMTstcServiceInfo *nmtstc_service_init (void); +void nmtstc_service_cleanup (NMTstcServiceInfo *info); + +static inline void _nmtstc_auto_service_cleanup (NMTstcServiceInfo **info) +{ + if (info && *info) { + nmtstc_service_cleanup (*info); + *info = NULL; + } +} + +#define NMTSTC_SERVICE_INFO_SETUP(sinfo) \ + NM_PRAGMA_WARNING_DISABLE ("-Wunused-variable") \ + __attribute__ ((cleanup(_nmtstc_auto_service_cleanup))) NMTstcServiceInfo *sinfo = nmtstc_service_init (); \ + NM_PRAGMA_WARNING_REENABLE + +/*****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + +#include "nm-client.h" +#include "nm-remote-settings.h" + +NMClient *nmtstc_nm_client_new (void); +NMRemoteSettings *nmtstc_nm_remote_settings_new (void); + +#else + +NMDevice *nmtstc_service_add_device (NMTstcServiceInfo *info, + NMClient *client, + const char *method, + const char *ifname); + +NMDevice * nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, + NMClient *client, + const char *ifname, + const char *hwaddr, + const char **subchannels); + +#endif + +/*****************************************************************************/ + +void nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, + NMConnection *connection, + gboolean verify_connection, + char **out_path); + +void nmtstc_service_add_connection_variant (NMTstcServiceInfo *sinfo, + GVariant *connection, + gboolean verify_connection, + char **out_path); + +void nmtstc_service_update_connection (NMTstcServiceInfo *sinfo, + const char *path, + NMConnection *connection, + gboolean verify_connection); + +void nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo, + const char *path, + GVariant *connection, + gboolean verify_connection); + diff --git a/tests/network/nm-utils/nm-test-utils-impl.c b/tests/network/nm-utils/nm-test-utils-impl.c new file mode 100644 index 0000000..998d792 --- /dev/null +++ b/tests/network/nm-utils/nm-test-utils-impl.c @@ -0,0 +1,457 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * 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 - 2015 Red Hat, Inc. + * + */ + +#include "nm-default.h" + +#include <string.h> + +#include "NetworkManager.h" +#include "nm-dbus-compat.h" + +#include "nm-test-libnm-utils.h" + +/*****************************************************************************/ + +static gboolean +name_exists (GDBusConnection *c, const char *name) +{ + GVariant *reply; + gboolean exists = FALSE; + + reply = g_dbus_connection_call_sync (c, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetNameOwner", + g_variant_new ("(s)", name), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + NULL); + if (reply != NULL) { + exists = TRUE; + g_variant_unref (reply); + } + + return exists; +} + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB +static DBusGProxy * +_libdbus_create_proxy_test (DBusGConnection *bus) +{ + DBusGProxy *proxy; + + proxy = dbus_g_proxy_new_for_name (bus, + NM_DBUS_SERVICE, + NM_DBUS_PATH, + "org.freedesktop.NetworkManager.LibnmGlibTest"); + g_assert (proxy); + + dbus_g_proxy_set_default_timeout (proxy, G_MAXINT); + + return proxy; +} +#endif + +NMTstcServiceInfo * +nmtstc_service_init (void) +{ + NMTstcServiceInfo *info; + const char *args[] = { TEST_NM_PYTHON, TEST_NM_SERVICE, NULL }; + GError *error = NULL; + int i; + + info = g_malloc0 (sizeof (*info)); + + info->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + + /* Spawn the test service. info->keepalive_fd will be a pipe to the service's + * stdin; if it closes, the service will exit immediately. We use this to + * make sure the service exits if the test program crashes. + */ + g_spawn_async_with_pipes (NULL, (char **) args, NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, + &info->pid, &info->keepalive_fd, NULL, NULL, &error); + g_assert_no_error (error); + + /* Wait until the service is registered on the bus */ + for (i = 1000; i > 0; i--) { + if (name_exists (info->bus, "org.freedesktop.NetworkManager")) + break; + g_usleep (G_USEC_PER_SEC / 50); + } + g_assert (i > 0); + + /* Grab a proxy to our fake NM service to trigger tests */ + info->proxy = g_dbus_proxy_new_sync (info->bus, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + NM_DBUS_SERVICE, + NM_DBUS_PATH, + "org.freedesktop.NetworkManager.LibnmGlibTest", + NULL, &error); + g_assert_no_error (error); + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + info->libdbus.bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + g_assert_no_error (error); + g_assert (info->libdbus.bus); +#endif + return info; +} + +void +nmtstc_service_cleanup (NMTstcServiceInfo *info) +{ + int i; + + g_object_unref (info->proxy); + kill (info->pid, SIGTERM); + + /* Wait until the bus notices the service is gone */ + for (i = 100; i > 0; i--) { + if (!name_exists (info->bus, "org.freedesktop.NetworkManager")) + break; + g_usleep (G_USEC_PER_SEC / 50); + } + g_assert (i > 0); + + g_object_unref (info->bus); + nm_close (info->keepalive_fd); + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + g_clear_pointer (&info->libdbus.bus, dbus_g_connection_unref); +#endif + + memset (info, 0, sizeof (*info)); + g_free (info); +} + +#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB) +typedef struct { + GMainLoop *loop; + const char *ifname; + char *path; + NMDevice *device; +} AddDeviceInfo; + +static void +device_added_cb (NMClient *client, + NMDevice *device, + gpointer user_data) +{ + AddDeviceInfo *info = user_data; + + g_assert (device); + g_assert_cmpstr (nm_object_get_path (NM_OBJECT (device)), ==, info->path); + g_assert_cmpstr (nm_device_get_iface (device), ==, info->ifname); + + info->device = device; + g_main_loop_quit (info->loop); +} + +static gboolean +timeout (gpointer user_data) +{ + g_assert_not_reached (); + return G_SOURCE_REMOVE; +} + +static GVariant * +call_add_wired_device (GDBusProxy *proxy, const char *ifname, const char *hwaddr, + const char **subchannels, GError **error) +{ + const char *empty[] = { NULL }; + + if (!hwaddr) + hwaddr = "/"; + if (!subchannels) + subchannels = empty; + + return g_dbus_proxy_call_sync (proxy, + "AddWiredDevice", + g_variant_new ("(ss^as)", ifname, hwaddr, subchannels), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 3000, + NULL, + error); +} + +static GVariant * +call_add_device (GDBusProxy *proxy, const char *method, const char *ifname, GError **error) +{ + return g_dbus_proxy_call_sync (proxy, + method, + g_variant_new ("(s)", ifname), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 3000, + NULL, + error); +} + +static NMDevice * +add_device_common (NMTstcServiceInfo *sinfo, NMClient *client, + const char *method, const char *ifname, + const char *hwaddr, const char **subchannels) +{ + AddDeviceInfo info; + GError *error = NULL; + GVariant *ret; + guint timeout_id; + + if (g_strcmp0 (method, "AddWiredDevice") == 0) + ret = call_add_wired_device (sinfo->proxy, ifname, hwaddr, subchannels, &error); + else + ret = call_add_device (sinfo->proxy, method, ifname, &error); + + g_assert_no_error (error); + g_assert (ret); + g_assert_cmpstr (g_variant_get_type_string (ret), ==, "(o)"); + g_variant_get (ret, "(o)", &info.path); + g_variant_unref (ret); + + /* Wait for libnm to find the device */ + info.ifname = ifname; + info.loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (client, "device-added", + G_CALLBACK (device_added_cb), &info); + timeout_id = g_timeout_add_seconds (5, timeout, NULL); + g_main_loop_run (info.loop); + + g_source_remove (timeout_id); + g_signal_handlers_disconnect_by_func (client, device_added_cb, &info); + g_free (info.path); + g_main_loop_unref (info.loop); + + return info.device; +} + +NMDevice * +nmtstc_service_add_device (NMTstcServiceInfo *sinfo, NMClient *client, + const char *method, const char *ifname) +{ + return add_device_common (sinfo, client, method, ifname, NULL, NULL); +} + +NMDevice * +nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, NMClient *client, + const char *ifname, const char *hwaddr, + const char **subchannels) +{ + return add_device_common (sinfo, client, "AddWiredDevice", ifname, hwaddr, subchannels); +} +#endif + +void +nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, + NMConnection *connection, + gboolean verify_connection, + char **out_path) +{ +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + gs_unref_hashtable GHashTable *new_settings = NULL; + gboolean success; + gs_free_error GError *error = NULL; + gs_free char *path = NULL; + gs_unref_object DBusGProxy *proxy = NULL; + + g_assert (sinfo); + g_assert (NM_IS_CONNECTION (connection)); + + new_settings = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL); + + proxy = _libdbus_create_proxy_test (sinfo->libdbus.bus); + + success = dbus_g_proxy_call (proxy, + "AddConnection", + &error, + DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, new_settings, + G_TYPE_BOOLEAN, verify_connection, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &path, + G_TYPE_INVALID); + g_assert_no_error (error); + g_assert (success); + + g_assert (path && *path); + + if (out_path) + *out_path = g_strdup (path); +#else + nmtstc_service_add_connection_variant (sinfo, + nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL), + verify_connection, + out_path); +#endif +} + +void +nmtstc_service_add_connection_variant (NMTstcServiceInfo *sinfo, + GVariant *connection, + gboolean verify_connection, + char **out_path) +{ + GVariant *result; + GError *error = NULL; + + g_assert (sinfo); + g_assert (G_IS_DBUS_PROXY (sinfo->proxy)); + g_assert (g_variant_is_of_type (connection, G_VARIANT_TYPE ("a{sa{sv}}"))); + + result = g_dbus_proxy_call_sync (sinfo->proxy, + "AddConnection", + g_variant_new ("(vb)", connection, verify_connection), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 3000, + NULL, + &error); + g_assert_no_error (error); + g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE ("(o)"))); + if (out_path) + g_variant_get (result, "(o)", out_path); + g_variant_unref (result); +} + +void +nmtstc_service_update_connection (NMTstcServiceInfo *sinfo, + const char *path, + NMConnection *connection, + gboolean verify_connection) +{ + if (!path) + path = nm_connection_get_path (connection); + g_assert (path); + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + { + gs_unref_hashtable GHashTable *new_settings = NULL; + gboolean success; + gs_free_error GError *error = NULL; + gs_unref_object DBusGProxy *proxy = NULL; + + g_assert (sinfo); + g_assert (NM_IS_CONNECTION (connection)); + + new_settings = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL); + + proxy = _libdbus_create_proxy_test (sinfo->libdbus.bus); + + success = dbus_g_proxy_call (proxy, + "UpdateConnection", + &error, + DBUS_TYPE_G_OBJECT_PATH, path, + DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, new_settings, + G_TYPE_BOOLEAN, verify_connection, + G_TYPE_INVALID, + G_TYPE_INVALID); + g_assert_no_error (error); + g_assert (success); + } +#else + nmtstc_service_update_connection_variant (sinfo, + path, + nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL), + verify_connection); +#endif +} + +void +nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo, + const char *path, + GVariant *connection, + gboolean verify_connection) +{ + GVariant *result; + GError *error = NULL; + + g_assert (sinfo); + g_assert (G_IS_DBUS_PROXY (sinfo->proxy)); + g_assert (g_variant_is_of_type (connection, G_VARIANT_TYPE ("a{sa{sv}}"))); + g_assert (path && path[0] == '/'); + + result = g_dbus_proxy_call_sync (sinfo->proxy, + "UpdateConnection", + g_variant_new ("(ovb)", path, connection, verify_connection), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 3000, + NULL, + &error); + g_assert_no_error (error); + g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE ("()"))); + g_variant_unref (result); +} + +/*****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB +NMClient * +nmtstc_nm_client_new (void) +{ + NMClient *client; + DBusGConnection *bus; + GError *error = NULL; + gboolean success; + + bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + g_assert_no_error (error); + g_assert (bus); + + client = g_object_new (NM_TYPE_CLIENT, + NM_OBJECT_DBUS_CONNECTION, bus, + NM_OBJECT_DBUS_PATH, NM_DBUS_PATH, + NULL); + g_assert (client != NULL); + + dbus_g_connection_unref (bus); + + success = g_initable_init (G_INITABLE (client), NULL, &error); + g_assert_no_error (error); + g_assert (success == TRUE); + + return client; +} + +NMRemoteSettings * +nmtstc_nm_remote_settings_new (void) +{ + NMRemoteSettings *settings; + DBusGConnection *bus; + GError *error = NULL; + + bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + g_assert_no_error (error); + g_assert (bus); + + settings = nm_remote_settings_new (bus); + g_assert (settings); + + dbus_g_connection_unref (bus); + + return settings; +} +#endif + +/*****************************************************************************/ diff --git a/tests/network/nm-utils/nm-test-utils.h b/tests/network/nm-utils/nm-test-utils.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/network/nm-utils/nm-test-utils.h diff --git a/tests/network/nm-utils/test-networkmanager-service.py b/tests/network/nm-utils/test-networkmanager-service.py new file mode 100755 index 0000000..a5984ad --- /dev/null +++ b/tests/network/nm-utils/test-networkmanager-service.py @@ -0,0 +1,1445 @@ +#!/usr/bin/env python +# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + +from __future__ import print_function + +from gi.repository import GLib +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +import random +import collections +import uuid + +mainloop = GLib.MainLoop() + +# NM State +NM_STATE_UNKNOWN = 0 +NM_STATE_ASLEEP = 10 +NM_STATE_DISCONNECTED = 20 +NM_STATE_DISCONNECTING = 30 +NM_STATE_CONNECTING = 40 +NM_STATE_CONNECTED_LOCAL = 50 +NM_STATE_CONNECTED_SITE = 60 +NM_STATE_CONNECTED_GLOBAL = 70 + +# Device state +NM_DEVICE_STATE_UNKNOWN = 0 +NM_DEVICE_STATE_UNMANAGED = 10 +NM_DEVICE_STATE_UNAVAILABLE = 20 +NM_DEVICE_STATE_DISCONNECTED = 30 +NM_DEVICE_STATE_PREPARE = 40 +NM_DEVICE_STATE_CONFIG = 50 +NM_DEVICE_STATE_NEED_AUTH = 60 +NM_DEVICE_STATE_IP_CONFIG = 70 +NM_DEVICE_STATE_IP_CHECK = 80 +NM_DEVICE_STATE_SECONDARIES = 90 +NM_DEVICE_STATE_ACTIVATED = 100 +NM_DEVICE_STATE_DEACTIVATING = 110 +NM_DEVICE_STATE_FAILED = 120 + +# Device state reason +NM_DEVICE_STATE_REASON_NONE = 0 +NM_DEVICE_STATE_REASON_UNKNOWN = 1 +NM_DEVICE_STATE_REASON_NOW_MANAGED = 2 +NM_DEVICE_STATE_REASON_NOW_UNMANAGED = 3 +NM_DEVICE_STATE_REASON_CONFIG_FAILED = 4 +NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE = 5 +NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED = 6 +NM_DEVICE_STATE_REASON_NO_SECRETS = 7 +NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8 +NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED = 9 +NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED = 10 +NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT = 11 +NM_DEVICE_STATE_REASON_PPP_START_FAILED = 12 +NM_DEVICE_STATE_REASON_PPP_DISCONNECT = 13 +NM_DEVICE_STATE_REASON_PPP_FAILED = 14 +NM_DEVICE_STATE_REASON_DHCP_START_FAILED = 15 +NM_DEVICE_STATE_REASON_DHCP_ERROR = 16 +NM_DEVICE_STATE_REASON_DHCP_FAILED = 17 +NM_DEVICE_STATE_REASON_SHARED_START_FAILED = 18 +NM_DEVICE_STATE_REASON_SHARED_FAILED = 19 +NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED = 20 +NM_DEVICE_STATE_REASON_AUTOIP_ERROR = 21 +NM_DEVICE_STATE_REASON_AUTOIP_FAILED = 22 +NM_DEVICE_STATE_REASON_MODEM_BUSY = 23 +NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE = 24 +NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER = 25 +NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT = 26 +NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED = 27 +NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED = 28 +NM_DEVICE_STATE_REASON_GSM_APN_FAILED = 29 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING = 30 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED = 31 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT = 32 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED = 33 +NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED = 34 +NM_DEVICE_STATE_REASON_FIRMWARE_MISSING = 35 +NM_DEVICE_STATE_REASON_REMOVED = 36 +NM_DEVICE_STATE_REASON_SLEEPING = 37 +NM_DEVICE_STATE_REASON_CONNECTION_REMOVED = 38 +NM_DEVICE_STATE_REASON_USER_REQUESTED = 39 +NM_DEVICE_STATE_REASON_CARRIER = 40 +NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED = 41 +NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE = 42 +NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND = 43 +NM_DEVICE_STATE_REASON_BT_FAILED = 44 +NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED = 45 +NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED = 46 +NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED = 47 +NM_DEVICE_STATE_REASON_GSM_SIM_WRONG = 48 +NM_DEVICE_STATE_REASON_INFINIBAND_MODE = 49 +NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED = 50 +NM_DEVICE_STATE_REASON_BR2684_FAILED = 51 +NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE = 52 +NM_DEVICE_STATE_REASON_SSID_NOT_FOUND = 53 +NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED = 54 +NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED = 55 +NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED = 56 +NM_DEVICE_STATE_REASON_MODEM_FAILED = 57 +NM_DEVICE_STATE_REASON_MODEM_AVAILABLE = 58 +NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT = 59 +NM_DEVICE_STATE_REASON_NEW_ACTIVATION = 60 +NM_DEVICE_STATE_REASON_PARENT_CHANGED = 61 +NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED = 62 + +# Device type +NM_DEVICE_TYPE_UNKNOWN = 0 +NM_DEVICE_TYPE_ETHERNET = 1 +NM_DEVICE_TYPE_WIFI = 2 +NM_DEVICE_TYPE_UNUSED1 = 3 +NM_DEVICE_TYPE_UNUSED2 = 4 +NM_DEVICE_TYPE_BT = 5 +NM_DEVICE_TYPE_OLPC_MESH = 6 +NM_DEVICE_TYPE_WIMAX = 7 +NM_DEVICE_TYPE_MODEM = 8 +NM_DEVICE_TYPE_INFINIBAND = 9 +NM_DEVICE_TYPE_BOND = 10 +NM_DEVICE_TYPE_VLAN = 11 +NM_DEVICE_TYPE_ADSL = 12 +NM_DEVICE_TYPE_BRIDGE = 13 +NM_DEVICE_TYPE_GENERIC = 14 +NM_DEVICE_TYPE_TEAM = 15 + +# AC state +NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0 +NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1 +NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2 +NM_ACTIVE_CONNECTION_STATE_DEACTIVATING = 3 +NM_ACTIVE_CONNECTION_STATE_DEACTIVATED = 4 + +######################################################### +IFACE_DBUS = 'org.freedesktop.DBus' + +class UnknownInterfaceException(dbus.DBusException): + _dbus_error_name = IFACE_DBUS + '.UnknownInterface' + +class UnknownPropertyException(dbus.DBusException): + _dbus_error_name = IFACE_DBUS + '.UnknownProperty' + +def to_path_array(src): + array = dbus.Array([], signature=dbus.Signature('o')) + for o in src: + array.append(to_path(o)) + return array + +def to_path(src): + if src: + return dbus.ObjectPath(src.path) + return dbus.ObjectPath("/") + +class ExportedObj(dbus.service.Object): + + DBusInterface = collections.namedtuple('DBusInterface', ['dbus_iface', 'get_props_func', 'prop_changed_func']) + + def __init__(self, bus, object_path): + dbus.service.Object.__init__(self, bus, object_path) + self._bus = bus + self.path = object_path + self.__ensure_dbus_ifaces() + object_manager.add_object(self) + + def __ensure_dbus_ifaces(self): + if not hasattr(self, '_ExportedObj__dbus_ifaces'): + self.__dbus_ifaces = {} + + def add_dbus_interface(self, dbus_iface, get_props_func, prop_changed_func): + self.__ensure_dbus_ifaces() + self.__dbus_ifaces[dbus_iface] = ExportedObj.DBusInterface(dbus_iface, get_props_func, prop_changed_func) + + def __dbus_interface_get(self, dbus_iface): + if dbus_iface not in self.__dbus_ifaces: + raise UnknownInterfaceException() + return self.__dbus_ifaces[dbus_iface] + + def _dbus_property_get(self, dbus_iface, propname = None): + props = self.__dbus_interface_get(dbus_iface).get_props_func() + if propname is None: + return props + if propname not in props: + raise UnknownPropertyException() + return props[propname] + + def _dbus_property_notify(self, dbus_iface, propname): + prop = self._dbus_property_get(dbus_iface, propname) + self.__dbus_interface_get(dbus_iface).prop_changed_func(self, { propname: prop }) + ExportedObj.PropertiesChanged(self, dbus_iface, { propname: prop }, []) + + @dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as') + def PropertiesChanged(self, iface, changed, invalidated): + pass + + @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}') + def GetAll(self, dbus_iface): + return self._dbus_property_get(dbus_iface) + + @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v') + def Get(self, dbus_iface, name): + return self._dbus_property_get(dbus_iface, name) + + def get_managed_ifaces(self): + my_ifaces = {} + for iface in self.__dbus_ifaces: + my_ifaces[iface] = self.__dbus_ifaces[iface].get_props_func() + return self.path, my_ifaces + + def remove_from_connection(self): + object_manager.remove_object(self) + dbus.service.Object.remove_from_connection(self) + +################################################################### +IFACE_DEVICE = 'org.freedesktop.NetworkManager.Device' + +class NotSoftwareException(dbus.DBusException): + _dbus_error_name = IFACE_DEVICE + '.NotSoftware' + +PD_UDI = "Udi" +PD_IFACE = "Interface" +PD_DRIVER = "Driver" +PD_STATE = "State" +PD_STATE_REASON = "StateReason" +PD_ACTIVE_CONNECTION = "ActiveConnection" +PD_IP4_CONFIG = "Ip4Config" +PD_IP6_CONFIG = "Ip6Config" +PD_DHCP4_CONFIG = "Dhcp4Config" +PD_DHCP6_CONFIG = "Dhcp6Config" +PD_MANAGED = "Managed" +PD_AUTOCONNECT = "Autoconnect" +PD_DEVICE_TYPE = "DeviceType" +PD_AVAILABLE_CONNECTIONS = "AvailableConnections" + +class Device(ExportedObj): + counter = 1 + + def __init__(self, bus, iface, devtype): + object_path = "/org/freedesktop/NetworkManager/Devices/%d" % Device.counter + Device.counter = Device.counter + 1 + + self.iface = iface + self.udi = "/sys/devices/virtual/%s" % iface + self.devtype = devtype + self.active_connection = None + self.state = NM_DEVICE_STATE_UNAVAILABLE + self.reason = NM_DEVICE_STATE_REASON_NONE + self.ip4_config = None + self.ip6_config = None + self.dhcp4_config = None + self.dhcp6_config = None + self.available_connections = [] + + self.add_dbus_interface(IFACE_DEVICE, self.__get_props, Device.PropertiesChanged) + ExportedObj.__init__(self, bus, object_path) + + # Properties interface + def __get_props(self): + props = {} + props[PD_UDI] = self.udi + props[PD_IFACE] = self.iface + props[PD_DRIVER] = "virtual" + props[PD_STATE] = dbus.UInt32(self.state) + props[PD_STATE_REASON] = dbus.Struct((dbus.UInt32(self.state), dbus.UInt32(self.reason)), signature='uu') + props[PD_ACTIVE_CONNECTION] = to_path(self.active_connection) + props[PD_IP4_CONFIG] = to_path(self.ip4_config) + props[PD_IP6_CONFIG] = to_path(self.ip6_config) + props[PD_DHCP4_CONFIG] = to_path(self.dhcp4_config) + props[PD_DHCP6_CONFIG] = to_path(self.dhcp6_config) + props[PD_MANAGED] = True + props[PD_AUTOCONNECT] = True + props[PD_DEVICE_TYPE] = dbus.UInt32(self.devtype) + props[PD_AVAILABLE_CONNECTIONS] = to_path_array(self.available_connections) + return props + + # methods + @dbus.service.method(dbus_interface=IFACE_DEVICE, in_signature='', out_signature='') + def Disconnect(self): + pass + + @dbus.service.method(dbus_interface=IFACE_DEVICE, in_signature='', out_signature='') + def Delete(self): + # We don't currently support any software device types, so... + raise NotSoftwareException() + pass + + def __notify(self, propname): + self._dbus_property_notify(IFACE_DEVICE, propname) + + @dbus.service.signal(IFACE_DEVICE, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + + def set_active_connection(self, ac): + self.active_connection = ac + self.__notify(PD_ACTIVE_CONNECTION) + + def set_state_reason(self, state, reason): + self.state = state + self.reason = reason + self.__notify(PD_STATE) + self.__notify(PD_STATE_REASON) + + +################################################################### + +def random_mac(): + return '%02X:%02X:%02X:%02X:%02X:%02X' % ( + random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), + random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) + ) + +################################################################### +IFACE_WIRED = 'org.freedesktop.NetworkManager.Device.Wired' + +PE_HW_ADDRESS = "HwAddress" +PE_PERM_HW_ADDRESS = "PermHwAddress" +PE_SPEED = "Speed" +PE_CARRIER = "Carrier" +PE_S390_SUBCHANNELS = "S390Subchannels" + +class WiredDevice(Device): + def __init__(self, bus, iface, mac, subchannels): + + if mac is None: + self.mac = random_mac() + else: + self.mac = mac + self.carrier = False + self.speed = 100 + self.s390_subchannels = subchannels + + self.add_dbus_interface(IFACE_WIRED, self.__get_props, WiredDevice.PropertiesChanged) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_ETHERNET) + + # Properties interface + def __get_props(self): + props = {} + props[PE_HW_ADDRESS] = self.mac + props[PE_PERM_HW_ADDRESS] = self.mac + props[PE_SPEED] = dbus.UInt32(self.speed) + props[PE_CARRIER] = self.carrier + props[PE_S390_SUBCHANNELS] = self.s390_subchannels + return props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_WIRED, propname) + + @dbus.service.signal(IFACE_WIRED, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + + def set_wired_speed(self, speed): + if speed > 0: + self.speed = speed + self.carrier = True + self.__notify(PE_SPEED) + self.__notify(PE_CARRIER) + else: + self.speed = 100 + self.carrier = False + self.__notify(PE_CARRIER) + self.__notify(PE_SPEED) + +################################################################### +IFACE_VLAN = 'org.freedesktop.NetworkManager.Device.Vlan' + +PV_HW_ADDRESS = "HwAddress" +PV_CARRIER = "Carrier" +PV_VLAN_ID = "VlanId" + +class VlanDevice(Device): + def __init__(self, bus, iface): + self.mac = random_mac() + self.carrier = False + self.vlan_id = 1 + + self.add_dbus_interface(IFACE_VLAN, self.__get_props, VlanDevice.PropertiesChanged) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_VLAN) + + # Properties interface + def __get_props(self): + props = {} + props[PV_HW_ADDRESS] = self.mac + props[PV_CARRIER] = self.carrier + props[PV_VLAN_ID] = dbus.UInt32(self.vlan_id) + return props + + @dbus.service.signal(IFACE_VLAN, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + +################################################################### +IFACE_WIFI_AP = 'org.freedesktop.NetworkManager.AccessPoint' + +PP_FLAGS = "Flags" +PP_WPA_FLAGS = "WpaFlags" +PP_RSN_FLAGS = "RsnFlags" +PP_SSID = "Ssid" +PP_FREQUENCY = "Frequency" +PP_HW_ADDRESS = "HwAddress" +PP_MODE = "Mode" +PP_MAX_BITRATE = "MaxBitrate" +PP_STRENGTH = "Strength" + +class WifiAp(ExportedObj): + counter = 0 + + def __init__(self, bus, ssid, mac, flags, wpaf, rsnf, freq): + path = "/org/freedesktop/NetworkManager/AccessPoint/%d" % WifiAp.counter + WifiAp.counter = WifiAp.counter + 1 + + self.ssid = ssid + if mac: + self.bssid = mac + else: + self.bssid = random_mac() + self.flags = flags + self.wpaf = wpaf + self.rsnf = rsnf + self.freq = freq + self.strength = random.randint(0, 100) + self.strength_id = GLib.timeout_add_seconds(10, self.strength_cb, None) + + self.add_dbus_interface(IFACE_WIFI_AP, self.__get_props, WifiAp.PropertiesChanged) + ExportedObj.__init__(self, bus, path) + + def __del__(self): + if self.strength_id > 0: + GLib.source_remove(self.strength_id) + self.strength_id = 0 + + def strength_cb(self, ignored): + self.strength = random.randint(0, 100) + self.__notify(PP_STRENGTH) + return True + + # Properties interface + def __get_props(self): + props = {} + props[PP_FLAGS] = dbus.UInt32(self.flags) + props[PP_WPA_FLAGS] = dbus.UInt32(self.wpaf) + props[PP_RSN_FLAGS] = dbus.UInt32(self.rsnf) + props[PP_SSID] = dbus.ByteArray(self.ssid.encode('utf-8')) + props[PP_FREQUENCY] = dbus.UInt32(self.freq) + props[PP_HW_ADDRESS] = self.bssid + props[PP_MODE] = dbus.UInt32(2) # NM_802_11_MODE_INFRA + props[PP_MAX_BITRATE] = dbus.UInt32(54000) + props[PP_STRENGTH] = dbus.Byte(self.strength) + return props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_WIFI_AP, propname) + + @dbus.service.signal(IFACE_WIFI_AP, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + +################################################################### +IFACE_WIFI = 'org.freedesktop.NetworkManager.Device.Wireless' + +class ApNotFoundException(dbus.DBusException): + _dbus_error_name = IFACE_WIFI + '.AccessPointNotFound' + +PW_HW_ADDRESS = "HwAddress" +PW_PERM_HW_ADDRESS = "PermHwAddress" +PW_MODE = "Mode" +PW_BITRATE = "Bitrate" +PW_ACCESS_POINTS = "AccessPoints" +PW_ACTIVE_ACCESS_POINT = "ActiveAccessPoint" +PW_WIRELESS_CAPABILITIES = "WirelessCapabilities" + +class WifiDevice(Device): + def __init__(self, bus, iface): + self.mac = random_mac() + self.aps = [] + self.active_ap = None + + self.add_dbus_interface(IFACE_WIFI, self.__get_props, WifiDevice.PropertiesChanged) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_WIFI) + + # methods + @dbus.service.method(dbus_interface=IFACE_WIFI, in_signature='', out_signature='ao') + def GetAccessPoints(self): + # only include non-hidden APs + return to_path_array([a for a in self.aps if a.ssid]) + + @dbus.service.method(dbus_interface=IFACE_WIFI, in_signature='', out_signature='ao') + def GetAllAccessPoints(self): + # include all APs including hidden ones + return to_path_array(self.aps) + + @dbus.service.method(dbus_interface=IFACE_WIFI, in_signature='a{sv}', out_signature='') + def RequestScan(self, props): + pass + + @dbus.service.signal(IFACE_WIFI, signature='o') + def AccessPointAdded(self, ap_path): + pass + + def add_ap(self, ap): + self.aps.append(ap) + self.__notify(PW_ACCESS_POINTS) + self.AccessPointAdded(to_path(ap)) + + @dbus.service.signal(IFACE_WIFI, signature='o') + def AccessPointRemoved(self, ap_path): + pass + + def remove_ap(self, ap): + self.aps.remove(ap) + self.__notify(PW_ACCESS_POINTS) + self.AccessPointRemoved(to_path(ap)) + + # Properties interface + def __get_props(self): + props = {} + props[PW_HW_ADDRESS] = self.mac + props[PW_PERM_HW_ADDRESS] = self.mac + props[PW_MODE] = dbus.UInt32(3) # NM_802_11_MODE_INFRA + props[PW_BITRATE] = dbus.UInt32(21000) + props[PW_WIRELESS_CAPABILITIES] = dbus.UInt32(0xFF) + props[PW_ACCESS_POINTS] = to_path_array(self.aps) + props[PW_ACTIVE_ACCESS_POINT] = to_path(self.active_ap) + return props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_WIFI, propname) + + @dbus.service.signal(IFACE_WIFI, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + + # test functions + def add_test_ap(self, ssid, mac): + ap = WifiAp(self._bus, ssid, mac, 0x1, 0x1cc, 0x1cc, 2412) + self.add_ap(ap) + return ap + + def remove_ap_by_path(self, path): + for ap in self.aps: + if ap.path == path: + self.remove_ap(ap) + return + raise ApNotFoundException("AP %s not found" % path) + + +################################################################### +IFACE_WIMAX_NSP = 'org.freedesktop.NetworkManager.WiMax.Nsp' + +PN_NAME = "Name" +PN_SIGNAL_QUALITY = "SignalQuality" +PN_NETWORK_TYPE = "NetworkType" + +class WimaxNsp(ExportedObj): + counter = 0 + + def __init__(self, bus, name): + path = "/org/freedesktop/NetworkManager/Nsp/%d" % WimaxNsp.counter + WimaxNsp.counter = WimaxNsp.counter + 1 + + self.name = name + self.strength = random.randint(0, 100) + self.strength_id = GLib.timeout_add_seconds(10, self.strength_cb, None) + + self.add_dbus_interface(IFACE_WIMAX_NSP, self.__get_props, WimaxNsp.PropertiesChanged) + ExportedObj.__init__(self, bus, path) + + def __del__(self): + if self.strength_id > 0: + GLib.source_remove(self.strength_id) + self.strength_id = 0 + + def strength_cb(self, ignored): + self.strength = random.randint(0, 100) + self.__notify(PN_SIGNAL_QUALITY) + return True + + # Properties interface + def __get_props(self): + props = {} + props[PN_NAME] = self.name + props[PN_SIGNAL_QUALITY] = dbus.UInt32(self.strength) + props[PN_NETWORK_TYPE] = dbus.UInt32(0x1) # NM_WIMAX_NSP_NETWORK_TYPE_HOME + return props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_WIMAX_NSP, propname) + + @dbus.service.signal(IFACE_WIMAX_NSP, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + +################################################################### +IFACE_WIMAX = 'org.freedesktop.NetworkManager.Device.WiMax' + +class NspNotFoundException(dbus.DBusException): + _dbus_error_name = IFACE_WIMAX + '.NspNotFound' + +PX_NSPS = "Nsps" +PX_HW_ADDRESS = "HwAddress" +PX_CENTER_FREQUENCY = "CenterFrequency" +PX_RSSI = "Rssi" +PX_CINR = "Cinr" +PX_TX_POWER = "TxPower" +PX_BSID = "Bsid" +PX_ACTIVE_NSP = "ActiveNsp" + +class WimaxDevice(Device): + def __init__(self, bus, iface): + self.mac = random_mac() + self.bsid = random_mac() + self.nsps = [] + self.active_nsp = None + + self.add_dbus_interface(IFACE_WIMAX, self.__get_props, WimaxDevice.PropertiesChanged) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_WIMAX) + + # methods + @dbus.service.method(dbus_interface=IFACE_WIMAX, in_signature='', out_signature='ao') + def GetNspList(self): + # include all APs including hidden ones + return to_path_array(self.nsps) + + @dbus.service.signal(IFACE_WIMAX, signature='o') + def NspAdded(self, nsp_path): + pass + + def add_nsp(self, nsp): + self.nsps.append(nsp) + self.__notify(PX_NSPS) + self.NspAdded(to_path(nsp)) + + @dbus.service.signal(IFACE_WIMAX, signature='o') + def NspRemoved(self, nsp_path): + pass + + def remove_nsp(self, nsp): + self.nsps.remove(nsp) + self.__notify(PX_NSPS) + self.NspRemoved(to_path(nsp)) + + # Properties interface + def __get_props(self): + props = {} + props[PX_HW_ADDRESS] = self.mac + props[PX_CENTER_FREQUENCY] = dbus.UInt32(2525) + props[PX_RSSI] = dbus.Int32(-48) + props[PX_CINR] = dbus.Int32(24) + props[PX_TX_POWER] = dbus.Int32(9) + props[PX_BSID] = self.bsid + props[PX_NSPS] = to_path_array(self.nsps) + props[PX_ACTIVE_NSP] = to_path(self.active_nsp) + return props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_WIMAX, propname) + + @dbus.service.signal(IFACE_WIMAX, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + + # test functions + def add_test_nsp(self, name): + nsp = WimaxNsp(self._bus, name) + self.add_nsp(nsp) + return nsp + + def remove_nsp_by_path(self, path): + for nsp in self.nsps: + if nsp.path == path: + self.remove_nsp(nsp) + return + raise NspNotFoundException("NSP %s not found" % path) + +################################################################### +IFACE_ACTIVE_CONNECTION = 'org.freedesktop.NetworkManager.Connection.Active' + +PAC_CONNECTION = "Connection" +PAC_SPECIFIC_OBJECT = "SpecificObject" +PAC_ID = "Id" +PAC_UUID = "Uuid" +PAC_TYPE = "Type" +PAC_DEVICES = "Devices" +PAC_STATE = "State" +PAC_DEFAULT = "Default" +PAC_IP4CONFIG = "Ip4Config" +PAC_DHCP4CONFIG = "Dhcp4Config" +PAC_DEFAULT6 = "Default6" +PAC_IP6CONFIG = "Ip6Config" +PAC_DHCP6CONFIG = "Dhcp6Config" +PAC_VPN = "Vpn" +PAC_MASTER = "Master" + +class ActiveConnection(ExportedObj): + counter = 1 + + def __init__(self, bus, device, connection, specific_object): + object_path = "/org/freedesktop/NetworkManager/ActiveConnection/%d" % ActiveConnection.counter + ActiveConnection.counter = ActiveConnection.counter + 1 + + self.device = device + self.conn = connection + self.specific_object = specific_object + self.state = NM_ACTIVE_CONNECTION_STATE_UNKNOWN + self.default = False + self.ip4config = None + self.dhcp4config = None + self.default6 = False + self.ip6config = None + self.dhcp6config = None + self.vpn = False + self.master = None + + self.add_dbus_interface(IFACE_ACTIVE_CONNECTION, self.__get_props, ActiveConnection.PropertiesChanged) + ExportedObj.__init__(self, bus, object_path) + + # Properties interface + def __get_props(self): + props = {} + props[PAC_CONNECTION] = to_path(self.conn) + props[PAC_SPECIFIC_OBJECT] = to_path(self.specific_object) + conn_settings = self.conn.GetSettings() + s_con = conn_settings['connection'] + props[PAC_ID] = s_con['id'] + props[PAC_UUID] = s_con['uuid'] + props[PAC_TYPE] = s_con['type'] + props[PAC_DEVICES] = to_path_array([self.device]) + props[PAC_STATE] = dbus.UInt32(self.state) + props[PAC_DEFAULT] = self.default + props[PAC_IP4CONFIG] = to_path(self.ip4config) + props[PAC_DHCP4CONFIG] = to_path(self.dhcp4config) + props[PAC_DEFAULT6] = self.default6 + props[PAC_IP6CONFIG] = to_path(self.ip6config) + props[PAC_DHCP6CONFIG] = to_path(self.dhcp6config) + props[PAC_VPN] = self.vpn + props[PAC_MASTER] = to_path(self.master) + return props + + @dbus.service.signal(IFACE_ACTIVE_CONNECTION, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + +################################################################### +IFACE_TEST = 'org.freedesktop.NetworkManager.LibnmGlibTest' +IFACE_NM = 'org.freedesktop.NetworkManager' + +class PermissionDeniedException(dbus.DBusException): + _dbus_error_name = IFACE_NM + '.PermissionDenied' + +class UnknownDeviceException(dbus.DBusException): + _dbus_error_name = IFACE_NM + '.UnknownDevice' + +class UnknownConnectionException(dbus.DBusException): + _dbus_error_name = IFACE_NM + '.UnknownConnection' + +PM_DEVICES = 'Devices' +PM_ALL_DEVICES = 'AllDevices' +PM_NETWORKING_ENABLED = 'NetworkingEnabled' +PM_WWAN_ENABLED = 'WwanEnabled' +PM_WWAN_HARDWARE_ENABLED = 'WwanHardwareEnabled' +PM_WIRELESS_ENABLED = 'WirelessEnabled' +PM_WIRELESS_HARDWARE_ENABLED = 'WirelessHardwareEnabled' +PM_WIMAX_ENABLED = 'WimaxEnabled' +PM_WIMAX_HARDWARE_ENABLED = 'WimaxHardwareEnabled' +PM_ACTIVE_CONNECTIONS = 'ActiveConnections' +PM_PRIMARY_CONNECTION = 'PrimaryConnection' +PM_ACTIVATING_CONNECTION = 'ActivatingConnection' +PM_STARTUP = 'Startup' +PM_STATE = 'State' +PM_VERSION = 'Version' +PM_CONNECTIVITY = 'Connectivity' + +def set_device_ac_cb(device, ac): + device.set_active_connection(ac) + +class NetworkManager(ExportedObj): + def __init__(self, bus, object_path): + self._bus = bus; + self.devices = [] + self.active_connections = [] + self.primary_connection = None + self.activating_connection = None + self.state = NM_STATE_DISCONNECTED + self.connectivity = 1 + + self.add_dbus_interface(IFACE_NM, self.__get_props, NetworkManager.PropertiesChanged) + ExportedObj.__init__(self, bus, object_path) + + @dbus.service.signal(IFACE_NM, signature='u') + def StateChanged(self, new_state): + pass + + def set_state(self, new_state): + self.state = new_state + self.__notify(PM_STATE) + self.StateChanged(dbus.UInt32(self.state)) + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='', out_signature='ao') + def GetDevices(self): + return to_path_array(self.devices) + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='', out_signature='ao') + def GetAllDevices(self): + return to_path_array(self.devices) + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='s', out_signature='o') + def GetDeviceByIpIface(self, ip_iface): + for d in self.devices: + # ignore iface/ip_iface distinction for now + if d.iface == ip_iface: + return to_path(d) + raise UnknownDeviceException("No device found for the requested iface.") + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='ooo', out_signature='o') + def ActivateConnection(self, conpath, devpath, specific_object): + try: + connection = settings.get_connection(conpath) + except Exception as e: + raise UnknownConnectionException("Connection not found") + + hash = connection.GetSettings() + s_con = hash['connection'] + + device = None + for d in self.devices: + if d.path == devpath: + device = d + break + if not device and s_con['type'] == 'vlan': + ifname = s_con['interface-name'] + device = VlanDevice(self._bus, ifname) + self.add_device(device) + if not device: + raise UnknownDeviceException("No device found for the requested iface.") + + # See if we need secrets. For the moment, we only support WPA + if '802-11-wireless-security' in hash: + s_wsec = hash['802-11-wireless-security'] + if (s_wsec['key-mgmt'] == 'wpa-psk' and 'psk' not in s_wsec): + secrets = agent_manager.get_secrets(hash, conpath, '802-11-wireless-security') + if secrets is None: + raise NoSecretsException("No secret agent available") + if '802-11-wireless-security' not in secrets: + raise NoSecretsException("No secrets provided") + s_wsec = secrets['802-11-wireless-security'] + if 'psk' not in s_wsec: + raise NoSecretsException("No secrets provided") + + ac = ActiveConnection(self._bus, device, connection, None) + self.active_connections.append(ac) + self.__notify(PM_ACTIVE_CONNECTIONS) + + if s_con['id'] == 'object-creation-failed-test': + self.active_connections.remove(ac) + self.__notify(PM_ACTIVE_CONNECTIONS) + ac.remove_from_connection() + else: + GLib.timeout_add(50, set_device_ac_cb, device, ac) + + return to_path(ac) + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='a{sa{sv}}oo', out_signature='oo') + def AddAndActivateConnection(self, connection, devpath, specific_object): + device = None + for d in self.devices: + if d.path == devpath: + device = d + break + if not device: + raise UnknownDeviceException("No device found for the requested iface.") + + conpath = settings.AddConnection(connection) + return (conpath, self.ActivateConnection(conpath, devpath, specific_object)) + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='o', out_signature='') + def DeactivateConnection(self, active_connection): + pass + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='b', out_signature='') + def Sleep(self, do_sleep): + if do_sleep: + self.state = NM_STATE_ASLEEP + else: + self.state = NM_STATE_DISCONNECTED + self.__notify(PM_STATE) + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='b', out_signature='') + def Enable(self, do_enable): + pass + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='', out_signature='a{ss}') + def GetPermissions(self): + return { "org.freedesktop.NetworkManager.enable-disable-network": "yes", + "org.freedesktop.NetworkManager.sleep-wake": "no", + "org.freedesktop.NetworkManager.enable-disable-wifi": "yes", + "org.freedesktop.NetworkManager.enable-disable-wwan": "yes", + "org.freedesktop.NetworkManager.enable-disable-wimax": "yes", + "org.freedesktop.NetworkManager.network-control": "yes", + "org.freedesktop.NetworkManager.wifi.share.protected": "yes", + "org.freedesktop.NetworkManager.wifi.share.open": "yes", + "org.freedesktop.NetworkManager.settings.modify.own": "yes", + "org.freedesktop.NetworkManager.settings.modify.system": "yes", + "org.freedesktop.NetworkManager.settings.modify.hostname": "yes", + "org.freedesktop.NetworkManager.settings.modify.global-dns": "no", + "org.freedesktop.NetworkManager.reload": "no", + } + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='ss', out_signature='') + def SetLogging(self, level, domains): + pass + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='', out_signature='ss') + def GetLogging(self): + return ("info", "HW,RFKILL,CORE,DEVICE,WIFI,ETHER") + + @dbus.service.method(dbus_interface=IFACE_NM, in_signature='', out_signature='u') + def CheckConnectivity(self): + raise PermissionDeniedException("You fail") + + @dbus.service.signal(IFACE_NM, signature='o') + def DeviceAdded(self, devpath): + pass + + def add_device(self, device): + self.devices.append(device) + self.__notify(PM_DEVICES) + self.__notify(PM_ALL_DEVICES) + self.DeviceAdded(to_path(device)) + + @dbus.service.signal(IFACE_NM, signature='o') + def DeviceRemoved(self, devpath): + pass + + def remove_device(self, device): + self.devices.remove(device) + self.__notify(PM_DEVICES) + self.__notify(PM_ALL_DEVICES) + self.DeviceRemoved(to_path(device)) + + ################# D-Bus Properties interface + def __get_props(self): + props = {} + props[PM_DEVICES] = to_path_array(self.devices) + props[PM_ALL_DEVICES] = to_path_array(self.devices) + props[PM_NETWORKING_ENABLED] = True + props[PM_WWAN_ENABLED] = True + props[PM_WWAN_HARDWARE_ENABLED] = True + props[PM_WIRELESS_ENABLED] = True + props[PM_WIRELESS_HARDWARE_ENABLED] = True + props[PM_WIMAX_ENABLED] = True + props[PM_WIMAX_HARDWARE_ENABLED] = True + props[PM_ACTIVE_CONNECTIONS] = to_path_array(self.active_connections) + props[PM_PRIMARY_CONNECTION] = to_path(self.primary_connection) + props[PM_ACTIVATING_CONNECTION] = to_path(self.activating_connection) + props[PM_STARTUP] = False + props[PM_STATE] = dbus.UInt32(self.state) + props[PM_VERSION] = "0.9.9.0" + props[PM_CONNECTIVITY] = dbus.UInt32(self.connectivity) + return props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_NM, propname) + + @dbus.service.signal(IFACE_NM, signature='a{sv}') + def PropertiesChanged(self, changed): + pass + + ################# Testing methods + @dbus.service.method(IFACE_TEST, in_signature='', out_signature='') + def Quit(self): + mainloop.quit() + + @dbus.service.method(IFACE_TEST, in_signature='ssas', out_signature='o') + def AddWiredDevice(self, ifname, mac, subchannels): + for d in self.devices: + if d.iface == ifname: + raise PermissionDeniedException("Device already added") + dev = WiredDevice(self._bus, ifname, mac, subchannels) + self.add_device(dev) + return to_path(dev) + + @dbus.service.method(IFACE_TEST, in_signature='s', out_signature='o') + def AddWifiDevice(self, ifname): + for d in self.devices: + if d.iface == ifname: + raise PermissionDeniedException("Device already added") + dev = WifiDevice(self._bus, ifname) + self.add_device(dev) + return to_path(dev) + + @dbus.service.method(IFACE_TEST, in_signature='s', out_signature='o') + def AddWimaxDevice(self, ifname): + for d in self.devices: + if d.iface == ifname: + raise PermissionDeniedException("Device already added") + dev = WimaxDevice(self._bus, ifname) + self.add_device(dev) + return to_path(dev) + + @dbus.service.method(IFACE_TEST, in_signature='o', out_signature='') + def RemoveDevice(self, path): + for d in self.devices: + if d.path == path: + self.remove_device(d) + return + raise UnknownDeviceException("Device not found") + + @dbus.service.method(IFACE_TEST, in_signature='sss', out_signature='o') + def AddWifiAp(self, ifname, ssid, mac): + for d in self.devices: + if d.iface == ifname: + return to_path(d.add_test_ap(ssid, mac)) + raise UnknownDeviceException("Device not found") + + @dbus.service.method(IFACE_TEST, in_signature='so', out_signature='') + def RemoveWifiAp(self, ifname, ap_path): + for d in self.devices: + if d.iface == ifname: + d.remove_ap_by_path(ap_path) + return + raise UnknownDeviceException("Device not found") + + @dbus.service.method(IFACE_TEST, in_signature='ss', out_signature='o') + def AddWimaxNsp(self, ifname, name): + for d in self.devices: + if d.iface == ifname: + return to_path(d.add_test_nsp(name)) + raise UnknownDeviceException("Device not found") + + @dbus.service.method(IFACE_TEST, in_signature='so', out_signature='') + def RemoveWimaxNsp(self, ifname, nsp_path): + for d in self.devices: + if d.iface == ifname: + d.remove_nsp_by_path(nsp_path) + return + raise UnknownDeviceException("Device not found") + + @dbus.service.method(IFACE_TEST, in_signature='', out_signature='') + def AutoRemoveNextConnection(self): + settings.auto_remove_next_connection() + + @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='a{sa{sv}}b', out_signature='o') + def AddConnection(self, connection, verify_connection): + return settings.add_connection(connection, verify_connection) + + @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='sa{sa{sv}}b', out_signature='') + def UpdateConnection(self, path, connection, verify_connection): + return settings.update_connection(connection, path, verify_connection) + + @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='suu', out_signature='') + def SetDeviceState(self, path, state, reason): + for d in self.devices: + if d.path == path: + d.set_state_reason(state, reason) + return + raise UnknownDeviceException("Device not found") + + @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='su', out_signature='') + def SetWiredSpeed(self, path, speed): + for d in self.devices: + if d.path == path: + d.set_wired_speed(speed) + return + raise UnknownDeviceException("Device not found") + + @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='', out_signature='') + def Restart(self): + bus.release_name("org.freedesktop.NetworkManager") + bus.request_name("org.freedesktop.NetworkManager") + + +################################################################### +IFACE_CONNECTION = 'org.freedesktop.NetworkManager.Settings.Connection' + +class InvalidPropertyException(dbus.DBusException): + _dbus_error_name = IFACE_CONNECTION + '.InvalidProperty' + +class MissingPropertyException(dbus.DBusException): + _dbus_error_name = IFACE_CONNECTION + '.MissingProperty' + +class InvalidSettingException(dbus.DBusException): + _dbus_error_name = IFACE_CONNECTION + '.InvalidSetting' + +class MissingSettingException(dbus.DBusException): + _dbus_error_name = IFACE_CONNECTION + '.MissingSetting' + +class Connection(ExportedObj): + def __init__(self, bus, object_path, settings, remove_func, verify_connection=True): + + if self.get_uuid(settings) is None: + if 'connection' not in settings: + settings['connection'] = { } + settings['connection']['uuid'] = uuid.uuid4() + self.verify(settings, verify_strict=verify_connection) + + self.path = object_path + self.settings = settings + self.remove_func = remove_func + self.visible = True + self.props = {} + self.props['Unsaved'] = False + + self.add_dbus_interface(IFACE_CONNECTION, self.__get_props, None) + ExportedObj.__init__(self, bus, object_path) + + def get_uuid(self, settings=None): + if settings is None: + settings = self.settings + if 'connection' in settings: + s_con = settings['connection'] + if 'uuid' in s_con: + return s_con['uuid'] + return None + + def verify(self, settings=None, verify_strict=True): + if settings is None: + settings = self.settings; + if 'connection' not in settings: + raise MissingSettingException('connection: setting is required') + s_con = settings['connection'] + if 'type' not in s_con: + raise MissingPropertyException('connection.type: property is required') + if 'uuid' not in s_con: + raise MissingPropertyException('connection.uuid: property is required') + if 'id' not in s_con: + raise MissingPropertyException('connection.id: property is required') + + if not verify_strict: + return; + t = s_con['type'] + if t not in ['802-3-ethernet', '802-11-wireless', 'vlan', 'wimax', 'vpn']: + raise InvalidPropertyException('connection.type: unsupported connection type "%s"' % (t)) + + def update_connection(self, settings, verify_connection): + self.verify(settings, verify_strict=verify_connection) + + old_uuid = self.get_uuid() + new_uuid = self.get_uuid(settings) + if old_uuid != new_uuid: + raise InvalidPropertyException('connection.uuid: cannot change the uuid from %s to %s' % (old_uuid, new_uuid)) + + self.settings = settings; + self.Updated() + + def __get_props(self): + return self.props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_CONNECTION, propname) + + # Connection methods + @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='', out_signature='a{sa{sv}}') + def GetSettings(self): + if not self.visible: + raise PermissionDeniedException() + return self.settings + + @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='b', out_signature='') + def SetVisible(self, vis): + self.visible = vis + self.Updated() + + @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='', out_signature='') + def Delete(self): + self.remove_func(self) + self.Removed() + self.remove_from_connection() + + @dbus.service.method(dbus_interface=IFACE_CONNECTION, in_signature='a{sa{sv}}', out_signature='') + def Update(self, settings): + self.update_connection(settings, TRUE) + + @dbus.service.signal(IFACE_CONNECTION, signature='') + def Removed(self): + pass + + @dbus.service.signal(IFACE_CONNECTION, signature='') + def Updated(self): + pass + +################################################################### +IFACE_SETTINGS = 'org.freedesktop.NetworkManager.Settings' + +class InvalidHostnameException(dbus.DBusException): + _dbus_error_name = IFACE_SETTINGS + '.InvalidHostname' + +class Settings(ExportedObj): + def __init__(self, bus, object_path): + self.connections = {} + self.bus = bus + self.counter = 1 + self.remove_next_connection = False + self.props = {} + self.props['Hostname'] = "foobar.baz" + self.props['CanModify'] = True + self.props['Connections'] = dbus.Array([], 'o') + + self.add_dbus_interface(IFACE_SETTINGS, self.__get_props, Settings.PropertiesChanged) + ExportedObj.__init__(self, bus, object_path) + + def auto_remove_next_connection(self): + self.remove_next_connection = True; + + def get_connection(self, path): + return self.connections[path] + + @dbus.service.method(dbus_interface=IFACE_SETTINGS, in_signature='', out_signature='ao') + def ListConnections(self): + return self.connections.keys() + + @dbus.service.method(dbus_interface=IFACE_SETTINGS, in_signature='a{sa{sv}}', out_signature='o') + def AddConnection(self, settings): + return self.add_connection(settings) + + def add_connection(self, settings, verify_connection=True): + path = "/org/freedesktop/NetworkManager/Settings/Connection/{0}".format(self.counter) + con = Connection(self.bus, path, settings, self.delete_connection, verify_connection) + + uuid = con.get_uuid() + if uuid in [c.get_uuid() for c in self.connections.values()]: + raise InvalidSettingException('cannot add duplicate connection with uuid %s' % (uuid)) + + self.counter = self.counter + 1 + self.connections[path] = con + self.props['Connections'] = dbus.Array(self.connections.keys(), 'o') + self.NewConnection(path) + self.__notify('Connections') + + if self.remove_next_connection: + self.remove_next_connection = False + self.connections[path].Delete() + + return path + + def update_connection(self, connection, path=None, verify_connection=True): + if path is None: + path = connection.path + if path not in self.connections: + raise UnknownConnectionException('Connection not found') + con = self.connections[path] + con.update_connection(connection, verify_connection) + + def delete_connection(self, connection): + del self.connections[connection.path] + self.props['Connections'] = dbus.Array(self.connections.keys(), 'o') + self.__notify('Connections') + + @dbus.service.method(dbus_interface=IFACE_SETTINGS, in_signature='s', out_signature='') + def SaveHostname(self, hostname): + # Arbitrary requirement to test error handling + if hostname.find('.') == -1: + raise InvalidHostnameException() + self.props['Hostname'] = hostname + self.__notify('Hostname') + + def __get_props(self): + return self.props + + def __notify(self, propname): + self._dbus_property_notify(IFACE_SETTINGS, propname) + + @dbus.service.signal(IFACE_SETTINGS, signature='o') + def NewConnection(self, path): + pass + + @dbus.service.signal(IFACE_SETTINGS, signature='a{sv}') + def PropertiesChanged(self, path): + pass + + @dbus.service.method(IFACE_SETTINGS, in_signature='', out_signature='') + def Quit(self): + mainloop.quit() + +################################################################### +IFACE_AGENT_MANAGER = 'org.freedesktop.NetworkManager.AgentManager' +IFACE_AGENT = 'org.freedesktop.NetworkManager.SecretAgent' + +PATH_SECRET_AGENT = '/org/freedesktop/NetworkManager/SecretAgent' + +FLAG_ALLOW_INTERACTION = 0x1 +FLAG_REQUEST_NEW = 0x2 +FLAG_USER_REQUESTED = 0x4 + +class NoSecretsException(dbus.DBusException): + _dbus_error_name = IFACE_AGENT_MANAGER + '.NoSecrets' + +class UserCanceledException(dbus.DBusException): + _dbus_error_name = IFACE_AGENT_MANAGER + '.UserCanceled' + +class AgentManager(dbus.service.Object): + def __init__(self, bus, object_path): + dbus.service.Object.__init__(self, bus, object_path) + self.agents = {} + self.bus = bus + + @dbus.service.method(dbus_interface=IFACE_AGENT_MANAGER, + in_signature='s', out_signature='', + sender_keyword='sender') + def Register(self, name, sender=None): + self.RegisterWithCapabilities(name, 0, sender) + + @dbus.service.method(dbus_interface=IFACE_AGENT_MANAGER, + in_signature='su', out_signature='', + sender_keyword='sender') + def RegisterWithCapabilities(self, name, caps, sender=None): + self.agents[sender] = self.bus.get_object(sender, PATH_SECRET_AGENT) + + @dbus.service.method(dbus_interface=IFACE_AGENT_MANAGER, + in_signature='', out_signature='', + sender_keyword='sender') + def Unregister(self, sender=None): + del self.agents[sender] + + def get_secrets(self, connection, path, setting_name): + if len(self.agents) == 0: + return None + + secrets = {} + for sender in self.agents: + agent = self.agents[sender] + try: + secrets = agent.GetSecrets(connection, path, setting_name, + dbus.Array([], 's'), + FLAG_ALLOW_INTERACTION | FLAG_USER_REQUESTED, + dbus_interface=IFACE_AGENT) + break + except dbus.DBusException as e: + if e.get_dbus_name() == IFACE_AGENT + '.UserCanceled': + raise UserCanceledException('User canceled') + continue + return secrets + +################################################################### +IFACE_OBJECT_MANAGER = 'org.freedesktop.DBus.ObjectManager' + +PATH_OBJECT_MANAGER = '/org/freedesktop' + +class ObjectManager(dbus.service.Object): + def __init__(self, bus, object_path): + dbus.service.Object.__init__(self, bus, object_path) + self.objs = [] + self.bus = bus + + @dbus.service.method(dbus_interface=IFACE_OBJECT_MANAGER, + in_signature='', out_signature='a{oa{sa{sv}}}', + sender_keyword='sender') + def GetManagedObjects(self, sender=None): + managed_objects = {} + for obj in self.objs: + name, ifaces = obj.get_managed_ifaces() + managed_objects[name] = ifaces + return managed_objects + + def add_object(self, obj): + self.objs.append(obj) + name, ifaces = obj.get_managed_ifaces() + self.InterfacesAdded(name, ifaces) + + def remove_object(self, obj): + self.objs.remove(obj) + name, ifaces = obj.get_managed_ifaces() + self.InterfacesRemoved(name, ifaces.keys()) + + @dbus.service.signal(IFACE_OBJECT_MANAGER, signature='oa{sa{sv}}') + def InterfacesAdded(self, name, ifaces): + pass + + @dbus.service.signal(IFACE_OBJECT_MANAGER, signature='oas') + def InterfacesRemoved(self, name, ifaces): + pass + +################################################################### +IFACE_DNS_MANAGER = 'org.freedesktop.NetworkManager.DnsManager' + +class DnsManager(ExportedObj): + def __init__(self, bus, object_path): + self.props = {} + self.props['Mode'] = "dnsmasq" + self.props['RcManager'] = "symlink" + self.props['Configuration'] = dbus.Array([ + dbus.Dictionary( + { 'nameservers' : dbus.Array(['1.2.3.4', '5.6.7.8'], 's'), + 'priority' : dbus.Int32(100) }, + 'sv') ], + 'a{sv}') + + self.add_dbus_interface(IFACE_DNS_MANAGER, self.__get_props, None) + ExportedObj.__init__(self, bus, object_path) + + @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}') + def GetAll(self, iface): + if iface != IFACE_DNS_MANAGER: + raise UnknownInterfaceException() + return self.props + + @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v') + def Get(self, iface, name): + if iface != IFACE_DNS_MANAGER: + raise UnknownInterfaceException() + if not name in self.props.keys(): + raise UnknownPropertyException() + return self.props[name] + + def __get_props(self): + return self.props + +################################################################### +def stdin_cb(io, condition): + mainloop.quit() + +def quit_cb(user_data): + mainloop.quit() + +def main(): + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + random.seed() + + global manager, settings, agent_manager, dns_manager, object_manager, bus + + bus = dbus.SessionBus() + object_manager = ObjectManager(bus, "/org/freedesktop") + manager = NetworkManager(bus, "/org/freedesktop/NetworkManager") + settings = Settings(bus, "/org/freedesktop/NetworkManager/Settings") + agent_manager = AgentManager(bus, "/org/freedesktop/NetworkManager/AgentManager") + dns_manager = DnsManager(bus, "/org/freedesktop/NetworkManager/DnsManager") + + if not bus.request_name("org.freedesktop.NetworkManager"): + sys.exit(1) + + # Watch stdin; if it closes, assume our parent has crashed, and exit + io = GLib.IOChannel(0) + GLib.io_add_watch(io, GLib.PRIORITY_LOW, GLib.IOCondition.HUP, stdin_cb) + + # also quit after inactivity to ensure we don't stick around if the above fails somehow + GLib.timeout_add_seconds(20, quit_cb, None) + + try: + mainloop.run() + except Exception as e: + pass + + sys.exit(0) + +if __name__ == '__main__': + main() + diff --git a/tests/network/nmtst-helpers.h b/tests/network/nmtst-helpers.h new file mode 100644 index 0000000..495d27f --- /dev/null +++ b/tests/network/nmtst-helpers.h @@ -0,0 +1,361 @@ +/* -*- 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; + NMConnection *connection; + + NMActiveConnection *ac; + NMRemoteConnection *rc; + + const gchar * const *client_props; + const gchar * const *device_props; + + int client_remaining; + int device_remaining; + int connection_remaining; + int other_remaining; +} EventWaitInfo; + + +#define WAIT_CHECK_REMAINING() \ + if (info->client_remaining == 0 && info->device_remaining == 0 && info->connection_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->connection_remaining < 0 || info->other_remaining < 0) { \ + g_error ("Pending events are negative: client: %d, device: %d, connection: %d, other: %d", info->client_remaining, info->device_remaining, info->connection_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); \ + info.device_props = (const char *[]) {__VA_ARGS__, NULL}; \ + g_signal_connect ((_device), "notify", G_CALLBACK (device_notify_cb), &info); \ + connect_signals (&info, G_OBJECT (_device), info.device_props, G_CALLBACK (device_signal_cb)); +#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); \ + connect_signals (&info, G_OBJECT (_client), info.client_props, G_CALLBACK (client_signal_cb)); +#define WAIT_CONNECTION(_connection, count, ...) \ + info.connection = NM_CONNECTION (_connection); \ + info.connection_remaining = (count); \ + { const gchar* const *_signals = (const char *[]) {__VA_ARGS__, NULL}; \ + connect_signals (&info, G_OBJECT (_connection), _signals, G_CALLBACK (connection_signal_cb)); } + +#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); \ + g_signal_handlers_disconnect_by_func (info.device, G_CALLBACK (device_signal_cb), &info); \ + } \ + if (info.client) { \ + g_signal_handlers_disconnect_by_func (info.client, G_CALLBACK (client_notify_cb), &info); \ + g_signal_handlers_disconnect_by_func (info.client, G_CALLBACK (client_signal_cb), &info); \ + } \ + if (info.connection) { \ + g_signal_handlers_disconnect_by_func (info.connection, G_CALLBACK (connection_signal_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, connection: %d, other: %d", info->client_remaining, info->device_remaining, info->connection_remaining, info->other_remaining); \ + g_assert_not_reached (); + return G_SOURCE_REMOVE; +} + +static void +connect_signals (EventWaitInfo *info, GObject *object, const gchar* const* signals, GCallback handler) +{ + const gchar * const* signal; + GObjectClass *class = G_OBJECT_GET_CLASS(object); + + for (signal = signals; *signal != NULL; signal++) { + if (g_object_class_find_property (class, *signal)) + continue; + + g_debug ("Connecting signal handler for %s", *signal); + g_signal_connect_data (object, *signal, handler, info, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED); + } +} + +static void +device_signal_cb (gpointer user_data) +{ + EventWaitInfo *info = user_data; + + g_debug ("Counting signal for device"); + + info->device_remaining--; + WAIT_CHECK_REMAINING() +} + +static void +client_signal_cb (gpointer user_data) +{ + EventWaitInfo *info = user_data; + + g_debug ("Counting signal for client"); + + info->client_remaining--; + WAIT_CHECK_REMAINING() +} + +static void +connection_signal_cb (gpointer user_data) +{ + EventWaitInfo *info = user_data; + + g_debug ("Counting signal for connection"); + + info->connection_remaining--; + WAIT_CHECK_REMAINING() +} + +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, 2, "state-reason", "state") + 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; +} diff --git a/tests/network/test-network-panel.c b/tests/network/test-network-panel.c new file mode 100644 index 0000000..b68c06f --- /dev/null +++ b/tests/network/test-network-panel.c @@ -0,0 +1,874 @@ +/* + * 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; + + GtkWindow *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); + + /* Insert into object storage so that we see the same events as the panel. */ + cc_object_storage_add_object (CC_OBJECT_NMCLIENT, fixture->client); + + fixture->shell = GTK_WINDOW (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_window_present (fixture->shell); +} + +static void +fixture_tear_down (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + g_clear_object (&fixture->panel); + g_clear_object (&fixture->client); + g_clear_pointer (&fixture->shell, gtk_window_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 GtkWidget * +find_label (GtkWidget *widget, + const gchar *label_pattern) +{ + GtkWidget *child; + GtkWidget *label = NULL; + + if (GTK_IS_LABEL (widget)) { + const gchar *text = gtk_label_get_text (GTK_LABEL (widget)); + if (g_pattern_match_simple (label_pattern, text)) + return widget; + } + + if (ADW_IS_PREFERENCES_ROW (widget)) { + const gchar *text = adw_preferences_row_get_title (ADW_PREFERENCES_ROW (widget)); + if (g_pattern_match_simple (label_pattern, text)) + return widget; + } + + if (ADW_IS_ACTION_ROW (widget)) { + const gchar *text = adw_action_row_get_subtitle (ADW_ACTION_ROW (widget)); + if (g_pattern_match_simple (label_pattern, text)) + return widget; + } + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) { + label = find_label (child, label_pattern); + if (label) + break; + } + + return label; +} + +static int +widget_geo_dist (GtkWidget *a, + GtkWidget *b, + GtkWidget *base) +{ + GtkAllocation allocation; + double ax0, ay0, ax1, ay1, bx0, by0, bx1, by1, xdist = 0, ydist = 0; + + gtk_widget_get_allocation (a, &allocation); + if (!gtk_widget_translate_coordinates (a, base, 0, 0, &ax0, &ay0) || + !gtk_widget_translate_coordinates (a, base, allocation.width, allocation.height, &ax1, &ay1)) + return -G_MAXINT; + + gtk_widget_get_allocation (b, &allocation); + if (!gtk_widget_translate_coordinates (b, base, 0, 0, &bx0, &by0) || + !gtk_widget_translate_coordinates (b, base, allocation.width, allocation.height, &bx1, &by1)) + return +G_MAXINT; + + if (bx0 >= ax1) + xdist = bx0 - ax1; + else if (ax0 >= bx1) + xdist = ax0 - bx1; + if (by0 >= ay1) + ydist = by0 - ay1; + else if (ay0 >= by1) + ydist = ay0 - by1; + + return xdist + ydist; +} + +static GList* +test_list_descendants (GtkWidget *widget, + GType widget_type) +{ + GtkWidget *child; + GList *results = NULL; + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) { + if (!widget_type || g_type_is_a (G_OBJECT_TYPE (child), widget_type)) + results = g_list_prepend (results, child); + else + results = g_list_concat (results, test_list_descendants (child, widget_type)); + } + + return results; +} + +static int +widget_geo_cmp (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + gpointer *data = user_data; + GtkWidget *wa = (void*) a, *wb = (void*) b, *toplevel = data[0], *base_widget = data[1]; + int adist = widget_geo_dist (wa, base_widget, toplevel); + int bdist = widget_geo_dist (wb, base_widget, toplevel); + return adist > bdist ? +1 : adist == bdist ? 0 : -1; +} + +static GtkWidget * +find_sibling (GtkWidget *widget, + GType parent_type, + GType sibling_type) +{ + g_autoptr(GList) siblings = NULL; + GtkWidget *tmpwidget = widget; + gpointer data[2]; + + /* find all sibling candidates */ + while ((tmpwidget = gtk_widget_get_parent (tmpwidget)) != NULL) + { + siblings = g_list_concat (siblings, test_list_descendants (tmpwidget, sibling_type)); + + /* Stop searching further up if we reached the defined parent */ + if (parent_type && g_type_is_a (G_OBJECT_TYPE (tmpwidget), parent_type)) + break; + } + + /* sort them by distance to base_widget */ + data[0] = gtk_widget_get_native (widget); + data[1] = widget; + siblings = g_list_sort_with_data (siblings, widget_geo_cmp, data); + + /* pick nearest != base_widget */ + siblings = g_list_remove (siblings, widget); + tmpwidget = siblings ? siblings->data : NULL; + + return tmpwidget; +} + +/*****************************************************************************/ + +#if 0 /* See /network-panel-wired/vpn-sorting note */ +static GtkWidget* +find_parent_of_type(GtkWidget *widget, GType parent) +{ + while (widget) { + widget = gtk_widget_get_parent (widget); + if (G_TYPE_CHECK_INSTANCE_TYPE (G_OBJECT (widget), parent)) + return widget; + } + + return NULL; +} +#endif + +/*****************************************************************************/ + +static void +test_empty_ui (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + GtkWidget *bt_header; + GtkWidget *wired_header; + + /* There should be no Wired or Bluetooth sections */ + wired_header = find_label (GTK_WIDGET (fixture->shell), "Wired"); + g_assert_false (wired_header && gtk_widget_is_visible(wired_header)); + + bt_header = find_label (GTK_WIDGET (fixture->shell), "Bluetooth"); + g_assert_false (bt_header && gtk_widget_is_visible(bt_header)); +} + +/*****************************************************************************/ + +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_nonnull (find_label (GTK_WIDGET (fixture->shell), "Wired")); +} + +static void +test_second_device_add (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMDevice *device; + const gchar *device_path; + + test_device_add (fixture, user_data); + + device = nmtstc_service_add_wired_device (fixture->sinfo, + fixture->client, + "eth1001", + "52:54:00:ab:db:24", + NULL); + device_path = nm_object_get_path (NM_OBJECT (device)); + g_debug("Second device added: %s\n", device_path); + + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "Wired")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "Ethernet (eth1000)")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "Ethernet (eth1001)")); +} + +static void +test_second_device_add_remove (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMDevice *device; + const gchar *device_path; + GtkWidget *bt_header; + + test_device_add (fixture, user_data); + + device = nmtstc_service_add_wired_device (fixture->sinfo, + fixture->client, + "eth1001", + "52:54:00:ab:db:24", + NULL); + device_path = nm_object_get_path (NM_OBJECT (device)); + g_debug("Second device added: %s\n", device_path); + + nmtst_remove_device (fixture->sinfo, fixture->client, device); + g_debug("Second device removed again\n"); + + /* eth1000 should be labeled "Wired" again */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "Wired")); + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "Ethernet (eth1000)")); + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "Ethernet (eth1001)")); + + /* Some more checks for unrelated UI not showing up randomly */ + bt_header = find_label (GTK_WIDGET (fixture->shell), "Bluetooth"); + g_assert_false (bt_header && gtk_widget_is_visible(bt_header)); +} + +/*****************************************************************************/ + +static void +add_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NMClient *client = NM_CLIENT (object); + EventWaitInfo *info = user_data; + g_autoptr(GError) error = NULL; + + info->rc = nm_client_add_connection_finish (client, result, &error); + g_assert_no_error (error); + g_assert_nonnull (info->rc); + + info->other_remaining--; + WAIT_CHECK_REMAINING() +} + +static void +delete_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NMRemoteConnection *connection = NM_REMOTE_CONNECTION (object); + EventWaitInfo *info = user_data; + g_autoptr(GError) error = NULL; + + nm_remote_connection_delete_finish (connection, result, &error); + g_assert_no_error (error); + + info->other_remaining--; + WAIT_CHECK_REMAINING() +} + +static void +test_connection_add (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + g_autoptr(GError) error = NULL; + WAIT_DECL() + + conn = nmtst_create_minimal_connection ("test-inactive", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + nm_device_connection_compatible (fixture->main_ether, conn, &error); + g_assert_no_error (error); + + nm_client_add_connection_async (fixture->client, conn, TRUE, NULL, add_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_ADDED); + + WAIT_FINISHED(5) + + g_clear_object (&info.rc); + g_object_unref (conn); + + /* We have one (non-active) connection only, so we get a special case */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "Cable unplugged")); +} + +/*****************************************************************************/ + +static void +test_unconnected_carrier_plug (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + nmtst_set_wired_speed (fixture->sinfo, fixture->main_ether, 1234); + nmtst_set_device_state (fixture->sinfo, fixture->main_ether, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_CARRIER); + + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "1234 Mb/s")); +} + + +/*****************************************************************************/ + + +static void +test_connection_add_activate (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + NMActiveConnection *active_conn = NULL; + g_autoptr(GError) error = NULL; + GtkWidget *label, *sw; + + /* First set us into disconnected state with a carrier. */ + nmtst_set_wired_speed (fixture->sinfo, fixture->main_ether, 1234); + nmtst_set_device_state (fixture->sinfo, fixture->main_ether, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_CARRIER); + + conn = nmtst_create_minimal_connection ("test-active", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + + nm_device_connection_compatible (fixture->main_ether, conn, &error); + g_assert_no_error (error); + + active_conn = nmtst_add_and_activate_connection (fixture->sinfo, fixture->client, fixture->main_ether, conn); + g_object_unref (active_conn); + + label = find_label (GTK_WIDGET (fixture->shell), "1234 Mb/s"); + sw = find_sibling (label, GTK_TYPE_LIST_BOX_ROW, GTK_TYPE_SWITCH); + g_assert_nonnull (sw); + g_assert_false (gtk_switch_get_state (GTK_SWITCH (sw))); + + /* Now set the state to connected and check the switch state */ + nmtst_set_device_state (fixture->sinfo, fixture->main_ether, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE); + g_assert_true (gtk_switch_get_state (GTK_SWITCH (sw))); + + /* Let's toggle the switch back and check we get events */ + gtk_switch_set_active (GTK_SWITCH (sw), FALSE); + + /* Only one connection, so a generic label. */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "Connected - 1234 Mb/s")); +} + +static void +test_connection_multi_add_activate (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + GtkWidget *sw, *bt_header; + g_autoptr(GError) error = NULL; + + /* Add a single connection (just changing up to other test). */ + test_connection_add (fixture, user_data); + + /* Basically same as test_connection_add_activate but with different assertions. */ + nmtst_set_wired_speed (fixture->sinfo, fixture->main_ether, 1234); + nmtst_set_device_state (fixture->sinfo, fixture->main_ether, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_CARRIER); + + conn = nmtst_create_minimal_connection ("test-active", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + + nm_device_connection_compatible (fixture->main_ether, conn, &error); + g_assert_no_error (error); + + g_object_unref (nmtst_add_and_activate_connection (fixture->sinfo, fixture->client, fixture->main_ether, conn)); + + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "test-inactive")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "test-active")); + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "52:54:00:ab:db:23")); + + /* We have no switch if there are multiple connections */ + sw = find_sibling (find_label (GTK_WIDGET (fixture->shell), "test-active"), GTK_TYPE_LIST_BOX_ROW, GTK_TYPE_SWITCH); + if (sw) + g_assert_false (gtk_widget_is_visible (sw)); + + /* Now set the state to connected and check the switch state */ + nmtst_set_device_state (fixture->sinfo, fixture->main_ether, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE); + + /* Hardware address is shown at this point */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "52:54:00:ab:db:23")); + + /* Some more checks for unrelated UI not showing up randomly */ + bt_header = find_label (GTK_WIDGET (fixture->shell), "Bluetooth"); + g_assert_false (bt_header && gtk_widget_is_visible(bt_header)); +} + +/*****************************************************************************/ + +static void +test_vpn_add (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + NMSettingConnection *connsetting; + NMSettingVpn *setting; + + WAIT_DECL() + + conn = nmtst_create_minimal_connection ("test_vpn_a", NULL, NM_SETTING_VPN_SETTING_NAME, &connsetting); + setting = nm_connection_get_setting_vpn (conn); + g_object_set (G_OBJECT (connsetting), NM_SETTING_CONNECTION_ID, "A", NULL); + g_object_set (G_OBJECT (setting), NM_SETTING_VPN_SERVICE_TYPE, "org.freedesktop.NetworkManager.vpnc", NULL); + + nm_client_add_connection_async (fixture->client, conn, TRUE, NULL, add_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_ADDED); + + g_object_unref (conn); + + WAIT_FINISHED(5) + + g_clear_object (&info.rc); + + /* Make sure it shows up. */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "A")); +} + +/*****************************************************************************/ + +static void +test_vpn_add_remove (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + NMSettingConnection *connsetting; + NMSettingVpn *setting; + WAIT_DECL() + + conn = nmtst_create_minimal_connection ("test_vpn_a", NULL, NM_SETTING_VPN_SETTING_NAME, &connsetting); + setting = nm_connection_get_setting_vpn (conn); + g_object_set (G_OBJECT (connsetting), NM_SETTING_CONNECTION_ID, "A", NULL); + g_object_set (G_OBJECT (setting), NM_SETTING_VPN_SERVICE_TYPE, "org.freedesktop.NetworkManager.vpnc", NULL); + + nm_client_add_connection_async (fixture->client, conn, TRUE, NULL, add_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_ADDED); + + WAIT_FINISHED(5) + + /* Make sure it shows up. */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "A")); + + /* And delete again */ + nm_remote_connection_delete_async (info.rc, NULL, delete_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_REMOVED); + + WAIT_FINISHED(5) + + g_clear_object (&info.rc); + g_object_unref (conn); + + /* Make sure it does not show up. */ + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "A")); +} + +/*****************************************************************************/ + +static void +test_vpn_updating (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + NMSettingConnection *connsetting; + NMSettingVpn *setting; + GVariantBuilder builder; + WAIT_DECL() + + conn = nmtst_create_minimal_connection ("test_vpn_a", NULL, NM_SETTING_VPN_SETTING_NAME, &connsetting); + setting = nm_connection_get_setting_vpn (conn); + g_object_set (G_OBJECT (connsetting), NM_SETTING_CONNECTION_ID, "A", NULL); + g_object_set (G_OBJECT (setting), NM_SETTING_VPN_SERVICE_TYPE, "org.freedesktop.NetworkManager.vpnc", NULL); + + nm_client_add_connection_async (fixture->client, conn, TRUE, NULL, add_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_ADDED); + + WAIT_FINISHED(5) + + g_object_unref (conn); + + /* Make sure it shows up. */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "A")); + + /* Rename VPN from A to B */ + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sa{sv}}")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sa{sv}}")); + g_variant_builder_add (&builder, "s", "connection"); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (&builder, "s", "id"); + g_variant_builder_add (&builder, "v", g_variant_new_string ("B")); + g_variant_builder_close (&builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (&builder, "s", "type"); + g_variant_builder_add (&builder, "v", g_variant_new_string (nm_connection_get_connection_type (NM_CONNECTION (info.rc)))); + g_variant_builder_close (&builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (&builder, "s", "uuid"); + g_variant_builder_add (&builder, "v", g_variant_new_string (nm_connection_get_uuid (NM_CONNECTION (info.rc)))); + g_variant_builder_close (&builder); + + g_variant_builder_close (&builder); + g_variant_builder_close (&builder); + + nmtstc_service_update_connection_variant ( + fixture->sinfo, + nm_object_get_path (NM_OBJECT (info.rc)), + g_variant_builder_end (&builder), + FALSE); + g_variant_builder_clear (&builder); + + WAIT_CONNECTION(info.rc, 1, "changed"); + + WAIT_FINISHED(5) + + g_clear_object (&info.rc); + + /* Make sure it the label got renamed. */ + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "A")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "B")); +} + +/*****************************************************************************/ + +#if 0 /* See note below, where this test is added */ +static void +test_vpn_sorting (NetworkPanelFixture *fixture, + gconstpointer user_data) +{ + NMConnection *conn; + NMSettingConnection *connsetting; + NMSettingVpn *setting; + g_autoptr(GError) error = NULL; + GVariantBuilder builder; + GtkWidget *a, *b, *container; + GList *list; + WAIT_DECL() + + conn = nmtst_create_minimal_connection ("test_vpn_a", NULL, NM_SETTING_VPN_SETTING_NAME, &connsetting); + setting = nm_connection_get_setting_vpn (conn); + g_object_set (G_OBJECT (connsetting), NM_SETTING_CONNECTION_ID, "A", NULL); + g_object_set (G_OBJECT (setting), NM_SETTING_VPN_SERVICE_TYPE, "org.freedesktop.NetworkManager.vpnc", NULL); + + nm_client_add_connection_async (fixture->client, conn, TRUE, NULL, add_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_ADDED); + + WAIT_FINISHED(5) + + g_object_unref (conn); + g_clear_object (&info.rc); + + /* Create a second VPN which should be in front in the list */ + conn = nmtst_create_minimal_connection ("test_vpn_b", NULL, NM_SETTING_VPN_SETTING_NAME, &connsetting); + setting = nm_connection_get_setting_vpn (conn); + g_object_set (G_OBJECT (connsetting), NM_SETTING_CONNECTION_ID, "1", NULL); + g_object_set (G_OBJECT (setting), NM_SETTING_VPN_SERVICE_TYPE, "org.freedesktop.NetworkManager.vpnc", NULL); + + nm_client_add_connection_async (fixture->client, conn, TRUE, NULL, add_cb, &info); + + info.other_remaining = 1; + WAIT_CLIENT(fixture->client, 2, NM_CLIENT_CONNECTIONS, NM_CLIENT_CONNECTION_ADDED); + + WAIT_FINISHED(5) + + g_object_unref (conn); + + /* Make sure both VPNs are there. */ + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "A")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "1")); + + /* And test that A is after 1 */ + a = find_parent_of_type (find_label (GTK_WIDGET (fixture->shell), "A"), GTK_TYPE_STACK); + b = find_parent_of_type (find_label (GTK_WIDGET (fixture->shell), "1"), GTK_TYPE_STACK); + container = gtk_widget_get_parent (a); + list = gtk_container_get_children (GTK_CONTAINER (container)); + g_assert_cmpint (g_list_index (list, a), >, g_list_index (list, b)); + g_list_free (list); + + /* Rename VPN from 1 to B */ + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sa{sv}}")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sa{sv}}")); + g_variant_builder_add (&builder, "s", "connection"); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (&builder, "s", "id"); + g_variant_builder_add (&builder, "v", g_variant_new_string ("B")); + g_variant_builder_close (&builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (&builder, "s", "type"); + g_variant_builder_add (&builder, "v", g_variant_new_string (nm_connection_get_connection_type (NM_CONNECTION (info.rc)))); + g_variant_builder_close (&builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (&builder, "s", "uuid"); + g_variant_builder_add (&builder, "v", g_variant_new_string (nm_connection_get_uuid (NM_CONNECTION (info.rc)))); + g_variant_builder_close (&builder); + + g_variant_builder_close (&builder); + g_variant_builder_close (&builder); + + nmtstc_service_update_connection_variant ( + fixture->sinfo, + nm_object_get_path (NM_OBJECT (info.rc)), + g_variant_builder_end (&builder), + FALSE); + g_variant_builder_clear (&builder); + + WAIT_CONNECTION(info.rc, 1, "changed"); + + WAIT_FINISHED(5) + + g_clear_object (&info.rc); + + /* Make sure it the label got renamed. */ + g_assert_null (find_label (GTK_WIDGET (fixture->shell), "1")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "A")); + g_assert_nonnull (find_label (GTK_WIDGET (fixture->shell), "B")); + + /* And test that A is before B */ + a = find_parent_of_type (find_label (GTK_WIDGET (fixture->shell), "A"), GTK_TYPE_STACK); + b = find_parent_of_type (find_label (GTK_WIDGET (fixture->shell), "B"), GTK_TYPE_STACK); + container = gtk_widget_get_parent (a); + list = gtk_container_get_children (GTK_CONTAINER (container)); + g_assert_cmpint (g_list_index (list, a), <, g_list_index (list, b)); + g_list_free (list); +} +#endif + +/*****************************************************************************/ + +int +main (int argc, char **argv) +{ + g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); + g_setenv ("LIBNM_USE_SESSION_BUS", "1", TRUE); + g_setenv ("LC_ALL", "C", TRUE); + + gtk_test_init (&argc, &argv, NULL); + adw_init (); + + g_test_add ("/network-panel-wired/empty-ui", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_empty_ui, + fixture_tear_down); + + g_test_add ("/network-panel-wired/device-add", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_device_add, + fixture_tear_down); + + g_test_add ("/network-panel-wired/second-device-add", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_second_device_add, + fixture_tear_down); + + g_test_add ("/network-panel-wired/second-device-add-remove", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_second_device_add_remove, + fixture_tear_down); + + g_test_add ("/network-panel-wired/unconnected-carrier-plug", + NetworkPanelFixture, + NULL, + fixture_set_up_wired, + test_unconnected_carrier_plug, + fixture_tear_down); + + g_test_add ("/network-panel-wired/connection-add", + NetworkPanelFixture, + NULL, + fixture_set_up_wired, + test_connection_add, + fixture_tear_down); + + g_test_add ("/network-panel-wired/connection-add-activate", + NetworkPanelFixture, + NULL, + fixture_set_up_wired, + test_connection_add_activate, + fixture_tear_down); + + g_test_add ("/network-panel-wired/connection-multi-add-activate", + NetworkPanelFixture, + NULL, + fixture_set_up_wired, + test_connection_multi_add_activate, + fixture_tear_down); + + g_test_add ("/network-panel-wired/vpn-add", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_vpn_add, + fixture_tear_down); + + g_test_add ("/network-panel-wired/vpn-add-remove", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_vpn_add_remove, + fixture_tear_down); + + g_test_add ("/network-panel-wired/vpn-updating", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_vpn_updating, + fixture_tear_down); + +#if 0 + /* + * FIXME: Currently broken, so test is disabled. Test will likely need + * updating when this is fixed to look for GTK_TYPE_LIST_BOX_ROW rather + * than GTK_TYPE_STACK. + */ + g_test_add ("/network-panel-wired/vpn-sorting", + NetworkPanelFixture, + NULL, + fixture_set_up_empty, + test_vpn_sorting, + fixture_tear_down); +#endif + + return g_test_run (); +} + diff --git a/tests/network/test-network-panel.py b/tests/network/test-network-panel.py new file mode 100644 index 0000000..1098bab --- /dev/null +++ b/tests/network/test-network-panel.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright © 2018 Red Hat, Inc +# +# 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 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/>. +# +# Authors: Benjamin Berg <bberg@redhat.com> + +import os +import sys +import unittest + +try: + import dbusmock +except ImportError: + sys.stderr.write('You need python-dbusmock (http://pypi.python.org/pypi/python-dbusmock) for this test suite.\n') + sys.exit(1) + +# Add the shared directory to the search path +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'shared')) + +from gtest import GTest +from x11session import X11SessionTestCase + +BUILDDIR = os.environ.get('BUILDDIR', os.path.join(os.path.dirname(__file__))) + + +class PanelTestCase(X11SessionTestCase, GTest): + g_test_exe = os.path.join(BUILDDIR, 'test-network-panel') + + +if __name__ == '__main__': + # avoid writing to stderr + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/tests/network/test-wifi-text.c b/tests/network/test-wifi-text.c new file mode 100644 index 0000000..39c79cb --- /dev/null +++ b/tests/network/test-wifi-text.c @@ -0,0 +1,74 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* utils.c + * + * Copyright 2019 Purism SPC + * + * 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 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#undef NDEBUG +#undef G_DISABLE_ASSERT +#undef G_DISABLE_CHECKS +#undef G_DISABLE_CAST_CHECKS +#undef G_LOG_DOMAIN + +#include <glib.h> + +/* Including ‘.c’ file to test static functions */ +#include "cc-wifi-panel.c" + +static void +test_escape_qr_string (void) +{ + char *str; + + str = escape_string (NULL, TRUE); + g_assert_null (str); + + str = escape_string ("Wifi's password:empty", TRUE); + g_assert_cmpstr (str, ==, "\"Wifi\'s password\\:empty\""); + g_free (str); + + str = escape_string ("random;string:;\\", TRUE); + g_assert_cmpstr (str, ==, "\"random\\;string\\:\\;\\\\\""); + g_free (str); + + str = escape_string ("random-string", TRUE); + g_assert_cmpstr (str, ==, "\"random-string\""); + g_free (str); + + str = escape_string ("random-string", FALSE); + g_assert_cmpstr (str, ==, "random-string"); + g_free (str); + + str = escape_string ("വൈഫൈ", TRUE); + g_assert_cmpstr (str, ==, "\"വൈഫൈ\""); + g_free (str); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/wifi/escape-qr-string", test_escape_qr_string); + + return g_test_run (); +} diff --git a/tests/printers/canonicalization-test.txt b/tests/printers/canonicalization-test.txt new file mode 100644 index 0000000..10ceee1 --- /dev/null +++ b/tests/printers/canonicalization-test.txt @@ -0,0 +1,16 @@ +# already_present_printer1, space, already_present_printer2, ...., tab, +# new device id, tab, +# new device make and model, tab, +# new device original name, tab, +# new device info, tab, +# canonicalized name +# - stripped leading and trailing spaces +# - characters not in ALLOWED_CHARACTERS replaced by '-' +# - everything behind strings in residues removed +# - leading and trailing '-' removed +# - recurring '-' merged +# - indexed if the name is already in any of given lists +Canon-BJ-30 Canon-BJ-30-2 Canon BJ-30 - CUPS+Gutenprint v5.2.9 Simplified Canon-BJ-30-3 +Canon-BJ-30 HP Business Inkjet 1000, hpcups 3.14.6 HP-Business-Inkjet-1000 + ...A---strange;;printer ;;;;info A-strange-printer-info +HP-Something MFG:HP;MDL:Officejet 7300 series; HP Officejet 7300 series Officejet 7300 series Officejet-7300 diff --git a/tests/printers/meson.build b/tests/printers/meson.build new file mode 100644 index 0000000..60f144f --- /dev/null +++ b/tests/printers/meson.build @@ -0,0 +1,22 @@ + +test_units = [ + #'test-canonicalization', + 'test-shift' +] + +includes = [top_inc, include_directories('../../panels/printers')] +cflags = '-DTEST_SRCDIR="@0@"'.format(meson.current_source_dir()) + +foreach unit: test_units + exe = executable( + unit, + [unit + '.c'], + include_directories : includes, + dependencies : common_deps, + link_with : [printers_panel_lib], + c_args : cflags + ) + + test(unit, exe) +endforeach + diff --git a/tests/printers/shift-test.txt b/tests/printers/shift-test.txt new file mode 100644 index 0000000..b9eced4 --- /dev/null +++ b/tests/printers/shift-test.txt @@ -0,0 +1,9 @@ +# String, tab, string shifted by 1 position left +Canon anon +-JetDirect JetDirect +localhost ocalhost +Šípková Růženka ípková Růženka + space space +[][ ][ +…... ... + diff --git a/tests/printers/test-canonicalization.c b/tests/printers/test-canonicalization.c new file mode 100644 index 0000000..d555660 --- /dev/null +++ b/tests/printers/test-canonicalization.c @@ -0,0 +1,119 @@ +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <locale.h> + +#include "pp-utils.h" + +static void +test_canonicalization (gconstpointer data) +{ + const char *contents = data; + guint i, j; + char **lines; + + lines = g_strsplit (contents, "\n", -1); + if (lines == NULL) + { + g_warning ("Test file is empty"); + g_test_fail (); + return; + } + + for (i = 0; lines[i] != NULL; i++) + { + char **items; + + if (*lines[i] == '#') + continue; + + if (*lines[i] == '\0') + break; + + items = g_strsplit (lines[i], "\t", -1); + if (g_strv_length (items) == 6) + { + PpPrintDevice *device; + gchar **already_present_printers; + gchar *canonicalized_name; + GList *devices = NULL; + + already_present_printers = g_strsplit (items[0], " ", -1); + + for (j = 0; already_present_printers[j] != NULL; j++) + devices = g_list_append (devices, g_strdup (already_present_printers[j])); + + device = g_object_new (PP_TYPE_PRINT_DEVICE, + "device-id", items[1], + "device-make-and-model", items[2], + "device-original-name", items[3], + "device-info", items[4], + NULL); + + canonicalized_name = + canonicalize_device_name (devices, NULL, NULL, 0, device); + + if (g_strcmp0 (canonicalized_name, items[5]) != 0) + { + g_error ("Result for ('%s', '%s', '%s', '%s') doesn't match '%s' (got: '%s')", + items[1], items[2], items[3], items[4], items[5], canonicalized_name); + g_test_fail (); + } + else + { + g_debug ("Result for ('%s', '%s', '%s', '%s') matches '%s'", + items[1], items[2], items[3], items[4], canonicalized_name); + } + + g_free (canonicalized_name); + g_object_unref (device); + g_list_free_full (devices, (GDestroyNotify) g_free); + g_strfreev (already_present_printers); + } + else + { + g_warning ("Line number %u has not correct number of items!", i); + g_test_fail (); + } + + g_strfreev (items); + } + + g_strfreev (lines); +} + +int +main (int argc, char **argv) +{ + char *locale; + char *contents; + + /* Running in some locales will + * break the tests as "ü" will be transliterated to + * "ue" in de_DE, and 'u"' in the C locale. + * + * Work around that by forcing en_US with UTF-8 in + * our tests + * https://bugzilla.gnome.org/show_bug.cgi?id=650342 */ + + locale = setlocale (LC_ALL, "en_US.UTF-8"); + if (locale == NULL) + { + g_debug("Missing en_US.UTF-8 locale, ignoring test."); + return 0; + } + + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + g_test_init (&argc, &argv, NULL); + + if (g_file_get_contents (TEST_SRCDIR "/canonicalization-test.txt", &contents, NULL, NULL) == FALSE) + { + g_warning ("Failed to load '%s'", TEST_SRCDIR "/canonicalization-test.txt"); + return 1; + } + + g_test_add_data_func ("/printers/canonicalization", contents, test_canonicalization); + + return g_test_run (); +} diff --git a/tests/printers/test-shift.c b/tests/printers/test-shift.c new file mode 100644 index 0000000..e85fe9a --- /dev/null +++ b/tests/printers/test-shift.c @@ -0,0 +1,95 @@ +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <locale.h> + +#include "pp-utils.h" + +static void +test_shift (gconstpointer data) +{ + const char *contents = data; + guint i; + char *str; + char **lines; + + lines = g_strsplit (contents, "\n", -1); + if (lines == NULL) + { + g_warning ("Test file is empty"); + g_test_fail (); + return; + } + + for (i = 0; lines[i] != NULL; i++) + { + char **items; + char *utf8; + + if (*lines[i] == '#') + continue; + + if (*lines[i] == '\0') + break; + + items = g_strsplit (lines[i], "\t", -1); + str = g_strdup (items[0]); + shift_string_left (str); + utf8 = g_locale_from_utf8 (items[0], -1, NULL, NULL, NULL); + if (g_strcmp0 (str, items[1]) != 0) + { + g_error ("Result for '%s' doesn't match '%s' (got: '%s')", + utf8, items[1], str); + g_test_fail (); + } + else + { + g_debug ("Result for '%s' matches '%s'", + utf8, str); + } + + g_free (str); + g_free (utf8); + + g_strfreev (items); + } + + g_strfreev (lines); + +} + +int +main (int argc, char **argv) +{ + char *locale; + char *contents; + + /* Running in some locales will + * break the tests as "ü" will be transliterated to + * "ue" in de_DE, and 'u"' in the C locale. + * + * Work around that by forcing en_US with UTF-8 in + * our tests + * https://bugzilla.gnome.org/show_bug.cgi?id=650342 */ + + locale = setlocale (LC_ALL, "en_US.UTF-8"); + if (locale == NULL) + { + g_debug("Missing en_US.UTF-8 locale, ignoring test."); + return 0; + } + + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + g_test_init (&argc, &argv, NULL); + + if (g_file_get_contents (TEST_SRCDIR "/shift-test.txt", &contents, NULL, NULL) == FALSE) + { + g_warning ("Failed to load '%s'", TEST_SRCDIR "/shift-test.txt"); + return 1; + } + + g_test_add_data_func ("/printers/shift", contents, test_shift); + + return g_test_run (); +} diff --git a/tests/shared/gtest.py b/tests/shared/gtest.py new file mode 100644 index 0000000..b77202b --- /dev/null +++ b/tests/shared/gtest.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# Copyright © 2018 Red Hat, Inc +# +# 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 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/>. +# +# Authors: Benjamin Berg <bberg@redhat.com> + +import os +import sys +import subprocess +import functools + +class _GTestSingleProp(object): + """Property which creates a bound method for calling the specified test.""" + def __init__(self, test): + self.test = test + + @staticmethod + def __func(self, test): + self._gtest_single(test) + + def __get__(self, obj, cls): + bound_method = self.__func.__get__(obj, cls) + partial_method = functools.partial(bound_method, self.test) + partial_method.__doc__ = bound_method.__doc__ + # Set a qualified name using the qualified name of the class and + # function. Note that this is different from the generated attribute + # name as it is missing the test_%03d_ prefix. + partial_method.__qualname__ = '%s.%s' % (cls.__qualname__, self.test) + + return partial_method + + +class _GTestMeta(type): + def __new__(cls, name, bases, namespace, **kwds): + result = type.__new__(cls, name, bases, dict(namespace)) + + if result.g_test_exe is not None: + try: + _GTestMeta.make_tests(result.g_test_exe, result) + except Exception as e: + print('') + print(e) + print('Error generating separate test funcs, will call binary once.') + result.test_all = result._gtest_all + + return result + + @staticmethod + def make_tests(exe, result): + env = os.environ.copy() + env['G_MESSAGES_DEBUG'] = '' + test = subprocess.Popen([exe, '-l'], stdout=subprocess.PIPE, stderr=None, env=env) + stdout, stderr = test.communicate() + + if test.returncode != 0: + raise AssertionError('Execution of GTest executable to query the tests returned non-zero exit code!') + + stdout = stdout.decode('utf-8') + + for i, test in enumerate(stdout.split('\n')): + if not test: + continue + + # Number it and make sure the function name is prefixed with 'test'. + # Keep the rest as is, we don't care about the fact that the function + # names cannot be typed in. + name = 'test_%03d_' % (i + 1) + test + setattr(result, name, _GTestSingleProp(test)) + + +class GTest(metaclass = _GTestMeta): + """Helper class to run GLib test. A test function will be created for each + test from the executable. + + Use by using this class as a mixin and setting g_test_exe to an appropriate + value. + """ + + #: The GTest based executable + g_test_exe = None + #: Timeout when running a single test + g_test_single_timeout = None + #: Timeout when running all tests in one go + g_test_all_timeout = None + + def _gtest_single(self, test): + assert(test) + p = subprocess.Popen([self.g_test_exe, '-q', '-p', test], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + try: + stdout, stderr = p.communicate(timeout=self.g_test_single_timeout) + except subprocess.TimeoutExpired: + p.kill() + stdout, stderr = p.communicate() + stdout += b'\n\nTest was aborted due to timeout' + + try: + stdout = stdout.decode('utf-8') + except UnicodeDecodeError: + pass + + if p.returncode != 0: + self.fail(stdout) + + def _gtest_all(self): + subprocess.check_call([self.g_test_exe], timeout=self.g_test_all_timeout) diff --git a/tests/shared/x11session.py b/tests/shared/x11session.py new file mode 100644 index 0000000..b9d1b6e --- /dev/null +++ b/tests/shared/x11session.py @@ -0,0 +1,101 @@ +# Copyright © 2018 Red Hat, Inc +# +# 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 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/>. +# +# Authors: Benjamin Berg <bberg@redhat.com> + +import os +import sys +import subprocess +from dbusmock import DBusTestCase + +# Intended to be shared across projects, submitted for inclusion into +# dbusmock but might need to live elsewhere +# The pull request contains python 2 compatibility code. +# https://github.com/martinpitt/python-dbusmock/pull/44 + +class X11SessionTestCase(DBusTestCase): + #: The display the X server is running on + X_display = -1 + #: The X server to start + Xserver = 'Xvfb' + #: Further parameters for the X server + Xserver_args = ['-screen', '0', '1280x1024x24', '+extension', 'GLX'] + #: Where to redirect the X stdout and stderr to. Set to None for debugging + #: purposes if the X server is failing for some reason. + Xserver_output = subprocess.DEVNULL + + @classmethod + def setUpClass(klass): + klass.start_xorg() + klass.start_system_bus() + klass.start_session_bus() + + @classmethod + def start_xorg(klass): + r, w = os.pipe() + + # Xvfb seems to randomly crash in some workloads if "-noreset" is not given. + # https://bugzilla.redhat.com/show_bug.cgi?id=1565847 + klass.xorg = subprocess.Popen([klass.Xserver, '-displayfd', "%d" % w, '-noreset'] + klass.Xserver_args, + pass_fds=(w,), + stdout=klass.Xserver_output, + stderr=subprocess.STDOUT) + os.close(w) + + # The X server will write "%d\n", we need to make sure to read the "\n". + # If the server dies we get zero bytes reads as EOF is reached. + display = b'' + while True: + tmp = os.read(r, 1024) + display += tmp + + # Break out if the read was empty or the line feed was read + if not tmp or tmp[-1] == b'\n': + break + + os.close(r) + + try: + display = int(display.strip()) + except ValueError: + # This should never happen, the X server didn't return a proper integer. + # Most likely it died for some odd reason. + # Note: Set Xserver_output to None to debug Xvfb startup issues. + klass.stop_xorg() + raise AssertionError('X server reported back no or an invalid display number (%s)' % (display)) + + klass.X_display = display + # Export information into our environment for tests to use + os.environ['DISPLAY'] = ":%d" % klass.X_display + os.environ['WAYLAND_DISPLAY'] = '' + + # Server should still be up and running at this point + assert klass.xorg.poll() is None + + return klass.X_display + + @classmethod + def stop_xorg(klass): + if hasattr(klass, 'xorg'): + klass.X_display = -1 + klass.xorg.terminate() + klass.xorg.wait() + del klass.xorg + + @classmethod + def tearDownClass(klass): + DBusTestCase.tearDownClass() + + klass.stop_xorg() |