summaryrefslogtreecommitdiff
path: root/egg
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2009-10-11 17:52:54 +0000
committerStef Walter <stef@memberwebs.com>2009-10-11 20:04:03 +0000
commit7efb2a3d9d07c31087925143975173e97d3ed8bc (patch)
treeed48555d70f4185e20c98c2570097b539f91e6f2 /egg
parent1286e3c553b3e03ca2b0fbb4cdcafa9cf06e6861 (diff)
[egg] Add spawn with callback functionality.
Allows executing an application and getting callbacks when stdin, stdout, or stderr need servicing.
Diffstat (limited to 'egg')
-rw-r--r--egg/Makefile.am1
-rw-r--r--egg/egg-spawn.c378
-rw-r--r--egg/egg-spawn.h65
-rw-r--r--egg/tests/Makefile.am3
-rw-r--r--egg/tests/test-data/echo-script.sh11
-rw-r--r--egg/tests/unit-test-spawn.c274
6 files changed, 731 insertions, 1 deletions
diff --git a/egg/Makefile.am b/egg/Makefile.am
index b42bc5f1..584df420 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 00000000..b4e242f1
--- /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 00000000..c611d4e6
--- /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 50b2d4d7..e95b8132 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 00000000..d3009fb7
--- /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 00000000..6b38d9b7
--- /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);
+}