blob: 0cc2af6c6fcb808dda17fe59583ae05fa3d02ac5 [file] [log] [blame]
/* 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;
}