mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice-gtk
synced 2025-12-26 05:26:12 +00:00
Signed-off-by: Frediano Ziglio <fziglio@redhat.com> Acked-by: Christophe Fergeau <cfergeau@redhat.com>
432 lines
16 KiB
C
432 lines
16 KiB
C
#include <gio/gio.h>
|
|
|
|
#include "spice-file-transfer-task-priv.h"
|
|
|
|
typedef struct _Fixture {
|
|
GFile **files;
|
|
guint num_files;
|
|
guint num_files_done;
|
|
GCancellable *cancellable;
|
|
GMainLoop *loop;
|
|
GHashTable *xfer_tasks;
|
|
} Fixture;
|
|
|
|
#define SINGLE_FILE 1
|
|
#define MULTIPLE_FILES 10
|
|
|
|
#define T10ns (G_TIME_SPAN_MILLISECOND / 100)
|
|
|
|
const gchar content[] = "0123456789_spice-file-transfer-task";
|
|
|
|
static void
|
|
f_setup(Fixture *f, gconstpointer user_data)
|
|
{
|
|
guint i;
|
|
GError *err = NULL;
|
|
|
|
f->loop = g_main_loop_new(NULL, FALSE);
|
|
f->num_files = GPOINTER_TO_UINT(user_data);
|
|
f->num_files_done = 0;
|
|
f->files = g_new0(GFile *, f->num_files + 1);
|
|
f->cancellable = g_cancellable_new();
|
|
for (i = 0; i < f->num_files; i++) {
|
|
gboolean success;
|
|
GFileIOStream *iostream;
|
|
|
|
f->files[i] = g_file_new_tmp("spice-file-transfer-XXXXXX", &iostream, &err);
|
|
g_assert_no_error(err);
|
|
g_assert_nonnull(iostream);
|
|
g_clear_object(&iostream);
|
|
|
|
success = g_file_replace_contents (f->files[i], content, strlen(content), NULL, FALSE,
|
|
G_FILE_CREATE_NONE, NULL, f->cancellable, &err);
|
|
g_assert_no_error(err);
|
|
g_assert_true(success);
|
|
}
|
|
}
|
|
|
|
static void
|
|
f_teardown(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
guint i;
|
|
GError *err = NULL;
|
|
|
|
g_main_loop_unref(f->loop);
|
|
g_clear_object(&f->cancellable);
|
|
g_clear_pointer(&f->xfer_tasks, g_hash_table_unref);
|
|
|
|
for (i = 0; i < f->num_files; i++) {
|
|
g_file_delete(f->files[i], NULL, &err);
|
|
g_assert_no_error(err);
|
|
g_object_unref(f->files[i]);
|
|
}
|
|
g_clear_pointer(&f->files, g_free);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* TEST SIMPLE TRANSFER
|
|
******************************************************************************/
|
|
static void
|
|
transfer_xfer_task_on_finished(SpiceFileTransferTask *xfer_task G_GNUC_UNUSED,
|
|
GError *error G_GNUC_UNUSED,
|
|
gpointer user_data)
|
|
{
|
|
Fixture *f = user_data;
|
|
|
|
f->num_files_done++;
|
|
if (f->num_files == f->num_files_done)
|
|
g_main_loop_quit(f->loop);
|
|
}
|
|
|
|
static void
|
|
transfer_read_async_cb(GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
SpiceFileTransferTask *xfer_task;
|
|
gssize count;
|
|
char *buffer;
|
|
GError *error = NULL;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(source_object);
|
|
count = spice_file_transfer_task_read_finish(xfer_task, res, &buffer, &error);
|
|
g_assert_no_error(error);
|
|
|
|
if (count == 0) {
|
|
spice_file_transfer_task_completed(xfer_task, NULL);
|
|
return;
|
|
}
|
|
|
|
spice_file_transfer_task_read_async(xfer_task, transfer_read_async_cb, NULL);
|
|
}
|
|
|
|
static void
|
|
transfer_init_async_cb(GObject *obj, GAsyncResult *res, gpointer data G_GNUC_UNUSED)
|
|
{
|
|
GFileInfo *info;
|
|
SpiceFileTransferTask *xfer_task;
|
|
GError *error = NULL;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(obj);
|
|
info = spice_file_transfer_task_init_task_finish(xfer_task, res, &error);
|
|
g_assert_no_error(error);
|
|
g_assert_nonnull(info);
|
|
g_object_unref(info);
|
|
|
|
/* read file loop */
|
|
spice_file_transfer_task_read_async(xfer_task, transfer_read_async_cb, NULL);
|
|
}
|
|
|
|
static void
|
|
test_simple_transfer(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
f->xfer_tasks = spice_file_transfer_task_create_tasks(f->files, NULL, G_FILE_COPY_NONE, f->cancellable);
|
|
g_hash_table_iter_init(&iter, f->xfer_tasks);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(value);
|
|
g_signal_connect(xfer_task, "finished", G_CALLBACK(transfer_xfer_task_on_finished), f);
|
|
spice_file_transfer_task_init_task_async(xfer_task, transfer_init_async_cb, NULL);
|
|
}
|
|
g_main_loop_run (f->loop);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* TEST CANCEL ON INIT TASK
|
|
******************************************************************************/
|
|
static void
|
|
transfer_cancelled_on_init_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
|
|
{
|
|
GFileInfo *info;
|
|
SpiceFileTransferTask *xfer_task;
|
|
GError *error = NULL;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(obj);
|
|
info = spice_file_transfer_task_init_task_finish(xfer_task, res, &error);
|
|
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_assert_null(info);
|
|
g_clear_error(&error);
|
|
|
|
transfer_xfer_task_on_finished(NULL, NULL, data);
|
|
}
|
|
|
|
static void
|
|
test_cancel_before_task_init(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
f->xfer_tasks = spice_file_transfer_task_create_tasks(f->files, NULL, G_FILE_COPY_NONE, f->cancellable);
|
|
g_hash_table_iter_init(&iter, f->xfer_tasks);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(value);
|
|
g_signal_connect(xfer_task, "finished", G_CALLBACK(transfer_xfer_task_on_finished), f);
|
|
g_cancellable_cancel(f->cancellable);
|
|
spice_file_transfer_task_init_task_async(xfer_task, transfer_cancelled_on_init_async_cb, f);
|
|
}
|
|
g_main_loop_run (f->loop);
|
|
}
|
|
|
|
static void
|
|
test_cancel_after_task_init(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
f->xfer_tasks = spice_file_transfer_task_create_tasks(f->files, NULL, G_FILE_COPY_NONE, f->cancellable);
|
|
g_hash_table_iter_init(&iter, f->xfer_tasks);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(value);
|
|
g_signal_connect(xfer_task, "finished", G_CALLBACK(transfer_xfer_task_on_finished), f);
|
|
spice_file_transfer_task_init_task_async(xfer_task, transfer_cancelled_on_init_async_cb, f);
|
|
g_cancellable_cancel(f->cancellable);
|
|
}
|
|
g_main_loop_run (f->loop);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* TEST CANCEL ON READ ASYNC
|
|
******************************************************************************/
|
|
|
|
static void
|
|
transfer_cancelled_read_async_cb(GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
SpiceFileTransferTask *xfer_task;
|
|
gssize count;
|
|
char *buffer;
|
|
GError *error = NULL;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(source_object);
|
|
count = spice_file_transfer_task_read_finish(xfer_task, res, &buffer, &error);
|
|
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
|
|
g_assert_cmpint(count, ==, -1);
|
|
g_clear_error(&error);
|
|
|
|
transfer_xfer_task_on_finished(NULL, NULL, user_data);
|
|
}
|
|
|
|
static void
|
|
transfer_on_init_async_cb_before_read_cancel(GObject *obj, GAsyncResult *res, gpointer data)
|
|
{
|
|
GFileInfo *info;
|
|
SpiceFileTransferTask *xfer_task;
|
|
GError *error = NULL;
|
|
GCancellable *cancellable;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(obj);
|
|
info = spice_file_transfer_task_init_task_finish(xfer_task, res, &error);
|
|
g_assert_no_error(error);
|
|
g_assert_nonnull(info);
|
|
g_object_unref(info);
|
|
|
|
cancellable = spice_file_transfer_task_get_cancellable(xfer_task);
|
|
g_cancellable_cancel(cancellable);
|
|
spice_file_transfer_task_read_async(xfer_task, transfer_cancelled_read_async_cb, data);
|
|
}
|
|
|
|
static void
|
|
transfer_on_init_async_cb_after_read_cancel(GObject *obj, GAsyncResult *res, gpointer data)
|
|
{
|
|
GFileInfo *info;
|
|
SpiceFileTransferTask *xfer_task;
|
|
GError *error = NULL;
|
|
GCancellable *cancellable;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(obj);
|
|
info = spice_file_transfer_task_init_task_finish(xfer_task, res, &error);
|
|
g_assert_no_error(error);
|
|
g_assert_nonnull(info);
|
|
g_object_unref(info);
|
|
|
|
cancellable = spice_file_transfer_task_get_cancellable(xfer_task);
|
|
spice_file_transfer_task_read_async(xfer_task, transfer_cancelled_read_async_cb, data);
|
|
g_cancellable_cancel(cancellable);
|
|
}
|
|
|
|
static void
|
|
test_cancel_before_read_async(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
f->xfer_tasks = spice_file_transfer_task_create_tasks(f->files, NULL, G_FILE_COPY_NONE, NULL);
|
|
g_hash_table_iter_init(&iter, f->xfer_tasks);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(value);
|
|
g_signal_connect(xfer_task, "finished", G_CALLBACK(transfer_xfer_task_on_finished), f);
|
|
spice_file_transfer_task_init_task_async(xfer_task, transfer_on_init_async_cb_before_read_cancel, f);
|
|
}
|
|
g_main_loop_run (f->loop);
|
|
}
|
|
|
|
static void
|
|
test_cancel_after_read_async(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
f->xfer_tasks = spice_file_transfer_task_create_tasks(f->files, NULL, G_FILE_COPY_NONE, NULL);
|
|
g_hash_table_iter_init(&iter, f->xfer_tasks);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(value);
|
|
g_signal_connect(xfer_task, "finished", G_CALLBACK(transfer_xfer_task_on_finished), f);
|
|
spice_file_transfer_task_init_task_async(xfer_task, transfer_on_init_async_cb_after_read_cancel, f);
|
|
}
|
|
g_main_loop_run (f->loop);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* TEST AGENT CANCEL ON READ
|
|
******************************************************************************/
|
|
|
|
static void
|
|
transfer_agent_cancelled_read_async_cb(GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
SpiceFileTransferTask *xfer_task;
|
|
gssize count;
|
|
char *buffer;
|
|
GError *error = NULL;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(source_object);
|
|
count = spice_file_transfer_task_read_finish(xfer_task, res, &buffer, &error);
|
|
g_assert_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED);
|
|
g_assert_cmpint(count, ==, -1);
|
|
g_clear_error(&error);
|
|
|
|
transfer_xfer_task_on_finished(NULL, NULL, user_data);
|
|
}
|
|
|
|
static void
|
|
transfer_on_init_async_cb_agent_cancel(GObject *obj, GAsyncResult *res, gpointer data)
|
|
{
|
|
GFileInfo *info;
|
|
SpiceFileTransferTask *xfer_task;
|
|
GError *error = NULL;
|
|
|
|
xfer_task = SPICE_FILE_TRANSFER_TASK(obj);
|
|
info = spice_file_transfer_task_init_task_finish(xfer_task, res, &error);
|
|
g_assert_no_error(error);
|
|
g_assert_nonnull(info);
|
|
g_object_unref(info);
|
|
|
|
spice_file_transfer_task_read_async(xfer_task, transfer_agent_cancelled_read_async_cb, data);
|
|
|
|
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
|
|
"transfer is cancelled by spice agent");
|
|
spice_file_transfer_task_completed(xfer_task, error);
|
|
}
|
|
|
|
static void
|
|
test_agent_cancel_on_read(Fixture *f, gconstpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
f->xfer_tasks = spice_file_transfer_task_create_tasks(f->files, NULL, G_FILE_COPY_NONE, NULL);
|
|
g_hash_table_iter_init(&iter, f->xfer_tasks);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(value);
|
|
g_signal_connect(xfer_task, "finished", G_CALLBACK(transfer_xfer_task_on_finished), f);
|
|
spice_file_transfer_task_init_task_async(xfer_task, transfer_on_init_async_cb_agent_cancel, f);
|
|
}
|
|
g_main_loop_run (f->loop);
|
|
}
|
|
|
|
/* Tests summary:
|
|
*
|
|
* This tests are specific to SpiceFileTransferTask in order to verify:
|
|
* - Cancelation from User;
|
|
* - Error/Cancelation from Agent;
|
|
* - Bad behavior from Agent;
|
|
*
|
|
* SpiceFileTransferTask is in charge of initializing, reading and finalizing
|
|
* all IO for the file user wants to transfer but being unaware of how the
|
|
* protocol works. As there are several combinations of events, these tests
|
|
* intend to find errors, leaks and crashes on SpiceFileTransferTask in common
|
|
* set of events in order to avoid regression in the drag-and-drop feature.
|
|
*
|
|
* Small overview of how File Transfer works.
|
|
*
|
|
* 1.) User calls spice_main_file_copy_async with a list of files to send to the
|
|
* guest
|
|
* 2.) SpiceMainChannel creates a SpiceFileTransferTask per File and request its
|
|
* initialization using spice_file_transfer_task_init_task_async(). The init
|
|
* function will open a GFileInputStream and request the GFileInfo of file;
|
|
* 3.) Using the GFileInfo object, SpiceMainChannel starts the File Transfer
|
|
* protocol with VD_AGENT_FILE_XFER_START. Agent responds with
|
|
* VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA which starts the read IO using
|
|
* spice_file_transfer_task_read_async()
|
|
* 4.) After the read is done, SpiceMainChannel does an async flush to the
|
|
* agent, using the buffer provided by SpiceFileTransferTask; The read IO
|
|
* will be kept going while SpiceMainChannel has agent tokens to use.
|
|
* 5-) After SpiceMainChannel sends enough data, it can always receive:
|
|
* - VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA: to send more data;
|
|
* - VD_AGENT_FILE_XFER_STATUS_SUCCESS: all data was sent;
|
|
* - VD_AGENT_FILE_XFER_STATUS_ERROR: unexpected behavior on agent side;
|
|
* - VD_AGENT_FILE_XFER_STATUS_CANCELLED: transfer was cancelled by agent;
|
|
* 6-) In any case of termination, the following SpiceFileTransferTask function
|
|
* should always be called: spice_file_transfer_task_completed(); This will
|
|
* trigger from SpiceFileTransferTask all necessary events to finalize user
|
|
* side, memory allocation and so on.
|
|
*/
|
|
int main(int argc, char* argv[])
|
|
{
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
g_test_add("/spice-file-transfer-task/single/simple-transfer",
|
|
Fixture, GUINT_TO_POINTER(SINGLE_FILE),
|
|
f_setup, test_simple_transfer, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/single/cancel/before-task-init",
|
|
Fixture, GUINT_TO_POINTER(SINGLE_FILE),
|
|
f_setup, test_cancel_before_task_init, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/single/cancel/after-task-init",
|
|
Fixture, GUINT_TO_POINTER(SINGLE_FILE),
|
|
f_setup, test_cancel_after_task_init, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/single/cancel/before-read-async",
|
|
Fixture, GUINT_TO_POINTER(SINGLE_FILE),
|
|
f_setup, test_cancel_before_read_async, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/single/cancel/after-read-async",
|
|
Fixture, GUINT_TO_POINTER(SINGLE_FILE),
|
|
f_setup, test_cancel_after_read_async, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/single/agent/cancel",
|
|
Fixture, GUINT_TO_POINTER(SINGLE_FILE),
|
|
f_setup, test_agent_cancel_on_read, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/multiple/simple-transfer",
|
|
Fixture, GUINT_TO_POINTER(MULTIPLE_FILES),
|
|
f_setup, test_simple_transfer, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/multiple/cancel/before-task-init",
|
|
Fixture, GUINT_TO_POINTER(MULTIPLE_FILES),
|
|
f_setup, test_cancel_before_task_init, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/multiple/cancel/after-task-init",
|
|
Fixture, GUINT_TO_POINTER(MULTIPLE_FILES),
|
|
f_setup, test_cancel_after_task_init, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/multiple/cancel/before--read-async",
|
|
Fixture, GUINT_TO_POINTER(MULTIPLE_FILES),
|
|
f_setup, test_cancel_before_read_async, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/multiple/cancel/after-read-async",
|
|
Fixture, GUINT_TO_POINTER(MULTIPLE_FILES),
|
|
f_setup, test_cancel_after_read_async, f_teardown);
|
|
|
|
g_test_add("/spice-file-transfer-task/multiple/agent/cancel",
|
|
Fixture, GUINT_TO_POINTER(MULTIPLE_FILES),
|
|
f_setup, test_agent_cancel_on_read, f_teardown);
|
|
|
|
return g_test_run();
|
|
}
|