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 2015 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 
 175 void
 176 test_debugf(test_t t, const char *format, ...)
 177 {
 178         va_list args;
 179 
 180         if (!debug)
 181                 return;
 182 
 183         (void) pthread_mutex_lock(&lk);
 184         if (t) {
 185                 (void) printf("TEST DEBUG %s: ", t->name);
 186         } else {
 187                 (void) printf("TEST DEBUG: ");
 188         }
 189         va_start(args, format);
 190         (void) vprintf(format, args);
 191         va_end(args);
 192         (void) printf("\n");
 193         (void) fflush(stdout);
 194         (void) pthread_mutex_unlock(&lk);
 195 }
 196 
 197 static void *
 198 test_thr_one(void *arg)
 199 {
 200         test_t t = arg;
 201         t->func(t, t->arg);
 202         return (NULL);
 203 }
 204 
 205 void
 206 test_run(int nthr, void (*func)(test_t, void *), void *arg,
 207     const char *tname, ...)
 208 {
 209         test_t          t;
 210         char            *s;
 211         va_list         args;
 212 
 213         t = calloc(1, sizeof (*t));
 214         t->ntids = nthr;
 215         t->tids = calloc(nthr, sizeof (pthread_t));
 216         t->func = func;
 217         t->arg = arg;
 218 
 219         va_start(args, tname);
 220         (void) vasprintf(&s, tname, args);
 221         va_end(args);
 222 
 223         (void) asprintf(&t->name, "%s (%s)", s, ARCH);
 224         free(s);
 225 
 226         (void) pthread_mutex_lock(&lk);
 227         (void) printf("TEST STARTING %s:\n", t->name);
 228         (void) fflush(stdout);
 229         (void) pthread_mutex_unlock(&lk);
 230 
 231         test_debugf(t, "running %d threads", nthr);
 232 
 233         for (int i = 0; i < nthr; i++) {
 234                 test_debugf(t, "started thread %d", i);
 235                 (void) pthread_create(&t->tids[i], NULL, test_thr_one, t);
 236         }
 237 
 238         for (int i = 0; i < nthr; i++) {
 239                 (void) pthread_join(t->tids[i], NULL);
 240                 test_debugf(t, "thread %d joined", i);
 241                 t->ntids--;
 242         }
 243         test_passed(t);
 244 }
 245 
 246 void
 247 test_trim(char **ptr)
 248 {
 249         char *p = *ptr;
 250         while (isspace(*p)) {
 251                 p++;
 252         }
 253         *ptr = p;
 254         p += strlen(p);
 255         while ((--p >= *ptr) && (isspace(*p))) {
 256                 *p = 0;
 257         }
 258 }
 259 
 260 #define MAXCB           20
 261 #define MAXFIELD        20
 262 
 263 int
 264 test_load_config(test_t t, const char *fname, ...)
 265 {
 266         va_list         va;
 267         const char      *keyws[MAXCB];
 268         test_cfg_func_t callbs[MAXCB];
 269         char            *fields[MAXFIELD];
 270         int             nfields;
 271 
 272         FILE            *cfg;
 273         char            line[1024];
 274         char            buf[1024];
 275         int             done;
 276         char            *ptr;
 277         char            *tok;
 278         char            *err;
 279         int             lineno;
 280         int             rv;
 281         int             found;
 282         char            path[MAXPATHLEN];
 283         int             i;
 284 
 285         va_start(va, fname);
 286         for (i = 0; i < MAXCB; i++) {
 287                 keyws[i] = (const char *)va_arg(va, const char *);
 288                 if (keyws[i] == NULL)
 289                         break;
 290                 callbs[i] = (test_cfg_func_t)va_arg(va, test_cfg_func_t);
 291         }
 292         va_end(va);
 293         if (i == MAXCB) {
 294                 test_debugf(t, "too many arguments to function >= %d", MAXCB);
 295         }
 296 
 297         found = 0;
 298 
 299         if (access(fname, F_OK) == 0) {
 300                 found++;
 301         }
 302         if (!found && fname[0] != '/') {
 303                 char *stf = getenv("STF_SUITE");
 304                 if (stf == NULL) {
 305                         stf = "../..";
 306                 }
 307                 (void) snprintf(path, sizeof (path), "%s/cfg/%s", stf, fname);
 308                 if (access(path, F_OK) == 0) {
 309                         fname = path;
 310                         found++;
 311                 } else {
 312                         (void) snprintf(path, sizeof (path), "cfg/%s", fname);
 313                         if (access(path, F_OK) == 0) {
 314                                 fname = path;
 315                                 found++;
 316                         }
 317                 }
 318         }
 319 
 320         if ((cfg = fopen(fname, "r")) ==  NULL) {
 321                 test_failed(t, "open(%s): %s", fname, strerror(errno));
 322                 return (-1);
 323         }
 324 
 325         line[0] = 0;
 326         done = 0;
 327         lineno = 0;
 328 
 329         while (!done) {
 330 
 331                 lineno++;
 332 
 333                 if (fgets(buf, sizeof (buf), cfg) == NULL) {
 334                         done++;
 335                 } else {
 336                         (void) strtok(buf, "\n");
 337                         if ((*buf != 0) && (buf[strlen(buf)-1] == '\\')) {
 338                                 /*
 339                                  * Continuation.  This isn't quite right,
 340                                  * as it doesn't allow for a "\" at the
 341                                  * end of line (no escaping).
 342                                  */
 343                                 buf[strlen(buf)-1] = 0;
 344                                 (void) strlcat(line, buf, sizeof (line));
 345                                 continue;
 346                         }
 347                         (void) strlcat(line, buf, sizeof (line));
 348                 }
 349 
 350                 /* got a line */
 351                 ptr = line;
 352                 test_trim(&ptr);
 353 
 354                 /* skip comments and empty lines */
 355                 if (ptr[0] == 0 || ptr[0] == '#') {
 356                         line[0] = 0;
 357                         continue;
 358                 }
 359 
 360                 tok = strsep(&ptr, "|");
 361                 if (tok == NULL) {
 362                         break;
 363                 }
 364                 test_trim(&tok);
 365 
 366                 for (nfields = 0; nfields < MAXFIELD; nfields++) {
 367                         fields[nfields] = strsep(&ptr, "|");
 368                         if (fields[nfields] == NULL) {
 369                                 break;
 370                         }
 371                         test_trim(&fields[nfields]);
 372                 }
 373 
 374                 found = 0;
 375                 rv = 0;
 376 
 377                 for (int i = 0; keyws[i] != NULL; i++) {
 378                         if (strcmp(tok, keyws[i]) == 0) {
 379                                 found++;
 380                                 err = NULL;
 381                                 rv = callbs[i](fields, nfields, &err);
 382                         }
 383                 }
 384                 if (!found) {
 385                         rv = -1;
 386                         err = NULL;
 387                         (void) asprintf(&err, "unknown keyword %s", tok);
 388                 }
 389                 if (rv != 0) {
 390                         if (err) {
 391                                 test_failed(t, "%s:%d: %s", fname,
 392                                     lineno, err);
 393                                 free(err);
 394                         } else {
 395                                 test_failed(t, "%s:%d: unknown error",
 396                                     fname, lineno);
 397                         }
 398                         (void) fclose(cfg);
 399                         return (rv);
 400                 }
 401 
 402                 line[0] = 0;
 403         }
 404         (void) fclose(cfg);
 405         return (0);
 406 }