/* * 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 "cockpitstream.h" #include "common/cockpitloopback.h" #include "common/cockpittest.h" #include "common/mock-io-stream.h" #include #include #include #include #include #include #include #include /* ---------------------------------------------------------------------------- * Mock */ static GType mock_echo_stream_get_type (void) G_GNUC_CONST; typedef struct { CockpitStream parent; GByteArray *received; gboolean closed; gchar *problem; } MockEchoStream; typedef CockpitStreamClass MockEchoStreamClass; G_DEFINE_TYPE (MockEchoStream, mock_echo_stream, COCKPIT_TYPE_STREAM); static void mock_echo_stream_read (CockpitStream *stream, GByteArray *buffer, gboolean end_of_data) { MockEchoStream *self = (MockEchoStream *)stream; g_byte_array_append (self->received, buffer->data, buffer->len); g_byte_array_set_size (buffer, 0); } static void mock_echo_stream_close (CockpitStream *stream, const gchar *problem) { MockEchoStream *self = (MockEchoStream *)stream; g_assert (!self->closed); self->closed = TRUE; self->problem = g_strdup (problem); } static void mock_echo_stream_init (MockEchoStream *self) { self->received = g_byte_array_new (); } static void mock_echo_stream_finalize (GObject *object) { MockEchoStream *self = (MockEchoStream *)object; g_byte_array_free (self->received, TRUE); g_free (self->problem); G_OBJECT_CLASS (mock_echo_stream_parent_class)->finalize (object); } static void mock_echo_stream_class_init (MockEchoStreamClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); CockpitStreamClass *stream_class = COCKPIT_STREAM_CLASS (klass); object_class->finalize = mock_echo_stream_finalize; stream_class->read = mock_echo_stream_read; stream_class->close = mock_echo_stream_close; } /* ---------------------------------------------------------------------------- * Testing */ typedef struct { CockpitStream *stream; guint timeout; } TestCase; typedef struct { const gchar *stream_type_name; 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 GIOStream * mock_io_stream_for_fds (int in_fd, int out_fd) { GInputStream *is; GOutputStream *os; GIOStream *io; g_assert (g_unix_set_fd_nonblocking (in_fd, TRUE, NULL)); g_assert (g_unix_set_fd_nonblocking (out_fd, TRUE, NULL)); is = g_unix_input_stream_new (in_fd, TRUE); os = g_unix_output_stream_new (out_fd, TRUE); io = mock_io_stream_new (is, os); g_object_unref (is); g_object_unref (os); return io; } static void setup_simple (TestCase *tc, gconstpointer data) { const TestFixture *fixture = data; const gchar *stream_type; GIOStream *io; int fds[2]; setup_timeout (tc, data); stream_type = "MockEchoStream"; if (fixture && fixture->stream_type_name) stream_type = fixture->stream_type_name; if (pipe (fds) < 0) g_assert_not_reached (); io = mock_io_stream_for_fds (fds[0], fds[1]); tc->stream = g_object_new (g_type_from_name (stream_type), "name", "test", "io-stream", io, NULL); g_object_unref (io); } static void teardown (TestCase *tc, gconstpointer data) { if (tc->stream) { g_object_add_weak_pointer (G_OBJECT (tc->stream), (gpointer *)&tc->stream); g_object_unref (tc->stream); /* If this asserts, outstanding references to transport */ g_assert (tc->stream == NULL); } if (tc->timeout) g_source_remove (tc->timeout); } static void test_echo_and_close (TestCase *tc, gconstpointer data) { MockEchoStream *echo_stream = (MockEchoStream *)tc->stream; GBytes *sent, *bytes; sent = g_bytes_new_static ("the message", 11); cockpit_stream_write (tc->stream, sent); while (echo_stream->received->len < 11) g_main_context_iteration (NULL, TRUE); g_byte_array_ref (echo_stream->received); bytes = g_byte_array_free_to_bytes (echo_stream->received); g_assert (g_bytes_equal (bytes, sent)); g_bytes_unref (sent); g_bytes_unref (bytes); cockpit_stream_close (tc->stream, NULL); while (!echo_stream->closed) g_main_context_iteration (NULL, TRUE); } static void test_echo_queue (TestCase *tc, gconstpointer data) { MockEchoStream *echo_stream = (MockEchoStream *)tc->stream; GBytes *sent; sent = g_bytes_new_static ("one", 3); cockpit_stream_write (tc->stream, sent); g_bytes_unref (sent); sent = g_bytes_new_static ("two", 3); cockpit_stream_write (tc->stream, sent); g_bytes_unref (sent); /* Only closes after above are sent */ cockpit_stream_close (tc->stream, NULL); while (!echo_stream->closed) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (echo_stream->received->len, ==, 6); g_assert (memcmp (echo_stream->received->data, "onetwo", 6) == 0); } static const TestFixture fixture_no_timeout = { .no_timeout = TRUE }; static void test_echo_large (TestCase *tc, gconstpointer data) { MockEchoStream *echo_stream = (MockEchoStream *)tc->stream; GBytes *sent; /* Medium length */ sent = g_bytes_new_take (g_strnfill (1020, '!'), 1020); cockpit_stream_write (tc->stream, sent); while (echo_stream->received->len < g_bytes_get_size (sent)) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (echo_stream->received->len, ==, g_bytes_get_size (sent)); g_assert (memcmp (echo_stream->received->data, g_bytes_get_data (sent, NULL), g_bytes_get_size (sent)) == 0); g_bytes_unref (sent); g_byte_array_set_size (echo_stream->received, 0); /* Extra large */ sent = g_bytes_new_take (g_strnfill (10 * 1000 * 1000, '?'), 10 * 1000 * 1000); cockpit_stream_write (tc->stream, sent); while (echo_stream->received->len < g_bytes_get_size (sent)) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (echo_stream->received->len, ==, g_bytes_get_size (sent)); g_assert (memcmp (echo_stream->received->data, g_bytes_get_data (sent, NULL), g_bytes_get_size (sent)) == 0); g_bytes_unref (sent); g_byte_array_set_size (echo_stream->received, 0); /* Double check that didn't csrew things up */ sent = g_bytes_new_static ("yello", 5); cockpit_stream_write (tc->stream, sent); while (echo_stream->received->len < g_bytes_get_size (sent)) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (echo_stream->received->len, ==, g_bytes_get_size (sent)); g_assert (memcmp (echo_stream->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) { MockEchoStream *echo_stream = (MockEchoStream *)tc->stream; cockpit_stream_close (tc->stream, "right now"); while (!echo_stream->closed) g_main_context_iteration (NULL, TRUE); g_assert_cmpstr (echo_stream->problem, ==, "right now"); } static const TestFixture fixture_buffer = { .stream_type_name = "CockpitStream" }; static void test_buffer (TestCase *tc, gconstpointer data) { GByteArray *buffer; GBytes *sent; buffer = cockpit_stream_get_buffer (tc->stream); g_assert (buffer != NULL); g_assert_cmpuint (buffer->len, ==, 0); /* Including null terminator */ sent = g_bytes_new_static ("blahdeedoo", 11); cockpit_stream_write (tc->stream, 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) { MockEchoStream *echo_stream = (MockEchoStream *)tc->stream; GBytes *sent; GBytes *zero; /* Including null terminator */ sent = g_bytes_new_static ("blah", 4); zero = g_bytes_new_static ("", 0); cockpit_stream_write (tc->stream, sent); cockpit_stream_write (tc->stream, zero); cockpit_stream_write (tc->stream, sent); g_bytes_unref (zero); g_bytes_unref (sent); while (echo_stream->received->len < 8) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (echo_stream->received->len, ==, 8); g_byte_array_append (echo_stream->received, (guint8 *)"", 1); g_assert_cmpstr ((gchar *)echo_stream->received->data, ==, "blahblah"); } static void test_read_error (void) { MockEchoStream *echo_stream; GIOStream *io; int fds[2]; int out; /* Just used so we have a valid fd */ if (pipe (fds) < 0) g_assert_not_reached (); out = dup (2); g_assert (out >= 0); cockpit_expect_message ("*Bad file descriptor"); /* Using wrong end of the pipe */ io = mock_io_stream_for_fds (fds[1], out); echo_stream = g_object_new (mock_echo_stream_get_type (), "name", "read-error", "io-stream", io, NULL); /* Close the file descriptor to cause error */ close (fds[1]); g_object_unref (io); while (!echo_stream->closed) g_main_context_iteration (NULL, TRUE); cockpit_assert_expected (); g_assert_cmpstr (echo_stream->problem, ==, "internal-error"); close (fds[0]); g_object_unref (echo_stream); cockpit_assert_expected (); } static void test_write_error (void) { MockEchoStream *echo_stream; GIOStream *io; GBytes *sent; int fds[2]; /* Just used so we have a valid fd */ if (pipe (fds) < 0) g_assert_not_reached (); cockpit_expect_message ("*Bad file descriptor"); io = mock_io_stream_for_fds (fds[0], fds[1]); /* Pass in a bad write descriptor */ echo_stream = g_object_new (mock_echo_stream_get_type (), "name", "write-error", "io-stream", io, NULL); /* Close the file descriptor to cause error */ close (fds[1]); g_object_unref (io); sent = g_bytes_new ("test", 4); cockpit_stream_write (COCKPIT_STREAM (echo_stream), sent); g_bytes_unref (sent); while (!echo_stream->closed) g_main_context_iteration (NULL, TRUE); cockpit_assert_expected (); g_assert_cmpstr (echo_stream->problem, ==, "internal-error"); g_object_unref (echo_stream); cockpit_assert_expected (); } static void test_read_combined (void) { MockEchoStream *echo_stream; struct iovec iov[4]; GIOStream *io; gint fds_a[2]; gint fds_b[2]; gint ret; if (pipe(fds_a) < 0) g_assert_not_reached (); if (pipe(fds_b) < 0) g_assert_not_reached (); io = mock_io_stream_for_fds (fds_a[0], fds_b[1]); /* Pass in a read end of the pipe */ echo_stream = g_object_new (mock_echo_stream_get_type (), "name", "read-combined", "io-stream", io, NULL); g_object_unref (io); /* Write two messages to the stream 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; do { ret = writev (fds_a[1], iov, 4); if (ret < 0 && (errno == EAGAIN || errno == EINTR)) continue; if (ret < 0) g_message ("writev failed with %d: %s", ret, g_strerror (errno)); g_assert_cmpint (ret, ==, 12); break; } while (TRUE); while (echo_stream->received->len < 12) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (echo_stream->received->len, ==, 12); g_assert_cmpstr ((gchar *)echo_stream->received->data, ==, "onetwothree"); g_object_add_weak_pointer (G_OBJECT (echo_stream), (gpointer *)&echo_stream); g_object_unref (echo_stream); g_assert (echo_stream == NULL); close (fds_a[1]); close (fds_b[0]); } static void test_properties (void) { CockpitStream *tstream; GIOStream *io; gchar *name; GIOStream *x; int fds[2]; if (pipe(fds) < 0) g_assert_not_reached (); io = mock_io_stream_for_fds (fds[0], fds[1]); tstream = g_object_new (mock_echo_stream_get_type (), "name", "testo", "io-stream", io, NULL); g_object_get (tstream, "name", &name, "io-stream", &x, NULL); g_assert_cmpstr (name, ==, "testo"); g_free (name); g_assert (io == x); g_object_unref (x); g_object_unref (io); g_object_unref (tstream); } static void on_close_get_problem (CockpitStream *stream, const gchar *problem, gpointer user_data) { gchar **retval = user_data; g_assert (retval != NULL && *retval == NULL); *retval = g_strdup (problem ? problem : ""); } typedef struct { GSocket *listen_sock; GSource *listen_source; GSocket *conn_sock; GSource *conn_source; GSocketAddress *address; gboolean skip_ipv6_loopback; guint16 port; } 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; GSocketFamily family = GPOINTER_TO_INT (data); if (family == G_SOCKET_FAMILY_INVALID) family = G_SOCKET_FAMILY_IPV4; inet = g_inet_address_new_loopback (family); address = g_inet_socket_address_new (inet, 0); g_object_unref (inet); tc->listen_sock = g_socket_new (family, 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); if (error != NULL && family == G_SOCKET_FAMILY_IPV6) { /* Some test runners don't have IPv6 loopback, strangely enough */ g_clear_error (&error); tc->skip_ipv6_loopback = TRUE; return; } g_assert_no_error (error); tc->address = g_socket_get_local_address (tc->listen_sock, &error); g_assert_no_error (error); tc->port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (tc->address)); 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) { if (tc->address) 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) { CockpitConnectable connectable = { .address = G_SOCKET_CONNECTABLE (tc->address) }; CockpitStream *stream; GError *error = NULL; GByteArray *buffer; stream = cockpit_stream_connect ("connect-and-read", &connectable); g_assert (stream != 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_stream_get_buffer (stream); 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 (stream); } static void test_connect_early_close (TestConnect *tc, gconstpointer user_data) { CockpitConnectable connectable = { .address = G_SOCKET_CONNECTABLE (tc->address) }; CockpitStream *stream; stream = cockpit_stream_connect ("connect-early-close", &connectable); g_assert (stream != NULL); cockpit_stream_close (stream, NULL); g_object_unref (stream); while (tc->conn_sock == NULL) g_main_context_iteration (NULL, TRUE); } static void test_connect_loopback (TestConnect *tc, gconstpointer user_data) { CockpitConnectable connectable = { 0 }; CockpitStream *stream; GError *error = NULL; GByteArray *buffer; if (tc->skip_ipv6_loopback) { cockpit_test_skip ("no loopback for ipv6 found"); return; } connectable.address = cockpit_loopback_new (tc->port); stream = cockpit_stream_connect ("loopback", &connectable); g_object_unref (connectable.address); g_assert (stream != 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_stream_get_buffer (stream); 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 (stream); } static void test_connect_and_write (TestConnect *tc, gconstpointer user_data) { CockpitConnectable connectable = { .address = G_SOCKET_CONNECTABLE (tc->address) }; gchar buffer[8]; CockpitStream *stream; GError *error = NULL; GBytes *sent; gssize ret; stream = cockpit_stream_connect ("connect-and-write", &connectable); g_assert (stream != NULL); /* Sending on the stream before actually connected */ sent = g_bytes_new_static ("J", 1); cockpit_stream_write (stream, 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 */ for (;;) { ret = g_socket_receive_with_blocking (tc->conn_sock, buffer, sizeof (buffer), FALSE, NULL, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_assert_cmpint (ret, ==, -1); g_main_context_iteration (NULL, TRUE); g_clear_error (&error); continue; } g_assert_no_error (error); g_assert_cmpint (ret, ==, 1); break; } g_assert_cmpint (buffer[0], ==, 'J'); g_object_unref (stream); } static void test_fail_not_found (void) { CockpitConnectable connectable = { 0 }; CockpitStream *stream; GSocketAddress *address; gchar *problem = NULL; cockpit_expect_message ("*No such file or directory"); address = g_unix_socket_address_new ("/non-existent"); connectable.address = G_SOCKET_CONNECTABLE (address); stream = cockpit_stream_connect ("bad", &connectable); g_object_unref (connectable.address); /* Should not have closed at this point */ g_assert (stream != NULL); g_signal_connect (stream, "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 (stream); } static void test_fail_access_denied (void) { CockpitConnectable connectable = { 0 }; CockpitStream *stream; 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); connectable.address = G_SOCKET_CONNECTABLE (address); stream = cockpit_stream_connect ("bad", &connectable); g_object_unref (address); /* Should not have closed at this point */ g_assert (stream != NULL); g_signal_connect (stream, "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 (stream); } static void test_problem_later (void) { gchar *problem = NULL; gchar *check; CockpitStream *stream; stream = g_object_new (COCKPIT_TYPE_STREAM, "problem", "i-have-a-problem", NULL); g_signal_connect (stream, "close", G_CALLBACK (on_close_get_problem), &problem); g_object_get (stream, "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 (stream, "problem", &check, NULL); g_assert_cmpstr (problem, ==, check); g_object_unref (stream); g_free (problem); g_free (check); } int main (int argc, char *argv[]) { cockpit_test_init (&argc, &argv); g_test_add_func ("/stream/properties", test_properties); /* * Fixture data is the GType name of the stream class * so register these types here. */ g_type_class_ref (mock_echo_stream_get_type ()); g_type_class_ref (cockpit_stream_get_type ()); g_test_add ("/stream/echo-message", TestCase, NULL, setup_simple, test_echo_and_close, teardown); g_test_add ("/stream/echo-queue", TestCase, NULL, setup_simple, test_echo_queue, teardown); g_test_add ("/stream/echo-large", TestCase, &fixture_no_timeout, setup_simple, test_echo_large, teardown); g_test_add ("/stream/close-problem", TestCase, NULL, setup_simple, test_close_problem, teardown); g_test_add ("/stream/buffer", TestCase, &fixture_buffer, setup_simple, test_buffer, teardown); g_test_add ("/stream/skip-zero", TestCase, NULL, setup_simple, test_skip_zero, teardown); g_test_add_func ("/stream/read-error", test_read_error); g_test_add_func ("/stream/write-error", test_write_error); g_test_add_func ("/stream/read-combined", test_read_combined); g_test_add ("/stream/connect/and-read", TestConnect, NULL, setup_connect, test_connect_and_read, teardown_connect); g_test_add ("/stream/connect/early-close", TestConnect, NULL, setup_connect, test_connect_early_close, teardown_connect); g_test_add ("/stream/connect/and-write", TestConnect, NULL, setup_connect, test_connect_and_write, teardown_connect); g_test_add ("/stream/connect/loopback-ipv4", TestConnect, GINT_TO_POINTER (G_SOCKET_FAMILY_IPV4), setup_connect, test_connect_loopback, teardown_connect); g_test_add ("/stream/connect/loopback-ipv6", TestConnect, GINT_TO_POINTER (G_SOCKET_FAMILY_IPV6), setup_connect, test_connect_loopback, teardown_connect); g_test_add_func ("/stream/problem-later", test_problem_later); g_test_add_func ("/stream/connect/not-found", test_fail_not_found); g_test_add_func ("/stream/connect/access-denied", test_fail_access_denied); return g_test_run (); }