tools: add unit test for test-modemmanager-service.py

The tests spawn the service via GTestDBus framework and make
some test API call while checking libmm interface to verify
that informations are well propagated.

To be able to use the fresh built libmm typelib, I used a
wrapper script to set GI_TYPELIB_PATH (because DBus activation
process clean the environment so it is not possible to set
it directly in the file).

This requires also the install of libgirepository-dev and python3-gi
in the CI docker.

Signed-off-by: Frederic Martinsons <frederic.martinsons@sigfox.com>
This commit is contained in:
Frederic Martinsons
2020-11-12 10:48:11 +01:00
committed by Frédéric Martinsons
parent 17ea1dfa13
commit 89cd1696c3
8 changed files with 523 additions and 2 deletions

View File

@@ -1 +1,3 @@
SUBDIRS = . tests
EXTRA_DIST = test-modemmanager-service.py

46
tools/tests/Makefile.am Normal file
View File

@@ -0,0 +1,46 @@
include $(top_srcdir)/gtester.make
################################################################################
# common
################################################################################
AM_CFLAGS = \
$(WARN_CFLAGS) \
$(MM_CFLAGS) \
-I$(top_srcdir) \
-I$(top_srcdir)/src \
-I$(top_srcdir)/include \
-I$(top_builddir)/include \
-I$(top_srcdir)/libmm-glib \
-I${top_srcdir}/libmm-glib/generated \
-I${top_builddir}/libmm-glib/generated \
$(NULL)
LDADD = \
$(top_builddir)/libmm-glib/libmm-glib.la \
$(NULL)
AM_LDFLAGS = \
$(WARN_LDFLAGS) \
$(MM_LIBS) \
$(MM_LDFLAGS) \
$(NULL)
noinst_PROGRAMS = test-stub
test_stub_CPPFLAGS = \
-DTEST_SERVICES=\""$(abs_top_builddir)/tools/tests/services"\" \
$(NULL)
TEST_PROGS += $(noinst_PROGRAMS)
test-wrapper.sh: test-wrapper.sh.in
@sed \
-e 's|@abs_top_builddir[@]|$(abs_top_builddir)|g' \
-e 's|@abs_top_srcdir[@]|$(abs_top_srcdir)|g' \
$< >$@
@chmod +x $@
BUILT_SOURCES = test-wrapper.sh
CLEANFILES = test-wrapper.sh
EXTRA_DIST += test-wrapper.sh.in services/org.freedesktop.ModemManager1.service.in

View File

@@ -0,0 +1,3 @@
[D-BUS Service]
Name=org.freedesktop.ModemManager1
Exec=@abs_top_builddir@/tools/tests/test-wrapper.sh

459
tools/tests/test-stub.c Normal file
View File

