[gnome-keyring/dbus-api] [egg] Add spawn with callback functionality.



commit 7efb2a3d9d07c31087925143975173e97d3ed8bc
Author: Stef Walter <stef memberwebs com>
Date:   Sun Oct 11 17:52:54 2009 +0000

    [egg] Add spawn with callback functionality.
    
    Allows executing an application and getting callbacks when
    stdin, stdout, or stderr need servicing.

 egg/Makefile.am                    |    1 +
 egg/egg-spawn.c                    |  378 ++++++++++++++++++++++++++++++++++++
 egg/egg-spawn.h                    |   65 ++++++
 egg/tests/Makefile.am              |    3 +-
 egg/tests/test-data/echo-script.sh |   11 +
 egg/tests/unit-test-spawn.c        |  274 ++++++++++++++++++++++++++
 6 files changed, 731 insertions(+), 1 deletions(-)
---
diff --git a/egg/Makefile.am b/egg/Makefile.am
index b42bc5f..584df42 100644
--- a/egg/Makefile.am
+++ b/egg/Makefile.am
@@ -29,6 +29,7 @@ libegg_la_SOURCES = \
 	egg-openssl.c egg-openssl.h \
 	egg-unix-credentials.c egg-unix-credentials.h \
 	egg-secure-memory.c egg-secure-memory.h \
+	egg-spawn.c egg-spawn.h \
 	egg-symkey.c egg-symkey.h \
 	$(BUILT_SOURCES)
 	
