root/usr/src/test/libc-tests/tests/common/test_common.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2015 Garrett D'Amore <garrett@damore.org>
 */

/*
 * Common handling for test programs.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/param.h>
#include "test_common.h"

static int debug = 0;
static int force = 0;
static pthread_mutex_t lk;

static int passes;
static int tests;

struct test {
        char            *name;
        int             ntids;
        pthread_t       *tids;
        int             fails;
        void            *arg;
        void            (*func)(test_t t, void *);
};

void
test_set_debug(void)
{
        debug++;
}

void
test_set_force(void)
{
        force++;
}

test_t
test_start(const char *format, ...)
{
        va_list args;
        test_t t;
        char *s;

        t = calloc(1, sizeof (*t));
        va_start(args, format);
        (void) vasprintf(&s, format, args);
        va_end(args);

        (void) asprintf(&t->name, "%s (%s)", s, ARCH);
        free(s);

        (void) pthread_mutex_lock(&lk);
        (void) printf("TEST STARTING %s:\n", t->name);
        (void) fflush(stdout);
        (void) pthread_mutex_unlock(&lk);

#ifdef  LINT
        /* We inject references to make avoid name unused warnings */
        test_run(0, NULL, NULL, NULL);
        test_debugf(t, NULL);
        test_failed(t, NULL);
        test_passed(t);
        test_set_debug();
        test_set_force();
        test_summary();
        (void) test_load_config(t, NULL, NULL);
#endif

        tests++;
        return (t);
}

void
test_failed(test_t t, const char *format, ...)
{
        va_list args;

        (void) pthread_mutex_lock(&lk);
        if (t == NULL) {
                (void) printf("FAILURE: ");
                va_start(args, format);
                (void) vprintf(format, args);
                va_end(args);
                (void) printf("\n");
                (void) fflush(stdout);
                (void) pthread_mutex_unlock(&lk);
                return;
        }
        if (force || (t->ntids > 0)) {
                (void) printf("TEST FAILING %s: ", t->name);
        } else {
                (void) printf("TEST FAILED %s: ", t->name);
        }

        va_start(args, format);
        (void) vprintf(format, args);
        va_end(args);
        (void) printf("\n");
        (void) fflush(stdout);
        (void) pthread_mutex_unlock(&lk);

        t->fails++;
        if (!force) {
                if (t->ntids > 0) {
                        pthread_exit(NULL);
                } else {
                        (void) exit(EXIT_FAILURE);
                }
        }
}

void
test_passed(test_t t)
{
        if (t == NULL) {
                return;
        }
        if (t->ntids > 0) {
                if (debug) {
                        (void) pthread_mutex_lock(&lk);
                        (void) printf("TEST PASSING: %s\n", t->name);
                        (void) pthread_mutex_unlock(&lk);
                }
                return;
        }
        (void) pthread_mutex_lock(&lk);
        if (t->fails == 0) {
                passes++;
                (void) printf("TEST PASS: %s\n", t->name);
        } else {
                (void) printf("TEST FAILED: %d failures\n", t->fails);
        }
        (void) fflush(stdout);
        (void) pthread_mutex_unlock(&lk);
        free(t->name);
        if (t->tids) {
                free(t->tids);
        }
        free(t);
}

void
test_summary(void)
{
        if (passes == tests) {
                (void) printf("TEST SUMMARY: %d / %d (ok)\n", passes, tests);
        } else {
                (void) printf("TEST SUMMARY: %d / %d (%d failing)\n",
                    passes, tests, tests - passes);
        }
}

void
test_debugf(test_t t, const char *format, ...)
{
        va_list args;

        if (!debug)
                return;

        (void) pthread_mutex_lock(&lk);
        if (t) {
                (void) printf("TEST DEBUG %s: ", t->name);
        } else {
                (void) printf("TEST DEBUG: ");
        }
        va_start(args, format);
        (void) vprintf(format, args);
        va_end(args);
        (void) printf("\n");
        (void) fflush(stdout);
        (void) pthread_mutex_unlock(&lk);
}

static void *
test_thr_one(void *arg)
{
        test_t t = arg;
        t->func(t, t->arg);
        return (NULL);
}

