core: add basic QCDM serial port unit tests

Test that a Version Info request/response works as expected, and
add a testcase for a bug where specific Sierra CnS responses to
the Version Info request do not properly return an error when
attempting to parse the response as a QCDM packet.  Fix for the
second thing forthcoming.
This commit is contained in:
Dan Williams
2010-10-14 00:10:46 -05:00
parent 1dae103484
commit e16a58e54b
3 changed files with 430 additions and 1 deletions

1
.gitignore vendored
View File

@@ -32,6 +32,7 @@ callouts/mm-modem-probe
test/lsudev test/lsudev
src/tests/test-modem-helpers src/tests/test-modem-helpers
src/tests/test-charsets src/tests/test-charsets
src/tests/test-qcdm-serial-port
policy/org.freedesktop.modem-manager.policy policy/org.freedesktop.modem-manager.policy
libqcdm/tests/test-qcdm libqcdm/tests/test-qcdm

View File

@@ -1,7 +1,10 @@
INCLUDES = \ INCLUDES = \
-I$(top_srcdir)/src -I$(top_srcdir)/src
noinst_PROGRAMS = test-modem-helpers test-charsets noinst_PROGRAMS = \
test-modem-helpers \
test-charsets \
test-qcdm-serial-port
test_modem_helpers_SOURCES = \ test_modem_helpers_SOURCES = \
test-modem-helpers.c test-modem-helpers.c
@@ -23,11 +26,26 @@ test_charsets_LDADD = \
$(top_builddir)/src/libmodem-helpers.la \ $(top_builddir)/src/libmodem-helpers.la \
$(MM_LIBS) $(MM_LIBS)
test_qcdm_serial_port_SOURCES = \
test-qcdm-serial-port.c
test_qcdm_serial_port_CPPFLAGS = \
$(MM_CFLAGS) \
-I$(top_srcdir)
test_qcdm_serial_port_LDADD = \
$(MM_LIBS) \
$(top_builddir)/src/libserial.la \
$(top_builddir)/src/libmodem-helpers.la \
$(top_builddir)/libqcdm/src/libqcdm.la \
-lutil
if WITH_TESTS if WITH_TESTS
check-local: test-modem-helpers check-local: test-modem-helpers
$(abs_builddir)/test-modem-helpers $(abs_builddir)/test-modem-helpers
$(abs_builddir)/test-charsets $(abs_builddir)/test-charsets
$(abs_builddir)/test-qcdm-serial-port
endif endif

View File

