/*
* This file is part of Cockpit.
*
* Copyright (C) 2015 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 "cockpitchannel.h"
#include "cockpitpeer.h"
#include "mock-transport.h"
#include "common/cockpitjson.h"
#include "common/cockpittest.h"
#include
#include
#include
#include
#include
static GType mock_echo_channel_get_type (void) G_GNUC_CONST;
typedef struct {
CockpitChannel parent;
gboolean close_called;
} MockEchoChannel;
typedef CockpitChannelClass MockEchoChannelClass;
G_DEFINE_TYPE (MockEchoChannel, mock_echo_channel, COCKPIT_TYPE_CHANNEL);
static gboolean
on_other_closed (CockpitTransport *transport,
const gchar *problem,
gpointer data)
{
gboolean *flag = data;
g_assert (*flag == FALSE);
*flag = TRUE;
return FALSE;
}
static void
mock_echo_channel_recv (CockpitChannel *channel,
GBytes *message)
{
cockpit_channel_send (channel, message, FALSE);
}
static void
mock_echo_channel_close (CockpitChannel *channel,
const gchar *problem)
{
MockEchoChannel *self = (MockEchoChannel *)channel;
self->close_called = TRUE;
COCKPIT_CHANNEL_CLASS (mock_echo_channel_parent_class)->close (channel, problem);
}
static void
mock_echo_channel_init (MockEchoChannel *self)
{
}
static void
mock_echo_channel_constructed (GObject *obj)
{
G_OBJECT_CLASS (mock_echo_channel_parent_class)->constructed (obj);
cockpit_channel_ready (COCKPIT_CHANNEL (obj), NULL);
}
static void
mock_echo_channel_class_init (MockEchoChannelClass *klass)
{
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = mock_echo_channel_constructed;
channel_class->recv = mock_echo_channel_recv;
channel_class->close = mock_echo_channel_close;
}
static CockpitChannel *
mock_echo_channel_open (CockpitTransport *transport,
const gchar *channel_id)
{
CockpitChannel *channel;
JsonObject *options;
g_assert (channel_id != NULL);
options = json_object_new ();
channel = g_object_new (mock_echo_channel_get_type (),
"transport", transport,
"id", channel_id,
"options", options,
NULL);
json_object_unref (options);
return channel;
}
typedef struct {
MockTransport *transport;
CockpitChannel *channel;
CockpitPeer *peer;
} TestCase;
static gboolean
on_transport_control (CockpitTransport *transport,
const char *command,
const gchar *channel,
JsonObject *options,
GBytes *message,
gpointer user_data)
{
TestCase *tc = user_data;
const gchar *payload;
JsonObject *object;
GBytes *data;
g_assert (tc != NULL);
if (channel && g_str_equal (command, "open"))
{
if (tc->peer && cockpit_peer_handle (tc->peer, channel, options, message))
{
return TRUE;
}
/* Fallback to echo implementation */
else if (!tc->channel && cockpit_json_get_string (options, "payload", NULL, &payload) &&
payload && g_str_equal (payload, "upper"))
{
tc->channel = mock_echo_channel_open (transport, channel);
return TRUE;
}
else
{
object = json_object_new ();
json_object_set_string_member (object, "command", "close");
json_object_set_string_member (object, "channel", channel);
json_object_set_string_member (object, "problem", "not-supported");
data = cockpit_json_write_bytes (object);
cockpit_transport_send (transport, NULL, data);
json_object_unref (object);
g_bytes_unref (data);
return TRUE;
}
}
return FALSE;
}
static void
setup (TestCase *tc,
gconstpointer unused)
{
tc->transport = g_object_new (mock_transport_get_type (), NULL);
while (g_main_context_iteration (NULL, FALSE));
/* Connect to fallback implementation */
g_signal_connect (tc->transport, "control", G_CALLBACK (on_transport_control), tc);
}
static void
teardown (TestCase *tc,
gconstpointer unused)
{
cockpit_assert_expected ();
g_clear_object (&tc->channel);
if (tc->peer)
{
g_object_add_weak_pointer (G_OBJECT (tc->peer), (gpointer *)&tc->peer);
g_object_unref (tc->peer);
g_assert (tc->peer == NULL);
}
g_object_add_weak_pointer (G_OBJECT (tc->transport), (gpointer *)&tc->transport);
g_object_unref (tc->transport);
g_assert (tc->transport == NULL);
}
static CockpitPeer *
peer_new (MockTransport *transport,
const gchar *bridge)
{
CockpitPeer *peer;
JsonObject *object;
GError *error = NULL;
object = cockpit_json_parse_object (bridge, -1, &error);
g_assert_no_error (error);
peer = cockpit_peer_new (COCKPIT_TRANSPORT (transport), object);
json_object_unref (object);
return peer;
}
static CockpitPeer *
mock_peer_simple_new (MockTransport *transport,
const gchar *payload)
{
CockpitPeer *peer;
gchar *bridge;
bridge = g_strdup_printf ("{ \"match\": { \"payload\": \"%s\" }, \"spawn\": [ \"%s\", \"--%s\" ] }",
payload, BUILDDIR "/mock-bridge", payload);
peer = peer_new (transport, bridge);
g_free (bridge);
return peer;
}
static CockpitPeer *
mock_peer_fail_new (MockTransport *transport,
const gchar *payload,
const gchar *problem)
{
CockpitPeer *peer;
gchar *bridge;
if (problem)
{
bridge = g_strdup_printf ("{ \"match\": { \"payload\": \"%s\" }, \"spawn\": [ \"/non-existant\" ], \"problem\": \"%s\" }", payload, problem);
}
else
{
bridge = g_strdup_printf ("{ \"match\": { \"payload\": \"%s\" }, \"spawn\": [ \"/non-existant\" ] }", payload);
}
peer = peer_new (transport, bridge);
g_free (bridge);
return peer;
}
static void
emit_string (TestCase *tc,
const gchar *channel,
const gchar *string)
{
GBytes *bytes = g_bytes_new (string, strlen (string));
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), channel, bytes);
g_bytes_unref (bytes);
}
static void
test_simple (TestCase *tc,
gconstpointer unused)
{
GBytes *sent;
tc->peer = mock_peer_simple_new (tc->transport, "upper");
/* The filter should ignore this */
emit_string (tc, NULL, "{\"command\": \"hello\"}");
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "oh marmalade");
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
}
static void
test_serial (TestCase *tc,
gconstpointer unused)
{
GBytes *sent;
tc->peer = mock_peer_simple_new (tc->transport, "upper");
/* The filter should ignore this */
emit_string (tc, NULL, "{\"command\": \"hello\"}");
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "oh marmalade");
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
/* Open a second channel */
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"upper\"}");
emit_string (tc, "b", "zero g");
while ((sent = mock_transport_pop_channel (tc->transport, "b")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "ZERO G", -1);
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
}
static void
test_parallel (TestCase *tc,
gconstpointer unused)
{
GBytes *sent;
tc->peer = mock_peer_simple_new (tc->transport, "upper");
/* The filter should ignore this */
emit_string (tc, NULL, "{\"command\": \"hello\"}");
/* Open two channels at the same time both bound for the peer */
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"upper\"}");
emit_string (tc, "b", "zero g");
emit_string (tc, "a", "oh marmalade");
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
while ((sent = mock_transport_pop_channel (tc->transport, "b")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "ZERO G", -1);
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
}
static void
test_init_problem (TestCase *tc,
gconstpointer unused)
{
JsonObject *sent;
/* The "lower" channel has no local implementation to fall back to */
tc->peer = mock_peer_simple_new (tc->transport, "problem");
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"problem\"}");
emit_string (tc, "a", "Oh Marmalade");
while ((sent = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (sent, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"canna-do-it\",\"another-field\":\"extra\"}");
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
}
static void
test_not_supported (TestCase *tc,
gconstpointer unused)
{
JsonObject *sent;
/* The "lower" channel has no local implementation to fall back to */
tc->peer = mock_peer_fail_new (tc->transport, "lower", NULL);
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
emit_string (tc, "a", "Oh Marmalade");
while ((sent = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (sent, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"not-supported\"}");
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
}
static void
test_fail_problem (TestCase *tc,
gconstpointer unused)
{
JsonObject *sent;
tc->peer = mock_peer_fail_new (tc->transport, "lower", "access-denied");
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
emit_string (tc, "a", "Oh Marmalade");
while ((sent = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (sent, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"access-denied\"}");
/* The fallback channel was not created */
g_assert (tc->channel == NULL);
}
static void
test_fallback (TestCase *tc,
gconstpointer unused)
{
GBytes *sent;
/* The "upper" channel has a local implementaiton to fallback to */
tc->peer = mock_peer_fail_new (tc->transport, "upper", NULL);
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "Oh MarmaLade");
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
/* The fallback just echos */
cockpit_assert_bytes_eq (sent, "Oh MarmaLade", -1);
/* The fallback channel was created */
g_assert (tc->channel != NULL);
}
static void
test_reopen (TestCase *tc,
gconstpointer unused)
{
const gchar *bridge;
GBytes *sent;
JsonObject *control;
bridge = "{ \"match\": { \"payload\": \"upper\" }, \"spawn\": [ \"/" BUILDDIR "/mock-bridge" "\", \"--upper\", \"--count\" ] }";
tc->peer = peer_new (tc->transport, bridge);
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "Oh MarmaLade");
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
control = NULL;
/* Get a good response */
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
/* Reset the peer. This closes the channel. */
cockpit_peer_reset (tc->peer);
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"terminated\"}");
control = NULL;
/* Sending again reopens with count at zero */
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "Oh MarmaLade");
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
control = NULL;
/* Get a good response */
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
}
static void
test_timeout (TestCase *tc,
gconstpointer unused)
{
const gchar *bridge;
GBytes *sent;
JsonObject *control;
CockpitTransport *other = NULL;
gboolean closed = FALSE;
bridge = "{ \"match\": { \"payload\": \"upper\" }, \"timeout\": 1, \"spawn\": [ \"/" BUILDDIR "/mock-bridge" "\", \"--upper\", \"--count\" ] }";
tc->peer = peer_new (tc->transport, bridge);
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "Oh MarmaLade");
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
control = NULL;
/* Get a good response */
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
/* Close the channel, timeout should close the peer transport */
other = g_object_ref (cockpit_peer_ensure (tc->peer));
g_signal_connect (other, "closed", G_CALLBACK (on_other_closed), &closed);
emit_string (tc, NULL, "{\"command\": \"close\", \"channel\": \"a\"}");
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"a\"}");
control = NULL;
while (!closed)
g_main_context_iteration (NULL, TRUE);
/* Sending again reopens peer with count at 0 */
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
emit_string (tc, "a", "Oh MarmaLade");
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
control = NULL;
/* Get a good response */
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
g_object_unref (other);
}
static void
test_reopen_fail (TestCase *tc,
gconstpointer unused)
{
gchar *link;
gchar *bridge;
GBytes *sent;
JsonObject *control;
link = g_strdup_printf ("%s/mock-bridge", g_get_tmp_dir ());
bridge = g_strdup_printf("{ \"match\": { \"payload\": \"lower\" }, \"spawn\": [ \"%s\", \"--lower\" ], \"problem\": \"custom-problem\" }", link);
tc->peer = peer_new (tc->transport, bridge);
if (g_file_test (link, G_FILE_TEST_EXISTS))
g_assert (g_unlink (link) == 0);
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
emit_string (tc, "a", "Oh Marmalade");
/* Bridge doesn't exist, get problem */
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"close\", \"channel\": \"a\", \"problem\":\"custom-problem\"}");
control = NULL;
cockpit_peer_reset (tc->peer);
g_assert (symlink (BUILDDIR "/mock-bridge", link) == 0);
/* Bridge exists, channel works */
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
emit_string (tc, "a", "Oh MarmaLade");
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\"}");
control = NULL;
/* Get a good response */
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_bytes_eq (sent, "oh marmalade", -1);
g_assert (g_unlink (link) == 0);
g_free (bridge);
g_free (link);
}
int
main (int argc,
char *argv[])
{
cockpit_test_init (&argc, &argv);
g_test_add ("/peer/simple", TestCase, NULL,
setup, test_simple, teardown);
g_test_add ("/peer/serial", TestCase, NULL,
setup, test_serial, teardown);
g_test_add ("/peer/parallel", TestCase, NULL,
setup, test_parallel, teardown);
g_test_add ("/peer/not-supported", TestCase, NULL,
setup, test_not_supported, teardown);
g_test_add ("/peer/fail-problem", TestCase, NULL,
setup, test_fail_problem, teardown);
g_test_add ("/peer/init-problem", TestCase, NULL,
setup, test_init_problem, teardown);
g_test_add ("/peer/fallback", TestCase, NULL,
setup, test_fallback, teardown);
g_test_add ("/peer/reopen", TestCase, NULL,
setup, test_reopen, teardown);
g_test_add ("/peer/reopen-fail", TestCase, NULL,
setup, test_reopen_fail, teardown);
g_test_add ("/peer/timeout", TestCase, NULL,
setup, test_timeout, teardown);
return g_test_run ();
}