gcr: Review changes for GcrGnupgProcess from Phillip Withnall
authorStef Walter <stefw@collabora.co.uk>
Tue, 12 Jul 2011 10:47:39 +0000 (12:47 +0200)
committerStef Walter <stefw@collabora.co.uk>
Tue, 12 Jul 2011 10:47:39 +0000 (12:47 +0200)
 * Better docs
 * Disconnect signal handlers properly.
 * Cleanup tests.
 * Other bits

gcr/gcr-gnupg-collection.c
gcr/gcr-gnupg-process.c
gcr/gcr-gnupg-process.h
gcr/tests/files/gnupg-mock/mock-status-and-attribute
gcr/tests/test-gnupg-process.c

index 44abe93..1fc2e21 100644 (file)
@@ -140,6 +140,12 @@ _gcr_gnupg_collection_class_init (GcrGnupgCollectionClass *klass)
        gobject_class->dispose = _gcr_gnupg_collection_dispose;
        gobject_class->finalize = _gcr_gnupg_collection_finalize;
 
+       /**
+        * GcrGnupgCollection:directory:
+        *
+        * Directory to load the gnupg keys from, or %NULL for default
+        * ~/.gnupg/ directory.
+        */
        g_object_class_install_property (gobject_class, PROP_DIRECTORY,
                   g_param_spec_string ("directory", "Directory", "Gnupg Directory",
                                        NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
@@ -239,16 +245,18 @@ _gcr_gnupg_collection_load_free (gpointer data)
        g_hash_table_destroy (load->difference);
        g_object_unref (load->collection);
 
-       if (load->process)
+       if (load->process) {
+               if (load->output_sig)
+                       g_signal_handler_disconnect (load->process, load->output_sig);
+               if (load->error_sig)
+                       g_signal_handler_disconnect (load->process, load->error_sig);
+               if (load->status_sig)
+                       g_signal_handler_disconnect (load->process, load->status_sig);
+               if (load->attribute_sig)
+                       g_signal_handler_disconnect (load->process, load->attribute_sig);
                g_object_unref (load->process);
-       if (load->output_sig)
-               g_source_remove (load->output_sig);
-       if (load->error_sig)
-               g_source_remove (load->error_sig);
-       if (load->status_sig)
-               g_source_remove (load->status_sig);
-       if (load->attribute_sig)
-               g_source_remove (load->attribute_sig);
+       }
+
        if (load->cancel)
                g_object_unref (load->cancel);
        g_slice_free (GcrGnupgCollectionLoad, load);
index e1c7a00..b4a4637 100644 (file)
 #include "gcr-marshal.h"
 #include "gcr-util.h"
 
-#include "egg/egg-spawn.h"
+#include <glib/gi18n-lib.h>
 
 #include <sys/wait.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <string.h>
 
+/**
+ * GcrGnupgProcessFlags:
+ * @GCR_GNUPG_PROCESS_NONE: No flags
+ * @GCR_GNUPG_PROCESS_RESPECT_LOCALE: Respect the user's locale when running gnupg.
+ * @GCR_GNUPG_PROCESS_WITH_STATUS: Ask the process to send status records.
+ * @GCR_GNUPG_PROCESS_WITH_ATTRIBUTES: Ask the process to output attribute data.
+ *
+ * Flags for running a gnupg process.
+ */
+
 enum {
        PROP_0,
        PROP_DIRECTORY,
@@ -73,6 +83,7 @@ typedef struct _GnupgSource {
        GPid child_pid;
        guint child_sig;
 
+       GCancellable *cancellable;
        guint cancel_sig;
 } GnupgSource;
 
@@ -178,29 +189,68 @@ _gcr_gnupg_process_class_init (GcrGnupgProcessClass *klass)
        gobject_class->set_property = _gcr_gnupg_process_set_property;
        gobject_class->finalize = _gcr_gnupg_process_finalize;
 
+       /**
+        * GcrGnupgProcess:directory:
+        *
+        * Directory to run as gnupg home directory, or %NULL for default
+        * ~/.gnupg/ directory.
+        */
        g_object_class_install_property (gobject_class, PROP_DIRECTORY,
                   g_param_spec_string ("directory", "Directory", "Gnupg Directory",
                                        NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
+       /**
+        * GcrGnupgProcess:executable:
+        *
+        * Path to the gnupg executable, or %NULL for default.
+        */
        g_object_class_install_property (gobject_class, PROP_EXECUTABLE,
                   g_param_spec_string ("executable", "Executable", "Gnupg Executable",
                                        GPG_EXECUTABLE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
+       /**
+        * GcrGnupgProcess::output-data:
+        * @data: a #GByteArray of output data.
+        *
+        * Signal emitted when normal output data is available from the gnupg
+        * process. The data does not necessarily come on line boundaries, and
+        * won't be null-terminated.
+        */
        signals[OUTPUT_DATA] = g_signal_new ("output-data", GCR_TYPE_GNUPG_PROCESS,
                   G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, output_data),
                   NULL, NULL, _gcr_marshal_VOID__BOXED,
                   G_TYPE_NONE, 1, G_TYPE_BYTE_ARRAY);
 
+       /**
+        * GcrGnupgProcess::error-line:
+        * @line: a line of error output.
+        *
+        * Signal emitted when a line of error output is available from the
+        * gnupg process.
+        */
        signals[ERROR_LINE] = g_signal_new ("error-line", GCR_TYPE_GNUPG_PROCESS,
                   G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, error_line),
                   NULL, NULL, _gcr_marshal_VOID__STRING,
                   G_TYPE_NONE, 1, G_TYPE_STRING);
 
+       /**
+        * GcrGnupgProcess::status-record:
+        * @record: a status record.
+        *
+        * Signal emitted when a status record is available from the gnupg process.
+        */
        signals[STATUS_RECORD] = g_signal_new ("status-record", GCR_TYPE_GNUPG_PROCESS,
                   G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, status_record),
                   NULL, NULL, _gcr_marshal_VOID__BOXED,
                   G_TYPE_NONE, 1, GCR_TYPE_RECORD);
 
+       /**
+        * GcrGnupgProcess::attribute-data:
+        * @data: a #GByteArray of attribute data.
+        *
+        * Signal emitted when attribute data is available from the gnupg
+        * process.
+        */
        signals[ATTRIBUTE_DATA] = g_signal_new ("attribute-data", GCR_TYPE_GNUPG_PROCESS,
                   G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, attribute_data),
                   NULL, NULL, _gcr_marshal_VOID__BOXED,
@@ -243,7 +293,7 @@ _gcr_gnupg_process_init_async (GAsyncResultIface *iface)
  * The executable will default to the compiled in path if a %NULL executable
  * argument is used.
  *
- * Returns: (transfer full) A newly allocated process.
+ * Returns: (transfer full): A newly allocated process.
  */
 GcrGnupgProcess*
 _gcr_gnupg_process_new (const gchar *directory, const gchar *executable)
@@ -393,10 +443,10 @@ on_gnupg_source_finalize (GSource *source)
        GnupgSource *gnupg_source = (GnupgSource*)source;
        gint i;
 
-       if (gnupg_source->cancel_sig) {
-               g_source_remove (gnupg_source->cancel_sig);
-               gnupg_source->cancel_sig = 0;
-       }
+       if (gnupg_source->cancel_sig)
+               g_signal_handler_disconnect (gnupg_source->cancellable, gnupg_source->cancel_sig);
+       if (gnupg_source->cancellable)
+               g_object_unref (gnupg_source->cancellable);
 
        for (i = 0; i < NUM_FDS; ++i)
                close_fd (&gnupg_source->polls[i].fd);
@@ -415,7 +465,7 @@ read_output (int fd, GByteArray *buffer)
        guchar block[1024];
        gssize result;
 
-       g_return_val_if_fail (fd >= 0, -1);
+       g_return_val_if_fail (fd >= 0, FALSE);
 
        do {
                result = read (fd, block, sizeof (block));
@@ -468,7 +518,7 @@ on_gnupg_source_dispatch (GSource *source, GSourceFunc unused, gpointer user_dat
        GByteArray *buffer;
        GPollFD *poll;
 
-       /* Standard input, no suport yet */
+       /* Standard input, no support yet */
        poll = &gnupg_source->polls[FD_INPUT];
        if (poll->fd >= 0 && poll->revents != 0) {
                close_poll (source, poll);
@@ -519,7 +569,7 @@ on_gnupg_source_dispatch (GSource *source, GSourceFunc unused, gpointer user_dat
                        if (!read_output (poll->fd, buffer)) {
                                g_warning ("couldn't read output data from gnupg process");
                        } else if (buffer->len > 0) {
-                               _gcr_debug ("received %d bytes of attribute data", (gint)buffer->len);
+                               _gcr_debug ("received %d bytes of output data", (gint)buffer->len);
                                g_signal_emit (gnupg_source->process, signals[OUTPUT_DATA], 0, buffer);
                        }
                        g_byte_array_unref (buffer);
@@ -579,14 +629,14 @@ on_gnupg_process_child_exited (GPid pid, gint status, gpointer user_data)
                code = WEXITSTATUS (status);
                if (code != 0) {
                        error = g_error_new (G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
-                                            "Gnupg process exited with code: %d", code);
+                                            _("Gnupg process exited with code: %d"), code);
                }
        } else if (WIFSIGNALED (status)) {
                code = WTERMSIG (status);
                /* Ignore cases where we've signaled the process because we were cancelled */
                if (!g_error_matches (self->pv->error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                        error = g_error_new (G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
-                                            "Gnupg process was terminated with signal: %d", code);
+                                            _("Gnupg process was terminated with signal: %d"), code);
        }
 
        /* Take this as the async result error */
@@ -596,7 +646,7 @@ on_gnupg_process_child_exited (GPid pid, gint status, gpointer user_data)
 
        /* Already have an error, just print out message */
        } else if (error) {
-               g_message ("%s", error->message);
+               g_warning ("%s", error->message);
                g_error_free (error);
        }
 
@@ -642,7 +692,7 @@ on_cancellable_cancelled (GCancellable *cancellable, gpointer user_data)
        /* Set an error, which is respected when this actually completes. */
        if (gnupg_source->process->pv->error == NULL)
                gnupg_source->process->pv->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED,
-                                                                       "The operation was cancelled");
+                                                                       _("The operation was cancelled"));
 
        complete_if_source_is_done (gnupg_source);
 }
@@ -650,11 +700,12 @@ on_cancellable_cancelled (GCancellable *cancellable, gpointer user_data)
 /**
  * _gcr_gnupg_process_run_async:
  * @self: The process
- * @argv: The arguments for the process, not including executable
- * @envp: (allow-none): The environment for new process.
+ * @argv: (array zero-terminated=1): The arguments for the process, not including executable, terminated with %NULL.
+ * @envp: (allow-none) (array zero-terminated=1): The environment for new process, terminated with %NULL.
  * @flags: Flags for starting the process.
  * @cancellable: (allow-none): Cancellation object
  * @callback: Will be called when operation completes.
+ * @user_data: (closure): Data passed to callback.
  *
  * Run the gpg process. Only one 'run' operation can run per GcrGnupgProcess
  * object. The GcrGnupgProcess:output_data and GcrGnupgProcess:error_line
@@ -688,6 +739,7 @@ _gcr_gnupg_process_run_async (GcrGnupgProcess *self, const gchar **argv, const g
        g_return_if_fail (GCR_IS_GNUPG_PROCESS (self));
        g_return_if_fail (argv);
        g_return_if_fail (callback);
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
        g_return_if_fail (self->pv->running == FALSE);
        g_return_if_fail (self->pv->complete == FALSE);
@@ -811,6 +863,7 @@ _gcr_gnupg_process_run_async (GcrGnupgProcess *self, const gchar **argv, const g
        }
 
        if (cancellable) {
+               gnupg_source->cancellable = g_object_ref (cancellable);
                gnupg_source->cancel_sig = g_cancellable_connect (cancellable,
                                                                  G_CALLBACK (on_cancellable_cancelled),
                                                                  g_source_ref (source),
@@ -838,6 +891,8 @@ _gcr_gnupg_process_run_async (GcrGnupgProcess *self, const gchar **argv, const g
  * @error: Location to raise an error on failure.
  *
  * Get the result of running a gnupg process.
+ *
+ * Return value: Whether the Gnupg process was run or not.
  */
 gboolean
 _gcr_gnupg_process_run_finish (GcrGnupgProcess *self, GAsyncResult *result,
index d3d0532..ecbab6d 100644 (file)
@@ -61,12 +61,13 @@ struct _GcrGnupgProcessClass {
 };
 
 typedef enum {
+       GCR_GNUPG_PROCESS_NONE              = 0,
        GCR_GNUPG_PROCESS_RESPECT_LOCALE    = 1 << 0,
        GCR_GNUPG_PROCESS_WITH_STATUS       = 1 << 1,
        GCR_GNUPG_PROCESS_WITH_ATTRIBUTES   = 1 << 2
 } GcrGnupgProcessFlags;
 
-GType               _gcr_gnupg_process_get_type                (void);
+GType               _gcr_gnupg_process_get_type                (void) G_GNUC_CONST;
 
 GcrGnupgProcess*    _gcr_gnupg_process_new                     (const gchar *directory,
                                                                 const gchar *executable);
index 4df073a..abf8607 100755 (executable)
@@ -2,7 +2,7 @@
 
 # This script is used with test-gnupg-process
 # Needs to be run with /bin/bash in order to handle two digit
-# file descripter redirects
+# file descriptor redirects
 
 set -euf
 
index 80dfe04..e9095f3 100644 (file)
@@ -52,10 +52,8 @@ setup (Test *test, gconstpointer unused)
 static void
 teardown (Test *test, gconstpointer unused)
 {
-       if (test->result)
-               g_object_unref (test->result);
-       if (test->process)
-               g_object_unref (test->process);
+       g_assert (!test->result);
+       g_assert (!test->process);
        if (test->output_buf)
                g_string_free (test->output_buf, TRUE);
        if (test->error_buf)
@@ -79,6 +77,8 @@ test_create (Test *test, gconstpointer unused)
        g_object_get (test->process, "executable", &value, NULL);
        g_assert_cmpstr (value, ==, "/path/to/executable");
        g_free (value);
+
+       g_clear_object (&test->process);
 }
 
 static void
@@ -158,6 +158,7 @@ test_run_simple_output (Test *test, gconstpointer unused)
 {
        const gchar *argv[] = { NULL };
        GError *error = NULL;
+       gboolean ret;
        gchar *script;
 
        script = build_script_path ("mock-simple-output");
@@ -170,10 +171,14 @@ test_run_simple_output (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_no_error (error);
+       g_assert (ret == TRUE);
 
        g_assert_cmpstr ("simple-output\n", ==, test->output_buf->str);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -182,6 +187,7 @@ test_run_simple_error (Test *test, gconstpointer unused)
        const gchar *argv[] = { NULL };
        GError *error = NULL;
        gchar *script;
+       gboolean ret;
 
        script = build_script_path ("mock-simple-error");
        test->process = _gcr_gnupg_process_new (NULL, script);
@@ -193,10 +199,14 @@ test_run_simple_error (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_no_error (error);
+       g_assert (ret == TRUE);
 
        g_assert_cmpstr ("line 1: more line 1\nline 2\nline 3\n", ==, test->error_buf->str);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -205,6 +215,7 @@ test_run_status_and_output (Test *test, gconstpointer unused)
        const gchar *argv[] = { NULL };
        GError *error = NULL;
        gchar *script;
+       gboolean ret;
 
        script = build_script_path ("mock-status-and-output");
        test->process = _gcr_gnupg_process_new (NULL, script);
@@ -218,8 +229,9 @@ test_run_status_and_output (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_no_error (error);
+       g_assert (ret == TRUE);
 
        g_assert (test->record);
        g_assert_cmpstr (_gcr_record_get_raw (test->record, 0), ==, "SCHEMA");
@@ -229,6 +241,9 @@ test_run_status_and_output (Test *test, gconstpointer unused)
        g_assert_cmpstr (_gcr_record_get_raw (test->record, 4), ==, "four");
        g_assert_cmpstr (_gcr_record_get_raw (test->record, 5), ==, NULL);
        g_assert_cmpstr ("Here's some output\nMore output\n", ==, test->output_buf->str);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -237,6 +252,7 @@ test_run_status_and_attribute (Test *test, gconstpointer unused)
        const gchar *argv[] = { NULL };
        GError *error = NULL;
        gchar *script;
+       gboolean ret;
 
        script = build_script_path ("mock-status-and-attribute");
        test->process = _gcr_gnupg_process_new (NULL, script);
@@ -251,8 +267,9 @@ test_run_status_and_attribute (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_no_error (error);
+       g_assert (ret == TRUE);
 
        g_assert (test->record);
        g_assert_cmpstr (_gcr_record_get_raw (test->record, 0), ==, "SCHEMA");
@@ -262,6 +279,9 @@ test_run_status_and_attribute (Test *test, gconstpointer unused)
        g_assert_cmpstr (_gcr_record_get_raw (test->record, 4), ==, "four");
        g_assert_cmpstr (_gcr_record_get_raw (test->record, 5), ==, NULL);
        g_assert_cmpstr ("1lc923g4laoeurc23rc241lcg2r23c4gr3", ==, test->attribute_buf->str);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 
@@ -270,6 +290,7 @@ test_run_arguments_and_environment (Test *test, gconstpointer unused)
 {
        GError *error = NULL;
        gchar *script;
+       gboolean ret;
 
        const gchar *argv[] = {
                "-1", "value1",
@@ -294,11 +315,15 @@ test_run_arguments_and_environment (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_no_error (error);
+       g_assert (ret == TRUE);
 
        g_assert_cmpstr ("value1\nvalue2\n", ==, test->output_buf->str);
        g_assert_cmpstr ("VALUE1VALUE2\n", ==, test->error_buf->str);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -309,6 +334,7 @@ test_run_with_homedir (Test *test, gconstpointer unused)
        gchar *script;
        gchar *directory;
        gchar *check;
+       gboolean ret;
 
        directory = g_get_current_dir ();
 
@@ -322,13 +348,17 @@ test_run_with_homedir (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_no_error (error);
+       g_assert (ret == TRUE);
 
        check = g_strdup_printf ("DIR: %s\n", directory);
        g_assert_cmpstr (check, ==, test->output_buf->str);
        g_free (check);
        g_free (directory);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -337,6 +367,7 @@ test_run_bad_executable (Test *test, gconstpointer unused)
        GError *error = NULL;
        gchar *script;
        const gchar *argv[] = { NULL };
+       gboolean ret;
 
        script = build_script_path ("mock-invalid");
        test->process = _gcr_gnupg_process_new (NULL, script);
@@ -346,9 +377,13 @@ test_run_bad_executable (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
        g_clear_error (&error);
+       g_assert (ret == FALSE);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -357,6 +392,7 @@ test_run_fail_exit (Test *test, gconstpointer unused)
        GError *error = NULL;
        gchar *script;
        const gchar *argv[] = { "55" };
+       gboolean ret;
 
        script = build_script_path ("mock-fail-exit");
        test->process = _gcr_gnupg_process_new (NULL, script);
@@ -366,10 +402,14 @@ test_run_fail_exit (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED);
        g_assert_cmpstr (error->message, ==, "Gnupg process exited with code: 55");
        g_clear_error (&error);
+       g_assert (ret == FALSE);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -378,6 +418,7 @@ test_run_fail_signal (Test *test, gconstpointer unused)
        GError *error = NULL;
        gchar *script;
        const gchar *argv[] = { "15" };
+       gboolean ret;
 
        script = build_script_path ("mock-fail-signal");
        test->process = _gcr_gnupg_process_new (NULL, script);
@@ -387,10 +428,14 @@ test_run_fail_signal (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED);
        g_assert_cmpstr (error->message, ==, "Gnupg process was terminated with signal: 15");
        g_clear_error (&error);
+       g_assert (ret == FALSE);
+
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 static void
@@ -400,6 +445,7 @@ test_run_and_cancel (Test *test, gconstpointer unused)
        gchar *script;
        const gchar *argv[] = { "15" };
        GCancellable *cancellable;
+       gboolean ret;
 
        cancellable = g_cancellable_new ();
 
@@ -412,9 +458,51 @@ test_run_and_cancel (Test *test, gconstpointer unused)
        egg_test_wait_until (500);
 
        g_assert (test->result);
-       _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_clear_error (&error);
+       g_assert (ret == FALSE);
+
+       g_object_unref (cancellable);
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
+}
+
+static void
+on_process_output_cancel (GcrGnupgProcess *process, GByteArray *buffer, gpointer user_data)
+{
+       GCancellable *cancellable = G_CANCELLABLE (user_data);
+       g_cancellable_cancel (cancellable);
+}
+
+static void
+test_run_and_cancel_later (Test *test, gconstpointer unused)
+{
+       GError *error = NULL;
+       gchar *script;
+       const gchar *argv[] = { "15" };
+       GCancellable *cancellable;
+       gboolean ret;
+
+       cancellable = g_cancellable_new ();
+
+       script = build_script_path ("mock-simple-output");
+       test->process = _gcr_gnupg_process_new (NULL, script);
+       g_signal_connect (test->process, "output-data", G_CALLBACK (on_process_output_cancel), cancellable);
+       g_free (script);
+
+       _gcr_gnupg_process_run_async (test->process, argv, NULL, 0, cancellable, on_async_ready, test);
+       egg_test_wait_until (500);
+
+       g_assert (test->result);
+       ret = _gcr_gnupg_process_run_finish (test->process, test->result, &error);
        g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
        g_clear_error (&error);
+       g_assert (ret == FALSE);
+
+       g_object_unref (cancellable);
+       g_clear_object (&test->result);
+       g_clear_object (&test->process);
 }
 
 int
@@ -441,6 +529,7 @@ main (int argc, char **argv)
        g_test_add ("/gcr/gnupg-process/run_fail_exit", Test, NULL, setup, test_run_fail_exit, teardown);
        g_test_add ("/gcr/gnupg-process/run_fail_signal", Test, NULL, setup, test_run_fail_signal, teardown);
        g_test_add ("/gcr/gnupg-process/run_and_cancel", Test, NULL, setup, test_run_and_cancel, teardown);
+       g_test_add ("/gcr/gnupg-process/run_and_cancel_later", Test, NULL, setup, test_run_and_cancel_later, teardown);
 
        return egg_tests_run_in_thread_with_loop ();
 }