@@ -0,0 +1,410 @@
/* -*- 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) 2010 Red Hat, Inc.
*/
#include <config.h>
#include <glib.h>
#include <string.h>
#include <pty.h>
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include "mm-errors.h"
#include "mm-qcdm-serial-port.h"
#include "libqcdm/src/commands.h"
#include "libqcdm/src/utils.h"
#include "libqcdm/src/com.h"
typedef struct {
int master;
int slave;
gboolean valid;
pid_t child;
} TestData;
static gboolean
wait_for_child (TestData *d, guint32 timeout)
{
GTimeVal start, now;
int status, ret;
g_get_current_time (&start);
do {
status = 0;
ret = waitpid (d->child, &status, WNOHANG);
g_get_current_time (&now);
if (d->child && (now.tv_sec - start.tv_sec > timeout)) {
/* Kill it */
if (g_test_verbose ())
g_message ("Killing running child process %d", d->child);
kill (d->child, SIGKILL);
d->child = 0;
}
if (ret == 0)
sleep (1);
} while ((ret <= 0) || (!WIFEXITED (status) && !WIFSIGNALED (status)));
d->child = 0;
return (WIFEXITED (status) && WEXITSTATUS (status) == 0) ? TRUE : FALSE;
}
static void
print_buf (const char *detail, const char *buf, gsize len)
{
int i = 0;
gboolean newline = FALSE;
g_print ("%s (%zu) ", detail, len);
for (i = 0; i < len; i++) {
g_print ("0x%02x ", buf[i] & 0xFF);
if (((i + 1) % 12) == 0) {
g_print ("\n");
newline = TRUE;
} else
newline = FALSE;
}
if (!newline)
g_print ("\n");
}
static void
server_send_response (int fd, const char *buf, gsize len)
{
int status;
gsize i = 0;
if (g_test_verbose ())
print_buf (">>>", buf, len);
while (i < len) {
errno = 0;
status = write (fd, &buf[i], 1);
g_assert_cmpint (errno, ==, 0);
g_assert (status == 1);
i++;
usleep (1000);
}
}
static gsize
server_wait_request (int fd, char *buf, gsize len)
{
fd_set in;
int result;
struct timeval timeout = { 1, 0 };
char readbuf[1024];
ssize_t bytes_read;
int total = 0, retries = 0;
gsize decap_len = 0;
FD_ZERO (&in);
FD_SET (fd, &in);
result = select (fd + 1, &in, NULL, NULL, &timeout);
g_assert (result == 1);
g_assert (FD_ISSET (fd, &in));
do {
errno = 0;
bytes_read = read (fd, &readbuf[total], 1);
if ((bytes_read == 0) || (errno == EAGAIN)) {
/* Haven't gotten the async control char yet */
if (retries > 20)
return 0; /* 2 seconds, give up */
/* Otherwise wait a bit and try again */
usleep (100000);
retries++;
continue;
} else if (bytes_read == 1) {
gboolean more = FALSE, success;
gsize used = 0;
total++;
decap_len = 0;
success = dm_decapsulate_buffer (readbuf, total, buf, len, &decap_len, &used, &more);
/* Discard used data */
if (used > 0) {
total -= used;
memmove (readbuf, &readbuf[used], total);
}
if (success && !more) {
/* Success; we have a packet */
break;
}
} else {
/* Some error occurred */
g_assert_not_reached ();
}
} while (total < sizeof (readbuf));
if (g_test_verbose ()) {
print_buf ("<<<", readbuf, total);
print_buf ("D<<", buf, decap_len);
}
return decap_len;
}
typedef void (*VerInfoCb) (MMQcdmSerialPort *port,
GByteArray *response,
GError *error,
gpointer user_data);
static void
qcdm_verinfo_cb (MMQcdmSerialPort *port,
GByteArray *response,
GError *error,
gpointer user_data)
{
GMainLoop *loop = user_data;
g_assert_no_error (error);
g_assert (response->len > 0);
g_main_loop_quit (loop);
}
static void
qcdm_verinfo (MMQcdmSerialPort *port, VerInfoCb cb, GMainLoop *loop)
{
GError *error = NULL;
GByteArray *verinfo;
gint len;
/* Build up the probe command */
verinfo = g_byte_array_sized_new (50);
len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error);
if (len <= 0) {
g_byte_array_free (verinfo, TRUE);
g_assert_no_error (error);
}
verinfo->len = len;
mm_qcdm_serial_port_queue_command (port, verinfo, 3, cb, loop);
}
/* Test that a Version Info request/response is processed correctly to
* make sure things in general are working.
*/
static void
test_verinfo (void *f)
{
TestData *d = f;
char req[512];
gsize req_len;
pid_t cpid;
const char rsp[] = {
0x00, 0x41, 0x75, 0x67, 0x20, 0x31, 0x39, 0x20, 0x32, 0x30, 0x30, 0x38,
0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x34, 0x37, 0x4f, 0x63, 0x74, 0x20,
0x32, 0x39, 0x20, 0x32, 0x30, 0x30, 0x37, 0x31, 0x39, 0x3a, 0x30, 0x30,
0x3a, 0x30, 0x30, 0x53, 0x43, 0x4e, 0x52, 0x5a, 0x2e, 0x2e, 0x2e, 0x2a,
0x06, 0x04, 0xb9, 0x0b, 0x02, 0x00, 0xb2, 0x19, 0xc4, 0x7e
};
signal (SIGCHLD, SIG_DFL);
cpid = fork ();
g_assert (cpid >= 0);
if (cpid == 0) {
MMQcdmSerialPort *port;
GMainLoop *loop;
gboolean success;
GError *error = NULL;
/* In the child */
g_type_init ();
loop = g_main_loop_new (NULL, FALSE);
port = mm_qcdm_serial_port_new_fd (d->slave, MM_PORT_TYPE_PRIMARY);
g_assert (port);
success = mm_serial_port_open (MM_SERIAL_PORT (port), &error);
g_assert_no_error (error);
g_assert (success);
qcdm_verinfo (port, qcdm_verinfo_cb, loop);
g_main_loop_run (loop);
mm_serial_port_close (MM_SERIAL_PORT (port));
g_object_unref (port);
exit (0);
}
/* Parent */
d->child = cpid;
req_len = server_wait_request (d->master, req, sizeof (req));
g_assert (req_len == 1);
g_assert_cmpint (req[0], ==, 0x00);
server_send_response (d->master, rsp, sizeof (rsp));
g_assert (wait_for_child (d, 3));
}
static void
qcdm_cns_cb (MMQcdmSerialPort *port,
GByteArray *response,
GError *error,
gpointer user_data)
{
GMainLoop *loop = user_data;
g_assert_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
g_main_loop_quit (loop);
}
/* Test that a Sierra CnS response to a Version Info command correctly
* raises an error in the child's response handler.
*/
static void
test_sierra_cns_rejected (void *f)
{
TestData *d = f;
char req[512];
gsize req_len;
pid_t cpid;
const char rsp[] = {
0x7e, 0x00, 0x0a, 0x6b, 0x6d, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e
};
signal (SIGCHLD, SIG_DFL);
cpid = fork ();
g_assert (cpid >= 0);
if (cpid == 0) {
MMQcdmSerialPort *port;
GMainLoop *loop;
gboolean success;
GError *error = NULL;
/* In the child */
g_type_init ();
loop = g_main_loop_new (NULL, FALSE);
port = mm_qcdm_serial_port_new_fd (d->slave, MM_PORT_TYPE_PRIMARY);
g_assert (port);
success = mm_serial_port_open (MM_SERIAL_PORT (port), &error);
g_assert_no_error (error);
g_assert (success);
qcdm_verinfo (port, qcdm_cns_cb, loop);
g_main_loop_run (loop);
mm_serial_port_close (MM_SERIAL_PORT (port));
g_object_unref (port);
exit (0);
}
/* Parent */
d->child = cpid;
req_len = server_wait_request (d->master, req, sizeof (req));
g_assert (req_len == 1);
g_assert_cmpint (req[0], ==, 0x00);
server_send_response (d->master, rsp, sizeof (rsp));
/* We expect the child to exit normally */
g_assert (wait_for_child (d, 3));
}
static void
test_pty_create (gpointer user_data)
{
TestData *d = user_data;
struct termios stbuf;
int ret;
GError *error = NULL;
gboolean success;
ret = openpty (&d->master, &d->slave, NULL, NULL, NULL);
g_assert (ret == 0);
d->valid = TRUE;
/* set raw mode on the slave using kernel default parameters */
memset (&stbuf, 0, sizeof (stbuf));
tcgetattr (d->slave, &stbuf);
tcflush (d->slave, TCIOFLUSH);
cfmakeraw (&stbuf);
tcsetattr (d->slave, TCSANOW, &stbuf);
fcntl (d->slave, F_SETFL, O_NONBLOCK);
fcntl (d->master, F_SETFL, O_NONBLOCK);
success = qcdm_port_setup (d->master, &error);
g_assert_no_error (error);
g_assert (success);
}
static void
test_pty_cleanup (gpointer user_data)
{
TestData *d = user_data;
/* For some reason the cleanup function gets called more times
* than the setup function does...
*/
if (d->valid) {
if (d->child)
kill (d->child, SIGKILL);
if (d->master >= 0)
close (d->master);
if (d->slave >= 0)
close (d->slave);
memset (d, 0, sizeof (*d));
}
}
#if GLIB_CHECK_VERSION(2,25,12)
typedef GTestFixtureFunc TCFunc;
#else
typedef void (*TCFunc)(void);
#endif
#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
#define TESTCASE_PTY(t, d) g_test_create_case (#t, sizeof (*d), d, (TCFunc) test_pty_create, (TCFunc) t, (TCFunc) test_pty_cleanup)
gboolean mm_options_debug (void);
gboolean mm_options_debug (void)
{
return g_test_verbose ();
}
int main (int argc, char **argv)
{
GTestSuite *suite;
gint result;
TestData *data = NULL;
g_test_init (&argc, &argv, NULL);
suite = g_test_get_root ();
g_test_suite_add (suite, TESTCASE_PTY (test_verinfo, data));
g_test_suite_add (suite, TESTCASE_PTY (test_sierra_cns_rejected, data));
result = g_test_run ();
return result;
}