@@ -0,0 +1,459 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; 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:
*
* Copyright (C) 2020 Frederic Martinsons <frederic.martinsons@sigfox.com>
*/
#include <config.h>
#include <glib.h>
#include <gio/gio.h>
#include <libmm-glib.h>
#include <ModemManager-names.h>
#define MM_LOG_NO_OBJECT
#include <mm-log-test.h>
#define MM_TEST_IFACE_NAME "org.freedesktop.ModemManager1.LibmmGlibTest"
typedef struct {
GMainLoop *loop;
MMManager *mm_manager;
GDBusConnection *gdbus_connection;
GDBusProxy *mm_proxy;
GDBusProxy *mm_modem_prop_proxy;
GTestDBus *test_bus;
gchar *modem_object_path;
guint timeout_id;
MMSim *sim;
gboolean pin_error;
} TestData;
static void
setup (TestData **ptdata,
gconstpointer data)
{
GError *error = NULL;
TestData *tdata = NULL;
tdata = (TestData *)g_malloc0 (sizeof(TestData));
*ptdata = tdata;
tdata->loop = g_main_loop_new (NULL, FALSE);
g_assert_nonnull (tdata->loop);
tdata->test_bus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_assert_nonnull (tdata->test_bus);
g_test_dbus_add_service_dir (tdata->test_bus, TEST_SERVICES);
g_test_dbus_up (tdata->test_bus);
/* Grab a proxy to the fake NM service to trigger tests */
tdata->mm_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
MM_DBUS_SERVICE,
MM_DBUS_PATH,
MM_TEST_IFACE_NAME,
NULL, &error);
g_assert_no_error (error);
tdata->gdbus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
tdata->mm_manager = mm_manager_new_sync (tdata->gdbus_connection,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
NULL,
&error);
g_assert_no_error (error);
g_assert_nonnull (tdata->mm_manager);
}
static void
teardown (TestData **ptdata,
gconstpointer data)
{
TestData *tdata = NULL;
tdata = *ptdata;
g_clear_object (&tdata->mm_modem_prop_proxy);
g_clear_object (&tdata->mm_proxy);
g_clear_object (&tdata->mm_manager);
g_clear_object (&tdata->gdbus_connection);
g_test_dbus_down (tdata->test_bus);
g_clear_object (&tdata->test_bus);
g_main_loop_unref (tdata->loop);
g_free (tdata);
}
static gboolean
loop_timeout_cb (gpointer user_data)
{
mm_err ("Timeout has elapsed");
g_assert_not_reached ();
return G_SOURCE_REMOVE;
}
static void
run_loop_for_ms (TestData *tdata,
guint32 timeout)
{
mm_info ("Run loop for %u ms", timeout);
tdata->timeout_id = g_timeout_add (timeout, loop_timeout_cb, tdata);
g_main_loop_run (tdata->loop);
}
static void
stop_loop (TestData *tdata)
{
if (tdata->timeout_id) {
g_source_remove (tdata->timeout_id);
tdata->timeout_id = 0;
}
mm_info ("Stop the loop");
g_main_loop_quit (tdata->loop);
}
static void
add_modem_completion_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
GVariant *dbus_return = NULL;
GVariant *obj_path_variant = NULL;
TestData *tdata = NULL;
tdata = (TestData*)user_data;
mm_info ("AddModem DBus call completed");
dbus_return = g_dbus_proxy_call_finish (tdata->mm_proxy, res, &error);
g_assert_no_error (error);
g_assert_nonnull (dbus_return);
g_assert_cmpstr (g_variant_get_type_string (dbus_return), == , "(o)");
obj_path_variant = g_variant_get_child_value (dbus_return, 0);
tdata->modem_object_path = g_variant_dup_string (obj_path_variant, NULL);
g_assert_null (tdata->mm_modem_prop_proxy);
tdata->mm_modem_prop_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
MM_DBUS_SERVICE,
tdata->modem_object_path,
"org.freedesktop.DBus.Properties",
NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (tdata->mm_modem_prop_proxy);
g_variant_unref (dbus_return);
g_variant_unref (obj_path_variant);
stop_loop (tdata);
}
static gchar*
add_modem (TestData *tdata,
gboolean add_sim,
const gchar *iccid)
{
g_dbus_proxy_call (tdata->mm_proxy,
"AddModem",
g_variant_new ("(bs)", add_sim, iccid),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
1000,
NULL,
add_modem_completion_cb,
tdata);
run_loop_for_ms (tdata, 1000);
return tdata->modem_object_path;
}
static void
emit_state_changed_completion_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
GVariant *dbus_return = NULL;
TestData *tdata = NULL;
tdata = (TestData*)user_data;
mm_info ("EmitStateChanged DBus call completed");
dbus_return = g_dbus_proxy_call_finish (tdata->mm_proxy, res, &error);
g_assert_no_error (error);
g_assert_nonnull (dbus_return);
g_variant_unref (dbus_return);
stop_loop (tdata);
}
static void
set_modem_state (TestData *tdata,
MMModemState state,
MMModemStateFailedReason reason)
{
GError *error = NULL;
GVariant *ret = NULL;
GVariant *old_state_variant = NULL;
guint old_state = 0;
g_assert_nonnull (tdata->mm_modem_prop_proxy);
/* Get current state */
ret = g_dbus_proxy_call_sync (tdata->mm_modem_prop_proxy,
"org.freedesktop.DBus.Properties.Get",
g_variant_new ("(ss)",
MM_DBUS_INTERFACE_MODEM,
MM_MODEM_PROPERTY_STATE),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
NULL,
&error);
g_assert_no_error (error);
g_variant_get (ret, "(v)", &old_state_variant);
old_state = (guint)g_variant_get_int32 (old_state_variant);
g_variant_unref (ret);
g_variant_unref (old_state_variant);
ret = g_dbus_proxy_call_sync (tdata->mm_modem_prop_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
MM_DBUS_INTERFACE_MODEM,
MM_MODEM_PROPERTY_STATE,
g_variant_new_int32 (state)),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
NULL,
&error);
g_assert_no_error (error);
g_variant_unref (ret);
ret = g_dbus_proxy_call_sync (tdata->mm_modem_prop_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
MM_DBUS_INTERFACE_MODEM,
MM_MODEM_PROPERTY_STATEFAILEDREASON,
g_variant_new_uint32 (reason)),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
NULL,
&error);
g_assert_no_error (error);
g_variant_unref (ret);
/* Emit state change signal */
g_dbus_proxy_call (tdata->mm_proxy,
"EmitStateChanged",
g_variant_new ("(uuu)", old_state, state, reason),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
1000,
NULL,
emit_state_changed_completion_cb,
tdata);
run_loop_for_ms (tdata, 1000);
}
static void
set_modem_unlock_completion_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
GVariant *dbus_return = NULL;
TestData *tdata = NULL;
tdata = (TestData*)user_data;
mm_info ("org.freedesktop.DBus.Properties.Set DBus call completed");
dbus_return = g_dbus_proxy_call_finish (tdata->mm_modem_prop_proxy, res, &error);
g_assert_no_error (error);
g_assert_nonnull (dbus_return);
g_variant_unref (dbus_return);
stop_loop (tdata);
}
static void
set_modem_unlock (TestData *tdata,
MMModemLock lock_state)
{
g_assert_nonnull (tdata->mm_modem_prop_proxy);
g_dbus_proxy_call (tdata->mm_modem_prop_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
MM_DBUS_INTERFACE_MODEM,
MM_MODEM_PROPERTY_UNLOCKREQUIRED,
g_variant_new_uint32 (lock_state)),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
1000,
NULL,
set_modem_unlock_completion_cb,
tdata);
run_loop_for_ms (tdata, 1000);
}
static void
set_modem_equipment_error (TestData *tdata,
MMMobileEquipmentError equipmentError,
gboolean clear)
{
GError *error = NULL;
GVariant *ret = NULL;
ret = g_dbus_proxy_call_sync (tdata->mm_proxy,
"SetMobileEquipmentError",
g_variant_new ("(ub)", equipmentError, clear),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
3000,
NULL,
&error);
g_assert_no_error (error);
g_variant_unref (ret);
}
static void
test_modem_interface (TestData **ptdata,
gconstpointer data)
{
TestData *tdata = NULL;
GDBusObject *modem_object = NULL;
MMModem *mm_modem = NULL;
g_autofree gchar *modem_path = NULL;
tdata = *ptdata;
/* Add a modem object (with no sim attached) */
modem_path = add_modem (tdata, FALSE, "");
modem_object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (tdata->mm_manager), modem_path);
g_assert_nonnull (modem_object);
mm_modem = mm_object_get_modem (MM_OBJECT (modem_object));
g_clear_object (&modem_object);
/* Check the modem states */
g_assert_cmpuint (mm_modem_get_state (mm_modem), ==, MM_MODEM_STATE_UNKNOWN);
g_assert_cmpuint (mm_modem_get_state_failed_reason (mm_modem), ==, MM_MODEM_STATE_FAILED_REASON_UNKNOWN);
/* Set new state and check that it is propagated */
set_modem_state (tdata, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_FAILED_REASON_NONE);
g_assert_cmpuint (mm_modem_get_state (mm_modem), ==, MM_MODEM_STATE_REGISTERED);
g_assert_cmpuint (mm_modem_get_state_failed_reason (mm_modem), ==, MM_MODEM_STATE_FAILED_REASON_NONE);
g_clear_object (&mm_modem);
}
static void
mm_sim_send_pin_completion_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
gboolean ret = FALSE;
TestData *tdata = NULL;
tdata = (TestData*)user_data;
mm_info("SendPin DBus method call completed");
ret = mm_sim_send_pin_finish (tdata->sim, res, &error);
if (tdata->pin_error) {
g_assert_nonnull (error);
g_assert_false (ret);
g_clear_error (&error);
} else {
g_assert_no_error (error);
g_assert_true (ret);
}
stop_loop (tdata);
}
static void
test_sim_interface (TestData **ptdata,
gconstpointer data)
{
TestData *tdata = NULL;
GDBusObject *modem_object = NULL;
MMModem *mm_modem = NULL;
GError *error = NULL;
g_autofree gchar *modem_path = NULL;
tdata = *ptdata;
/* Add a modem with a sim object */
modem_path = add_modem (tdata, TRUE, "89330122503000800750");
modem_object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (tdata->mm_manager), modem_path);
g_assert_nonnull (modem_object);
mm_modem = mm_object_get_modem (MM_OBJECT (modem_object));
g_clear_object (&modem_object);
g_assert_cmpuint (mm_modem_get_unlock_required (mm_modem), ==, MM_MODEM_LOCK_NONE);
/* Lock the modem */
set_modem_unlock (tdata, MM_MODEM_LOCK_SIM_PIN);
g_assert_cmpuint (mm_modem_get_unlock_required (mm_modem), ==, MM_MODEM_LOCK_SIM_PIN);
tdata->sim = mm_modem_get_sim_sync (mm_modem, NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (tdata->sim);
g_assert_cmpstr (mm_sim_get_identifier(tdata->sim), ==, "89330122503000800750");
/* Send a pin code */
tdata->pin_error = FALSE;
mm_sim_send_pin (tdata->sim, "1234", NULL, mm_sim_send_pin_completion_cb, tdata);
run_loop_for_ms (tdata, 1000);
/* Check that the modem has been unlocked */
g_assert_cmpuint (mm_modem_get_unlock_required (mm_modem), ==, MM_MODEM_LOCK_NONE);
/* Re lock it */
set_modem_unlock (tdata, MM_MODEM_LOCK_SIM_PIN);
g_assert_cmpuint (mm_modem_get_unlock_required (mm_modem), ==, MM_MODEM_LOCK_SIM_PIN);
/* Set an error that will simulate wrong pin code */
set_modem_equipment_error (tdata, MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, FALSE);
tdata->pin_error = TRUE;
mm_sim_send_pin (tdata->sim, "0000", NULL, mm_sim_send_pin_completion_cb, tdata);
run_loop_for_ms (tdata, 1000);
g_assert_cmpuint (mm_modem_get_unlock_required (mm_modem), ==, MM_MODEM_LOCK_SIM_PIN);
/* Clear the error and retry the pin code */
set_modem_equipment_error (tdata, 0, TRUE);
tdata->pin_error = FALSE;
mm_sim_send_pin (tdata->sim, "1234", NULL, mm_sim_send_pin_completion_cb, tdata);
run_loop_for_ms (tdata, 1000);
g_assert_cmpuint (mm_modem_get_unlock_required (mm_modem), ==, MM_MODEM_LOCK_NONE);
g_clear_object (&tdata->sim);
g_clear_object (&mm_modem);
}
int main (int argc,
char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add ("/MM/stub/modem/interface",
TestData *, NULL, setup,
test_modem_interface,
teardown);
g_test_add ("/MM/stub/sim/interface",
TestData *, NULL, setup,
test_sim_interface,
teardown);
return g_test_run ();
}

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# For debugging behavior of test-modemmanager-service.py, you can modify
# this line to add --log-file option
GI_TYPELIB_PATH=@abs_top_builddir@/libmm-glib @abs_top_srcdir@/tools/test-modemmanager-service.py