/*
* This file is part of Cockpit.
*
* Copyright (C) 2014 Red Hat, Inc.
*
* Cockpit 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.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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 Cockpit; If not, see .
*/
#include "config.h"
#include "cockpitpipe.h"
#include "common/cockpittest.h"
#include
#include
#include
#include
#include
#include
/* ----------------------------------------------------------------------------
* Mock
*/
static GType mock_echo_pipe_get_type (void) G_GNUC_CONST;
typedef struct {
CockpitPipe parent;
GByteArray *received;
gboolean closed;
gchar *problem;
} MockEchoPipe;
typedef CockpitPipeClass MockEchoPipeClass;
G_DEFINE_TYPE (MockEchoPipe, mock_echo_pipe, COCKPIT_TYPE_PIPE);
static void
mock_echo_pipe_read (CockpitPipe *pipe,
GByteArray *buffer,
gboolean end_of_data)
{
MockEchoPipe *self = (MockEchoPipe *)pipe;
g_byte_array_append (self->received, buffer->data, buffer->len);
g_byte_array_set_size (buffer, 0);
}
static void
mock_echo_pipe_close (CockpitPipe *pipe,
const gchar *problem)
{
MockEchoPipe *self = (MockEchoPipe *)pipe;
g_assert (!self->closed);
self->closed = TRUE;
self->problem = g_strdup (problem);
}
static void
mock_echo_pipe_init (MockEchoPipe *self)
{
self->received = g_byte_array_new ();
}
static void
mock_echo_pipe_finalize (GObject *object)
{
MockEchoPipe *self = (MockEchoPipe *)object;
g_byte_array_free (self->received, TRUE);
g_free (self->problem);
G_OBJECT_CLASS (mock_echo_pipe_parent_class)->finalize (object);
}
static void
mock_echo_pipe_class_init (MockEchoPipeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
CockpitPipeClass *pipe_class = COCKPIT_PIPE_CLASS (klass);
object_class->finalize = mock_echo_pipe_finalize;
pipe_class->read = mock_echo_pipe_read;
pipe_class->close = mock_echo_pipe_close;
}
/* ----------------------------------------------------------------------------
* Testing
*/
typedef struct {
CockpitPipe *pipe;
guint timeout;
} TestCase;
typedef struct {
const gchar *pipe_type_name;
const gchar *command;
gboolean no_timeout;
} TestFixture;
static gboolean
on_timeout_abort (gpointer unused)
{
g_error ("timed out");
return FALSE;
}
static void
setup_timeout (TestCase *tc,
gconstpointer data)
{
const TestFixture *fixture = data;
if (!fixture || !fixture->no_timeout)
tc->timeout = g_timeout_add_seconds (10, on_timeout_abort, tc);
}
static void
setup_simple (TestCase *tc,
gconstpointer data)
{
const TestFixture *fixture = data;
const gchar *pipe_type;
GError *error = NULL;
gchar **argv;
int fds[2];
GPid pid = 0;
setup_timeout (tc, data);
pipe_type = "MockEchoPipe";
if (fixture && fixture->pipe_type_name)
pipe_type = fixture->pipe_type_name;
if (fixture && fixture->command)
{
g_shell_parse_argv (fixture->command, NULL, &argv, &error);
g_assert_no_error (error);
g_spawn_async_with_pipes (NULL, argv, NULL,
G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, NULL,
&pid, fds + 1, fds + 0, NULL,
&error);
g_assert_no_error (error);
g_strfreev (argv);
}
else
{
if (pipe (fds) < 0)
g_assert_not_reached ();
}
tc->pipe = g_object_new (g_type_from_name (pipe_type),
"name", "test",
"in-fd", fds[0],
"out-fd", fds[1],
"pid", pid,
NULL);
}
static void
teardown (TestCase *tc,
gconstpointer data)
{
if (tc->pipe)
{
g_object_add_weak_pointer (G_OBJECT (tc->pipe),
(gpointer *)&tc->pipe);
g_object_unref (tc->pipe);
/* If this asserts, outstanding references to transport */
g_assert (tc->pipe == NULL);
}
if (tc->timeout)
g_source_remove (tc->timeout);
}
static void
test_echo_and_close (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
GBytes *sent, *bytes;
sent = g_bytes_new_static ("the message", 11);
cockpit_pipe_write (tc->pipe, sent);
while (echo_pipe->received->len < 11)
g_main_context_iteration (NULL, TRUE);
g_byte_array_ref (echo_pipe->received);
bytes = g_byte_array_free_to_bytes (echo_pipe->received);
g_assert (g_bytes_equal (bytes, sent));
g_bytes_unref (sent);
g_bytes_unref (bytes);
cockpit_pipe_close (tc->pipe, NULL);
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
}
static void
test_echo_queue (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
GBytes *sent;
sent = g_bytes_new_static ("one", 3);
cockpit_pipe_write (tc->pipe, sent);
g_bytes_unref (sent);
sent = g_bytes_new_static ("two", 3);
cockpit_pipe_write (tc->pipe, sent);
g_bytes_unref (sent);
/* Only closes after above are sent */
cockpit_pipe_close (tc->pipe, NULL);
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (echo_pipe->received->len, ==, 6);
g_assert (memcmp (echo_pipe->received->data, "onetwo", 6) == 0);
}
static const TestFixture fixture_no_timeout = {
.no_timeout = TRUE
};
static void
test_echo_large (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
GBytes *sent;
/* Medium length */
sent = g_bytes_new_take (g_strnfill (1020, '!'), 1020);
cockpit_pipe_write (tc->pipe, sent);
while (echo_pipe->received->len < g_bytes_get_size (sent))
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (echo_pipe->received->len, ==, g_bytes_get_size (sent));
g_assert (memcmp (echo_pipe->received->data, g_bytes_get_data (sent, NULL), g_bytes_get_size (sent)) == 0);
g_bytes_unref (sent);
g_byte_array_set_size (echo_pipe->received, 0);
/* Extra large */
sent = g_bytes_new_take (g_strnfill (10 * 1000 * 1000, '?'), 10 * 1000 * 1000);
cockpit_pipe_write (tc->pipe, sent);
while (echo_pipe->received->len < g_bytes_get_size (sent))
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (echo_pipe->received->len, ==, g_bytes_get_size (sent));
g_assert (memcmp (echo_pipe->received->data, g_bytes_get_data (sent, NULL), g_bytes_get_size (sent)) == 0);
g_bytes_unref (sent);
g_byte_array_set_size (echo_pipe->received, 0);
/* Double check that didn't csrew things up */
sent = g_bytes_new_static ("yello", 5);
cockpit_pipe_write (tc->pipe, sent);
while (echo_pipe->received->len < g_bytes_get_size (sent))
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (echo_pipe->received->len, ==, g_bytes_get_size (sent));
g_assert (memcmp (echo_pipe->received->data, g_bytes_get_data (sent, NULL), g_bytes_get_size (sent)) == 0);
g_bytes_unref (sent);
}
static void
test_close_problem (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
cockpit_pipe_close (tc->pipe, "right now");
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpstr (echo_pipe->problem, ==, "right now");
}
static const TestFixture fixture_pid = {
.command = "cat"
};
static void
test_pid (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
GPid pid;
GPid check;
g_assert (cockpit_pipe_get_pid (tc->pipe, &pid));
g_assert (pid != 0);
/* Test it's real */
g_assert_cmpint (kill (pid, SIGTERM), ==, 0);
/* Should still be available after closing */
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
g_assert (cockpit_pipe_get_pid (tc->pipe, &check));
g_assert_cmpuint (pid, ==, check);
}
static const TestFixture fixture_buffer = {
.pipe_type_name = "CockpitPipe"
};
static void
test_buffer (TestCase *tc,
gconstpointer data)
{
GByteArray *buffer;
GBytes *sent;
buffer = cockpit_pipe_get_buffer (tc->pipe);
g_assert (buffer != NULL);
g_assert_cmpuint (buffer->len, ==, 0);
/* Including null terminator */
sent = g_bytes_new_static ("blahdeedoo", 11);
cockpit_pipe_write (tc->pipe, sent);
g_bytes_unref (sent);
while (buffer->len == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (buffer->len, ==, 11);
g_assert_cmpstr ((gchar *)buffer->data, ==, "blahdeedoo");
}
static void
test_skip_zero (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
GBytes *sent;
GBytes *zero;
/* Including null terminator */
sent = g_bytes_new_static ("blah", 4);
zero = g_bytes_new_static ("", 0);
cockpit_pipe_write (tc->pipe, sent);
cockpit_pipe_write (tc->pipe, zero);
cockpit_pipe_write (tc->pipe, sent);
g_bytes_unref (zero);
g_bytes_unref (sent);
while (echo_pipe->received->len < 8)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (echo_pipe->received->len, ==, 8);
g_byte_array_append (echo_pipe->received, (guint8 *)"", 1);
g_assert_cmpstr ((gchar *)echo_pipe->received->data, ==, "blahblah");
}
static const TestFixture fixture_exit_success = {
.command = "true"
};
static void
test_exit_success (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
gint status;
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
status = cockpit_pipe_exit_status (tc->pipe);
g_assert (WIFEXITED (status));
g_assert_cmpint (WEXITSTATUS (status), ==, 0);
}
static const TestFixture fixture_exit_fail = {
.command = "sh -c 'exit 5'"
};
static void
test_exit_fail (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
gint status;
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
status = cockpit_pipe_exit_status (tc->pipe);
g_assert (WIFEXITED (status));
g_assert_cmpint (WEXITSTATUS (status), ==, 5);
}
static const TestFixture fixture_exit_signal = {
.command = "cat"
};
static void
test_exit_signal (TestCase *tc,
gconstpointer data)
{
MockEchoPipe *echo_pipe = (MockEchoPipe *)tc->pipe;
gint status;
GPid pid = 0;
g_object_get (tc->pipe, "pid", &pid, NULL);
g_assert (pid != 0);
kill (pid, SIGINT);
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
status = cockpit_pipe_exit_status (tc->pipe);
g_assert (!WIFEXITED (status));
g_assert (WIFSIGNALED (status));
g_assert_cmpint (WTERMSIG (status), ==, SIGINT);
}
static void
test_read_error (void)
{
MockEchoPipe *echo_pipe;
int out;
/* Assuming FD 1000 is not taken */
g_assert (write (1000, "1", 1) < 0);
out = dup (2);
g_assert (out >= 0);
cockpit_expect_warning ("*Bad file descriptor");
cockpit_expect_warning ("*Bad file descriptor");
/* Pass in a bad read descriptor */
echo_pipe = g_object_new (mock_echo_pipe_get_type (),
"name", "test",
"in-fd", 1000,
"out-fd", out,
NULL);
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_expected ();
g_assert_cmpstr (echo_pipe->problem, ==, "internal-error");
g_object_unref (echo_pipe);
}
static void
test_write_error (void)
{
MockEchoPipe *echo_pipe;
GBytes *sent;
int fds[2];
/* Just used so we have a valid fd */
if (pipe(fds) < 0)
g_assert_not_reached ();
cockpit_expect_warning ("*Bad file descriptor");
cockpit_expect_warning ("*Bad file descriptor");
/* Pass in a bad write descriptor */
echo_pipe = g_object_new (mock_echo_pipe_get_type (),
"name", "test",
"in-fd", fds[0],
"out-fd", 1000,
NULL);
sent = g_bytes_new ("test", 4);
cockpit_pipe_write (COCKPIT_PIPE (echo_pipe), sent);
g_bytes_unref (sent);
while (!echo_pipe->closed)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_expected ();
g_assert_cmpstr (echo_pipe->problem, ==, "internal-error");
close (fds[1]);
g_object_unref (echo_pipe);
}
static void
test_read_combined (void)
{
MockEchoPipe *echo_pipe;
struct iovec iov[4];
gint fds[2];
int out;
if (pipe(fds) < 0)
g_assert_not_reached ();
out = dup (2);
g_assert (out >= 0);
/* Pass in a read end of the pipe */
echo_pipe = g_object_new (mock_echo_pipe_get_type (),
"name", "test",
"in-fd", fds[0],
"out-fd", out,
NULL);
/* Write two messages to the pipe at once */
iov[0].iov_base = "one";
iov[0].iov_len = 3;
iov[1].iov_base = "two";
iov[1].iov_len = 3;
iov[2].iov_base = "three";
iov[2].iov_len = 5;
iov[3].iov_base = "\0";
iov[3].iov_len = 1;
g_assert_cmpint (writev (fds[1], iov, 4), ==, 12);
while (echo_pipe->received->len < 12)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (echo_pipe->received->len, ==, 12);
g_assert_cmpstr ((gchar *)echo_pipe->received->data, ==, "onetwothree");
close (fds[1]);
g_object_unref (echo_pipe);
}
static void
test_consume_entire (void)
{
GByteArray *buffer;
GBytes *bytes;
buffer = g_byte_array_new ();
g_byte_array_append (buffer, (guint8 *)"Marmaalaaaade!", 15);
bytes = cockpit_pipe_consume (buffer, 0, 15, 0);
g_assert_cmpuint (buffer->len, ==, 0);
g_byte_array_free (buffer, TRUE);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 15);
g_assert_cmpstr (g_bytes_get_data (bytes, NULL), ==, "Marmaalaaaade!");
g_bytes_unref (bytes);
}
static void
test_consume_partial (void)
{
GByteArray *buffer;
GBytes *bytes;
buffer = g_byte_array_new ();
g_byte_array_append (buffer, (guint8 *)"Marmaalaaaade!", 15);
bytes = cockpit_pipe_consume (buffer, 0, 7, 0);
g_assert_cmpuint (buffer->len, ==, 8);
g_assert_cmpstr ((gchar *)buffer->data, ==, "aaaade!");
g_byte_array_free (buffer, TRUE);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 7);
g_assert (memcmp (g_bytes_get_data (bytes, NULL), "Marmaal", 7) == 0);
g_bytes_unref (bytes);
}
static void
test_consume_skip (void)
{
GByteArray *buffer;
GBytes *bytes;
buffer = g_byte_array_new ();
g_byte_array_append (buffer, (guint8 *)"Marmaalaaaade!", 15);
bytes = cockpit_pipe_consume (buffer, 7, 8, 0);
g_assert_cmpuint (buffer->len, ==, 0);
g_byte_array_free (buffer, TRUE);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, 8);
g_assert_cmpstr (g_bytes_get_data (bytes, NULL), ==, "aaaade!");
g_bytes_unref (bytes);
}
static void
test_buffer_skip (void)
{
GByteArray *buffer;
buffer = g_byte_array_new ();
g_byte_array_append (buffer, (guint8 *)"Marmaalaaaade!", 15);
cockpit_pipe_skip (buffer, 7);
g_assert_cmpuint (buffer->len, ==, 8);
g_assert_cmpstr ((char *)buffer->data, ==, "aaaade!");
g_byte_array_free (buffer, TRUE);
}
static void
test_properties (void)
{
CockpitPipe *tpipe;
gchar *name;
gint in;
gint out;
int fds[2];
if (pipe(fds) < 0)
g_assert_not_reached ();
tpipe = g_object_new (mock_echo_pipe_get_type (),
"name", "testo",
"in-fd", fds[0],
"out-fd", fds[1],
NULL);
g_object_get (tpipe, "name", &name, "in-fd", &in, "out-fd", &out, NULL);
g_assert_cmpstr (name, ==, "testo");
g_free (name);
g_assert_cmpint (in, ==, fds[0]);
g_assert_cmpint (out, ==, fds[1]);
g_object_unref (tpipe);
}
static void
on_close_get_flag (CockpitPipe *pipe,
const gchar *problem,
gpointer user_data)
{
gboolean *retval = user_data;
g_assert (*retval == FALSE);
*retval = TRUE;
}
static void
on_close_get_problem (CockpitPipe *pipe,
const gchar *problem,
gpointer user_data)
{
gchar **retval = user_data;
g_assert (retval != NULL && *retval == NULL);
*retval = g_strdup (problem ? problem : "");
}
static void
test_spawn_and_read (void)
{
gboolean closed = FALSE;
GByteArray *buffer;
CockpitPipe *pipe;
const gchar *argv[] = { "/bin/sh", "-c", "set", NULL };
const gchar *env[] = { "ENVIRON=Marmalaaade", NULL, };
pipe = cockpit_pipe_spawn (argv, env, NULL, COCKPIT_PIPE_FLAGS_NONE);
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_flag), &closed);
while (closed == FALSE)
g_main_context_iteration (NULL, TRUE);
buffer = cockpit_pipe_get_buffer (pipe);
g_byte_array_append (buffer, (const guint8 *)"\0", 1);
cockpit_assert_strmatch ((gchar *)buffer->data, "*ENVIRON*Marmalaaade*");
buffer = cockpit_pipe_get_stderr (pipe);
g_assert (buffer == NULL);
g_object_unref (pipe);
}
static void
test_spawn_and_write (void)
{
CockpitPipe *pipe;
GByteArray *buffer;
GBytes *sent;
const gchar *argv[] = { "/bin/cat", NULL };
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
g_assert (pipe != NULL);
/* Sending on the pipe before actually connected */
sent = g_bytes_new_static ("jola", 5);
cockpit_pipe_write (pipe, sent);
g_bytes_unref (sent);
buffer = cockpit_pipe_get_buffer (pipe);
while (buffer->len == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpuint (buffer->len, ==, 5);
g_assert_cmpstr ((gchar *)buffer->data, ==, "jola");
g_object_unref (pipe);
}
static void
test_spawn_and_fail (void)
{
gchar *problem = NULL;
CockpitPipe *pipe;
const gchar *argv[] = { "/non-existant", NULL };
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_problem), &problem);
while (problem == NULL)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpstr (problem, ==, "not-found");
g_free (problem);
g_object_unref (pipe);
}
static void
test_spawn_close_terminate (TestCase *tc,
gconstpointer unused)
{
CockpitPipe *pipe;
gboolean closed = FALSE;
gint status;
const gchar *argv[] = { "/bin/sleep", "500", NULL };
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_flag), &closed);
cockpit_pipe_close (pipe, "terminate");
while (!closed)
g_main_context_iteration (NULL, TRUE);
status = cockpit_pipe_exit_status (pipe);
g_assert (WIFSIGNALED (status));
g_assert_cmpint (WTERMSIG (status), ==, SIGTERM);
g_object_unref (pipe);
}
static void
test_spawn_close_clean (TestCase *tc,
gconstpointer unused)
{
CockpitPipe *pipe;
gboolean closed = FALSE;
gint status;
const gchar *argv[] = { "/bin/cat", NULL };
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_flag), &closed);
cockpit_pipe_close (pipe, NULL);
while (!closed)
g_main_context_iteration (NULL, TRUE);
status = cockpit_pipe_exit_status (pipe);
g_assert (!WIFSIGNALED (status));
g_assert_cmpint (WEXITSTATUS (status), ==, 0);
g_object_unref (pipe);
}
static void
test_spawn_and_buffer_stderr (void)
{
gboolean closed = FALSE;
GByteArray *buffer;
CockpitPipe *pipe;
const gchar *argv[] = { "/bin/sh", "-c", "echo error >&2; echo output; echo error2 >&2", NULL };
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_STDERR_TO_MEMORY);
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_flag), &closed);
while (closed == FALSE)
g_main_context_iteration (NULL, TRUE);
buffer = cockpit_pipe_get_buffer (pipe);
g_assert (buffer != NULL);
g_byte_array_append (buffer, (const guint8 *)"\0", 1);
g_assert_cmpstr ((gchar *)buffer->data, ==, "output\n");
buffer = cockpit_pipe_get_stderr (pipe);
g_assert (buffer != NULL);
g_byte_array_append (buffer, (const guint8 *)"\0", 1);
g_assert_cmpstr ((gchar *)buffer->data, ==, "error\nerror2\n");
g_object_unref (pipe);
}
static void
test_pty_shell (void)
{
gboolean closed = FALSE;
GByteArray *buffer;
CockpitPipe *pipe;
GBytes *sent;
const gchar *argv[] = { "/bin/bash", "-i", NULL };
pipe = cockpit_pipe_pty (argv, NULL, NULL, 24, 80);
g_assert (pipe != NULL);
sent = g_bytes_new_static ("echo booyah\nexit\n", 17);
cockpit_pipe_write (pipe, sent);
g_bytes_unref (sent);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_flag), &closed);
while (closed == FALSE)
g_main_context_iteration (NULL, TRUE);
buffer = cockpit_pipe_get_buffer (pipe);
g_byte_array_append (buffer, (const guint8 *)"\0", 1);
cockpit_assert_strmatch ((gchar *)buffer->data, "*booyah*");
g_object_unref (pipe);
}
typedef struct {
GSocket *listen_sock;
GSource *listen_source;
GSocket *conn_sock;
GSource *conn_source;
GSocketAddress *address;
} TestConnect;
static gboolean
on_socket_input (GSocket *socket,
GIOCondition cond,
gpointer user_data)
{
gchar buffer[1024];
GError *error = NULL;
gssize ret, wret;
ret = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &error);
g_assert_no_error (error);
if (ret == 0)
{
g_socket_shutdown (socket, FALSE, TRUE, &error);
g_assert_no_error (error);
return FALSE;
}
g_assert (ret > 0);
wret = g_socket_send (socket, buffer, ret, NULL, &error);
g_assert_no_error (error);
g_assert (wret == ret);
return TRUE;
}
static gboolean
on_socket_connection (GSocket *socket,
GIOCondition cond,
gpointer user_data)
{
TestConnect *tc = user_data;
GError *error = NULL;
g_assert (tc->conn_source == NULL);
tc->conn_sock = g_socket_accept (tc->listen_sock, NULL, &error);
g_assert_no_error (error);
tc->conn_source = g_socket_create_source (tc->conn_sock, G_IO_IN, NULL);
g_source_set_callback (tc->conn_source, (GSourceFunc)on_socket_input, tc, NULL);
g_source_attach (tc->conn_source, NULL);
/* Only one connection */
return FALSE;
}
static void
setup_connect (TestConnect *tc,
gconstpointer data)
{
GError *error = NULL;
GInetAddress *inet;
GSocketAddress *address;
inet = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
address = g_inet_socket_address_new (inet, 0);
g_object_unref (inet);
tc->listen_sock = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT, &error);
g_assert_no_error (error);
g_socket_bind (tc->listen_sock, address, TRUE, &error);
g_object_unref (address);
g_assert_no_error (error);
tc->address = g_socket_get_local_address (tc->listen_sock, &error);
g_assert_no_error (error);
g_socket_listen (tc->listen_sock, &error);
g_assert_no_error (error);
tc->listen_source = g_socket_create_source (tc->listen_sock, G_IO_IN, NULL);
g_source_set_callback (tc->listen_source, (GSourceFunc)on_socket_connection, tc, NULL);
g_source_attach (tc->listen_source, NULL);
}
static void
teardown_connect (TestConnect *tc,
gconstpointer data)
{
g_object_unref (tc->address);
if (tc->conn_source)
{
g_source_destroy (tc->conn_source);
g_source_unref (tc->conn_source);
}
if (tc->listen_source)
{
g_source_destroy (tc->listen_source);
g_source_unref (tc->listen_source);
}
g_clear_object (&tc->listen_sock);
g_clear_object (&tc->conn_sock);
}
static void
test_connect_and_read (TestConnect *tc,
gconstpointer user_data)
{
CockpitPipe *pipe;
GError *error = NULL;
GByteArray *buffer;
pipe = cockpit_pipe_connect ("broooo", tc->address);
g_assert (pipe != NULL);
while (tc->conn_sock == NULL)
g_main_context_iteration (NULL, TRUE);
/* Send the null terminator */
g_assert_cmpint (g_socket_send (tc->conn_sock, "eier", 5, NULL, &error), ==, 5);
g_assert_no_error (error);
buffer = cockpit_pipe_get_buffer (pipe);
while (buffer->len == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpuint (buffer->len, ==, 5);
g_assert_cmpstr ((gchar *)buffer->data, ==, "eier");
g_object_unref (pipe);
}
static void
test_connect_and_write (TestConnect *tc,
gconstpointer user_data)
{
gchar buffer[8];
CockpitPipe *pipe;
GError *error = NULL;
GBytes *sent;
pipe = cockpit_pipe_connect ("broooo", tc->address);
g_assert (pipe != NULL);
/* Sending on the pipe before actually connected */
sent = g_bytes_new_static ("jola", 5);
cockpit_pipe_write (pipe, sent);
g_bytes_unref (sent);
g_assert (tc->conn_sock == NULL);
/* Now we connect in main loop */
while (tc->conn_sock == NULL)
g_main_context_iteration (NULL, TRUE);
/* Read from the socket */
g_assert_cmpint (g_socket_receive (tc->conn_sock, buffer, sizeof (buffer), NULL, &error), ==, 5);
g_assert_no_error (error);
g_assert_cmpstr (buffer, ==, "jola");
g_object_unref (pipe);
}
static void
test_fail_not_found (void)
{
CockpitPipe *pipe;
GSocketAddress *address;
gchar *problem = NULL;
cockpit_expect_message ("*No such file or directory");
address = g_unix_socket_address_new ("/non-existent");
pipe = cockpit_pipe_connect ("bad", address);
g_object_unref (address);
/* Should not have closed at this point */
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_problem), &problem);
/* closes in main loop */
while (problem == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_expected ();
g_assert_cmpstr (problem, ==, "not-found");
g_free (problem);
g_object_unref (pipe);
}
static void
test_fail_access_denied (void)
{
CockpitPipe *pipe;
GSocketAddress *address;
gchar *unix_path;
gchar *problem = NULL;
gint fd;
if (geteuid () == 0)
{
cockpit_test_skip ("running as root");
return;
}
unix_path = g_strdup ("/tmp/cockpit-test-XXXXXX.sock");
fd = g_mkstemp (unix_path);
g_assert_cmpint (fd, >=, 0);
/* Take away all permissions from the file */
g_assert_cmpint (fchmod (fd, 0000), ==, 0);
cockpit_expect_message ("*Permission denied");
address = g_unix_socket_address_new (unix_path);
pipe = cockpit_pipe_connect ("bad", address);
g_object_unref (address);
/* Should not have closed at this point */
g_assert (pipe != NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_problem), &problem);
/* closes in main loop */
while (problem == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_expected ();
g_assert_cmpstr (problem, ==, "access-denied");
g_free (unix_path);
g_free (problem);
g_object_unref (pipe);
}
static void
test_problem_later (void)
{
gchar *problem = NULL;
gchar *check;
CockpitPipe *pipe;
pipe = g_object_new (COCKPIT_TYPE_PIPE,
"problem", "i-have-a-problem",
NULL);
g_signal_connect (pipe, "close", G_CALLBACK (on_close_get_problem), &problem);
g_object_get (pipe, "problem", &check, NULL);
g_assert_cmpstr (check, ==, "i-have-a-problem");
g_free (check);
check = NULL;
g_assert (problem == NULL);
while (problem == NULL)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpstr (problem, ==, "i-have-a-problem");
g_object_get (pipe, "problem", &check, NULL);
g_assert_cmpstr (problem, ==, check);
g_object_unref (pipe);
g_free (problem);
g_free (check);
}
static void
test_get_environ (void)
{
const gchar *input[] = { "ENVIRON=Marmalaaade", "ANOTHER=zerog", NULL };
gchar **environ;
g_setenv ("BLAH", "exists", TRUE);
g_setenv ("ANOTHER", "original", TRUE);
environ = cockpit_pipe_get_environ (input, "/directory");
g_assert_cmpstr (g_environ_getenv (environ, "ENVIRON"), ==, "Marmalaaade");
g_assert_cmpstr (g_environ_getenv (environ, "ANOTHER"), ==, "zerog");
g_assert_cmpstr (g_environ_getenv (environ, "BLAH"), ==, "exists");
g_assert_cmpstr (g_environ_getenv (environ, "PWD"), ==, "/directory");
g_strfreev (environ);
}
static void
test_get_environ_with_pwd (void)
{
const gchar *input[] = { "ENVIRON=Marmalaaade", "PWD=/mine", NULL };
gchar **environ;
environ = cockpit_pipe_get_environ (input, "/directory");
g_assert_cmpstr (g_environ_getenv (environ, "ENVIRON"), ==, "Marmalaaade");
g_assert_cmpstr (g_environ_getenv (environ, "PWD"), ==, "/mine");
g_strfreev (environ);
}
static void
test_get_environ_null (void)
{
gchar **environ;
g_setenv ("BLAH", "exists", TRUE);
g_setenv ("ANOTHER", "original", TRUE);
environ = cockpit_pipe_get_environ (NULL, NULL);
g_assert_cmpstr (g_environ_getenv (environ, "BLAH"), ==, "exists");
g_assert_cmpstr (g_environ_getenv (environ, "ANOTHER"), ==, "original");
g_strfreev (environ);
}
int
main (int argc,
char *argv[])
{
cockpit_test_init (&argc, &argv);
g_test_add_func ("/pipe/buffer/consume-entire", test_consume_entire);
g_test_add_func ("/pipe/buffer/consume-partial", test_consume_partial);
g_test_add_func ("/pipe/buffer/consume-skip", test_consume_skip);
g_test_add_func ("/pipe/buffer/skip", test_buffer_skip);
g_test_add_func ("/pipe/properties", test_properties);
/*
* Fixture data is the GType name of the pipe class
* so register these types here.
*/
g_type_class_ref (mock_echo_pipe_get_type ());
g_type_class_ref (cockpit_pipe_get_type ());
g_test_add ("/pipe/echo-message", TestCase, NULL,
setup_simple, test_echo_and_close, teardown);
g_test_add ("/pipe/echo-queue", TestCase, NULL,
setup_simple, test_echo_queue, teardown);
g_test_add ("/pipe/echo-large", TestCase, &fixture_no_timeout,
setup_simple, test_echo_large, teardown);
g_test_add ("/pipe/close-problem", TestCase, NULL,
setup_simple, test_close_problem, teardown);
g_test_add ("/pipe/buffer", TestCase, &fixture_buffer,
setup_simple, test_buffer, teardown);
g_test_add ("/pipe/skip-zero", TestCase, NULL,
setup_simple, test_skip_zero, teardown);
g_test_add ("/pipe/pid", TestCase, &fixture_pid,
setup_simple, test_pid, teardown);
g_test_add ("/pipe/exit-success", TestCase, &fixture_exit_success,
setup_simple, test_exit_success, teardown);
g_test_add ("/pipe/exit-fail", TestCase, &fixture_exit_fail,
setup_simple, test_exit_fail, teardown);
g_test_add ("/pipe/exit-signal", TestCase, &fixture_exit_signal,
setup_simple, test_exit_signal, teardown);
g_test_add_func ("/pipe/read-error", test_read_error);
g_test_add_func ("/pipe/write-error", test_write_error);
g_test_add_func ("/pipe/read-combined", test_read_combined);
g_test_add_func ("/pipe/spawn/and-read", test_spawn_and_read);
g_test_add_func ("/pipe/spawn/and-write", test_spawn_and_write);
g_test_add_func ("/pipe/spawn/and-fail", test_spawn_and_fail);
g_test_add_func ("/pipe/spawn/buffer-stderr", test_spawn_and_buffer_stderr);
g_test_add ("/pipe/spawn/close-clean", TestCase, NULL,
setup_timeout, test_spawn_close_clean, teardown);
g_test_add ("/pipe/spawn/close-terminate", TestCase, NULL,
setup_timeout, test_spawn_close_terminate, teardown);
g_test_add_func ("/pipe/pty/shell", test_pty_shell);
g_test_add ("/pipe/connect/and-read", TestConnect, NULL,
setup_connect, test_connect_and_read, teardown_connect);
g_test_add ("/pipe/connect/and-write", TestConnect, NULL,
setup_connect, test_connect_and_write, teardown_connect);
g_test_add_func ("/pipe/problem-later", test_problem_later);
g_test_add_func ("/pipe/connect/not-found", test_fail_not_found);
g_test_add_func ("/pipe/connect/access-denied", test_fail_access_denied);
g_test_add_func ("/pipe/environ/simple", test_get_environ);
g_test_add_func ("/pipe/environ/pwd", test_get_environ_with_pwd);
g_test_add_func ("/pipe/environ/null", test_get_environ_null);
return g_test_run ();
}