#include #include #ifdef G_OS_UNIX #include #include #include #include #endif #ifdef G_OS_WIN32 #define LINEEND "\r\n" #define EXEEXT ".exe" #else #define LINEEND "\n" #define EXEEXT #endif static GPtrArray * get_test_subprocess_args (const char *mode, ...) G_GNUC_NULL_TERMINATED; static GPtrArray * get_test_subprocess_args (const char *mode, ...) { GPtrArray *ret; char *path; const char *binname; va_list args; gpointer arg; ret = g_ptr_array_new_with_free_func (g_free); #ifdef G_OS_WIN32 binname = "gsubprocess-testprog.exe"; #else binname = "gsubprocess-testprog"; #endif path = g_test_build_filename (G_TEST_BUILT, binname, NULL); g_ptr_array_add (ret, path); g_ptr_array_add (ret, g_strdup (mode)); va_start (args, mode); while ((arg = va_arg (args, gpointer)) != NULL) g_ptr_array_add (ret, g_strdup (arg)); va_end (args); g_ptr_array_add (ret, NULL); return ret; } static void test_noop (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; const gchar *id; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); id = g_subprocess_get_identifier (proc); g_assert (id != NULL); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); g_assert (g_subprocess_get_successful (proc)); g_object_unref (proc); } static void check_ready (GObject *source, GAsyncResult *res, gpointer user_data) { gboolean ret; GError *error = NULL; ret = g_subprocess_wait_check_finish (G_SUBPROCESS (source), res, &error); g_assert (ret); g_assert_no_error (error); g_object_unref (source); } static void test_noop_all_to_null (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check_async (proc, NULL, check_ready, NULL); } static void test_noop_no_wait (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_object_unref (proc); } static void test_noop_stdin_inherit (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDIN_INHERIT, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); g_object_unref (proc); } #ifdef G_OS_UNIX static void test_search_path (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; proc = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE, error, "true", NULL); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); g_object_unref (proc); } #endif static void test_exit1 (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("exit1", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_object_unref (proc); } typedef struct { GMainLoop *loop; GCancellable *cancellable; gboolean cb_called; } TestExit1CancelData; static gboolean test_exit1_cancel_idle_quit_cb (gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); return G_SOURCE_REMOVE; } static void test_exit1_cancel_wait_check_cb (GObject *source, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (source); TestExit1CancelData *data = user_data; gboolean ret; GError *error = NULL; g_assert_false (data->cb_called); data->cb_called = TRUE; ret = g_subprocess_wait_check_finish (subprocess, result, &error); g_assert (!ret); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&error); g_idle_add (test_exit1_cancel_idle_quit_cb, data->loop); } static void test_exit1_cancel (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; TestExit1CancelData data = { 0 }; g_test_bug ("786456"); args = get_test_subprocess_args ("exit1", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); data.loop = g_main_loop_new (NULL, FALSE); data.cancellable = g_cancellable_new (); g_subprocess_wait_check_async (proc, data.cancellable, test_exit1_cancel_wait_check_cb, &data); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_cancellable_cancel (data.cancellable); g_main_loop_run (data.loop); g_object_unref (proc); g_main_loop_unref (data.loop); g_clear_object (&data.cancellable); } static void test_exit1_cancel_in_cb_wait_check_cb (GObject *source, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (source); TestExit1CancelData *data = user_data; gboolean ret; GError *error = NULL; g_assert_false (data->cb_called); data->cb_called = TRUE; ret = g_subprocess_wait_check_finish (subprocess, result, &error); g_assert (!ret); g_assert_error (error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (&error); g_cancellable_cancel (data->cancellable); g_idle_add (test_exit1_cancel_idle_quit_cb, data->loop); } static void test_exit1_cancel_in_cb (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; TestExit1CancelData data = { 0 }; g_test_bug ("786456"); args = get_test_subprocess_args ("exit1", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); data.loop = g_main_loop_new (NULL, FALSE); data.cancellable = g_cancellable_new (); g_subprocess_wait_check_async (proc, data.cancellable, test_exit1_cancel_in_cb_wait_check_cb, &data); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_main_loop_run (data.loop); g_object_unref (proc); g_main_loop_unref (data.loop); g_clear_object (&data.cancellable); } static gchar * splice_to_string (GInputStream *stream, GError **error) { GMemoryOutputStream *buffer = NULL; char *ret = NULL; buffer = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free); if (g_output_stream_splice ((GOutputStream*)buffer, stream, 0, NULL, error) < 0) goto out; if (!g_output_stream_write ((GOutputStream*)buffer, "\0", 1, NULL, error)) goto out; if (!g_output_stream_close ((GOutputStream*)buffer, NULL, error)) goto out; ret = g_memory_output_stream_steal_data (buffer); out: g_clear_object (&buffer); return ret; } static void test_echo1 (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GInputStream *stdout; gchar *result; args = get_test_subprocess_args ("echo", "hello", "world!", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDOUT_PIPE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout, error); g_assert_no_error (local_error); g_assert_cmpstr (result, ==, "hello" LINEEND "world!" LINEEND); g_free (result); g_object_unref (proc); } #ifdef G_OS_UNIX static void test_echo_merged (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GInputStream *stdout; gchar *result; args = get_test_subprocess_args ("echo-stdout-and-stderr", "merge", "this", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout, error); g_assert_no_error (local_error); g_assert_cmpstr (result, ==, "merge\nmerge\nthis\nthis\n"); g_free (result); g_object_unref (proc); } #endif typedef struct { guint events_pending; GMainLoop *loop; } TestCatData; static void test_cat_on_input_splice_complete (GObject *object, GAsyncResult *result, gpointer user_data) { TestCatData *data = user_data; GError *error = NULL; (void)g_output_stream_splice_finish ((GOutputStream*)object, result, &error); g_assert_no_error (error); data->events_pending--; if (data->events_pending == 0) g_main_loop_quit (data->loop); } static void test_cat_utf8 (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GBytes *input_buf; GBytes *output_buf; GInputStream *input_buf_stream = NULL; GOutputStream *output_buf_stream = NULL; GOutputStream *stdin_stream = NULL; GInputStream *stdout_stream = NULL; TestCatData data; memset (&data, 0, sizeof (data)); data.loop = g_main_loop_new (NULL, TRUE); args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdin_stream = g_subprocess_get_stdin_pipe (proc); stdout_stream = g_subprocess_get_stdout_pipe (proc); input_buf = g_bytes_new_static ("hello, world!", strlen ("hello, world!")); input_buf_stream = g_memory_input_stream_new_from_bytes (input_buf); g_bytes_unref (input_buf); output_buf_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); g_output_stream_splice_async (stdin_stream, input_buf_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, test_cat_on_input_splice_complete, &data); data.events_pending++; g_output_stream_splice_async (output_buf_stream, stdout_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, test_cat_on_input_splice_complete, &data); data.events_pending++; g_main_loop_run (data.loop); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); output_buf = g_memory_output_stream_steal_as_bytes ((GMemoryOutputStream*)output_buf_stream); g_assert_cmpmem (g_bytes_get_data (output_buf, NULL), g_bytes_get_size (output_buf), "hello, world!", 13); g_bytes_unref (output_buf); g_main_loop_unref (data.loop); g_object_unref (input_buf_stream); g_object_unref (output_buf_stream); g_object_unref (proc); } static gpointer cancel_soon (gpointer user_data) { GCancellable *cancellable = user_data; g_usleep (G_TIME_SPAN_SECOND); g_cancellable_cancel (cancellable); g_object_unref (cancellable); return NULL; } static void test_cat_eof (void) { GCancellable *cancellable; GError *error = NULL; GSubprocess *cat; gboolean result; gchar buffer; gssize s; #ifdef G_OS_WIN32 g_test_skip ("This test has not been ported to Win32"); return; #endif /* Spawn 'cat' */ cat = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "cat", NULL); g_assert_no_error (error); g_assert (cat); /* Make sure that reading stdout blocks (until we cancel) */ cancellable = g_cancellable_new (); g_thread_unref (g_thread_new ("cancel thread", cancel_soon, g_object_ref (cancellable))); s = g_input_stream_read (g_subprocess_get_stdout_pipe (cat), &buffer, sizeof buffer, cancellable, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert_cmpint (s, ==, -1); g_object_unref (cancellable); g_clear_error (&error); /* Close the stream (EOF on cat's stdin) */ result = g_output_stream_close (g_subprocess_get_stdin_pipe (cat), NULL, &error); g_assert_no_error (error); g_assert (result); /* Now check that reading cat's stdout gets us an EOF (since it quit) */ s = g_input_stream_read (g_subprocess_get_stdout_pipe (cat), &buffer, sizeof buffer, NULL, &error); g_assert_no_error (error); g_assert (!s); /* Check that the process has exited as a result of the EOF */ result = g_subprocess_wait (cat, NULL, &error); g_assert_no_error (error); g_assert (g_subprocess_get_if_exited (cat)); g_assert_cmpint (g_subprocess_get_exit_status (cat), ==, 0); g_assert (result); g_object_unref (cat); } typedef struct { guint events_pending; gboolean caught_error; GError *error; GMainLoop *loop; gint counter; GOutputStream *first_stdin; } TestMultiSpliceData; static void on_one_multi_splice_done (GObject *obj, GAsyncResult *res, gpointer user_data) { TestMultiSpliceData *data = user_data; if (!data->caught_error) { if (g_output_stream_splice_finish ((GOutputStream*)obj, res, &data->error) < 0) data->caught_error = TRUE; } data->events_pending--; if (data->events_pending == 0) g_main_loop_quit (data->loop); } static gboolean on_idle_multisplice (gpointer user_data) { TestMultiSpliceData *data = user_data; /* We write 2^1 + 2^2 ... + 2^10 or 2047 copies of "Hello World!\n" * ultimately */ if (data->counter >= 2047 || data->caught_error) { if (!g_output_stream_close (data->first_stdin, NULL, &data->error)) data->caught_error = TRUE; data->events_pending--; if (data->events_pending == 0) { g_main_loop_quit (data->loop); } return FALSE; } else { int i; for (i = 0; i < data->counter; i++) { gsize bytes_written; if (!g_output_stream_write_all (data->first_stdin, "hello world!\n", strlen ("hello world!\n"), &bytes_written, NULL, &data->error)) { data->caught_error = TRUE; return FALSE; } } data->counter *= 2; return TRUE; } } static void on_subprocess_exited (GObject *object, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (object); TestMultiSpliceData *data = user_data; GError *error = NULL; if (!g_subprocess_wait_finish (subprocess, result, &error)) { if (!data->caught_error) { data->caught_error = TRUE; g_propagate_error (&data->error, error); } } g_spawn_check_exit_status (g_subprocess_get_exit_status (subprocess), &error); g_assert_no_error (error); data->events_pending--; if (data->events_pending == 0) g_main_loop_quit (data->loop); } static void test_multi_1 (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocessLauncher *launcher; GSubprocess *first; GSubprocess *second; GSubprocess *third; GOutputStream *first_stdin; GInputStream *first_stdout; GOutputStream *second_stdin; GInputStream *second_stdout; GOutputStream *third_stdin; GInputStream *third_stdout; GOutputStream *membuf; TestMultiSpliceData data; int splice_flags = G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET; args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE); first = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_assert_no_error (local_error); second = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_assert_no_error (local_error); third = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_assert_no_error (local_error); g_ptr_array_free (args, TRUE); membuf = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); first_stdin = g_subprocess_get_stdin_pipe (first); first_stdout = g_subprocess_get_stdout_pipe (first); second_stdin = g_subprocess_get_stdin_pipe (second); second_stdout = g_subprocess_get_stdout_pipe (second); third_stdin = g_subprocess_get_stdin_pipe (third); third_stdout = g_subprocess_get_stdout_pipe (third); memset (&data, 0, sizeof (data)); data.loop = g_main_loop_new (NULL, TRUE); data.counter = 1; data.first_stdin = first_stdin; data.events_pending++; g_output_stream_splice_async (second_stdin, first_stdout, splice_flags, G_PRIORITY_DEFAULT, NULL, on_one_multi_splice_done, &data); data.events_pending++; g_output_stream_splice_async (third_stdin, second_stdout, splice_flags, G_PRIORITY_DEFAULT, NULL, on_one_multi_splice_done, &data); data.events_pending++; g_output_stream_splice_async (membuf, third_stdout, splice_flags, G_PRIORITY_DEFAULT, NULL, on_one_multi_splice_done, &data); data.events_pending++; g_timeout_add (250, on_idle_multisplice, &data); data.events_pending++; g_subprocess_wait_async (first, NULL, on_subprocess_exited, &data); data.events_pending++; g_subprocess_wait_async (second, NULL, on_subprocess_exited, &data); data.events_pending++; g_subprocess_wait_async (third, NULL, on_subprocess_exited, &data); g_main_loop_run (data.loop); g_assert (!data.caught_error); g_assert_no_error (data.error); g_assert_cmpint (g_memory_output_stream_get_data_size ((GMemoryOutputStream*)membuf), ==, 26611); g_main_loop_unref (data.loop); g_object_unref (membuf); g_object_unref (launcher); g_object_unref (first); g_object_unref (second); g_object_unref (third); } typedef struct { GSubprocessFlags flags; gboolean is_utf8; gboolean running; GError *error; } TestAsyncCommunicateData; static void on_communicate_complete (GObject *proc, GAsyncResult *result, gpointer user_data) { TestAsyncCommunicateData *data = user_data; GBytes *stdout_bytes = NULL, *stderr_bytes = NULL; char *stdout_str = NULL, *stderr_str = NULL; const guint8 *stdout_data; gsize stdout_len; data->running = FALSE; if (data->is_utf8) (void) g_subprocess_communicate_utf8_finish ((GSubprocess*)proc, result, &stdout_str, &stderr_str, &data->error); else (void) g_subprocess_communicate_finish ((GSubprocess*)proc, result, &stdout_bytes, &stderr_bytes, &data->error); if (data->error) return; if (data->flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE) { if (data->is_utf8) { stdout_data = (guint8*)stdout_str; stdout_len = strlen (stdout_str); } else stdout_data = g_bytes_get_data (stdout_bytes, &stdout_len); g_assert_cmpmem (stdout_data, stdout_len, "# hello world\n", 14); } else { g_assert_null (stdout_str); g_assert_null (stdout_bytes); } if (data->flags & G_SUBPROCESS_FLAGS_STDERR_PIPE) { if (data->is_utf8) g_assert_nonnull (stderr_str); else g_assert_nonnull (stderr_bytes); } else { g_assert_null (stderr_str); g_assert_null (stderr_bytes); } g_clear_pointer (&stdout_bytes, g_bytes_unref); g_clear_pointer (&stderr_bytes, g_bytes_unref); g_free (stdout_str); g_free (stderr_str); } /* Test g_subprocess_communicate_async() works correctly with a variety of flags, * as passed in via @test_data. */ static void test_communicate_async (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, }; GSubprocess *proc; GCancellable *cancellable = NULL; GBytes *input; const char *hellostring; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ hellostring = "# hello world\n"; input = g_bytes_new_static (hellostring, strlen (hellostring)); g_subprocess_communicate_async (proc, input, cancellable, on_communicate_complete, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_no_error (data.error); g_bytes_unref (input); g_object_unref (proc); } /* Test g_subprocess_communicate() works correctly with a variety of flags, * as passed in via @test_data. */ static void test_communicate (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; GBytes *input; const gchar *hellostring; GBytes *stdout_bytes, *stderr_bytes; const gchar *stdout_data; gsize stdout_len; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ hellostring = "# hello world\n"; input = g_bytes_new_static (hellostring, strlen (hellostring)); g_subprocess_communicate (proc, input, cancellable, &stdout_bytes, &stderr_bytes, &error); g_assert_no_error (error); if (flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE) { stdout_data = g_bytes_get_data (stdout_bytes, &stdout_len); g_assert_cmpmem (stdout_data, stdout_len, "# hello world\n", 14); } else g_assert_null (stdout_bytes); if (flags & G_SUBPROCESS_FLAGS_STDERR_PIPE) g_assert_nonnull (stderr_bytes); else g_assert_null (stderr_bytes); g_bytes_unref (input); g_clear_pointer (&stdout_bytes, g_bytes_unref); g_clear_pointer (&stderr_bytes, g_bytes_unref); g_object_unref (proc); } /* Test g_subprocess_communicate_utf8_async() works correctly with a variety of * flags, as passed in via @test_data. */ static void test_communicate_utf8_async (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, }; GSubprocess *proc; GCancellable *cancellable = NULL; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); data.is_utf8 = TRUE; g_subprocess_communicate_utf8_async (proc, "# hello world\n", cancellable, on_communicate_complete, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_no_error (data.error); g_object_unref (proc); } /* Test g_subprocess_communicate_utf8() works correctly with a variety of flags, * as passed in via @test_data. */ static void test_communicate_utf8 (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; const gchar *stdin_buf; gchar *stdout_buf, *stderr_buf; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ stdin_buf = "# hello world\n"; g_subprocess_communicate_utf8 (proc, stdin_buf, cancellable, &stdout_buf, &stderr_buf, &error); g_assert_no_error (error); if (flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE) g_assert_cmpstr (stdout_buf, ==, "# hello world\n"); else g_assert_null (stdout_buf); if (flags & G_SUBPROCESS_FLAGS_STDERR_PIPE) g_assert_nonnull (stderr_buf); else g_assert_null (stderr_buf); g_free (stdout_buf); g_free (stderr_buf); g_object_unref (proc); } static void test_communicate_nothing (void) { GError *error = NULL; GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; gchar *stdout_buf; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); g_subprocess_communicate_utf8 (proc, "", cancellable, &stdout_buf, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (stdout_buf, ==, ""); g_free (stdout_buf); g_object_unref (proc); } static void test_communicate_utf8_invalid (void) { GSubprocessFlags flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE; GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, }; GSubprocess *proc; GCancellable *cancellable = NULL; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); data.is_utf8 = TRUE; g_subprocess_communicate_utf8_async (proc, "\xFF\xFF", cancellable, on_communicate_complete, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_FAILED); g_error_free (data.error); g_object_unref (proc); } static gboolean send_terminate (gpointer user_data) { GSubprocess *proc = user_data; g_subprocess_force_exit (proc); return FALSE; } static void on_request_quit_exited (GObject *object, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (object); GError *error = NULL; g_subprocess_wait_finish (subprocess, result, &error); g_assert_no_error (error); #ifdef G_OS_UNIX g_assert (g_subprocess_get_if_signaled (subprocess)); g_assert (g_subprocess_get_term_sig (subprocess) == 9); #endif g_spawn_check_exit_status (g_subprocess_get_status (subprocess), &error); g_assert (error != NULL); g_clear_error (&error); g_main_loop_quit ((GMainLoop*)user_data); } static void test_terminate (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GMainLoop *loop; args = get_test_subprocess_args ("sleep-forever", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); loop = g_main_loop_new (NULL, TRUE); g_subprocess_wait_async (proc, NULL, on_request_quit_exited, loop); g_timeout_add_seconds (3, send_terminate, proc); g_main_loop_run (loop); g_main_loop_unref (loop); g_object_unref (proc); } #ifdef G_OS_UNIX static gboolean send_signal (gpointer user_data) { GSubprocess *proc = user_data; g_subprocess_send_signal (proc, SIGKILL); return FALSE; } static void test_signal (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GMainLoop *loop; args = get_test_subprocess_args ("sleep-forever", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); loop = g_main_loop_new (NULL, TRUE); g_subprocess_wait_async (proc, NULL, on_request_quit_exited, loop); g_timeout_add_seconds (3, send_signal, proc); g_main_loop_run (loop); g_main_loop_unref (loop); g_object_unref (proc); } #endif static void test_env (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GInputStream *stdout; gchar *result; gchar *envp[] = { "ONE=1", "TWO=1", "THREE=3", "FOUR=1", NULL }; gchar **split; args = get_test_subprocess_args ("env", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_environ (launcher, envp); g_subprocess_launcher_setenv (launcher, "TWO", "2", TRUE); g_subprocess_launcher_setenv (launcher, "THREE", "1", FALSE); g_subprocess_launcher_unsetenv (launcher, "FOUR"); g_assert_null (g_subprocess_launcher_getenv (launcher, "FOUR")); proc = g_subprocess_launcher_spawn (launcher, error, args->pdata[0], "env", NULL); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout, error); split = g_strsplit (result, "\n", -1); g_assert_cmpstr (g_environ_getenv (split, "ONE"), ==, "1"); g_assert_cmpstr (g_environ_getenv (split, "TWO"), ==, "2"); g_assert_cmpstr (g_environ_getenv (split, "THREE"), ==, "3"); g_assert_null (g_environ_getenv (split, "FOUR")); g_strfreev (split); g_free (result); g_object_unref (proc); g_object_unref (launcher); } /* Test that explicitly inheriting and modifying the parent process’ * environment works. */ static void test_env_inherit (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GInputStream *stdout; gchar *result; gchar **split; g_setenv ("TEST_ENV_INHERIT1", "1", TRUE); g_setenv ("TEST_ENV_INHERIT2", "2", TRUE); args = get_test_subprocess_args ("env", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_environ (launcher, NULL); g_subprocess_launcher_setenv (launcher, "TWO", "2", TRUE); g_subprocess_launcher_unsetenv (launcher, "TEST_ENV_INHERIT1"); g_assert_null (g_subprocess_launcher_getenv (launcher, "TEST_ENV_INHERIT1")); g_assert_cmpstr (g_subprocess_launcher_getenv (launcher, "TEST_ENV_INHERIT2"), ==, "2"); g_assert_cmpstr (g_subprocess_launcher_getenv (launcher, "TWO"), ==, "2"); proc = g_subprocess_launcher_spawn (launcher, error, args->pdata[0], "env", NULL); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout, error); split = g_strsplit (result, "\n", -1); g_assert_null (g_environ_getenv (split, "TEST_ENV_INHERIT1")); g_assert_cmpstr (g_environ_getenv (split, "TEST_ENV_INHERIT2"), ==, "2"); g_assert_cmpstr (g_environ_getenv (split, "TWO"), ==, "2"); g_strfreev (split); g_free (result); g_object_unref (proc); g_object_unref (launcher); } static void test_cwd (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GInputStream *stdout; gchar *result; const char *basename; args = get_test_subprocess_args ("cwd", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_cwd (launcher, "/tmp"); proc = g_subprocess_launcher_spawnv (launcher, (const char * const *)args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout, error); basename = g_strrstr (result, "/"); g_assert (basename != NULL); g_assert_cmpstr (basename, ==, "/tmp" LINEEND); g_free (result); g_object_unref (proc); g_object_unref (launcher); } #ifdef G_OS_UNIX static void test_stdout_file (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GFile *tmpfile; GFileIOStream *iostream; GOutputStream *stdin; const char *test_data = "this is some test data\n"; char *tmp_contents; char *tmp_file_path; tmpfile = g_file_new_tmp ("gsubprocessXXXXXX", &iostream, error); g_assert_no_error (local_error); g_clear_object (&iostream); tmp_file_path = g_file_get_path (tmpfile); args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE); g_subprocess_launcher_set_stdout_file_path (launcher, tmp_file_path); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdin = g_subprocess_get_stdin_pipe (proc); g_output_stream_write_all (stdin, test_data, strlen (test_data), NULL, NULL, error); g_assert_no_error (local_error); g_output_stream_close (stdin, NULL, error); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_object_unref (launcher); g_object_unref (proc); g_file_load_contents (tmpfile, NULL, &tmp_contents, NULL, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (test_data, ==, tmp_contents); g_free (tmp_contents); (void) g_file_delete (tmpfile, NULL, NULL); g_object_unref (tmpfile); g_free (tmp_file_path); } static void test_stdout_fd (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GFile *tmpfile; GFileIOStream *iostream; GFileDescriptorBased *descriptor_stream; GOutputStream *stdin; const char *test_data = "this is some test data\n"; char *tmp_contents; tmpfile = g_file_new_tmp ("gsubprocessXXXXXX", &iostream, error); g_assert_no_error (local_error); args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE); descriptor_stream = G_FILE_DESCRIPTOR_BASED (g_io_stream_get_output_stream (G_IO_STREAM (iostream))); g_subprocess_launcher_take_stdout_fd (launcher, dup (g_file_descriptor_based_get_fd (descriptor_stream))); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_clear_object (&iostream); stdin = g_subprocess_get_stdin_pipe (proc); g_output_stream_write_all (stdin, test_data, strlen (test_data), NULL, NULL, error); g_assert_no_error (local_error); g_output_stream_close (stdin, NULL, error); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_object_unref (launcher); g_object_unref (proc); g_file_load_contents (tmpfile, NULL, &tmp_contents, NULL, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (test_data, ==, tmp_contents); g_free (tmp_contents); (void) g_file_delete (tmpfile, NULL, NULL); g_object_unref (tmpfile); } static void child_setup (gpointer user_data) { dup2 (GPOINTER_TO_INT (user_data), 1); } static void test_child_setup (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GFile *tmpfile; GFileIOStream *iostream; GOutputStream *stdin; const char *test_data = "this is some test data\n"; char *tmp_contents; int fd; tmpfile = g_file_new_tmp ("gsubprocessXXXXXX", &iostream, error); g_assert_no_error (local_error); fd = g_file_descriptor_based_get_fd (G_FILE_DESCRIPTOR_BASED (g_io_stream_get_output_stream (G_IO_STREAM (iostream)))); args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE); g_subprocess_launcher_set_child_setup (launcher, child_setup, GINT_TO_POINTER (fd), NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_clear_object (&iostream); stdin = g_subprocess_get_stdin_pipe (proc); g_output_stream_write_all (stdin, test_data, strlen (test_data), NULL, NULL, error); g_assert_no_error (local_error); g_output_stream_close (stdin, NULL, error); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_object_unref (launcher); g_object_unref (proc); g_file_load_contents (tmpfile, NULL, &tmp_contents, NULL, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (test_data, ==, tmp_contents); g_free (tmp_contents); (void) g_file_delete (tmpfile, NULL, NULL); g_object_unref (tmpfile); } static void test_pass_fd (void) { GError *local_error = NULL; GError **error = &local_error; GInputStream *child_input; GDataInputStream *child_datainput; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; int basic_pipefds[2]; int needdup_pipefds[2]; char *buf; gsize len; char *basic_fd_str; char *needdup_fd_str; g_unix_open_pipe (basic_pipefds, FD_CLOEXEC, error); g_assert_no_error (local_error); g_unix_open_pipe (needdup_pipefds, FD_CLOEXEC, error); g_assert_no_error (local_error); basic_fd_str = g_strdup_printf ("%d", basic_pipefds[1]); needdup_fd_str = g_strdup_printf ("%d", needdup_pipefds[1] + 1); args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_take_fd (launcher, basic_pipefds[1], basic_pipefds[1]); g_subprocess_launcher_take_fd (launcher, needdup_pipefds[1], needdup_pipefds[1] + 1); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_free (basic_fd_str); g_free (needdup_fd_str); child_input = g_unix_input_stream_new (basic_pipefds[0], TRUE); child_datainput = g_data_input_stream_new (child_input); buf = g_data_input_stream_read_line_utf8 (child_datainput, &len, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (buf, ==, "hello world"); g_object_unref (child_datainput); g_object_unref (child_input); g_free (buf); child_input = g_unix_input_stream_new (needdup_pipefds[0], TRUE); child_datainput = g_data_input_stream_new (child_input); buf = g_data_input_stream_read_line_utf8 (child_datainput, &len, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (buf, ==, "hello world"); g_free (buf); g_object_unref (child_datainput); g_object_unref (child_input); g_object_unref (launcher); g_object_unref (proc); } #endif static void test_launcher_environment (void) { GSubprocessLauncher *launcher; GError *error = NULL; GSubprocess *proc; GPtrArray *args; gchar *out; g_setenv ("A", "B", TRUE); g_setenv ("C", "D", TRUE); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); /* unset a variable */ g_subprocess_launcher_unsetenv (launcher, "A"); /* and set a diffferent one */ g_subprocess_launcher_setenv (launcher, "E", "F", TRUE); args = get_test_subprocess_args ("printenv", "A", "C", "E", NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar **) args->pdata, &error); g_assert_no_error (error); g_assert (proc); g_subprocess_communicate_utf8 (proc, NULL, NULL, &out, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (out, ==, "C=D\nE=F\n"); g_free (out); g_object_unref (proc); g_object_unref (launcher); g_ptr_array_unref (args); } int main (int argc, char **argv) { const struct { const gchar *subtest; GSubprocessFlags flags; } flags_vectors[] = { { "", G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE }, { "/no-pipes", G_SUBPROCESS_FLAGS_NONE }, { "/separate-stderr", G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE }, { "/stdout-only", G_SUBPROCESS_FLAGS_STDOUT_PIPE }, { "/stderr-only", G_SUBPROCESS_FLAGS_STDERR_PIPE }, { "/stdout-silence", G_SUBPROCESS_FLAGS_STDOUT_SILENCE }, }; gsize i; g_test_init (&argc, &argv, NULL); g_test_bug_base ("https://bugzilla.gnome.org/"); g_test_add_func ("/gsubprocess/noop", test_noop); g_test_add_func ("/gsubprocess/noop-all-to-null", test_noop_all_to_null); g_test_add_func ("/gsubprocess/noop-no-wait", test_noop_no_wait); g_test_add_func ("/gsubprocess/noop-stdin-inherit", test_noop_stdin_inherit); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/search-path", test_search_path); g_test_add_func ("/gsubprocess/signal", test_signal); #endif g_test_add_func ("/gsubprocess/exit1", test_exit1); g_test_add_func ("/gsubprocess/exit1/cancel", test_exit1_cancel); g_test_add_func ("/gsubprocess/exit1/cancel_in_cb", test_exit1_cancel_in_cb); g_test_add_func ("/gsubprocess/echo1", test_echo1); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/echo-merged", test_echo_merged); #endif g_test_add_func ("/gsubprocess/cat-utf8", test_cat_utf8); g_test_add_func ("/gsubprocess/cat-eof", test_cat_eof); g_test_add_func ("/gsubprocess/multi1", test_multi_1); /* Add various tests for g_subprocess_communicate() with different flags. */ for (i = 0; i < G_N_ELEMENTS (flags_vectors); i++) { gchar *test_path = NULL; test_path = g_strdup_printf ("/gsubprocess/communicate%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/async%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_async); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/utf8%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_utf8); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/utf8/async%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_utf8_async); g_free (test_path); } g_test_add_func ("/gsubprocess/communicate/utf8/invalid", test_communicate_utf8_invalid); g_test_add_func ("/gsubprocess/communicate/nothing", test_communicate_nothing); g_test_add_func ("/gsubprocess/terminate", test_terminate); g_test_add_func ("/gsubprocess/env", test_env); g_test_add_func ("/gsubprocess/env/inherit", test_env_inherit); g_test_add_func ("/gsubprocess/cwd", test_cwd); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file); g_test_add_func ("/gsubprocess/stdout-fd", test_stdout_fd); g_test_add_func ("/gsubprocess/child-setup", test_child_setup); g_test_add_func ("/gsubprocess/pass-fd", test_pass_fd); #endif g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment); return g_test_run (); }