glib/gio/tests/tls-interaction.c
Dan Winship 15f1ba4721 gio/tests/tls-interaction: fix two sporadic errors
The threaded tests are using the default main context in the worker
thread, but were not g_main_context_acquire()ing it first, which meant
that g_tls_interaction_invoke_ask_password() in the main thread would
sometimes succeed in acquiring it itself and thus performing the
operation in the wrong thread. Fix that.

Also, we can't unref the loop from the worker thread, because the main
thread isn't holding a reference on it, and so it might end up being
destroyed while that thread is still inside g_main_loop_quit().
2011-09-04 10:33:37 -04:00

662 lines
23 KiB
C

/* GLib testing framework examples and tests
*
* Copyright (C) 2011 Collabora Ltd.
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Stef Walter <stefw@collobora.co.uk>
*/
#include "config.h"
#include <gio/gio.h>
typedef struct {
/* Class virtual interaction methods */
gpointer ask_password_func;
gpointer ask_password_async_func;
gpointer ask_password_finish_func;
/* Expected results */
GTlsInteractionResult result;
GQuark error_domain;
gint error_code;
const gchar *error_message;
} Fixture;
typedef struct {
GTlsInteraction *interaction;
GTlsPassword *password;
GMainLoop *loop;
GThread *interaction_thread;
GThread *test_thread;
GThread *loop_thread;
const Fixture *fixture;
} Test;
typedef struct {
GTlsInteraction parent;
Test *test;
} TestInteraction;
typedef struct {
GTlsInteractionClass parent;
} TestInteractionClass;
G_DEFINE_TYPE (TestInteraction, test_interaction, G_TYPE_TLS_INTERACTION);
#define TEST_TYPE_INTERACTION (test_interaction_get_type ())
#define TEST_INTERACTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TEST_TYPE_INTERACTION, TestInteraction))
#define TEST_IS_INTERACTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TEST_TYPE_INTERACTION))
static void
test_interaction_init (TestInteraction *self)
{
}
static void
test_interaction_class_init (TestInteractionClass *klass)
{
/* By default no virtual methods */
}
static void
test_interaction_ask_password_async_success (GTlsInteraction *interaction,
GTlsPassword *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
TestInteraction *self;
g_assert (TEST_IS_INTERACTION (interaction));
self = TEST_INTERACTION (interaction);
g_assert (g_thread_self () == self->test->interaction_thread);
g_assert (G_IS_TLS_PASSWORD (password));
g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
test_interaction_ask_password_async_success);
/* Don't do this in real life. Include a null terminator for testing */
g_tls_password_set_value (password, (const guchar *)"the password", 13);
g_simple_async_result_complete_in_idle (res);
g_object_unref (res);
}
static GTlsInteractionResult
test_interaction_ask_password_finish_success (GTlsInteraction *interaction,
GAsyncResult *result,
GError **error)
{
TestInteraction *self;
g_assert (TEST_IS_INTERACTION (interaction));
self = TEST_INTERACTION (interaction);
g_assert (g_thread_self () == self->test->interaction_thread);
g_assert (g_simple_async_result_is_valid (result, G_OBJECT (interaction),
test_interaction_ask_password_async_success));
g_assert (error != NULL);
g_assert (*error == NULL);
return G_TLS_INTERACTION_HANDLED;
}
static void
test_interaction_ask_password_async_failure (GTlsInteraction *interaction,
GTlsPassword *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
TestInteraction *self;
g_assert (TEST_IS_INTERACTION (interaction));
self = TEST_INTERACTION (interaction);
g_assert (g_thread_self () == self->test->interaction_thread);
g_assert (G_IS_TLS_PASSWORD (password));
g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
test_interaction_ask_password_async_failure);
g_simple_async_result_set_error (res, G_FILE_ERROR, G_FILE_ERROR_ACCES, "The message");
g_simple_async_result_complete_in_idle (res);
g_object_unref (res);
}
static GTlsInteractionResult
test_interaction_ask_password_finish_failure (GTlsInteraction *interaction,
GAsyncResult *result,
GError **error)
{
TestInteraction *self;
g_assert (TEST_IS_INTERACTION (interaction));
self = TEST_INTERACTION (interaction);
g_assert (g_thread_self () == self->test->interaction_thread);
g_assert (g_simple_async_result_is_valid (result, G_OBJECT (interaction),
test_interaction_ask_password_async_failure));
g_assert (error != NULL);
g_assert (*error == NULL);
if (!g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
g_assert_not_reached ();
return G_TLS_INTERACTION_FAILED;
}
static GTlsInteractionResult
test_interaction_ask_password_sync_success (GTlsInteraction *interaction,
GTlsPassword *password,
GCancellable *cancellable,
GError **error)
{
TestInteraction *self;
g_assert (TEST_IS_INTERACTION (interaction));
self = TEST_INTERACTION (interaction);
g_assert (g_thread_self () == self->test->interaction_thread);
g_assert (G_IS_TLS_PASSWORD (password));
g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_assert (error != NULL);
g_assert (*error == NULL);
/* Don't do this in real life. Include a null terminator for testing */
g_tls_password_set_value (password, (const guchar *)"the password", 13);
return G_TLS_INTERACTION_HANDLED;
}
static GTlsInteractionResult
test_interaction_ask_password_sync_failure (GTlsInteraction *interaction,
GTlsPassword *password,
GCancellable *cancellable,
GError **error)
{
TestInteraction *self;
g_assert (TEST_IS_INTERACTION (interaction));
self = TEST_INTERACTION (interaction);
g_assert (g_thread_self () == self->test->interaction_thread);
g_assert (G_IS_TLS_PASSWORD (password));
g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_assert (error != NULL);
g_assert (*error == NULL);
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES, "The message");
return G_TLS_INTERACTION_FAILED;
}
/* ----------------------------------------------------------------------------
* ACTUAL TESTS
*/
static void
on_ask_password_async_call (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
Test *test = user_data;
GTlsInteractionResult res;
GError *error = NULL;
g_assert (G_IS_TLS_INTERACTION (source));
g_assert (G_TLS_INTERACTION (source) == test->interaction);
/* Check that this callback is being run in the right place */
g_assert (g_thread_self () == test->interaction_thread);
res = g_tls_interaction_ask_password_finish (test->interaction, result,
&error);
/* Check that the results match the fixture */
g_assert_cmpuint (test->fixture->result, ==, res);
switch (test->fixture->result)
{
case G_TLS_INTERACTION_HANDLED:
g_assert_no_error (error);
g_assert_cmpstr ((const gchar *)g_tls_password_get_value (test->password, NULL), ==, "the password");
break;
case G_TLS_INTERACTION_FAILED:
g_assert_error (error, test->fixture->error_domain, test->fixture->error_code);
g_assert_cmpstr (error->message, ==, test->fixture->error_message);
g_clear_error (&error);
break;
case G_TLS_INTERACTION_UNHANDLED:
g_assert_no_error (error);
break;
default:
g_assert_not_reached ();
}
/* Signal the end of the test */
g_main_loop_quit (test->loop);
}
static void
test_ask_password_async (Test *test,
gconstpointer unused)
{
/* This test only works with a main loop */
g_assert (test->loop);
g_tls_interaction_ask_password_async (test->interaction,
test->password, NULL,
on_ask_password_async_call,
test);
/* teardown waits until g_main_loop_quit(). called from callback */
}
static void
test_invoke_ask_password (Test *test,
gconstpointer unused)
{
GTlsInteractionResult res;
GError *error = NULL;
res = g_tls_interaction_invoke_ask_password (test->interaction, test->password,
NULL, &error);
/* Check that the results match the fixture */
g_assert_cmpuint (test->fixture->result, ==, res);
switch (test->fixture->result)
{
case G_TLS_INTERACTION_HANDLED:
g_assert_no_error (error);
g_assert_cmpstr ((const gchar *)g_tls_password_get_value (test->password, NULL), ==, "the password");
break;
case G_TLS_INTERACTION_FAILED:
g_assert_error (error, test->fixture->error_domain, test->fixture->error_code);
g_assert_cmpstr (error->message, ==, test->fixture->error_message);
g_clear_error (&error);
break;
case G_TLS_INTERACTION_UNHANDLED:
g_assert_no_error (error);
break;
default:
g_assert_not_reached ();
}
/* This allows teardown to stop if running with loop */
if (test->loop)
g_main_loop_quit (test->loop);
}
static void
test_ask_password (Test *test,
gconstpointer unused)
{
GTlsInteractionResult res;
GError *error = NULL;
res = g_tls_interaction_ask_password (test->interaction, test->password,
NULL, &error);
/* Check that the results match the fixture */
g_assert_cmpuint (test->fixture->result, ==, res);
switch (test->fixture->result)
{
case G_TLS_INTERACTION_HANDLED:
g_assert_no_error (error);
g_assert_cmpstr ((const gchar *)g_tls_password_get_value (test->password, NULL), ==, "the password");
break;
case G_TLS_INTERACTION_FAILED:
g_assert_error (error, test->fixture->error_domain, test->fixture->error_code);
g_assert_cmpstr (error->message, ==, test->fixture->error_message);
g_clear_error (&error);
break;
case G_TLS_INTERACTION_UNHANDLED:
g_assert_no_error (error);
break;
default:
g_assert_not_reached ();
}
/* This allows teardown to stop if running with loop */
if (test->loop)
g_main_loop_quit (test->loop);
}
/* ----------------------------------------------------------------------------
* TEST SETUP
*/
static void
setup_without_loop (Test *test,
gconstpointer user_data)
{
const Fixture *fixture = user_data;
GTlsInteractionClass *klass;
test->fixture = fixture;
test->interaction = g_object_new (TEST_TYPE_INTERACTION, NULL);
g_assert (TEST_IS_INTERACTION (test->interaction));
TEST_INTERACTION (test->interaction)->test = test;
klass = G_TLS_INTERACTION_GET_CLASS (test->interaction);
klass->ask_password = fixture->ask_password_func;
klass->ask_password_async = fixture->ask_password_async_func;
klass->ask_password_finish = fixture->ask_password_finish_func;
test->password = g_tls_password_new (0, "Description");
test->test_thread = g_thread_self ();
/*
* If no loop is running then interaction should happen in the same
* thread that the tests are running in.
*/
test->interaction_thread = test->test_thread;
}
static void
teardown_without_loop (Test *test,
gconstpointer unused)
{
g_object_unref (test->password);
g_object_unref (test->interaction);
g_assert (!G_IS_OBJECT (test->interaction));
}
typedef struct {
GMutex *loop_mutex;
GCond *loop_started;
Test *test;
} ThreadLoop;
static gpointer
thread_loop (gpointer user_data)
{
GMainContext *context = g_main_context_default ();
ThreadLoop *closure = user_data;
Test *test = closure->test;
g_mutex_lock (closure->loop_mutex);
g_assert (test->loop_thread == g_thread_self ());
g_assert (test->loop == NULL);
test->loop = g_main_loop_new (context, TRUE);
g_main_context_acquire (context);
g_cond_signal (closure->loop_started);
g_mutex_unlock (closure->loop_mutex);
while (g_main_loop_is_running (test->loop))
g_main_context_iteration (context, TRUE);
g_main_context_release (context);
return test;
}
static void
setup_with_thread_loop (Test *test,
gconstpointer user_data)
{
GError *error = NULL;
ThreadLoop closure;
setup_without_loop (test, user_data);
closure.loop_mutex = g_mutex_new ();
closure.loop_started = g_cond_new ();
closure.test = test;
g_mutex_lock (closure.loop_mutex);
test->loop_thread = g_thread_create (thread_loop, &closure, TRUE, &error);
g_cond_wait (closure.loop_started, closure.loop_mutex);
g_mutex_unlock (closure.loop_mutex);
/*
* When a loop is running then interaction should always occur in the main
* context of that loop.
*/
test->interaction_thread = test->loop_thread;
g_mutex_free (closure.loop_mutex);
g_cond_free (closure.loop_started);
}
static void
teardown_with_thread_loop (Test *test,
gconstpointer unused)
{
gpointer check;
g_assert (test->loop_thread);
check = g_thread_join (test->loop_thread);
g_assert (check == test);
test->loop_thread = NULL;
g_main_loop_unref (test->loop);
teardown_without_loop (test, unused);
}
static void
setup_with_normal_loop (Test *test,
gconstpointer user_data)
{
GMainContext *context;
setup_without_loop (test, user_data);
context = g_main_context_default ();
if (!g_main_context_acquire (context))
g_assert_not_reached ();
test->loop = g_main_loop_new (context, TRUE);
g_assert (g_main_loop_is_running (test->loop));
}
static void
teardown_with_normal_loop (Test *test,
gconstpointer unused)
{
GMainContext *context;
context = g_main_context_default ();
while (g_main_loop_is_running (test->loop))
g_main_context_iteration (context, TRUE);
g_main_context_release (context);
/* Run test until complete */
g_main_loop_unref (test->loop);
test->loop = NULL;
teardown_without_loop (test, unused);
}
typedef void (*TestFunc) (Test *test, gconstpointer data);
static void
test_with_async_ask_password_implementations (const gchar *name,
TestFunc setup,
TestFunc func,
TestFunc teardown,
GPtrArray *fixtures)
{
gchar *test_name;
Fixture *fixture;
/* Async implementation that succeeds */
fixture = g_new0 (Fixture, 1);
fixture->ask_password_async_func = test_interaction_ask_password_async_success;
fixture->ask_password_finish_func = test_interaction_ask_password_finish_success;
fixture->ask_password_func = NULL;
fixture->result = G_TLS_INTERACTION_HANDLED;
test_name = g_strdup_printf ("%s/async-implementation-success", name);
g_test_add (test_name, Test, fixture, setup, func, teardown);
g_free (test_name);
g_ptr_array_add (fixtures, fixture);
/* Async implementation that fails */
fixture = g_new0 (Fixture, 1);
fixture->ask_password_async_func = test_interaction_ask_password_async_failure;
fixture->ask_password_finish_func = test_interaction_ask_password_finish_failure;
fixture->ask_password_func = NULL;
fixture->result = G_TLS_INTERACTION_FAILED;
fixture->error_domain = G_FILE_ERROR;
fixture->error_code = G_FILE_ERROR_ACCES;
fixture->error_message = "The message";
test_name = g_strdup_printf ("%s/async-implementation-failure", name);
g_test_add (test_name, Test, fixture, setup, func, teardown);
g_free (test_name);
g_ptr_array_add (fixtures, fixture);
}
static void
test_with_unhandled_ask_password_implementations (const gchar *name,
TestFunc setup,
TestFunc func,
TestFunc teardown,
GPtrArray *fixtures)
{
gchar *test_name;
Fixture *fixture;
/* Unhandled implementation */
fixture = g_new0 (Fixture, 1);
fixture->ask_password_async_func = NULL;
fixture->ask_password_finish_func = NULL;
fixture->ask_password_func = NULL;
fixture->result = G_TLS_INTERACTION_UNHANDLED;
test_name = g_strdup_printf ("%s/unhandled-implementation", name);
g_test_add (test_name, Test, fixture, setup, func, teardown);
g_free (test_name);
g_ptr_array_add (fixtures, fixture);
}
static void
test_with_sync_ask_password_implementations (const gchar *name,
TestFunc setup,
TestFunc func,
TestFunc teardown,
GPtrArray *fixtures)
{
gchar *test_name;
Fixture *fixture;
/* Sync implementation that succeeds */
fixture = g_new0 (Fixture, 1);
fixture->ask_password_async_func = NULL;
fixture->ask_password_finish_func = NULL;
fixture->ask_password_func = test_interaction_ask_password_sync_success;
fixture->result = G_TLS_INTERACTION_HANDLED;
test_name = g_strdup_printf ("%s/sync-implementation-success", name);
g_test_add (test_name, Test, fixture, setup, func, teardown);
g_free (test_name);
g_ptr_array_add (fixtures, fixture);
/* Async implementation that fails */
fixture = g_new0 (Fixture, 1);
fixture->ask_password_async_func = NULL;
fixture->ask_password_finish_func = NULL;
fixture->ask_password_func = test_interaction_ask_password_sync_failure;
fixture->result = G_TLS_INTERACTION_FAILED;
fixture->error_domain = G_FILE_ERROR;
fixture->error_code = G_FILE_ERROR_ACCES;
fixture->error_message = "The message";
test_name = g_strdup_printf ("%s/sync-implementation-failure", name);
g_test_add (test_name, Test, fixture, setup, func, teardown);
g_free (test_name);
g_ptr_array_add (fixtures, fixture);
}
int
main (int argc,
char *argv[])
{
GPtrArray *fixtures;
gint ret;
g_type_init ();
g_thread_init (NULL);
g_test_init (&argc, &argv, NULL);
fixtures = g_ptr_array_new_with_free_func (g_free);
/* Tests for g_tls_interaction_invoke_ask_password */
test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop",
setup_with_thread_loop, test_invoke_ask_password,
teardown_with_thread_loop, fixtures);
test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop",
setup_with_thread_loop, test_invoke_ask_password,
teardown_with_thread_loop, fixtures);
test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop",
setup_with_thread_loop, test_invoke_ask_password,
teardown_with_thread_loop, fixtures);
test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop",
setup_without_loop, test_invoke_ask_password,
teardown_without_loop, fixtures);
test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop",
setup_without_loop, test_invoke_ask_password,
teardown_without_loop, fixtures);
test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop",
setup_without_loop, test_invoke_ask_password,
teardown_without_loop, fixtures);
test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop",
setup_with_normal_loop, test_invoke_ask_password,
teardown_with_normal_loop, fixtures);
test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop",
setup_with_normal_loop, test_invoke_ask_password,
teardown_with_normal_loop, fixtures);
test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop",
setup_with_normal_loop, test_invoke_ask_password,
teardown_with_normal_loop, fixtures);
/* Tests for g_tls_interaction_ask_password */
test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/sync",
setup_without_loop, test_ask_password,
teardown_without_loop, fixtures);
test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/sync",
setup_without_loop, test_ask_password,
teardown_without_loop, fixtures);
/* Tests for g_tls_interaction_ask_password_async */
test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/async",
setup_with_normal_loop, test_ask_password_async,
teardown_with_normal_loop, fixtures);
test_with_async_ask_password_implementations ("/tls-interaction/ask-password/async",
setup_with_normal_loop, test_ask_password_async,
teardown_with_normal_loop, fixtures);
ret = g_test_run();
g_ptr_array_free (fixtures, TRUE);
return ret;
}