Logo Search packages:      
Sourcecode: libnih version File versions  Download package

test_main.c

/* libnih
 *
 * test_main.c - test suite for nih/main.c
 *
 * Copyright © 2009 Scott James Remnant <scott@netsplit.com>.
 * Copyright © 2009 Canonical Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 *
 * 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 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <nih/test.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <fcntl.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <nih/macros.h>
#include <nih/list.h>
#include <nih/main.h>
#include <nih/timer.h>
#include <nih/error.h>


void
test_init_gettext (void)
{
      /* Check that the macro to initialise gettext sets the text domain to
       * the PACKAGE_NAME macro, and binds that to the LOCALEDIR macro.
       */
      TEST_FUNCTION ("nih_main_init_gettext");
      nih_main_init_gettext();

      TEST_EQ_STR (textdomain (NULL), PACKAGE_NAME);
      TEST_EQ_STR (bindtextdomain (PACKAGE_NAME, NULL), LOCALEDIR);
}

void
test_init (void)
{
      TEST_FUNCTION ("nih_main_init_full");

      /* Check that we can initialise the program with all of the arguments
       * and that they're all copied correctly into the globals.  When the
       * program and package names are the same, the package string should
       * only contain one copy.
       */
      TEST_FEATURE ("with same program and package names");
      TEST_ALLOC_FAIL {
            nih_main_init_full ("test", "test", "1.0",
                            "bugreport", "copyright");

            TEST_EQ_STR (program_name, "test");
            TEST_EQ_STR (package_name, "test");
            TEST_EQ_STR (package_version, "1.0");
            TEST_EQ_STR (package_bugreport, "bugreport");
            TEST_EQ_STR (package_copyright, "copyright");

            TEST_EQ_STR (package_string, "test 1.0");
      }


      /* Check that when the program and package names differ, the
       * package string contains both.
       */
      TEST_FEATURE ("with different program and package names");
      TEST_ALLOC_FAIL {
            nih_main_init_full ("test", "wibble", "1.0",
                            "bugreport", "copyright");

            TEST_EQ_STR (program_name, "test");
            TEST_EQ_STR (package_name, "wibble");
            TEST_EQ_STR (package_version, "1.0");
            TEST_EQ_STR (package_bugreport, "bugreport");
            TEST_EQ_STR (package_copyright, "copyright");

            TEST_EQ_STR (package_string, "test (wibble 1.0)");
      }


      /* Check that we can pass NULL for both the bug report address and
       * the copyright message.
       */
      TEST_FEATURE ("with missing arguments");
      package_bugreport = package_copyright = NULL;
      nih_main_init_full ("argv0", "package", "1.0", NULL, NULL);

      TEST_EQ_P (package_bugreport, NULL);
      TEST_EQ_P (package_copyright, NULL);


      /* Check that the bug report address and copyright message are set
       * to NULL if empty strings are passed instead.
       */
      TEST_FEATURE ("with empty arguments");
      package_bugreport = package_copyright = NULL;
      nih_main_init_full ("argv0", "package", "1.0", "", "");

      TEST_EQ_P (package_bugreport, NULL);
      TEST_EQ_P (package_copyright, NULL);


      /* Check that the program name contains only the basename of a
       * full path supplied, and this is replicated into the package
       * string.
       */
      TEST_FEATURE ("with full program path");
      TEST_ALLOC_FAIL {
            nih_main_init_full ("/usr/bin/argv0", "package", "1.0",
                            "bugreport", "copyright");

            TEST_EQ_STR (program_name, "argv0");
            TEST_EQ_STR (package_name, "package");

            TEST_EQ_STR (package_string, "argv0 (package 1.0)");
      }


      /* Check that the program name contains only the actual name
       * of the program when it's supplied as a login shell path
       * (prefixed with a dash).
       */
      TEST_FEATURE ("with login shell path");
      TEST_ALLOC_FAIL {
            nih_main_init_full ("-argv0", "package", "1.0",
                            "bugreport", "copyright");

            TEST_EQ_STR (program_name, "argv0");
            TEST_EQ_STR (package_name, "package");

            TEST_EQ_STR (package_string, "argv0 (package 1.0)");
      }


      /* Check that the nih_main_init macro passes all the arguments for
       * us, except the program name, which we pass.
       */
      TEST_FUNCTION ("nih_main_init");
      TEST_ALLOC_FAIL {
            nih_main_init ("argv[0]");

            TEST_EQ_STR (program_name, "argv[0]");
            TEST_EQ_STR (package_name, PACKAGE_NAME);
            TEST_EQ_STR (package_version, PACKAGE_VERSION);
            TEST_EQ_STR (package_bugreport, PACKAGE_BUGREPORT);
            TEST_EQ_STR (package_copyright, PACKAGE_COPYRIGHT);
      }
}

