aboutsummaryrefslogtreecommitdiff
path: root/vendor/voclient/voapps/lib/voTask.c
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/voclient/voapps/lib/voTask.c')
-rw-r--r--vendor/voclient/voapps/lib/voTask.c856
1 files changed, 856 insertions, 0 deletions
diff --git a/vendor/voclient/voapps/lib/voTask.c b/vendor/voclient/voapps/lib/voTask.c
new file mode 100644
index 00000000..df9f039e
--- /dev/null
+++ b/vendor/voclient/voapps/lib/voTask.c
@@ -0,0 +1,856 @@
+/**
+ * VOTASK.C -- Utilities to run a VOApps task as a connected subprocess.
+ *
+ * @file voTask.c
+ * @author Mike Fitzpatrick
+ * @date 6/23/12
+ *
+ * @brief Utilities to run a VOApps task as a connected subprocess.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include "voApps.h"
+
+
+#define PARENT_READ rpipe[0] /* read from parent */
+#define CHILD_WRITE rpipe[1] /* write to parent */
+#define CHILD_READ wpipe[0] /* read from child */
+#define PARENT_WRITE wpipe[1] /* write to parent */
+
+#ifdef ERR
+#undef ERR
+#endif
+#ifdef OK
+#undef OK
+#endif
+#define ERR 1
+#define OK 0
+
+#define MAX_TASK_ARGS 64
+
+
+static int rpipe[2] = {-1, -1}; /* subprocess read pipes */
+static int wpipe[2] = {-1, -1}; /* subprocess write pipes */
+static int nr = 0;
+
+
+
+/* Internal procedures.
+ */
+static pid_t vo_taskExec (char *method, Task *apps, int argc, char *argv[]);
+static int vo_taskWait (pid_t pid, size_t *len);
+static void *vo_taskResult (pid_t pid, size_t len);
+static void vo_taskReaper (int sig, int *arg1, int *arg2);
+static size_t vo_taskRead (int fd, void *data, size_t len);
+static size_t vo_taskWrite (int fd, void *data, size_t len);
+
+typedef void (*SIGFUNC)();
+
+
+
+
+/*****************************************************************************/
+/** Public procedures **/
+/*****************************************************************************/
+
+
+/**
+ * VO_RUNTASK -- Run a VOApps task as a connected subprocess.
+ *
+ * @brief Run a VOApps task as a connected subprocess.
+ * @fn int vo_runTask (char *method, Task *apps, int argc, char *argv[],
+ * size_t *len, void **result)
+ *
+ * @param method task name to call
+ * @param apps application table
+ * @param argc argument count
+ * @param argv argument vector
+ * @param len length of result object
+ * @param result pointer to result object
+ * @return status (0=OK, 1=ERR)
+ */
+
+static int vo_signal_status = 0;
+static int vo_exit_status = 0;
+sigjmp_buf vo_env;
+
+int
+vo_runTask (char *method, Task *apps, int argc, char **argv, size_t *len, void **result)
+{
+ pid_t pid;
+ int status = 0, retStatus = EXIT_SUCCESS;
+ size_t resLen = 0;
+ void *res = (void *) NULL;
+ static SIGFUNC old_sigcld;
+ char err[128];
+
+
+ memset (err, 0, 128);
+
+ old_sigcld = (SIGFUNC) signal (SIGCHLD, (SIGFUNC) vo_taskReaper);
+ vo_signal_status = 0;
+ vo_exit_status = 0;
+
+ sigsetjmp (vo_env, 1);
+ if (vo_signal_status || vo_exit_status) {
+ if (vo_signal_status)
+ sprintf (err, "Child exited with SIGNAL %d", vo_signal_status);
+ else if (vo_exit_status > 1)
+ sprintf (err, "Child exited with status %d", vo_exit_status);
+ *len = strlen (err);
+ *result = (void *) strdup (err);
+
+ return (EXIT_FAILURE);
+ }
+
+ if ((pid = vo_taskExec (method, apps, argc, argv)) > 0) {
+
+ if ((status = vo_taskWait (pid, &resLen)) == EXIT_SUCCESS) {
+
+ if (resLen && (res = vo_taskResult (pid, resLen)))
+ *result = res; /* save result from client */
+ else
+ *result = (void *) NULL; /* no result from client */
+ *len = resLen;
+
+ } else {
+ sprintf (err, "Child exited with status %d", status);
+ *len = strlen (err);
+ *result = (void *) strdup (err);
+ return ( status ); /* child exits with error */
+ }
+
+ close (CHILD_WRITE); /* Close descriptors */
+ close (CHILD_READ);
+
+ } else {
+ fprintf (stderr, "Error executing task\n");
+ retStatus = EXIT_FAILURE;
+ }
+
+ signal (SIGCHLD, old_sigcld); /* reset the SIGCHLD handler */
+ return (retStatus);
+}
+
+
+/**
+ * VO_TASKTEST -- Execute a task as a unit test.
+ *
+ * @brief Execute a task as a unit test.
+ * @fn int vo_taskTest (Task *self, char *arg, ...)
+ *
+ * @param self task struct pointer
+ * @param arg first of variable arg list
+ * @return status (0=OK, 1=ERR)
+ */
+int
+vo_taskTest (Task *self, char *arg, ...)
+{
+ int i, status = OK, argc = 0;
+ char *argv[MAX_TASK_ARGS], *aval = NULL;
+ size_t reslen = 0;
+ void *result = (void *) NULL;
+ va_list argp;
+
+ extern int optind;
+#ifdef Darwin
+ extern int optreset;
+#endif
+
+ va_start (argp, arg); /* initialize */
+ memset (argv, 0, sizeof (char *) * MAX_TASK_ARGS);
+
+ optind = 0; /* required for unit test */
+#ifdef Darwin
+ optreset = 1; /* required for unit test */
+#endif
+
+ /* Turn the varargs list into an argv[] we can pass to the task.
+ */
+ argc = 1;
+ argv[0] = strdup (self->name);
+
+ if (arg) {
+ argv[1] = strdup (arg);
+ for (i=2; (aval = (char *)va_arg(argp,char *)) != NULL; i++) {
+ if (aval)
+ argv[i] = strdup (aval);
+ }
+ argc = i;
+ va_end (argp);
+ }
+
+
+ /* Initialize the test output....
+ */
+ fprintf (stderr, "\n=======================================");
+ fprintf (stderr, "======================================\n");
+ fprintf (stderr, "== Test Cmd: %s ", self->name);
+ for (i=1; i < argc; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ fprintf (stderr, "\n\n");
+
+
+ /* Execute the task, throw away any returned value.
+ */
+ if (!getenv ("VOAPP_CONNECTED"))
+ status = vo_runTask (self->name, self, argc, argv, &reslen, &result);
+ else
+ status = (*(self->func)) (argc, argv, &reslen, &result);
+
+ fprintf (stderr, "===================================");
+ fprintf (stderr, "============================= status = %2d\n", status);
+
+vo_taskDbg();
+
+ /* Clean up.
+ */
+ for (i=0; i < argc; i++) {
+ if (argv[i])
+ free (argv[i]);
+ }
+
+ if (status && result) /* print the error message */
+ fprintf (stderr, "%s\n", (char *) result);
+ if (reslen && result) /* free result pointer */
+ free (result);
+
+ self->ntests++; /* update testing struct */
+ if (status)
+ self->nfail++;
+ else
+ self->npass++;
+
+ return (status);
+}
+
+
+/**
+ * VO_TASKDBG -- Tasking debug breakpoint.
+ *
+ * @brief Tasking debug breakpoint.
+ * @fn void vo_taskDbg (void)
+ *
+ * @return nothing
+ */
+void vo_taskDbg (void)
+{
+ static int i = 0;
+
+ i++;
+}
+
+
+/**
+ * VO_TASKTESTREPORT -- Report from the task testing interface.
+ *
+ * @brief Report from the task testing interface.
+ * @fn void vo_taskTestReport (self)
+ *
+ * @param self task struct pointer
+ * @return status (0=OK, 1=ERR)
+ */
+void
+vo_taskTestReport (Task self)
+{
+ fprintf (stderr, "\n");
+ fprintf (stderr,
+ "Task: %-12.12s No. of Tests: %d Passed: %d Failed: %d\n",
+ self.name, self.ntests, self.npass, self.nfail);
+
+ fprintf (stderr, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ fprintf (stderr, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
+}
+
+
+/**
+ * VO_TASKTESTFILE -- Create a small named test file.
+ *
+ * @brief Create a small named test file.
+ * @fn void vo_taskTestFile (char *str, char *fname)
+ *
+ * @param str test string
+ * @param fname test filename
+ * @return status (0=OK, 1=ERR)
+ */
+void
+vo_taskTestFile (char *str, char *fname)
+{
+ FILE *fd = (FILE *) NULL;
+
+ if (access (fname, F_OK) == 0)
+ unlink (fname);
+ if ((fd = fopen (fname, "w+"))) {
+ fprintf (fd, "%s", str);
+ fclose (fd);
+ }
+}
+
+
+/**
+ * VO_SETRESULTFROMFILE -- Set a result object to a file's contents.
+ *
+ * @brief Set a result object to a file's contents.
+ * @fn int vo_setResultFromFile (char *fname, size_t *len, void **data)
+ *
+ * @param fname file name to read
+ * @param len length of result object
+ * @param data pointer to result object
+ * @return status (0=OK, 1=ERR)
+ */
+int
+vo_setResultFromFile (char *fname, size_t *len, void **data)
+{
+ struct stat st;
+ int fd = 0;
+
+
+ if (stat (fname, &st) < 0)
+ return (ERR);
+
+ if ((fd = open (fname, O_RDONLY)) > 0) {
+ *len = st.st_size;
+ *data = calloc (1, st.st_size + 1);
+ if (vo_taskRead (fd, *data, *len) < *len)
+ return (ERR);
+ close (fd);
+ } else
+ return (ERR);
+
+ return (OK);
+}
+
+
+/**
+ * VO_SETRESULTFROMINT -- Set a result object to an int value.
+ *
+ * @brief Set a result object to an int value.
+ * @fn int vo_setResultFromInt (int value, size_t *len, void **data)
+ *
+ * @param value value to set
+ * @param len length of result object
+ * @param data pointer to result object
+ * @return status (0=OK, 1=ERR)
+ */
+int
+vo_setResultFromInt (int value, size_t *len, void **data)
+{
+ char buf[SZ_FNAME];
+
+ memset (buf, 0, SZ_FNAME);
+ sprintf (buf, "%d", value);
+
+ *len = strlen (buf);
+ *data = (char *) calloc (1, *len + 1);
+ strcpy ((char *) data, buf);
+ return (OK);
+}
+
+
+/**
+ * VO_SETRESULTFROMREAL -- Set a result object to a real value.
+ *
+ * @brief Set a result object to a real value.
+ * @fn int vo_setResultFromReal (float value, size_t *len, void **data)
+ *
+ * @param value value to set
+ * @param len length of result object
+ * @param data pointer to result object
+ * @return status (0=OK, 1=ERR)
+ */
+int
+vo_setResultFromReal (float value, size_t *len, void **data)
+{
+ char buf[SZ_FNAME];
+
+ memset (buf, 0, SZ_FNAME);
+ sprintf (buf, "%g", value);
+
+ *len = strlen (buf);
+ *data = (char *) calloc (1, *len + 1);
+ strcpy ((char *) data, buf);
+ return (OK);
+}
+
+
+/**
+ * VO_SETRESULTFROMSTRING -- Set a result object to a string value.
+ *
+ * @brief Set a result object to a string value.
+ * @fn int vo_setResultFromString (char *str, size_t *len, void **data)
+ *
+ * @param value value to set
+ * @param len length of result object
+ * @param data pointer to result object
+ * @return status (0=OK, 1=ERR)
+ */
+int
+vo_setResultFromString (char *str, size_t *len, void **data)
+{
+ if (str) {
+ *len = strlen (str);
+ *data = (char *) calloc (1, *len + 1);
+ strcpy ((char *) *data, str);
+ } else {
+ *len = 0;
+ *data = NULL;
+ }
+ return (OK);
+}
+
+
+/**
+ * VO_APPENDRESULTFROMSTRING -- Append a result object to a string value.
+ *
+ * @brief Append a result object to a string value.
+ * @fn int vo_appendResultFromString (char *str, size_t *len,
+ * void **data, size_t *maxlen)
+ *
+ * @param value value to set
+ * @param len length of result object
+ * @param data pointer to result object
+ * @param maxlen max length of result object
+ * @return status (0=OK, 1=ERR)
+ */
+int
+vo_appendResultFromString (char *str, size_t *len, void **data, size_t *maxlen)
+{
+ int slen = strlen (str);
+
+ if (str) {
+ *len += slen;
+
+ /* Reallocate in case we overrun the data buffer.
+ */
+ if ((*len) > (*maxlen)) {
+ char *new = (char *) calloc (1, (*maxlen) + SZ_LINE);
+ strcpy (new, *data);
+ free ((void *) *data);
+ *data = new;
+ }
+
+ strcat ((char *) *data, str);
+ }
+ return (OK);
+}
+
+
+
+
+/*****************************************************************************/
+/** Private procedures **/
+/*****************************************************************************/
+
+
+static void
+vo_taskReaper (
+ int sig, /* signal which was trapped */
+ int *arg1, /* not used */
+ int *arg2 /* not used */
+)
+{
+ int status = 0, pid = 0;
+
+
+ vo_signal_status = vo_exit_status = 0;
+
+ while ((pid = (int) waitpid ((pid_t) 0, &status, WNOHANG)) > 0)
+ if (status) {
+ if (status & 0xFF)
+ vo_signal_status = (status & 0xFF);
+ else if (status >> 8)
+ vo_exit_status = (status >> 8);
+ siglongjmp (vo_env, 1);
+ }
+}
+
+
+/**
+ * VO_TASKEXEC -- Execute a task as a connected subprocess.
+ *
+ * @brief Get the result object (if any) from the task.
+ * @fn Task *vo_taskExec (char *method, Task *apps, int argc, char *argv[])
+ *
+ * @param method task name to call
+ * @param apps task application table
+ * @param argc argument count
+ * @param argv argument vector
+ * @return pid of child process
+ */
+
+#define GO_MSG "__go__"
+#define GO_LEN 6
+#define E_NOTFOUND "Task not found"
+
+static pid_t
+vo_taskExec (char *method, Task *apps, int argc, char **argv)
+{
+ Task *app = (Task *) NULL;
+ int status = 0;
+ pid_t cpid;
+ size_t resLen = 0;
+ void *result = (void *) NULL;
+ char msg[128];
+
+
+ /* Create the pipes to/from the child process.
+ */
+ if (pipe (rpipe) < 0 || pipe (wpipe) < 0) {
+ perror ("pipe failure");
+ _exit (EXIT_FAILURE);
+ }
+
+
+ /* Fork so the child does all the work.
+ */
+ if ( (cpid = fork ()) < 0 )
+ return (cpid);
+
+ if (cpid == 0) { /* Child */
+
+ close (CHILD_WRITE); /* Close unused descriptors */
+ close (CHILD_READ);
+
+ /* Wait for the '_go_' message.
+ */
+ do {
+ memset (msg, 0, 128);
+ nr = vo_taskRead (PARENT_READ, msg, GO_LEN);
+ } while (strncmp (msg, GO_MSG, GO_LEN) != 0);
+
+
+ /* Loop through the application table. If the names match, call
+ * the entry-point function.
+ */
+ for (app = apps; app->name; app++) {
+ if (strcmp (app->name, method) == 0) {
+ status = (*app->func)(argc, argv, &resLen, &result);
+
+ /* Send the parent the status reply,result length and
+ * the result object, if there is one.
+ */
+ vo_taskWrite (PARENT_WRITE, &status, sizeof (int));
+ vo_taskWrite (PARENT_WRITE, &resLen, sizeof (size_t));
+ if (resLen)
+ vo_taskWrite (PARENT_WRITE, result, resLen);
+ break;
+ }
+ }
+
+
+ /* If we get here, the task wasn't found.
+ */
+ if (app->name == NULL) {
+ status = -1;
+ vo_taskWrite (PARENT_WRITE, &status, sizeof (int));
+ vo_taskWrite (PARENT_WRITE, E_NOTFOUND, strlen (E_NOTFOUND));
+ }
+
+ /* Exit the child process.
+ */
+ close (PARENT_READ); /* Close descriptors */
+ close (PARENT_WRITE);
+ _exit (EXIT_SUCCESS); /* child exits */
+
+ } else { /* Parent */
+ close (PARENT_READ); /* Close unneded descriptors */
+ close (PARENT_WRITE);
+
+ vo_taskWrite (CHILD_WRITE, GO_MSG, GO_LEN);
+
+ return (cpid);
+ }
+}
+
+
+/**
+ * VO_TASKWAIT -- Wait for a child process to complete.
+ *
+ * @brief Wait for a child process to complete.
+ * @fn int vo_taskWait (pid_t pid, int *len)
+ *
+ * @param pid child process pid
+ * @param len length of result object
+ * @return exit status of child process
+ */
+static int
+vo_taskWait (pid_t pid, size_t *len)
+{
+ int status = 0;
+
+
+ /* Wait for status message.
+ */
+ vo_taskRead (CHILD_READ, &status, sizeof (int));
+ vo_taskRead (CHILD_READ, len, sizeof (size_t));
+
+ wait (NULL); /* Wait for child */
+ return (status);
+}
+
+
+/**
+ * VO_TASKRESULT -- Get the result object (if any) from the task.
+ *
+ * @brief Get the result object (if any) from the task.
+ * @fn void *vo_taskResult (pid_t pid, size_t len)
+ *
+ * @return (allocated) pointer to the Task
+ */
+static void *
+vo_taskResult (pid_t pid, size_t len)
+{
+ void *result = (void *) calloc (1, len);
+
+ vo_taskRead (CHILD_READ, result, len);
+
+ return (result);
+}
+
+
+#define SELWIDTH 32
+
+/**
+ * VO_TASKREAD -- Read exactly "n" bytes from a task descriptor.
+ *
+ * @brief Read exactly "n" bytes from a task descriptor.
+ * @fn size_t vo_taskRead (int fd, void *vptr, size_t nbytes)
+ *
+ * @param fd file descriptor
+ * @param vptr data buffer to be written
+ * @param nbytes number of bytes to write
+ * @return number of bytes written
+ */
+static size_t
+vo_taskRead (int fd, void *vptr, size_t nbytes)
+{
+ char *ptr = vptr;
+ int nread = 0, nleft = nbytes, nb = 0, flags = 0;
+ fd_set allset, fds;
+ struct timeval tm = { 2, 0 };
+
+
+ /* Set non-blocking mode on the descriptor.
+ */
+ if ((flags = fcntl (fd, F_GETFL, 0)) == 0)
+ if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ ; /* ignore error */
+
+ FD_ZERO (&allset);
+ FD_SET (fd, &allset);
+
+ while (nleft > 0) {
+ memcpy (&fds, &allset, sizeof(allset));
+ if (select (SELWIDTH, &fds, NULL, NULL, &tm)) {
+ if ( (nb = read (fd, ptr, nleft)) < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ nb = 0; /* and call recv() again */
+ else
+ return (-1);
+ } else if (nb == 0)
+ break; /* EOF */
+ nleft -= nb;
+ ptr += nb;
+ nread += nb;
+ }
+ }
+
+ return (nread); /* return no. of bytes read */
+}
+
+
+/**
+ * VO_TASKWRITE -- Write exactly "n" bytes to a task descriptor.
+ *
+ * @brief Send exactly "n" bytes to a task descriptor.
+ * @fn size_t vo_taskWrite (int fd, void *vptr, size_t nbytes)
+ *
+ * @param fd file descriptor
+ * @param vptr data buffer to be written
+ * @param nbytes number of bytes to write
+ * @return number of bytes written
+ */
+static size_t
+vo_taskWrite (int fd, void *vptr, size_t nbytes)
+{
+ char *ptr = vptr;
+ int nwritten = 0, nleft = nbytes, nb = 0, flags = 0;
+ fd_set allset, fds;
+ struct timeval tm = { 2, 0 };
+
+
+ /* Set non-blocking mode on the descriptor.
+ */
+ if ((flags = fcntl (fd, F_GETFL, 0)) == 0)
+ if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ ; /* ignore error */
+
+ FD_ZERO (&allset);
+ FD_SET (fd, &allset);
+
+ while (nleft > 0) {
+ memcpy (&fds, &allset, sizeof(allset));
+ if (select (SELWIDTH, NULL, &fds, NULL, &tm)) {
+ if ( (nb = write (fd, ptr, nleft)) <= 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ nb = 0; /* and call write() again */
+ else
+ return (-1);
+ }
+ nleft -= nb;
+ ptr += nb;
+ nwritten += nb;
+ }
+ }
+
+ return (nwritten);
+}
+
+
+
+#ifdef VO_UNIT_TESTS
+
+/************************************************************************/
+ * Test Task declarations.
+ */
+int test_0 (int argc, char **argv, size_t *len, void **result);
+int test_1 (int argc, char **argv, size_t *len, void **result);
+int test_2 (int argc, char **argv, size_t *len, void **result);
+int test_3 (int argc, char **argv, size_t *len, void **result);
+int test_4 (int argc, char **argv, size_t *len, void **result);
+int test_5 (int argc, char **argv, size_t *len, void **result);
+int test_6 (int argc, char **argv, size_t *len, void **result);
+int test_7 (int argc, char **argv, size_t *len, void **result);
+
+Task testApps[] = {
+ { "test0", test_0 }, /* test applications */
+ { "test1", test_1 },
+ { "test2", test_2 },
+ { "test3", test_3 },
+ { "test4", test_4 },
+ { "test5", test_5 },
+ { "test6", test_6 },
+ { "test7", test_7 },
+ { NULL, NULL },
+};
+
+
+/* Child doesn't return a result.
+ */
+int test_0 (int argc, char **argv, size_t *len, void **result)
+{
+ return (0);
+}
+
+/* Child returns an error code.
+ */
+int test_1 (int argc, char **argv, size_t *len, void **result)
+{
+ return (1);
+}
+
+/* Child returns a result.
+ */
+int test_2 (int argc, char **argv, size_t *len, void **result)
+{
+ char *str = strdup ("this is a test reply object");
+
+ *len = strlen (str);
+ if (*result == NULL)
+ *result = calloc (1, strlen (str) + 1);
+ strcpy ((char *) *result, str);
+
+ free (str);
+ return (0);
+}
+
+/* Child exits with status code
+ */
+int test_3 (int argc, char **argv, size_t *len, void **result)
+{
+ _exit (5);
+}
+
+/* Child exits w/ FPE
+ */
+int test_4 (int argc, char **argv, size_t *len, void **result)
+{
+ int i=1, j=0, k = 0;
+ k = i / j;
+}
+
+/* Child exits w/ segfault
+ */
+int test_5 (int argc, char **argv, size_t *len, void **result)
+{
+ char *foo = NULL;
+ strcpy (foo, "bar");
+}
+
+/* Child exits w/ SIGINT
+ */
+int test_6 (int argc, char **argv, size_t *len, void **result)
+{
+ raise (SIGINT);
+}
+
+/* Multiple children....
+ */
+int test_7 (int argc, char **argv, size_t *len, void **result)
+{
+ pid_t pid;
+
+ if ((pid = fork()) < 0)
+ perror ("fork fails");
+ else if (pid == 0)
+ raise (SIGILL); /* child */
+ else {
+ sleep (1); wait (NULL); return (0);
+ }
+}
+
+
+/**
+ * Program entry point.
+ */
+int
+main (int argc, char *argv[])
+{
+ runTest ("test0", argc, argv);
+ runTest ("test1", argc, argv);
+ runTest ("test2", argc, argv);
+ runTest ("test3", argc, argv);
+ runTest ("test4", argc, argv);
+ runTest ("test5", argc, argv);
+ runTest ("test6", argc, argv);
+ runTest ("test7", argc, argv);
+
+ return (0);
+}
+
+int runTest (char *task, int argc, char **argv)
+{
+ int status = 0, len = 0;
+ void *res = (void *) NULL;
+
+ fprintf (stderr, "Running '%s' .... ", task);
+ status = vo_runTask (task, argc, argv, &len, &res);
+ fprintf (stderr, "status=%d len=%d res='%s'\n", status, len, (char *)res);
+ if (res)
+ free (res);
+}
+
+#endif /* VO_UNIT_TESTS */