1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
  14  */
  15 
  16 /*
  17  * Common handling for test programs.
  18  */
  19 
  20 #include <stdio.h>
  21 #include <stdlib.h>
  22 #include <stdarg.h>
  23 #include <string.h>
  24 #include <errno.h>
  25 #include <pthread.h>
  26 #include <ctype.h>
  27 #include <unistd.h>
  28 #include <sys/param.h>
  29 #include "test_common.h"
  30 
  31 static int debug = 0;
  32 static int force = 0;
  33 static pthread_mutex_t lk;
  34 
  35 static int passes;
  36 static int tests;
  37 
  38 struct test {
  39         char            *name;
  40         int             ntids;
  41         pthread_t       *tids;
  42         int             fails;
  43         void            *arg;
  44         void            (*func)(test_t t, void *);
  45 };
  46 
  47 void
  48 test_set_debug(void)
  49 {
  50         debug++;
  51 }
  52 
  53 void
  54 test_set_force(void)
  55 {
  56         force++;
  57 }
  58 
  59 test_t
  60 test_start(const char *format, ...)
  61 {
  62         va_list args;
  63         test_t t;
  64         char *s;
  65 
  66         t = calloc(1, sizeof (*t));
  67         va_start(args, format);
  68         (void) vasprintf(&s, format, args);
  69         va_end(args);
  70 
  71         (void) asprintf(&t->name, "%s (%s)", s, ARCH);
  72         free(s);
  73 
  74         (void) pthread_mutex_lock(&lk);
  75         (void) printf("TEST STARTING %s:\n", t->name);
  76         (void) fflush(stdout);
  77         (void) pthread_mutex_unlock(&lk);
  78 
  79 #ifdef  LINT
  80         /* We inject references to make avoid name unused warnings */
  81         test_run(0, NULL, NULL, NULL);
  82         test_debugf(t, NULL);
  83         test_failed(t, NULL);
  84         test_passed(t);
  85         test_set_debug();
  86         test_set_force();
  87         test_summary();
  88         (void) test_load_config(t, NULL, NULL);
  89 #endif
  90 
  91         tests++;
  92         return (t);
  93 }
  94 
  95 void
  96 test_failed(test_t t, const char *format, ...)
  97 {
  98         va_list args;
  99 
 100         (void) pthread_mutex_lock(&lk);
 101         if (t == NULL) {
 102                 (void) printf("FAILURE: ");
 103                 va_start(args, format);
 104                 (void) vprintf(format, args);
 105                 va_end(args);
 106                 (void) printf("\n");
 107                 (void) fflush(stdout);
 108                 (void) pthread_mutex_unlock(&lk);
 109                 return;
 110         }
 111         if (force || (t->ntids > 0)) {
 112                 (void) printf("TEST FAILING %s: ", t->name);
 113         } else {
 114                 (void) printf("TEST FAILED %s: ", t->name);
 115         }
 116 
 117         va_start(args, format);
 118         (void) vprintf(format, args);
 119         va_end(args);
 120         (void) printf("\n");
 121         (void) fflush(stdout);
 122         (void) pthread_mutex_unlock(&lk);
 123 
 124         t->fails++;
 125         if (!force) {
 126                 if (t->ntids > 0) {
 127                         pthread_exit(NULL);
 128                 } else {
 129                         (void) exit(EXIT_FAILURE);
 130                 }
 131         }
 132 }
 133 
 134 void
 135 test_passed(test_t t)
 136 {
 137         if (t == NULL) {
 138                 return;
 139         }
 140         if (t->ntids > 0) {
 141                 if (debug) {
 142                         (void) pthread_mutex_lock(&lk);
 143                         (void) printf("TEST PASSING: %s\n", t->name);
 144                         (void) pthread_mutex_unlock(&lk);
 145                 }
 146                 return;
 147         }
 148         (void) pthread_mutex_lock(&lk);
 149         if (t->fails == 0) {
 150                 passes++;
 151                 (void) printf("TEST PASS: %s\n", t->name);
 152         } else {
 153                 (void) printf("TEST FAILED: %d failures\n", t->fails);
 154         }
 155         (void) fflush(stdout);
 156         (void) pthread_mutex_unlock(&lk);
 157         free(t->name);
 158         if (t->tids) {
 159                 free(t->tids);
 160         }
 161         free(t);
 162 }
 163 
 164 void
 165 test_summary(void)
 166 {
 167         if (passes == tests) {
 168                 (void) printf("TEST SUMMARY: %d / %d (ok)\n", passes, tests);
 169         } else {
 170                 (void) printf("TEST SUMMARY: %d / %d (%d failing)\n",
 171                     passes, tests, tests - passes);
 172         }
 173 }
 174 void
 175 test_debugf(test_t t, const char *format, ...)
 176 {
 177         va_list args;
 178 
 179         if (!debug)
 180                 return;
 181 
 182         (void) pthread_mutex_lock(&lk);
 183         if (t) {
 184                 (void) printf("TEST DEBUG %s: ", t->name);
 185         } else {
 186                 (void) printf("TEST DEBUG: ");
 187         }
 188         va_start(args, format);
 189         (void) vprintf(format, args);
 190         va_end(args);
 191         (void) printf("\n");
 192         (void) fflush(stdout);
 193         (void) pthread_mutex_unlock(&lk);
 194 }
 195 
 196 static void *
 197 test_thr_one(void *arg)
 198 {
 199         test_t t = arg;
 200         t->func(t, t->arg);
 201         return (NULL);
 202 }
 203 
 204 void
 205 test_run(int nthr, void (*func)(test_t, void *), void *arg,
 206     const char *tname, ...)
 207 {
 208         test_t          t;
 209         char            *s;
 210         va_list         args;
 211 
 212         t = calloc(1, sizeof (*t));
 213         t->ntids = nthr;
 214         t->tids = calloc(nthr, sizeof (pthread_t));
 215         t->func = func;
 216         t->arg = arg;
 217 
 218         va_start(args, tname);
 219         (void) vasprintf(&s, tname, args);
 220         va_end(args);
 221 
 222         (void) asprintf(&t->name, "%s (%s)", s, ARCH);
 223         free(s);
 224 
 225         (void) pthread_mutex_lock(&lk);
 226         (void) printf("TEST STARTING %s:\n", t->name);
 227         (void) fflush(stdout);
 228         (void) pthread_mutex_unlock(&lk);
 229 
 230         test_debugf(t, "running %d threads", nthr);
 231 
 232         for (int i = 0; i < nthr; i++) {
 233                 test_debugf(t, "started thread %d", i);
 234                 (void) pthread_create(&t->tids[i], NULL, test_thr_one, t);
 235         }
 236 
 237         for (int i = 0; i < nthr; i++) {
 238                 (void) pthread_join(t->tids[i], NULL);
 239                 test_debugf(t, "thread %d joined", i);
 240                 t->ntids--;
 241         }
 242         test_passed(t);
 243 }
 244 
 245 void
 246 test_trim(char **ptr)
 247 {
 248         char *p = *ptr;
 249         while (isspace(*p)) {
 250                 p++;
 251         }
 252         *ptr = p;
 253         p += strlen(p);
 254         while ((--p >= *ptr) && (isspace(*p))) {
 255                 *p = 0;
 256         }
 257 }
 258 
 259 #define MAXCB           20
 260 #define MAXFIELD        20
 261 
 262 int
 263 test_load_config(test_t t, const char *fname, ...)
 264 {
 265         va_list         va;
 266         const char      *keyws[MAXCB];
 267         test_cfg_func_t callbs[MAXCB];
 268         char            *fields[MAXFIELD];
 269         int             nfields;
 270 
 271         FILE            *cfg;
 272         char            line[1024];
 273         char            buf[1024];
 274         int             done;
 275         char            *ptr;
 276         char            *tok;
 277         char            *err;
 278         int             lineno;
 279         int             rv;
 280         int             found;
 281         char            path[MAXPATHLEN];
 282 
 283         va_start(va, fname);
 284         for (int i = 0; i < MAXCB; i++) {
 285                 keyws[i] = (const char *)va_arg(va, const char *);
 286                 if (keyws[i] == NULL)
 287                         break;
 288                 callbs[i] = (test_cfg_func_t)va_arg(va, test_cfg_func_t);
 289         }
 290         va_end(va);
 291 
 292         found = 0;
 293 
 294         if (access(fname, F_OK) == 0) {
 295                 found++;
 296         }
 297         if (!found && fname[0] != '/') {
 298                 char *stf = getenv("STF_SUITE");
 299                 if (stf == NULL) {
 300                         stf = "../..";
 301                 }
 302                 (void) snprintf(path, sizeof (path), "%s/cfg/%s", stf, fname);
 303                 if (access(path, F_OK) == 0) {
 304                         fname = path;
 305                         found++;
 306                 } else {
 307                         (void) snprintf(path, sizeof (path), "cfg/%s", fname);
 308                         if (access(path, F_OK) == 0) {
 309                                 fname = path;
 310                                 found++;
 311                         }
 312                 }
 313         }
 314 
 315         if ((cfg = fopen(fname, "r")) ==  NULL) {
 316                 test_failed(t, "open(%s): %s", fname, strerror(errno));
 317                 return (-1);
 318         }
 319 
 320         line[0] = 0;
 321         done = 0;
 322         lineno = 0;
 323 
 324         while (!done) {
 325 
 326                 lineno++;
 327 
 328                 if (fgets(buf, sizeof (buf), cfg) == NULL) {
 329                         done++;
 330                 } else {
 331                         (void) strtok(buf, "\n");
 332                         if ((*buf != 0) && (buf[strlen(buf)-1] == '\\')) {
 333                                 /*
 334                                  * Continuation.  This isn't quite right,
 335                                  * as it doesn't allow for a "\" at the
 336                                  * end of line (no escaping).
 337                                  */
 338                                 buf[strlen(buf)-1] = 0;
 339                                 (void) strlcat(line, buf, sizeof (line));
 340                                 continue;
 341                         }
 342                         (void) strlcat(line, buf, sizeof (line));
 343                 }
 344 
 345                 /* got a line */
 346                 ptr = line;
 347                 test_trim(&ptr);
 348 
 349                 /* skip comments and empty lines */
 350                 if (ptr[0] == 0 || ptr[0] == '#') {
 351                         line[0] = 0;
 352                         continue;
 353                 }
 354 
 355                 tok = strsep(&ptr, "|");
 356                 if (tok == NULL) {
 357                         break;
 358                 }
 359                 test_trim(&tok);
 360 
 361                 for (nfields = 0; nfields < MAXFIELD; nfields++) {
 362                         fields[nfields] = strsep(&ptr, "|");
 363                         if (fields[nfields] == NULL) {
 364                                 break;
 365                         }
 366                         test_trim(&fields[nfields]);
 367                 }
 368 
 369                 found = 0;
 370                 rv = 0;
 371 
 372                 for (int i = 0; keyws[i] != NULL; i++) {
 373                         if (strcmp(tok, keyws[i]) == 0) {
 374                                 found++;
 375                                 err = NULL;
 376                                 rv = callbs[i](fields, nfields, &err);
 377                         }
 378                 }
 379                 if (!found) {
 380                         rv = -1;
 381                         err = NULL;
 382                         (void) asprintf(&err, "unknown keyword %s", tok);
 383                 }
 384                 if (rv != 0) {
 385                         if (err) {
 386                                 test_failed(t, "%s:%d: %s", fname,
 387                                     lineno, err);
 388                                 free(err);
 389                         } else {
 390                                 test_failed(t, "%s:%d: unknown error",
 391                                     fname, lineno);
 392                         }
 393                         (void) fclose(cfg);
 394                         return (rv);
 395                 }
 396 
 397                 line[0] = 0;
 398         }
 399         (void) fclose(cfg);
 400         return (0);
 401 }