(root)/
glib-2.79.0/
glib/
tests/
spawn-singlethread.c
       1  /* 
       2   * Copyright (C) 2011 Red Hat, Inc.
       3   *
       4   * SPDX-License-Identifier: LicenseRef-old-glib-tests
       5   *
       6   * This work is provided "as is"; redistribution and modification
       7   * in whole or in part, in any medium, physical or electronic is
       8   * permitted without restriction.
       9   *
      10   * This work is distributed in the hope that it will be useful,
      11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      13   *
      14   * In no event shall the authors or contributors be liable for any
      15   * direct, indirect, incidental, special, exemplary, or consequential
      16   * damages (including, but not limited to, procurement of substitute
      17   * goods or services; loss of use, data, or profits; or business
      18   * interruption) however caused and on any theory of liability, whether
      19   * in contract, strict liability, or tort (including negligence or
      20   * otherwise) arising in any way out of the use of this software, even
      21   * if advised of the possibility of such damage.
      22   *
      23   * Author: Colin Walters <walters@verbum.org> 
      24   */
      25  
      26  #include "config.h"
      27  
      28  #include <glib.h>
      29  #include <locale.h>
      30  #include <string.h>
      31  #include <fcntl.h>
      32  #include <glib/gstdio.h>
      33  
      34  #ifdef G_OS_UNIX
      35  #include <glib-unix.h>
      36  #include <sys/types.h>
      37  #include <sys/socket.h>
      38  #include <sys/stat.h>
      39  #include <unistd.h>
      40  #endif
      41  
      42  #ifdef G_OS_WIN32
      43  #include <winsock2.h>
      44  #include <io.h>
      45  #define LINEEND "\r\n"
      46  #else
      47  #define LINEEND "\n"
      48  #endif
      49  
      50  /* MinGW builds are likely done using a BASH-style shell, so run the
      51   * normal script there, as on non-Windows builds, as it is more likely
      52   * that one will run 'make check' in such shells to test the code
      53   */
      54  #if defined (G_OS_WIN32) && defined (_MSC_VER)
      55  #define SCRIPT_EXT ".bat"
      56  #else
      57  #define SCRIPT_EXT
      58  #endif
      59  
      60  static char *echo_prog_path;
      61  static char *echo_script_path;
      62  
      63  typedef struct {
      64    GMainLoop *loop;
      65    gboolean child_exited;
      66    gboolean stdout_done;
      67    GString *stdout_buf;
      68  } SpawnAsyncMultithreadedData;
      69  
      70  static gboolean
      71  on_child_exited (GPid     pid,
      72  		 gint     status,
      73  		 gpointer datap)
      74  {
      75    SpawnAsyncMultithreadedData *data = datap;
      76  
      77    data->child_exited = TRUE;
      78    if (data->child_exited && data->stdout_done)
      79      g_main_loop_quit (data->loop);
      80    
      81    return G_SOURCE_REMOVE;
      82  }
      83  
      84  static gboolean
      85  on_child_stdout (GIOChannel   *channel,
      86  		 GIOCondition  condition,
      87  		 gpointer      datap)
      88  {
      89    char buf[1024];
      90    GError *error = NULL;
      91    gsize bytes_read;
      92    SpawnAsyncMultithreadedData *data = datap;
      93  
      94    if (condition & G_IO_IN)
      95      {
      96        GIOStatus status;
      97        status = g_io_channel_read_chars (channel, buf, sizeof (buf), &bytes_read, &error);
      98        g_assert_no_error (error);
      99        g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
     100        if (status == G_IO_STATUS_EOF)
     101  	data->stdout_done = TRUE;
     102      }
     103    if (condition & G_IO_HUP)
     104      data->stdout_done = TRUE;
     105    if (condition & G_IO_ERR)
     106      g_error ("Error reading from child stdin");
     107  
     108    if (data->child_exited && data->stdout_done)
     109      g_main_loop_quit (data->loop);
     110  
     111    return !data->stdout_done;
     112  }
     113  
     114  static void
     115  test_spawn_async (void)
     116  {
     117    int tnum = 1;
     118    GError *error = NULL;
     119    GPtrArray *argv;
     120    char *arg;
     121    GPid pid;
     122    GMainContext *context;
     123    GMainLoop *loop;
     124    GIOChannel *channel;
     125    GSource *source;
     126    int child_stdout_fd;
     127    SpawnAsyncMultithreadedData data;
     128  
     129    context = g_main_context_new ();
     130    loop = g_main_loop_new (context, TRUE);
     131  
     132    arg = g_strdup_printf ("thread %d", tnum);
     133  
     134    argv = g_ptr_array_new ();
     135    g_ptr_array_add (argv, echo_prog_path);
     136    g_ptr_array_add (argv, arg);
     137    g_ptr_array_add (argv, NULL);
     138  
     139    g_spawn_async_with_pipes (NULL, (char**)argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL,
     140  			    &child_stdout_fd, NULL, &error);
     141    g_assert_no_error (error);
     142    g_ptr_array_free (argv, TRUE);
     143  
     144    data.loop = loop;
     145    data.stdout_done = FALSE;
     146    data.child_exited = FALSE;
     147    data.stdout_buf = g_string_new (0);
     148  
     149    source = g_child_watch_source_new (pid);
     150    g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
     151    g_source_attach (source, context);
     152    g_source_unref (source);
     153  
     154    channel = g_io_channel_unix_new (child_stdout_fd);
     155    source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
     156    g_source_set_callback (source, (GSourceFunc)on_child_stdout, &data, NULL);
     157    g_source_attach (source, context);
     158    g_source_unref (source);
     159  
     160    g_main_loop_run (loop);
     161  
     162    g_assert (data.child_exited);
     163    g_assert (data.stdout_done);
     164    g_assert_cmpstr (data.stdout_buf->str, ==, arg);
     165    g_string_free (data.stdout_buf, TRUE);
     166  
     167    g_io_channel_unref (channel);
     168    g_main_context_unref (context);
     169    g_main_loop_unref (loop);
     170  
     171    g_free (arg);
     172  }
     173  
     174  /* Windows close() causes failure through the Invalid Parameter Handler
     175   * Routine if the file descriptor does not exist.
     176   */
     177  static void
     178  safe_close (int fd)
     179  {
     180    if (fd >= 0)
     181      close (fd);
     182  }
     183  
     184  /* Test g_spawn_async_with_fds() with a variety of different inputs */
     185  static void
     186  test_spawn_async_with_fds (void)
     187  {
     188    int tnum = 1;
     189    GPtrArray *argv;
     190    char *arg;
     191    gsize i;
     192  
     193    /* Each test has 3 variable parameters: stdin, stdout, stderr */
     194    enum fd_type {
     195      NO_FD,        /* pass fd -1 (unset) */
     196      FD_NEGATIVE,  /* pass fd of negative value (equivalent to unset) */
     197      PIPE,         /* pass fd of new/unique pipe */
     198      STDOUT_PIPE,  /* pass the same pipe as stdout */
     199    } tests[][3] = {
     200      { NO_FD, NO_FD, NO_FD },       /* Test with no fds passed */
     201      { NO_FD, FD_NEGATIVE, NO_FD }, /* Test another negative fd value */
     202      { PIPE, PIPE, PIPE },          /* Test with unique fds passed */
     203      { NO_FD, PIPE, STDOUT_PIPE },  /* Test the same fd for stdout + stderr */
     204    };
     205  
     206    arg = g_strdup_printf ("# thread %d\n", tnum);
     207  
     208    argv = g_ptr_array_new ();
     209    g_ptr_array_add (argv, echo_prog_path);
     210    g_ptr_array_add (argv, arg);
     211    g_ptr_array_add (argv, NULL);
     212  
     213    for (i = 0; i < G_N_ELEMENTS (tests); i++)
     214      {
     215        GError *error = NULL;
     216        GPid pid;
     217        GMainContext *context;
     218        GMainLoop *loop;
     219        GIOChannel *channel = NULL;
     220        GSource *source;
     221        SpawnAsyncMultithreadedData data;
     222        enum fd_type *fd_info = tests[i];
     223        gint test_pipe[3][2];
     224        int j;
     225  
     226        for (j = 0; j < 3; j++)
     227          {
     228            switch (fd_info[j])
     229              {
     230              case NO_FD:
     231                test_pipe[j][0] = -1;
     232                test_pipe[j][1] = -1;
     233                break;
     234              case FD_NEGATIVE:
     235                test_pipe[j][0] = -5;
     236                test_pipe[j][1] = -5;
     237                break;
     238              case PIPE:
     239  #ifdef G_OS_UNIX
     240                g_unix_open_pipe (test_pipe[j], O_CLOEXEC, &error);
     241                g_assert_no_error (error);
     242  #else
     243                g_assert_cmpint (_pipe (test_pipe[j], 4096, _O_BINARY), >=, 0);
     244  #endif
     245                break;
     246              case STDOUT_PIPE:
     247                g_assert_cmpint (j, ==, 2); /* only works for stderr */
     248                test_pipe[j][0] = test_pipe[1][0];
     249                test_pipe[j][1] = test_pipe[1][1];
     250                break;
     251              default:
     252                g_assert_not_reached ();
     253              }
     254          }
     255  
     256        context = g_main_context_new ();
     257        loop = g_main_loop_new (context, TRUE);
     258  
     259        g_spawn_async_with_fds (NULL, (char**)argv->pdata, NULL,
     260  			      G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid,
     261  			      test_pipe[0][0], test_pipe[1][1], test_pipe[2][1],
     262  			      &error);
     263        g_assert_no_error (error);
     264        safe_close (test_pipe[0][0]);
     265        safe_close (test_pipe[1][1]);
     266        if (fd_info[2] != STDOUT_PIPE)
     267          safe_close (test_pipe[2][1]);
     268  
     269        data.loop = loop;
     270        data.stdout_done = FALSE;
     271        data.child_exited = FALSE;
     272        data.stdout_buf = g_string_new (0);
     273  
     274        source = g_child_watch_source_new (pid);
     275        g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
     276        g_source_attach (source, context);
     277        g_source_unref (source);
     278  
     279        if (test_pipe[1][0] >= 0)
     280          {
     281            channel = g_io_channel_unix_new (test_pipe[1][0]);
     282            source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
     283            g_source_set_callback (source, (GSourceFunc)on_child_stdout,
     284                                   &data, NULL);
     285            g_source_attach (source, context);
     286            g_source_unref (source);
     287          }
     288        else
     289          {
     290            /* Don't check stdout data if we didn't pass a fd */
     291            data.stdout_done = TRUE;
     292          }
     293  
     294        g_main_loop_run (loop);
     295  
     296        g_assert_true (data.child_exited);
     297  
     298        if (test_pipe[1][0] >= 0)
     299          {
     300            gchar *tmp = g_strdup_printf ("# thread %d" LINEEND, tnum);
     301            /* Check for echo on stdout */
     302            g_assert_true (data.stdout_done);
     303            g_assert_cmpstr (data.stdout_buf->str, ==, tmp);
     304            g_io_channel_unref (channel);
     305            g_free (tmp);
     306          }
     307        g_string_free (data.stdout_buf, TRUE);
     308  
     309        g_main_context_unref (context);
     310        g_main_loop_unref (loop);
     311        safe_close (test_pipe[0][1]);
     312        safe_close (test_pipe[1][0]);
     313        if (fd_info[2] != STDOUT_PIPE)
     314          safe_close (test_pipe[2][0]);
     315      }
     316  
     317    g_ptr_array_free (argv, TRUE);
     318    g_free (arg);
     319  }
     320  
     321  static void
     322  test_spawn_async_with_invalid_fds (void)
     323  {
     324    const gchar *argv[] = { echo_prog_path, "thread 0", NULL };
     325    gint source_fds[1000];
     326    GError *local_error = NULL;
     327    gboolean retval;
     328    gsize i;
     329  
     330    /* Create an identity mapping from [0, …, 999]. This is very likely going to
     331     * conflict with the internal FDs, as it covers a lot of the FD space
     332     * (including stdin, stdout and stderr, though we don’t care about them in
     333     * this test).
     334     *
     335     * Skip the test if we somehow avoid a collision. */
     336    for (i = 0; i < G_N_ELEMENTS (source_fds); i++)
     337      source_fds[i] = i;
     338  
     339    retval = g_spawn_async_with_pipes_and_fds (NULL, argv, NULL, G_SPAWN_DEFAULT,
     340                                               NULL, NULL, -1, -1, -1,
     341                                               source_fds, source_fds, G_N_ELEMENTS (source_fds),
     342                                               NULL, NULL, NULL, NULL,
     343                                               &local_error);
     344    if (retval)
     345      {
     346        g_test_skip ("Skipping internal FDs check as test didn’t manage to trigger a collision");
     347        return;
     348      }
     349    g_assert_false (retval);
     350    g_assert_error (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_INVAL);
     351    g_error_free (local_error);
     352  }
     353  
     354  static void
     355  test_spawn_sync (void)
     356  {
     357    int tnum = 1;
     358    GError *error = NULL;
     359    char *arg = g_strdup_printf ("thread %d", tnum);
     360    /* Include arguments with special symbols to test that they are correctly passed to child.
     361     * This is tested on all platforms, but the most prone to failure is win32,
     362     * where args are specially escaped during spawning.
     363     */
     364    const char * const argv[] = {
     365      echo_prog_path,
     366      arg,
     367      "doublequotes\\\"after\\\\\"\"backslashes", /* this would be special escaped on win32 */
     368      "\\\"\"doublequotes spaced after backslashes\\\\\"", /* this would be special escaped on win32 */
     369      "even$$dollars",
     370      "even%%percents",
     371      "even\"\"doublequotes",
     372      "even''singlequotes",
     373      "even\\\\backslashes",
     374      "even//slashes",
     375      "$odd spaced$dollars$",
     376      "%odd spaced%spercents%",
     377      "\"odd spaced\"doublequotes\"",
     378      "'odd spaced'singlequotes'",
     379      "\\odd spaced\\backslashes\\", /* this wasn't handled correctly on win32 in glib <=2.58 */
     380      "/odd spaced/slashes/",
     381      NULL
     382    };
     383    char *joined_args_str = g_strjoinv ("", (char**)argv + 1);
     384    char *stdout_str;
     385    int estatus;
     386  
     387    g_spawn_sync (NULL, (char**)argv, NULL, 0, NULL, NULL, &stdout_str, NULL, &estatus, &error);
     388    g_assert_no_error (error);
     389    g_assert_cmpstr (joined_args_str, ==, stdout_str);
     390    g_free (arg);
     391    g_free (stdout_str);
     392    g_free (joined_args_str);
     393  }
     394  
     395  static void
     396  init_networking (void)
     397  {
     398  #ifdef G_OS_WIN32
     399    WSADATA wsadata;
     400  
     401    if (WSAStartup (MAKEWORD (2, 0), &wsadata) != 0)
     402      g_error ("Windows Sockets could not be initialized");
     403  #endif
     404  }
     405  
     406  static void
     407  test_spawn_stderr_socket (void)
     408  {
     409    GError *error = NULL;
     410    GPtrArray *argv;
     411    int estatus;
     412    int fd;
     413  
     414    g_test_summary ("Test calling g_spawn_sync() with its stderr FD set to a socket");
     415  
     416    if (g_test_subprocess ())
     417      {
     418        init_networking ();
     419        fd = socket (AF_INET, SOCK_STREAM, 0);
     420        g_assert_cmpint (fd, >=, 0);
     421  #ifdef G_OS_WIN32
     422        fd = _open_osfhandle (fd, 0);
     423        g_assert_cmpint (fd, >=, 0);
     424  #endif
     425        /* Set the socket as FD 2, stderr */
     426        estatus = dup2 (fd, 2);
     427        g_assert_cmpint (estatus, >=, 0);
     428  
     429        argv = g_ptr_array_new ();
     430        g_ptr_array_add (argv, echo_script_path);
     431        g_ptr_array_add (argv, NULL);
     432  
     433        g_spawn_sync (NULL, (char**) argv->pdata, NULL, 0, NULL, NULL, NULL, NULL, NULL, &error);
     434        g_assert_no_error (error);
     435        g_ptr_array_free (argv, TRUE);
     436        g_close (fd, &error);
     437        g_assert_no_error (error);
     438        return;
     439      }
     440  
     441    g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
     442    g_test_trap_assert_passed ();
     443  }
     444  
     445  /* Like test_spawn_sync but uses spawn flags that trigger the optimized
     446   * posix_spawn codepath.
     447   */
     448  static void
     449  test_posix_spawn (void)
     450  {
     451    int tnum = 1;
     452    GError *error = NULL;
     453    GPtrArray *argv;
     454    char *arg;
     455    char *stdout_str;
     456    int estatus;
     457    GSpawnFlags flags = G_SPAWN_CLOEXEC_PIPES | G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
     458  
     459    arg = g_strdup_printf ("thread %d", tnum);
     460  
     461    argv = g_ptr_array_new ();
     462    g_ptr_array_add (argv, echo_prog_path);
     463    g_ptr_array_add (argv, arg);
     464    g_ptr_array_add (argv, NULL);
     465  
     466    g_spawn_sync (NULL, (char**)argv->pdata, NULL, flags, NULL, NULL, &stdout_str, NULL, &estatus, &error);
     467    g_assert_no_error (error);
     468    g_assert_cmpstr (arg, ==, stdout_str);
     469    g_free (arg);
     470    g_free (stdout_str);
     471    g_ptr_array_free (argv, TRUE);
     472  }
     473  
     474  static void
     475  test_spawn_script (void)
     476  {
     477    GError *error = NULL;
     478    GPtrArray *argv;
     479    char *stdout_str;
     480    int estatus;
     481  
     482    argv = g_ptr_array_new ();
     483    g_ptr_array_add (argv, echo_script_path);
     484    g_ptr_array_add (argv, NULL);
     485  
     486    g_spawn_sync (NULL, (char**)argv->pdata, NULL, 0, NULL, NULL, &stdout_str, NULL, &estatus, &error);
     487    g_assert_no_error (error);
     488    g_assert_cmpstr ("echo" LINEEND, ==, stdout_str);
     489    g_free (stdout_str);
     490    g_ptr_array_free (argv, TRUE);
     491  }
     492  
     493  /* Test that spawning a non-existent executable returns %G_SPAWN_ERROR_NOENT. */
     494  static void
     495  test_spawn_nonexistent (void)
     496  {
     497    GError *error = NULL;
     498    GPtrArray *argv = NULL;
     499    gchar *stdout_str = NULL;
     500    gint wait_status = -1;
     501  
     502    argv = g_ptr_array_new ();
     503    g_ptr_array_add (argv, "this does not exist");
     504    g_ptr_array_add (argv, NULL);
     505  
     506    g_spawn_sync (NULL, (char**) argv->pdata, NULL, 0, NULL, NULL, &stdout_str,
     507                  NULL, &wait_status, &error);
     508    g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
     509    g_assert_null (stdout_str);
     510    g_assert_cmpint (wait_status, ==, -1);
     511  
     512    g_ptr_array_free (argv, TRUE);
     513  
     514    g_clear_error (&error);
     515  }
     516  
     517  /* Test that FD assignments in a spawned process don’t overwrite and break the
     518   * child_err_report_fd which is used to report error information back from the
     519   * intermediate child process to the parent.
     520   *
     521   * https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */
     522  static void
     523  test_spawn_fd_assignment_clash (void)
     524  {
     525    int tmp_fd;
     526    guint i;
     527  #define N_FDS 10
     528    gint source_fds[N_FDS];
     529    gint target_fds[N_FDS];
     530    const gchar *argv[] = { "/nonexistent", NULL };
     531    gboolean retval;
     532    GError *local_error = NULL;
     533    struct stat statbuf;
     534  
     535    /* Open a temporary file and duplicate its FD several times so we have several
     536     * FDs to remap in the child process. */
     537    tmp_fd = g_file_open_tmp ("glib-spawn-test-XXXXXX", NULL, NULL);
     538    g_assert_cmpint (tmp_fd, >=, 0);
     539  
     540    for (i = 0; i < (N_FDS - 1); ++i)
     541      {
     542        int source;
     543  #ifdef F_DUPFD_CLOEXEC
     544        source = fcntl (tmp_fd, F_DUPFD_CLOEXEC, 3);
     545  #else
     546        source = dup (tmp_fd);
     547  #endif
     548        g_assert_cmpint (source, >=, 0);
     549        source_fds[i] = source;
     550        target_fds[i] = source + N_FDS;
     551      }
     552  
     553    source_fds[i] = tmp_fd;
     554    target_fds[i] = tmp_fd + N_FDS;
     555  
     556    /* Print out the FD map. */
     557    g_test_message ("FD map:");
     558    for (i = 0; i < N_FDS; i++)
     559      g_test_message ("%d%d", source_fds[i], target_fds[i]);
     560  
     561    /* Spawn the subprocess. This should fail because the executable doesn’t
     562     * exist. */
     563    retval = g_spawn_async_with_pipes_and_fds (NULL, argv, NULL, G_SPAWN_DEFAULT,
     564                                               NULL, NULL, -1, -1, -1,
     565                                               source_fds, target_fds, N_FDS,
     566                                               NULL, NULL, NULL, NULL,
     567                                               &local_error);
     568    g_assert_error (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
     569    g_assert_false (retval);
     570  
     571    g_clear_error (&local_error);
     572  
     573    /* Check nothing was written to the temporary file, as would happen if the FD
     574     * mapping was messed up to conflict with the child process error reporting FD.
     575     * See https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */
     576    g_assert_no_errno (fstat (tmp_fd, &statbuf));
     577    g_assert_cmpuint (statbuf.st_size, ==, 0);
     578  
     579    /* Clean up. */
     580    for (i = 0; i < N_FDS; i++)
     581      g_close (source_fds[i], NULL);
     582  }
     583  
     584  int
     585  main (int   argc,
     586        char *argv[])
     587  {
     588    char *dirname;
     589    int ret;
     590  
     591    setlocale (LC_ALL, "");
     592  
     593    g_test_init (&argc, &argv, NULL);
     594  
     595    dirname = g_path_get_dirname (argv[0]);
     596    echo_prog_path = g_build_filename (dirname, "test-spawn-echo" EXEEXT, NULL);
     597    echo_script_path = g_build_filename (dirname, "echo-script" SCRIPT_EXT, NULL);
     598    if (!g_file_test (echo_script_path, G_FILE_TEST_EXISTS))
     599      {
     600        g_free (echo_script_path);
     601        echo_script_path = g_test_build_filename (G_TEST_DIST, "echo-script" SCRIPT_EXT, NULL);
     602      }
     603    g_free (dirname);
     604  
     605    g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS));
     606    g_assert (g_file_test (echo_script_path, G_FILE_TEST_EXISTS));
     607  
     608    g_test_add_func ("/gthread/spawn-single-sync", test_spawn_sync);
     609    g_test_add_func ("/gthread/spawn-stderr-socket", test_spawn_stderr_socket);
     610    g_test_add_func ("/gthread/spawn-single-async", test_spawn_async);
     611    g_test_add_func ("/gthread/spawn-single-async-with-fds", test_spawn_async_with_fds);
     612    g_test_add_func ("/gthread/spawn-async-with-invalid-fds", test_spawn_async_with_invalid_fds);
     613    g_test_add_func ("/gthread/spawn-script", test_spawn_script);
     614    g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent);
     615    g_test_add_func ("/gthread/spawn-posix-spawn", test_posix_spawn);
     616    g_test_add_func ("/gthread/spawn/fd-assignment-clash", test_spawn_fd_assignment_clash);
     617  
     618    ret = g_test_run();
     619  
     620    g_free (echo_script_path);
     621    g_free (echo_prog_path);
     622  
     623    return ret;
     624  }