diff --git a/egg/egg-spawn.c b/egg/egg-spawn.c
new file mode 100644
index 0000000..b4e242f
--- /dev/null
+++ b/egg/egg-spawn.c
@@ -0,0 +1,378 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2009 Stefan Walter
+ *
+ * This program 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.
+ *
+ * This program 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 program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "egg-spawn.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/select.h>
+#include <sys/wait.h>
+
+typedef struct _CallbackSource {
+	GSource source;
+	EggSpawnCallbacks callbacks;
+	GPollFD polls[3];
+} CallbackSource;
+
+static void
+close_fd (int *fd)
+{
+	g_assert (fd);
+	if (*fd >= 0)
+		close (*fd);
+	*fd = -1;
+}
+
+static void
+close_poll (GSource *source, GPollFD *poll)
+{
+	g_source_remove_poll (source, poll);
+	close_fd (&poll->fd);
+	poll->revents = 0;
+}
+
+static gboolean
+unused_callback (gpointer data)
+{
+	/* Never called */
+	g_assert_not_reached ();
+	return FALSE;
+}
+
+static gboolean
+cb_source_prepare (GSource *source, gint *timeout_)
+{
+	CallbackSource *cb_source = (CallbackSource*)source;
+	gint i;
+
+	for (i = 0; i < 3; ++i) {
+		if (cb_source->polls[i].fd >= 0)
+			return FALSE;
+	}
+
+	/* If none of the FDs are valid, then process immediately */
+	return TRUE;
+}
+
+static gboolean
+cb_source_check (GSource *source)
+{
+	CallbackSource *cb_source = (CallbackSource*)source;
+	gint i;
+
+	for (i = 0; i < 3; ++i) {
+		if (cb_source->polls[i].fd >= 0 && cb_source->polls[i].revents != 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+cb_source_finalize (GSource *source)
+{
+	CallbackSource *cb_source = (CallbackSource*)source;
+	gint i;
+
+	for (i = 0; i < 3; ++i)
+		close_fd (&cb_source->polls[i].fd);
+}
+
+static gboolean
+cb_source_dispatch (GSource *source, GSourceFunc unused, gpointer user_data)
+{
+	CallbackSource *cb_source = (CallbackSource*)source;
+	GPollFD *poll;
+	gint i;
+
+	/* Standard input */
+	poll = &cb_source->polls[0];
+	if (poll->fd >= 0 && poll->revents != 0) {
+		g_assert (cb_source->callbacks.standard_input);
+		if (!(cb_source->callbacks.standard_input) (poll->fd, user_data))
+			close_poll (source, poll);
+	}
+
+	/* Standard output */
+	poll = &cb_source->polls[1];
+	if (poll->fd >= 0 && poll->revents != 0) {
+		g_assert (cb_source->callbacks.standard_output);
+		if (!(cb_source->callbacks.standard_output) (poll->fd, user_data))
+			close_poll (source, poll);
+	}
+
+	/* Standard error */
+	poll = &cb_source->polls[2];
+	if (poll->fd >= 0 && poll->revents != 0) {
+		g_assert (cb_source->callbacks.standard_error);
+		if (!(cb_source->callbacks.standard_error) (poll->fd, user_data))
+			close_poll (source, poll);
+	}
+
+	for (i = 0; i < 3; ++i) {
+		if (cb_source->polls[i].fd >= 0)
+			return TRUE;
+	}
+
+	/* All input and output is done */
+	if (cb_source->callbacks.completed)
+		(cb_source->callbacks.completed) (user_data);
+	return FALSE;
+}
+
+static GSourceFuncs cb_source_funcs = {
+	cb_source_prepare,
+	cb_source_check,
+	cb_source_dispatch,
+	cb_source_finalize,
+};
+
+guint
+egg_spawn_async_with_callbacks (const gchar *working_directory, gchar **argv,
+                                gchar **envp, GSpawnFlags flags, GPid *child_pid,
+                                EggSpawnCallbacks *cbs, gpointer user_data,
+                                GMainContext *context, GError **error)
+{
+	gint in_fd, out_fd, err_fd;
+	CallbackSource *cb_source;
+	GSource *source;
+	guint tag;
+
+	g_return_val_if_fail (argv != NULL, FALSE);
+	g_return_val_if_fail ((cbs && cbs->standard_input == NULL) ||
+	                      !(flags & G_SPAWN_CHILD_INHERITS_STDIN), 0);
+	g_return_val_if_fail ((cbs && cbs->standard_output == NULL) ||
+	                      !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), 0);
+	g_return_val_if_fail ((cbs && cbs->standard_error == NULL) ||
+	                      !(flags & G_SPAWN_STDERR_TO_DEV_NULL), 0);
+
+	in_fd = out_fd = err_fd = -1;
+
+	if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags,
+	                               cbs ? cbs->child_setup : NULL,
+	                               user_data, child_pid,
+	                               cbs && cbs->standard_input ? &in_fd : NULL,
+	                               cbs && cbs->standard_output ? &out_fd : NULL,
+	                               cbs && cbs->standard_error ? &err_fd : NULL,
+	                               error))
+		return 0;
+
+	source = g_source_new (&cb_source_funcs, sizeof (CallbackSource));
+
+	cb_source = (CallbackSource*)source;
+	if (cbs != NULL)
+		memcpy (&cb_source->callbacks, cbs, sizeof (EggSpawnCallbacks));
+
+	cb_source->polls[0].fd = in_fd;
+	if (in_fd >= 0) {
+		g_assert (cb_source->callbacks.standard_input);
+		cb_source->polls[0].events = G_IO_ERR | G_IO_OUT;
+		g_source_add_poll (source, &cb_source->polls[0]);
+	}
+	cb_source->polls[1].fd = out_fd;
+	if (out_fd >= 0) {
+		g_assert (cb_source->callbacks.standard_output);
+		cb_source->polls[1].events = G_IO_ERR | G_IO_HUP | G_IO_IN;
+		g_source_add_poll (source, &cb_source->polls[1]);
+	}
+	cb_source->polls[2].fd = err_fd;
+	if (err_fd >= 0) {
+		g_assert (cb_source->callbacks.standard_error);
+		cb_source->polls[2].events = G_IO_ERR | G_IO_HUP | G_IO_IN;
+		g_source_add_poll (source, &cb_source->polls[2]);
+	}
+
+	if (context == NULL)
+		context = g_main_context_default ();
+	g_source_set_callback (source, unused_callback, user_data,
+	                       cbs ? cbs->finalize_func : NULL);
+	tag = g_source_attach (source, context);
+	g_source_unref (source);
+
+	return tag;
+}
+
+gboolean
+egg_spawn_sync_with_callbacks  (const gchar *working_directory, gchar **argv,
+                                gchar **envp, GSpawnFlags flags, GPid *child_pid,
+                                EggSpawnCallbacks *cbs, gpointer user_data,
+                                gint *exit_status, GError **error)
+{
+	gint in_fd, out_fd, err_fd, max_fd;
+	fd_set read_fds, write_fds;
+	gboolean failed = FALSE;
+	gint status;
+	GPid pid;
+	gint ret;
+
+	g_return_val_if_fail (argv != NULL, FALSE);
+	g_return_val_if_fail ((cbs && cbs->standard_input == NULL) ||
+	                      !(flags & G_SPAWN_CHILD_INHERITS_STDIN), 0);
+	g_return_val_if_fail ((cbs && cbs->standard_output == NULL) ||
+	                      !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), 0);
+	g_return_val_if_fail ((cbs && cbs->standard_error == NULL) ||
+	                      !(flags & G_SPAWN_STDERR_TO_DEV_NULL), 0);
+
+	in_fd = out_fd = err_fd = -1;
+
+	if (exit_status)
+		flags |= G_SPAWN_DO_NOT_REAP_CHILD;
+
+	if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags,
+	                               cbs ? cbs->child_setup : NULL,
+	                               user_data, &pid,
+	                               cbs && cbs->standard_input ? &in_fd : NULL,
+	                               cbs && cbs->standard_output ? &out_fd : NULL,
+	                               cbs && cbs->standard_error ? &err_fd : NULL,
+	                               error))
+		return FALSE;
+
+	if (child_pid)
+		*child_pid = pid;
+
+	max_fd = MAX (in_fd, MAX (out_fd, err_fd)) + 1;
+
+	while (in_fd >= 0 || out_fd >= 0 || err_fd >= 0) {
+
+		FD_ZERO (&write_fds);
+		if (in_fd >= 0)
+			FD_SET (in_fd, &write_fds);
+		FD_ZERO (&read_fds);
+		if (out_fd >= 0)
+			FD_SET (out_fd, &read_fds);
+		if (err_fd >= 0)
+			FD_SET (err_fd, &read_fds);
+
+		ret = select (max_fd, &read_fds, &write_fds, NULL, NULL);
+		if (ret < 0 && errno != EINTR) {
+			failed = TRUE;
+			g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ,
+			             _("Unexpected error in select() reading data from a child process (%s)"),
+			             g_strerror (errno));
+			break;
+		}
+
+		if (in_fd >= 0 && FD_ISSET (in_fd, &write_fds)) {
+			g_assert (cbs && cbs->standard_input);
+			if (!(cbs->standard_input) (in_fd, user_data))
+				close_fd (&in_fd);
+		}
+		if (out_fd >= 0 && FD_ISSET (out_fd, &read_fds)) {
+			g_assert (cbs && cbs->standard_output);
+			if (!(cbs->standard_output) (out_fd, user_data))
+				close_fd (&out_fd);
+		}
+		if (err_fd >= 0 && FD_ISSET (err_fd, &read_fds)) {
+			g_assert (cbs && cbs->standard_error);
+			if (!(cbs->standard_error) (err_fd, user_data))
+				close_fd (&err_fd);
+		}
+	}
+
+	if (in_fd >= 0)
+		close_fd (&in_fd);
+	if (out_fd >= 0)
+		close_fd (&out_fd);
+	if (err_fd >= 0)
+		close_fd (&err_fd);
+
+	if (!failed) {
+		if (cbs && cbs->completed)
+			(cbs->completed) (user_data);
+	}
+
+again:
+	ret = waitpid (pid, &status, 0);
+	if (ret < 0) {
+		if (errno == EINTR)
+			goto again;
+		else if (errno == ECHILD) {
+			if (exit_status)
+				g_warning ("In call to g_spawn_sync(), exit status of a child process was requested but SIGCHLD action was set to SIG_IGN and ECHILD was received by waitpid(), so exit status can't be returned. This is a bug in the program calling g_spawn_sync(); either don't request the exit status, or don't set the SIGCHLD action.");
+			else
+				; /* We don't need the exit status. */
+		} else if (!failed) { /* avoid error pileups */
+			failed = TRUE;
+			g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ,
+			             _("Unexpected error in waitpid() (%s)"),
+			             g_strerror (errno));
+		}
+	} else {
+		if (exit_status)
+			*exit_status = status;
+	}
+
+	if (!child_pid)
+		g_spawn_close_pid (pid);
+
+	if (cbs && cbs->finalize_func)
+		(cbs->finalize_func) (user_data);
+
+	return !failed;
+}
+
+gssize
+egg_spawn_write_input (int fd, gconstpointer data, gsize n_data)
+{
+	gssize result;
+
+	g_return_val_if_fail (fd >= 0, -1);
+
+	for (;;) {
+		result = write (fd, data, n_data);
+		if (result < 0) {
+			if (errno == EINTR)
+				continue;
+			else if (errno == EAGAIN)
+				result = 0;
+		}
+		break;
+	}
+
+	return result;
+}
+
+gssize
+egg_spawn_read_output (int fd, gpointer data, gsize n_data)
+{
+	gssize result;
+
+	g_return_val_if_fail (fd >= 0, -1);
+
+	for (;;) {
+		result = read (fd, data, n_data);
+		if (result < 0) {
+			if (errno == EINTR)
+				continue;
+			else if (errno == EAGAIN)
+				result = 0;
+		}
+		break;
+	}
+
+	return result;
+}
+
diff --git a/egg/egg-spawn.h b/egg/egg-spawn.h
new file mode 100644
index 0000000..c611d4e
--- /dev/null
+++ b/egg/egg-spawn.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* egg-sub-process.h - spawn a subprocess and perform IO
+
+   Copyright (C) 2009 Stefan Walter
+
+   Gnome keyring is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   Gnome keyring 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
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Author: Stef Walter <stef memberwebs com>
+*/
+
+#ifndef EGG_SPAWN_H_
+#define EGG_SUB_PROCESS_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EggSpawnCallbacks {
+	gboolean (*standard_input) (int fd, gpointer user_data);
+	gboolean (*standard_output) (int fd, gpointer user_data);
+	gboolean (*standard_error) (int fd, gpointer user_data);
+	void (*completed) (gpointer user_data);
+	GDestroyNotify finalize_func;
+	GSpawnChildSetupFunc child_setup;
+} EggSpawnCallbacks;
+
+guint              egg_spawn_async_with_callbacks (const gchar *working_directory,
+                                                   gchar **argv,
+                                                   gchar **envp,
+                                                   GSpawnFlags flags,
+                                                   GPid *child_pid,
+                                                   EggSpawnCallbacks *callbacks,
+                                                   gpointer user_data,
+                                                   GMainContext *context,
+                                                   GError **error);
+
+gboolean           egg_spawn_sync_with_callbacks  (const gchar *working_directory,
+                                                   gchar **argv,
+                                                   gchar **envp,
+                                                   GSpawnFlags flags,
+                                                   GPid *child_pid,
+                                                   EggSpawnCallbacks *callbacks,
+                                                   gpointer user_data,
+                                                   gint *exit_status,
+                                                   GError **error);
+
+gssize              egg_spawn_write_input          (int fd, gconstpointer data, gsize n_data);
+
+gssize              egg_spawn_read_output          (int fd, gpointer data, gsize n_data);
+
+G_END_DECLS
+
+#endif /*EGG_SPAWN_H_*/
diff --git a/egg/tests/Makefile.am b/egg/tests/Makefile.am
index 50b2d4d..e95b813 100644
--- a/egg/tests/Makefile.am
+++ b/egg/tests/Makefile.am
@@ -10,7 +10,8 @@ UNIT_AUTO = \
 	unit-test-secmem.c \
 	unit-test-symkey.c \
 	unit-test-openssl.c \