void
test_run(int nthr, void (*func)(test_t, void *), void *arg,
    const char *tname, ...)
{
        test_t          t;
        char            *s;
        va_list         args;

        t = calloc(1, sizeof (*t));
        t->ntids = nthr;
        t->tids = calloc(nthr, sizeof (pthread_t));
        t->func = func;
        t->arg = arg;

        va_start(args, tname);
        (void) vasprintf(&s, tname, args);
        va_end(args);

        (void) asprintf(&t->name, "%s (%s)", s, ARCH);
        free(s);

        (void) pthread_mutex_lock(&lk);
        (void) printf("TEST STARTING %s:\n", t->name);
        (void) fflush(stdout);
        (void) pthread_mutex_unlock(&lk);

        test_debugf(t, "running %d threads", nthr);

        for (int i = 0; i < nthr; i++) {
                test_debugf(t, "started thread %d", i);
                (void) pthread_create(&t->tids[i], NULL, test_thr_one, t);
        }

        for (int i = 0; i < nthr; i++) {
                (void) pthread_join(t->tids[i], NULL);
                test_debugf(t, "thread %d joined", i);
                t->ntids--;
        }
        test_passed(t);
}

void
test_trim(char **ptr)
{
        char *p = *ptr;
        while (isspace(*p)) {
                p++;
        }
        *ptr = p;
        p += strlen(p);
        while ((--p >= *ptr) && (isspace(*p))) {
                *p = '\0';
        }
}

#define MAXCB           20
#define MAXFIELD        20

int
test_load_config(test_t t, const char *fname, ...)
{
        va_list         va;
        const char      *keyws[MAXCB];
        test_cfg_func_t callbs[MAXCB];
        char            *fields[MAXFIELD];
        int             nfields;

        FILE            *cfg;
        char            line[1024];
        char            buf[1024];
        int             done;
        char            *ptr;
        char            *tok;
        char            *err;
        int             lineno;
        int             rv;
        int             found;
        char            path[MAXPATHLEN];
        int             i;

        va_start(va, fname);
        for (i = 0; i < MAXCB; i++) {
                keyws[i] = (const char *)va_arg(va, const char *);
                if (keyws[i] == NULL)
                        break;
                callbs[i] = (test_cfg_func_t)va_arg(va, test_cfg_func_t);
        }
        va_end(va);
        if (i == MAXCB) {
                test_debugf(t, "too many arguments to function >= %d", MAXCB);
        }

        found = 0;

        if (access(fname, F_OK) == 0) {
                found++;
        }
        if (!found && fname[0] != '/') {
                char *stf = getenv("STF_SUITE");
                if (stf == NULL) {
                        stf = "../..";
                }
                (void) snprintf(path, sizeof (path), "%s/cfg/%s", stf, fname);
                if (access(path, F_OK) == 0) {
                        fname = path;
                        found++;
                } else {
                        (void) snprintf(path, sizeof (path), "cfg/%s", fname);
                        if (access(path, F_OK) == 0) {
                                fname = path;
                                found++;
                        }
                }
        }

        if ((cfg = fopen(fname, "r")) ==  NULL) {
                test_failed(t, "open(%s): %s", fname, strerror(errno));
                return (-1);
        }

        line[0] = 0;
        done = 0;
        lineno = 0;

        while (!done) {

                lineno++;

                if (fgets(buf, sizeof (buf), cfg) == NULL) {
                        done++;
                } else {
                        (void) strtok(buf, "\n");
                        if ((*buf != 0) && (buf[strlen(buf)-1] == '\\')) {
                                /*
                                 * Continuation.  This isn't quite right,
                                 * as it doesn't allow for a "\" at the
                                 * end of line (no escaping).
                                 */
                                buf[strlen(buf)-1] = 0;
                                (void) strlcat(line, buf, sizeof (line));
                                continue;
                        }
                        (void) strlcat(line, buf, sizeof (line));
                }

                /* got a line */
                ptr = line;
                test_trim(&ptr);

                /* skip comments and empty lines */
                if (ptr[0] == 0 || ptr[0] == '#') {
                        line[0] = 0;
                        continue;
                }

                tok = strsep(&ptr, "|");
                if (tok == NULL) {
                        break;
                }
                test_trim(&tok);

                for (nfields = 0; nfields < MAXFIELD; nfields++) {
                        fields[nfields] = strsep(&ptr, "|");
                        if (fields[nfields] == NULL) {
                                break;
                        }
                        test_trim(&fields[nfields]);
                }

                found = 0;
                rv = 0;

                for (int i = 0; keyws[i] != NULL; i++) {
                        if (strcmp(tok, keyws[i]) == 0) {
                                found++;
                                err = NULL;
                                rv = callbs[i](fields, nfields, &err);
                        }
                }
                if (!found) {
                        rv = -1;
                        err = NULL;
                        (void) asprintf(&err, "unknown keyword %s", tok);
                }
                if (rv != 0) {
                        if (err) {
                                test_failed(t, "%s:%d: %s", fname,
                                    lineno, err);
                                free(err);
                        } else {
                                test_failed(t, "%s:%d: unknown error",
                                    fname, lineno);
                        }
                        (void) fclose(cfg);
                        return (rv);
                }

                line[0] = 0;
        }
        (void) fclose(cfg);
        return (0);
}