void
test_suggest_help (void)
{
      FILE *output;

      /* Check that the message to suggest help is placed on standard
       * error, and formatted as we expect.
       */
      TEST_FUNCTION ("nih_main_suggest_help");
      program_name = "test";

      output = tmpfile ();
      TEST_DIVERT_STDERR (output) {
            nih_main_suggest_help ();
      }
      rewind (output);

      TEST_FILE_EQ (output, "Try `test --help' for more information.\n");
      TEST_FILE_END (output);

      fclose (output);
}

void
test_version (void)
{
      FILE *output;

      /* Check that the version message is placed on standard output,
       * includes the package string, copyright message and GPL notice.
       */
      TEST_FUNCTION ("nih_main_version");
      nih_main_init_full ("test", "wibble", "1.0", NULL,
                      "Copyright Message");

      TEST_ALLOC_FAIL {

            unsetenv ("COLUMNS");
            output = tmpfile ();
            TEST_DIVERT_STDOUT (output) {
                  nih_main_version ();
            }
            rewind (output);

            TEST_FILE_EQ (output, "test (wibble 1.0)\n");
            TEST_FILE_EQ (output, "Copyright Message\n");
            TEST_FILE_EQ (output, "\n");
            TEST_FILE_EQ_N (output, "This is free software;");
            TEST_FILE_EQ_N (output, "warranty; not even for");
            TEST_FILE_END (output);

            fclose (output);
      }
}

void
test_daemonise (void)
{
      pid_t pid;
      char  result[2];
      int   status, fds[2];

      /* Check that nih_main_daemonise does all of the right things,
       * our immediate child should exit with a zero status, and the
       * child within that should be run with a working directory of /
       */
      TEST_FUNCTION ("nih_main_daemonise");

      assert0 (pipe (fds));
      TEST_CHILD (pid) {
            char buf[80];
            int  fd;

            program_name = "test";
            fd = open ("/dev/null", O_WRONLY);
            assert (fd >= 0);
            assert (dup2 (fd, STDERR_FILENO) >= 0);
            assert0 (close (fd));

            if (nih_main_daemonise () < 0)
                  exit (50);

            assert (getcwd (buf, sizeof (buf)));
            if (strcmp (buf, "/")) {
                  assert (write (fds[1], "wd", 2) == 2);
                  exit (10);
            }

            assert (write (fds[1], "ok", 2) == 2);
            exit (10);
      }

      waitpid (pid, &status, 0);

      TEST_TRUE (WIFEXITED (status));
      TEST_EQ (WEXITSTATUS (status), 0);

      if (read (fds[0], result, 2) != 2)
            TEST_FAILED ("expected return code from child");

      if (! memcmp (result, "wd", 2))
            TEST_FAILED ("wrong working directory for child");

      if (memcmp (result, "ok", 2))
            TEST_FAILED ("wrong return code from child, expected 'ok' got '%.2s'",
                       result);
}


void
test_set_pidfile (void)
{
      const char *filename, *ptr;

      TEST_FUNCTION ("nih_main_set_pidfile");
      program_name = "test";

      /* Check that we can set a pidfile for use, and have the string
       * copied and returned.
       */
      TEST_FEATURE ("with new location");
      filename = "/path/to/pid";
      nih_main_set_pidfile (filename);

      ptr = nih_main_get_pidfile ();
      TEST_EQ_STR (ptr, filename);
      TEST_NE_P (ptr, filename);


      /* Check that we can pass NULL to have the default location set
       * instead.
       */
      TEST_FEATURE ("with default location");
      nih_main_set_pidfile (NULL);

      ptr = nih_main_get_pidfile ();
      TEST_EQ_STR (ptr, "/var/run/test.pid");


      nih_main_set_pidfile (NULL);
}

void
test_read_pidfile (void)
{
      FILE *f;
      char  filename[PATH_MAX];

      TEST_FUNCTION ("nih_main_read_pidfile");
      TEST_FILENAME (filename);
      nih_main_set_pidfile (filename);

      /* Check that reading from a valid pid file will return the pid
       * stored there.
       */
      TEST_FEATURE ("with valid pid file");
      f = fopen (filename, "w");
      fprintf (f, "1234\n");
      fclose (f);

      TEST_EQ (nih_main_read_pidfile (), 1234);


      /* Check that reading from a pid file without a newline will still
       * return the pid stored there.
       */
      TEST_FEATURE ("with no newline in pid file");
      f = fopen (filename, "w");
      fprintf (f, "1234");
      fclose (f);

      TEST_EQ (nih_main_read_pidfile (), 1234);


      /* Check that reading from an invalid pid file returns -1. */
      TEST_FEATURE ("with invalid pid file");
      f = fopen (filename, "w");
      fprintf (f, "foo\n1234\n");
      fclose (f);

      TEST_EQ (nih_main_read_pidfile (), -1);


      /* Check that reading from a non-existant pid file returns -1. */
      TEST_FEATURE ("with non-existant pid file");
      nih_main_unlink_pidfile ();

      TEST_EQ (nih_main_read_pidfile (), -1);


      nih_main_set_pidfile (NULL);
}