-	unit-test-dh.c
+	unit-test-dh.c \
+	unit-test-spawn.c \
 	asn1-def-test.h
 
 UNIT_PROMPT = 
diff --git a/egg/tests/test-data/echo-script.sh b/egg/tests/test-data/echo-script.sh
new file mode 100644
index 0000000..d3009fb
--- /dev/null
+++ b/egg/tests/test-data/echo-script.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+count=0
+output=""
+while read line; do
+	output="$output $line"
+	count=`expr $count + 1`
+	echo "$count" >&2
+done
+echo $output
+exit 3
diff --git a/egg/tests/unit-test-spawn.c b/egg/tests/unit-test-spawn.c
new file mode 100644
index 0000000..6b38d9b
--- /dev/null
+++ b/egg/tests/unit-test-spawn.c
@@ -0,0 +1,274 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* unit-test-dh.c: Test egg-spawn.c
+
+   Copyright (C) 2009 Stefan Walter
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stef memberwebs com>
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "run-auto-test.h"
+
+#include "egg-spawn.h"
+
+#include <sys/wait.h>
+
+typedef struct _EchoData {
+	gint index;
+	gchar *output;
+	gchar *error;
+	gboolean finalized;
+	gboolean completed;
+	gboolean is_async;
+	pid_t parent_pid;
+} EchoData;
+
+static gboolean
+want_input (gint fd, gpointer user_data)
+{
+	EchoData *data = user_data;
+	gchar *buffer;
+
+	g_assert (data);
+	if (data->index == 85)
+		return FALSE;
+
+	g_assert (data->index >= 80);
+	g_assert (data->index < 85);
+
+	buffer = g_strdup_printf ("%d\n", data->index);
+	if (egg_spawn_write_input (fd, (guchar*)buffer, strlen (buffer)) < 0)
+		g_assert_not_reached ();
+	g_free (buffer);
+	++data->index;
+	return TRUE;
+}
+
+static gboolean
+have_output (gint fd, gpointer user_data)
+{
+	EchoData *data = user_data;
+	gchar buffer[1024];
+	gssize length;
+	gchar *output;
+
+	g_assert (data);
+
+	length = egg_spawn_read_output (fd, (guchar*)buffer, 1023);
+	g_assert (length >= 0);
+
+	buffer[length] = 0;
+	output = g_strconcat (data->output ? data->output : "", buffer, NULL);
+	g_free (data->output);
+	data->output = output;
+
+	return (length > 0);
+}
+
+static gboolean
+have_error (gint fd, gpointer user_data)
+{
+	EchoData *data = user_data;
+	gchar buffer[1024];
+	gssize length;
+	gchar *error;
+
+	g_assert (data);
+
+	length = egg_spawn_read_output (fd, (guchar*)buffer, 1023);
+	g_assert (length >= 0);
+
+	buffer[length] = 0;
+	error = g_strconcat (data->error ? data->error : "", buffer, NULL);
+	g_free (data->error);
+	data->error = error;
+
+	return (length > 0);
+}
+
+static void
+completed_func (gpointer user_data)
+{
+	EchoData *data = user_data;
+	g_assert (data);
+	g_assert (!data->finalized);
+	g_assert (!data->completed);
+	data->completed = TRUE;
+	if (data->is_async)
+		test_mainloop_quit ();
+}
+
+static void
+finalize_func (gpointer user_data)
+{
+	EchoData *data = user_data;
+	g_assert (!data->finalized);
+	data->finalized = 1;
+}
+
+static void
+child_setup (gpointer user_data)
+{
+	EchoData *data = user_data;
+	g_assert (data->parent_pid != getpid ());
+}
+
+static EggSpawnCallbacks echo_callbacks = {
+	want_input,
+	have_output,
+	have_error,
+	completed_func,
+	finalize_func,
+	child_setup,
+};
+
+static char* echo_argv[] = {
+	"/bin/sh",
+	"./echo-script.sh",
+	NULL
+};
+
+static char* error_argv[] = {
+	"/nonexistent",
+	NULL
+};
+
+static EggSpawnCallbacks null_callbacks = {
+	NULL,
+	NULL,
+	NULL,
+	completed_func,
+	finalize_func,
+	child_setup,
+};
+
+DEFINE_TEST(test_spawn_sync)
+{
+	GError *error = NULL;
+	gboolean ret;
+	gint exit_status;
+	EchoData data;
+	GPid pid = 0;
+
+	memset (&data, 0, sizeof (data));
+	data.parent_pid = getpid();
+	data.index = 80;
+
+	ret = egg_spawn_sync_with_callbacks (test_dir_testdata (),
+	                                     echo_argv, NULL, 0, &pid,
+	                                     &echo_callbacks, &data,
+	                                     &exit_status, &error);
+	g_assert (ret == TRUE);
+	g_assert (pid != 0);
+	g_assert (WIFEXITED (exit_status));
+	g_assert (WEXITSTATUS(exit_status) == 3);
+	g_assert (error == NULL);
+	g_assert (data.finalized);
+	g_assert (data.completed);
+	g_assert_cmpstr (data.output, ==, "80 81 82 83 84\n");
+	g_assert_cmpstr (data.error, ==, "1\n2\n3\n4\n5\n");
+}
+
+DEFINE_TEST(test_spawn_sync_error)
+{
+	GError *error = NULL;
+	gboolean ret;
+
+	ret = egg_spawn_sync_with_callbacks (test_dir_testdata (),
+	                                     error_argv, NULL, 0, NULL,
+	                                     NULL, NULL,
+	                                     NULL, &error);
+	g_assert (ret == FALSE);
+	g_assert (error != NULL);
+	g_clear_error (&error);
+}
+
+
+DEFINE_TEST(test_spawn_async)
+{
+	GError *error = NULL;
+	EchoData data;
+	guint ret;
+	GPid pid;
+
+	memset (&data, 0, sizeof (data));
+	data.parent_pid = getpid();
+	data.index = 80;
+	data.is_async = TRUE;
+
+	ret = egg_spawn_async_with_callbacks (test_dir_testdata (),
+	                                     echo_argv, NULL, 0, &pid,
+	                                     &echo_callbacks, &data,
+	                                     NULL, &error);
+	g_assert (ret != 0);
+	g_assert (error == NULL);
+	g_assert (!data.finalized);
+	g_assert (!data.output);
+	g_assert (!data.completed);
+
+	test_mainloop_run (2000);
+
+	g_assert (data.finalized);
+	g_assert (data.completed);
+	g_assert_cmpstr (data.output, ==, "80 81 82 83 84\n");
+	g_assert_cmpstr (data.error, ==, "1\n2\n3\n4\n5\n");
+}
+
+DEFINE_TEST(test_spawn_async_none)
+{
+	GError *error = NULL;
+	EchoData data;
+	guint ret;
+
+	memset (&data, 0, sizeof (data));
+	data.parent_pid = getpid();
+	data.is_async = TRUE;
+
+	ret = egg_spawn_async_with_callbacks (test_dir_testdata (),
+	                                     echo_argv, NULL, 0, NULL,
+	                                     &null_callbacks, &data,
+	                                     NULL, &error);
+	g_assert (ret != 0);
+	g_assert (error == NULL);
+	g_assert (!data.finalized);
+	g_assert (!data.completed);
+	g_assert (!data.output);
+
+	test_mainloop_run (2000);
+
+	g_assert (data.finalized);
+	g_assert (data.completed);
+	g_assert (!data.output);
+}
+
+DEFINE_TEST(test_spawn_async_error)
+{
+	GError *error = NULL;
+	guint ret;
+
+	ret = egg_spawn_async_with_callbacks (test_dir_testdata (),
+	                                     error_argv, NULL, 0, NULL,
+	                                     NULL, NULL,
+	                                     NULL, &error);
+	g_assert (ret == 0);
+	g_assert (error != NULL);
+	g_clear_error (&error);
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]