void
test_write_pidfile (void)
{
      FILE     *f;
      NihError *err;
      char      dirname[PATH_MAX], filename[PATH_MAX], tmpname[PATH_MAX];
      int       ret;

      TEST_FUNCTION ("nih_main_write_pidfile");
      TEST_FILENAME (dirname);
      mkdir (dirname, 0755);

      strcpy (filename, dirname);
      strcat (filename, "/test.pid");

      strcpy (tmpname, dirname);
      strcat (tmpname, "/.test.pid.tmp");

      nih_main_set_pidfile (filename);

      /* Check that we can write a pid to the file, and have it appaer
       * on disk where we expect.
       */
      TEST_FEATURE ("with successful write");
      ret = nih_main_write_pidfile (1234);

      TEST_EQ (ret, 0);

      f = fopen (filename, "r");
      TEST_FILE_EQ (f, "1234\n");
      fclose (f);


      /* Check that we can overwrite an existing pid file with a new
       * value.
       */
      TEST_FEATURE ("with overwrite of existing pid");
      ret = nih_main_write_pidfile (5678);

      TEST_EQ (ret, 0);

      f = fopen (filename, "r");
      TEST_FILE_EQ (f, "5678\n");
      fclose (f);


      /* Check that an error writing to the temporary file does not result
       * in the replacement of the existing file and does not result in
       * the unlinking of the temporary file.
       */
      TEST_FEATURE ("with failure to write to temporary file");
      f = fopen (tmpname, "w");
      fclose (f);
      chmod (tmpname, 0000);

      ret = nih_main_write_pidfile (1234);

      TEST_LT (ret, 0);

      err = nih_error_get ();
      TEST_EQ (err->number, EACCES);
      nih_free (err);

      f = fopen (filename, "r");
      TEST_FILE_EQ (f, "5678\n");
      fclose (f);

      TEST_EQ (chmod (tmpname, 0644), 0);


      nih_main_unlink_pidfile ();
      unlink (tmpname);
      rmdir (dirname);

      nih_main_set_pidfile (NULL);
}


static int callback_called = 0;
static void *last_data = NULL;

static void
my_callback (void            *data,
           NihMainLoopFunc *func)
{
      callback_called++;
      last_data = data;
}

static void
my_timeout (void *data, NihTimer *timer)
{
      nih_main_term_signal (NULL, NULL);
      nih_main_loop_exit (42);
}

void
test_main_loop (void)
{
      NihMainLoopFunc *func;
      NihTimer        *timer;
      int              ret;

      /* Check that we can run through the main loop, and that the
       * callback function will be run.  Also schedule an immediate
       * timeout and make sure that's run too, that'll terminate the
       * main loop with an exit value, make sure it's returned.
       */
      TEST_FUNCTION ("nih_main_loop");
      callback_called = 0;
      last_data = NULL;
      func = nih_main_loop_add_func (NULL, my_callback, &func);
      timer = nih_timer_add_timeout (NULL, 1, my_timeout, NULL);
      ret = nih_main_loop ();

      TEST_EQ (ret, 42);
      TEST_TRUE (callback_called);
      TEST_EQ_P (last_data, &func);

      nih_free (func);
}

void
test_main_loop_add_func (void)
{
      NihMainLoopFunc *func;

      /* Check that we can add a callback function to the main loop,
       * and that the structure returned is correctly populated and
       * placed in a list.
       */
      TEST_FUNCTION ("nih_main_loop_add_func");
      TEST_ALLOC_FAIL {
            func = nih_main_loop_add_func (NULL, my_callback, &func);

            if (test_alloc_failed) {
                  TEST_EQ_P (func, NULL);
                  continue;
            }

            TEST_ALLOC_SIZE (func, sizeof (NihMainLoopFunc));
            TEST_LIST_NOT_EMPTY (&func->entry);
            TEST_EQ_P (func->callback, my_callback);
            TEST_EQ_P (func->data, &func);

            nih_free (func);
      }
}


int
main (int   argc,
      char *argv[])
{
      test_init_gettext ();
      test_init ();
      test_suggest_help ();
      test_version ();
      test_daemonise ();
      test_set_pidfile ();
      test_read_pidfile ();
      test_write_pidfile ();
      test_main_loop ();
      test_main_loop_add_func ();

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index